Skip to content

Commit

Permalink
ControlFlowFlattening part1
Browse files Browse the repository at this point in the history
  • Loading branch information
echo094 committed Dec 1, 2024
1 parent fefcebc commit 799dd70
Showing 1 changed file with 134 additions and 0 deletions.
134 changes: 134 additions & 0 deletions src/plugin/jsconfuser.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,19 @@ function safeGetName(path) {
return null
}

function safeGetLiteral(path) {
if (path.isUnaryExpression()) {
if (path.node.operator === '-' && path.get('argument').isNumericLiteral()) {
return -1 * path.get('argument').node.value
}
return null
}
if (path.isLiteral()) {
return path.node.value
}
return null
}

function safeDeleteNode(name, path) {
let binding
if (path.isFunctionDeclaration()) {
Expand Down Expand Up @@ -1557,6 +1570,124 @@ const deGlobalConcealing = {
},
}

function checkControlVar(path) {
const parent = path.parentPath
if (path.key !== 'right' || !parent.isAssignmentExpression()) {
return false
}
const var_path = parent.get('left')
const var_name = var_path.node?.name
if (!var_name) {
return false
}
let root_path = parent.parentPath
if (root_path.isExpressionStatement) {
root_path = root_path.parentPath
}
const binding = parent.scope.getBinding(var_name)
for (const ref of binding.referencePaths) {
if (ref === var_path) {
continue
}
let cur = ref
let valid = false
while (cur && cur !== root_path) {
if (cur.isSwitchCase() || cur === path) {
valid = true
break
}
cur = cur.parentPath
}
if (!valid) {
return false
}
if (ref.key === 'object') {
const prop = ref.parentPath.get('property')
if (!prop.isLiteral() && !prop.isIdentifier()) {
return false
}
continue
}
if (ref.key === 'right') {
const left = ref.parentPath.get('left')
if (!left.isMemberExpression()) {
return false
}
const obj = safeGetName(left.get('object'))
if (obj !== var_name) {
return false
}
continue
}
}
return true
}

/**
* Process the constant properties in the controlVar
*
* Template:
* ```javascript
* controlVar = {
* // strings
* key_string: 'StringLiteral',
* // numbers
* key_number: 'NumericLiteral',
* }
* ```
*
* Some kinds of deadCode may in inserted to the fake chunks:
*
* ```javascript
* controlVar = false
* controlVar = undefined
* controlVar[randomControlKey] = undefined
* delete controlVar[randomControlKey]
* ```
*/
const deControlFlowFlatteningStateless = {
ObjectExpression(path) {
if (!checkControlVar(path)) {
return
}
const parent = path.parentPath
const var_name = parent.get('left').node?.name
console.log(`[ControlFlowFlattening] parse stateless in obj: ${var_name}`)
const props = {}
const prop_num = path.node.properties.length
for (let i = 0; i < prop_num; ++i) {
const prop = path.get(`properties.${i}`)
const key = safeGetName(prop.get('key'))
const value = safeGetLiteral(prop.get('value'))
if (!key || !value) {
continue
}
props[key] = value
}
const binding = parent.scope.getBinding(var_name)
for (const ref of binding.referencePaths) {
if (ref.key !== 'object') {
continue
}
const prop = safeGetName(ref.parentPath.get('property'))
if (!prop) {
continue
}
if (!Object.prototype.hasOwnProperty.call(props, prop)) {
continue
}
const upper = ref.parentPath
if (upper.key === 'left' && upper.parentPath.isAssignmentExpression()) {
// this is in the fake chunk
ref.parentPath.parentPath.remove()
continue
}
safeReplace(ref.parentPath, props[prop])
}
binding.scope.crawl()
},
}

module.exports = function (code) {
let ast
try {
Expand Down Expand Up @@ -1587,6 +1718,9 @@ module.exports = function (code) {
traverse(ast, pruneIfBranch)
// GlobalConcealing
traverse(ast, deGlobalConcealing)
// ControlFlowFlattening
traverse(ast, deControlFlowFlatteningStateless)
traverse(ast, calculateConstantExp)
code = generator(ast, {
comments: false,
jsescOption: { minimal: true },
Expand Down

0 comments on commit 799dd70

Please sign in to comment.