Skip to content

Commit cd2859e

Browse files
committed
ControlFlowFlattening part1
1 parent 7ea4533 commit cd2859e

File tree

1 file changed

+134
-0
lines changed

1 file changed

+134
-0
lines changed

src/plugin/jsconfuser.js

+134
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,19 @@ function safeGetName(path) {
3434
return null
3535
}
3636

37+
function safeGetLiteral(path) {
38+
if (path.isUnaryExpression()) {
39+
if (path.node.operator === '-' && path.get('argument').isNumericLiteral()) {
40+
return -1 * path.get('argument').node.value
41+
}
42+
return null
43+
}
44+
if (path.isLiteral()) {
45+
return path.node.value
46+
}
47+
return null
48+
}
49+
3750
function safeDeleteNode(name, path) {
3851
let binding
3952
if (path.isFunctionDeclaration()) {
@@ -1557,6 +1570,124 @@ const deGlobalConcealing = {
15571570
},
15581571
}
15591572

1573+
function checkControlVar(path) {
1574+
const parent = path.parentPath
1575+
if (path.key !== 'right' || !parent.isAssignmentExpression()) {
1576+
return false
1577+
}
1578+
const var_path = parent.get('left')
1579+
const var_name = var_path.node?.name
1580+
if (!var_name) {
1581+
return false
1582+
}
1583+
let root_path = parent.parentPath
1584+
if (root_path.isExpressionStatement) {
1585+
root_path = root_path.parentPath
1586+
}
1587+
const binding = parent.scope.getBinding(var_name)
1588+
for (const ref of binding.referencePaths) {
1589+
if (ref === var_path) {
1590+
continue
1591+
}
1592+
let cur = ref
1593+
let valid = false
1594+
while (cur && cur !== root_path) {
1595+
if (cur.isSwitchCase() || cur === path) {
1596+
valid = true
1597+
break
1598+
}
1599+
cur = cur.parentPath
1600+
}
1601+
if (!valid) {
1602+
return false
1603+
}
1604+
if (ref.key === 'object') {
1605+
const prop = ref.parentPath.get('property')
1606+
if (!prop.isLiteral() && !prop.isIdentifier()) {
1607+
return false
1608+
}
1609+
continue
1610+
}
1611+
if (ref.key === 'right') {
1612+
const left = ref.parentPath.get('left')
1613+
if (!left.isMemberExpression()) {
1614+
return false
1615+
}
1616+
const obj = safeGetName(left.get('object'))
1617+
if (obj !== var_name) {
1618+
return false
1619+
}
1620+
continue
1621+
}
1622+
}
1623+
return true
1624+
}
1625+
1626+
/**
1627+
* Process the constant properties in the controlVar
1628+
*
1629+
* Template:
1630+
* ```javascript
1631+
* controlVar = {
1632+
* // strings
1633+
* key_string: 'StringLiteral',
1634+
* // numbers
1635+
* key_number: 'NumericLiteral',
1636+
* }
1637+
* ```
1638+
*
1639+
* Some kinds of deadCode may in inserted to the fake chunks:
1640+
*
1641+
* ```javascript
1642+
* controlVar = false
1643+
* controlVar = undefined
1644+
* controlVar[randomControlKey] = undefined
1645+
* delete controlVar[randomControlKey]
1646+
* ```
1647+
*/
1648+
const deControlFlowFlatteningStateless = {
1649+
ObjectExpression(path) {
1650+
if (!checkControlVar(path)) {
1651+
return
1652+
}
1653+
const parent = path.parentPath
1654+
const var_name = parent.get('left').node?.name
1655+
console.log(`[ControlFlowFlattening] parse stateless in obj: ${var_name}`)
1656+
const props = {}
1657+
const prop_num = path.node.properties.length
1658+
for (let i = 0; i < prop_num; ++i) {
1659+
const prop = path.get(`properties.${i}`)
1660+
const key = safeGetName(prop.get('key'))
1661+
const value = safeGetLiteral(prop.get('value'))
1662+
if (!key || !value) {
1663+
continue
1664+
}
1665+
props[key] = value
1666+
}
1667+
const binding = parent.scope.getBinding(var_name)
1668+
for (const ref of binding.referencePaths) {
1669+
if (ref.key !== 'object') {
1670+
continue
1671+
}
1672+
const prop = safeGetName(ref.parentPath.get('property'))
1673+
if (!prop) {
1674+
continue
1675+
}
1676+
if (!Object.prototype.hasOwnProperty.call(props, prop)) {
1677+
continue
1678+
}
1679+
const upper = ref.parentPath
1680+
if (upper.key === 'left' && upper.parentPath.isAssignmentExpression()) {
1681+
// this is in the fake chunk
1682+
ref.parentPath.parentPath.remove()
1683+
continue
1684+
}
1685+
safeReplace(ref.parentPath, props[prop])
1686+
}
1687+
binding.scope.crawl()
1688+
},
1689+
}
1690+
15601691
module.exports = function (code) {
15611692
let ast
15621693
try {
@@ -1587,6 +1718,9 @@ module.exports = function (code) {
15871718
traverse(ast, pruneIfBranch)
15881719
// GlobalConcealing
15891720
traverse(ast, deGlobalConcealing)
1721+
// ControlFlowFlattening
1722+
traverse(ast, deControlFlowFlatteningStateless)
1723+
traverse(ast, calculateConstantExp)
15901724
code = generator(ast, {
15911725
comments: false,
15921726
jsescOption: { minimal: true },

0 commit comments

Comments
 (0)