观察结构发现有多层字符串解密赋值
其本质多层调用函数,我们可以对CallExpression做解析
判断为字符串和数字的结构下,就通过scope找到原函数
判断函数是否是一个return,然后调用了函数继续传入
如果符合条件就进行拍平
直接上代码
traverse(ast, {
CallExpression(path) {
if (path.node.arguments.length === 2) {
const type0 = path.node.arguments[0].type
const type1 = path.node.arguments[1].type
const isLikelyNumber=(type)=>{
return type==='UnaryExpression'||type==='NumericLiteral'
}
if ((type0 === 'StringLiteral' && isLikelyNumber(type1)) || (type1 === 'StringLiteral' && isLikelyNumber(type0))) {
const funcBinding = path.scope.getBinding(path.node.callee.name)
const funcNode = funcBinding.path.node
if (funcNode.params.length !== 2) {
return
}
if (funcNode.body.body.length !== 1) {
return
}
if (funcNode.body.body[0].type !== 'ReturnStatement') {
return
}
const funcArgs0 = funcNode.params[0].name
const funcArgs1 = funcNode.params[1].name
const bodyCallArgs = funcNode.body.body[0].argument.arguments
let isSwap = false
for (let index = 0; index < bodyCallArgs.length; index++) {
const item = bodyCallArgs[index];
if (item.type === 'Identifier') {
if (item.name === funcArgs0 && index === 1) {
isSwap = true
} else if (item.name === funcArgs1 && index === 0) {
isSwap = true
}
break;
}
}
const handleExpression = (bodyExpress, argsIdentifier) => {
if (bodyExpress.type !== 'BinaryExpression') {
return argsIdentifier
}
const handleIdentifier = (item) => {
if (item.type !== 'Identifier') {
return item
} else {
return argsIdentifier
}
}
const numAst = types.binaryExpression(bodyExpress.operator, handleIdentifier(bodyExpress.left), handleIdentifier(bodyExpress.right))
const numResult = eval(generator(numAst).code)
return types.numericLiteral(numResult)
}
const firstIdentifier = path.node.arguments[0]
const secondIdentifier = path.node.arguments[1]
let newCalleeArgs = [handleExpression(bodyCallArgs[0], isSwap ? secondIdentifier : firstIdentifier), handleExpression(bodyCallArgs[1], isSwap ? firstIdentifier : secondIdentifier)]
let newNode = types.callExpression(funcNode.body.body[0].argument.callee, newCalleeArgs);
path.replaceInline(newNode)
}
}
},
});
这样变成了对字符串解密函数的直接引用
我们直接对字符串解密进行处理
crackCharFunc直接对抄网页中的解密函数做抽取即可
traverse(ast, {
CallExpression(path) {
if (path.node.arguments.length === 2) {
if (path.node.callee.name !== 'e') {
return
}
if (path.node.arguments[0].type !== 'NumericLiteral') {
return;
}
if (path.node.arguments[1].type !== 'StringLiteral') {
return;
}
const nodeResult = crackCharFunc(path.node.arguments[0].value, path.node.arguments[1].value)
path.replaceInline(types.stringLiteral(nodeResult))
}
},
});
这个时候还存在花指令混淆
判断CallExpression和MemberExpression函数,在符合的条件下重写花指令,去除花指令的函数调用
const handleObfs = {
CallExpression: {
exit(outerPath) {
const node = outerPath.node.callee
const parentPath = outerPath
if (node?.object?.type === 'Identifier' && node?.property?.type === 'StringLiteral') {
const objBinding = outerPath.scope.getBinding(node.object.name)
if (objBinding === undefined) {
return;
}
const objNode = objBinding.path.node
const funcList = objNode.init?.properties ?? []
const funcInstance = funcList.find((item) => {
const keyName = item.key.name
return keyName === node.property.value
})
if (funcInstance) {
const parentNode = parentPath.node
let replaceAst = null
if (funcInstance.value.type === 'FunctionExpression') {
const originNode = funcInstance.value.body.body[0].argument
//函数
if (originNode.type === 'CallExpression') {
replaceAst = types.callExpression(parentNode.arguments[0], [...parentNode.arguments].splice(1))
} else if (originNode.type === 'BinaryExpression') {
replaceAst = types.binaryExpression(originNode.operator, parentNode.arguments[0], parentNode.arguments[1])
}
} else {
//字符串
debugger
replaceAst = types.stringLiteral(funcInstance.value.value)
}
if (replaceAst) {
parentPath.replaceWith(replaceAst)
}
}
}
}
},
MemberExpression: {
enter(path) {
const node = path.node
if (node?.object?.type === 'Identifier' && node?.property?.type === 'StringLiteral') {
const objBinding = path.scope.getBinding(node.object.name)
if (objBinding === undefined) {
return;
}
const objNode = objBinding.path.node
const funcList = objNode.init?.properties ?? []
const funcInstance = funcList.find((item) => {
const keyName = item.key.name
return keyName === node.property.value
})
if (funcInstance) {
let replaceAst = null
if (funcInstance.value.type === 'StringLiteral') {
replaceAst = types.stringLiteral(funcInstance.value.value)
}
if (replaceAst) {
path.replaceWith(replaceAst)
}
}
}
}
}
}
traverse(ast, handleObfs);
一开始我遍历MemberExpress对象触发了Container is falsy
因为n["Pvaqs"](n["Pvaqs"])
需要先遍历里层后遍历外层
所以需要exit,但是如果使用MemberExpress,此时里层不存在层级
所以会直接替换,然后内层的再被遍历时已经丧失了上下文
所以一定要注意遍历的节点,确保需要遍历的节点一定保持正确的前后顺序
为了让里面的节点被先遍历
我将MemberExpress改成了CallExpression来进行递归函数的解析,往上挪了一层
此时因为有内层节点,可以触发exit先遍历内层后遍历外层
这时就得到了正确解密的内容