逆向分析 AST 解混淆 400行代码解混淆猿人学第二届第三题然后进行完美反编译 Alan Hays 2023-10-16 2024-04-01 # 题目
猿人学 2023 届第三题 点击跳转
# 0x01 降维打击
这里 if 都是多层嵌套的,非常影响阅读体验,直接一步将它变为一维结构吧!
还原前先手动还原小部分代码,接着写反混淆脚本批量修改。
我们单看 if (o < 2)
这个 if 块,很明显 o < 1
其实就是 o==0
而 else 就是 o==1
, 以此类推。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 (function (e ) { var o = e || 2 ; for (;;) if (o < 2 ) { if (o < 1 ) { o += 88 ; } else { v.v = !1 ; o += 226 ; } } else { if (o < 3 ) { o += 243 ; } else { L = Y || l.length ; o += 4 ; } } })(); (function (e ) { var o = e || 2 ; switch (o) { case 0 : o += 88 ; case 1 : v.v = !1 ; o += 226 ; case 2 : o += 243 ; case 3 : L = Y || l.length ; o += 4 ; } })();
这是我写的插件,将多层嵌套的 if 转换为 Switch 语法,可以方便我们后续调试。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 const IfToCase = { IfStatement : { exit (path, state ) { const { test, consequent, alternate } = path.node ; let { name, cases } = state; if (!types.isBinaryExpression (test, { operator : "<" })) return ; if (!types.isIdentifier (test.left , { name : name })) return ; if (!types.isNumericLiteral (test.right )) return ; const right = test.right .value ; if (right % 2 === 0 ) { if (types.isIfStatement (alternate.body [0 ])) return ; cases.push (types.SwitchCase (types.valueToNode (right), alternate.body )); return ; } const case1 = types.SwitchCase ( types.valueToNode (right - 1 ), consequent.body ); const case2 = types.SwitchCase (types.valueToNode (right), alternate.body ); cases.push (case1, case2); }, }, }; const IfToSwitch = { ForStatement (path) { const { init, test, update, body } = path.node ; const prev = path.getPrevSibling (); if (!types.isIfStatement (body)) return ; const discriminant = prev.node .declarations [0 ].id ; let cases = []; path.traverse (IfToCase , { name : discriminant.name , cases : cases }); if (!cases.length ) return ; const switchNode = types.SwitchStatement (discriminant, cases); path.get ("body" ).replaceInline (switchNode); }, };
对比效果图如下,处理后清晰了不少。
# 0x02 打回原形
经过上一步还原后发现有很多 o += xx | o -= xx
的代码,这里的 o 其实就是 case 的条件,那么简化一下吧! o = o - xxx
其中 o - xxx
部分我们计算出来。
下面插件来喽!又香又脆,嘎嘎香!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 const RestoreJump = { AssignmentExpression (path) { let { operator, left, right } = path.node ; if (!(operator[1 ] === "=" && types.isNumericLiteral (right))) return ; let caseNode = path.findParent ((p ) => p.isSwitchCase ()); let { consequent, test } = caseNode.node ; let _node = types.assignmentExpression ( "=" , left, types.valueToNode ( operator[0 ] === "-" ? test.value - right.value : test.value + right.value ) ); path.replaceInline (_node); }, };
对比效果图如下,处理后清晰了不少。
# 0x03 迷阵寻踪
经过上面还原后 发现很多 case 块只做 o 的修改,那我就知道它下一步要到那个 case 块,唉!我们是不是可以将他们合并起来,插件如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 function getJump (node, name ) { if (!types.isExpressionStatement (node)) return ; let { expression } = node; if (!types.isAssignmentExpression (expression)) return ; if (expression.left .name !== name) return ; return expression.right .value ; } function getCaseJump (map, name, node ) { let { test, consequent } = node; let list = []; let con = consequent[consequent.length - 1 ]; let num = getJump (con, name); if (num !== undefined ) { list.push (num); } if (types.isIfStatement (con)) { let { consequent, alternate } = con; let num = getJump (consequent, name); if (num !== undefined ) { list.push (num); } num = getJump (alternate, name); if (num !== undefined ) { list.push (num); } } map[test.value ] = list; } function removeDuplicates (arr1, arr2 ) { let result = []; for (let i = 0 ; i < arr1.length ; i++) { let found = false ; for (let j = 0 ; j < arr2.length ; j++) { if (arr1[i] === arr2[j]) { result.push (arr1[i]); arr2.splice (j, 1 ); found = true ; break ; } } if (found) { arr1.splice (i, 1 ); i--; } } return result; } function controlFlowStructure (si, map, cases, stack = [], body = [] ) { if (!map.loop ) map.loop = []; if (stack.includes (si)) { if (map.loop .indexOf (si) === -1 ) map.loop .push (si); return body; } let item = map[si]; body = body.concat (cases[si].consequent ); switch (item.length ) { case 0 : return body; case 1 : return controlFlowStructure (item[0 ], map, cases, stack, body); case 2 : stack.push (si); body[body.length - 1 ].consequent = types.blockStatement ( controlFlowStructure (item[0 ], map, cases, stack, []) ); if (map.loop .includes (si)) { let { test, consequent } = body[body.length - 1 ]; body[body.length - 1 ] = types.whileStatement (test, consequent); body = body.concat ( controlFlowStructure (item[1 ], map, cases, stack, []) ); } else { body[body.length - 1 ].alternate = types.blockStatement ( controlFlowStructure (item[1 ], map, cases, stack, []) ); body = body.concat ( removeDuplicates ( body[body.length - 1 ].consequent .body , body[body.length - 1 ].alternate .body ) ); } stack.pop (); return body; } } const MergeCases = { SwitchStatement (path) { const { discriminant, cases } = path.node ; const name = discriminant.name ; let binding = path.scope .getBinding (name); let start = binding.path .node .init .right .value ; let map = {}; for (let i = 0 ; i < cases.length ; i++) { getCaseJump (map, name, cases[i]); } path.replaceInline (controlFlowStructure (start, map, cases)); }, };
对比图如下,都有实质的代码了。
# 0x04 移除污秽
将指针修改的代码去除
1 2 3 4 5 6 7 8 9 10 11 12 const CleaningUpGarbage = { ForStatement (path) { let p = path.getPrevSibling (); let name = p.node .declarations [0 ].id .name ; path.scope .traverse (path.scope .block , { AssignmentExpression (_path) { if (!types.isIdentifier (_path.node .left , { name : name })) return ; _path.remove (); }, }); }, };
移除无关代码
# 0x05 结构优化
优化 ifelse 结构,使其更便于阅读。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 function isEndNode (nodes ) { let typeList = ["ReturnStatement" , "ThrowStatement" ]; for (let i = 0 ; i < nodes.length ; i++) { if (typeList.includes (nodes[i].type )) { return true ; } } return false ; } const ifOptimization = { IfStatement (path) { let { test, consequent, alternate } = path.node ; if (!alternate) return ; if (isEndNode (consequent.body )) { path.insertAfter (alternate.body ); path.node .alternate = null ; } else if (isEndNode (alternate.body )) { path.insertAfter (consequent.body ); path.replaceInline ( types.ifStatement (types.unaryExpression ("!" , test), alternate, null ) ); } else if ( types.isIfStatement (consequent.body [consequent.body .length - 1 ]) && isEndNode (consequent.body [consequent.body .length - 1 ].consequent .body ) ) { if ( generator (alternate.body [0 ]).code === generator (consequent.body [consequent.body .length - 1 ].alternate .body [0 ]) .code ) { path.insertAfter ( consequent.body [consequent.body .length - 1 ].alternate .body ); consequent.body [consequent.body .length - 1 ].alternate = null ; path.node .alternate = null ; } } }, };
处理后逻辑尽现。
# 0x06 答辩还原
这里其实就是嵌套的三元表达式,先手动还原几条能发现他本质是 switch
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 const TernaryToSwitch = { ConditionalExpression (path, { cases, _name }) { let { test, consequent, alternate } = path.node ; if (!types.isBinaryExpression (test, { operator : "==" })) return ; let { right, left } = test; if (!types.isIdentifier (right) || !types.isNumericLiteral (left)) return ; if (!cases[right.name ]) { cases[right.name ] = []; } let body = [ types.expressionStatement ( types.assignmentExpression ("=" , types.identifier (_name), consequent) ), ]; body.push (types.breakStatement ()); cases[right.name ].push (types.switchCase (test.left , body)); }, }; const TernaryReturn = { ReturnStatement (path) { let { argument } = path.node ; if (!types.isConditionalExpression (argument)) return ; if (!types.isBinaryExpression (argument.test , { operator : "==" })) return ; let cases = {}; let name = argument.test .right .name ; if (!name) return ; let _name = `${name} ${path.node.start} ` ; path.traverse (TernaryToSwitch , { cases : cases, _name : _name }); path.insertBefore ( types.variableDeclaration ("var" , [ types.variableDeclarator (types.identifier (_name), null ), ]) ); path.replaceInline ( types.switchStatement (types.identifier (name), cases[name]) ); path.insertAfter (types.returnStatement (types.identifier (_name))); }, };
对比效果图如下,处理后犹如拨云见日,茅塞顿开。(还原它主要是为了反编译 jsvmp)
# 0x07 窥探本源
反编译后魔改点与环境检测清晰可见