关注它,不迷路。
本文章中所有内容仅供学习交流,不可用于任何商业用途和非法用途,否则后果自负,如有侵权,请联系作者立即删除!
1. 需求分析
之前有星友发了这个链接在群里:
https://thetanuts.org/
发现有混淆的代码,拿来做实战还原的案例不错,因此有了这篇文章。
但是现在网站访问不了了,大家可以自行到知识星球里下载混淆样本。
https://t.zsxq.com/10m1trpX3
2. 思路详解
拿到混淆代码,首先的是去分析,那些地方是可以进行还原处理的,一步一步来,会有种 "柳暗花明又一村"的感觉。
将代码格式化以后,它长这样:
这种十六进制字符串和Unicode字符串先还原,使用下面的插件:
const simplifyLiteral = {
NumericLiteral({node}) {
if (node.extra && /^0[obx]/i.test(node.extra.raw)) {
node.extra = undefined;
}
},
StringLiteral({node})
{
if (node.extra && /\\[ux]/gi.test(node.extra.raw)) {
node.extra = undefined;
}
},
}
这一步还原后,继续分析:
可以看到,它有很多的 CallExpression,并且,其callee子节点的类型是有差异的,如:
__p_6473343178["call"](undefined, 226)
和:
__p_6473343178["apply"](undefined, [144]);
或者:
__p_6473343178(134);
这是三种不同的函数调用,因此,写个代码将其统一一下,更有利于我们进行下一步分析:
__p_6473343178(226);
__p_6473343178(144);
__p_6473343178(134);
还原的AST代码如下:
const changeForCall =
{
CallExpression(path)
{
let {callee,arguments} = path.node;
if (!types.isMemberExpression(callee) || arguments.length < 2 || !types.isIdentifier(arguments[0],{"name":"undefined"}))
{
return;
}
let {object,property} = callee;
if (types.isStringLiteral(property,{"value":"call"}))
{
let callNode = types.CallExpression(object,arguments.slice(1));
path.replaceWith(callNode);
return;
}
if (types.isStringLiteral(property,{"value":"apply"}) &&
arguments.length == 2 && types.isArrayExpression(arguments[1]))
{
let callNode = types.CallExpression(object,arguments[1].elements);
path.replaceWith(callNode);
}
}
}
traverse(ast, changeForCall);
这一波还原之后,代码成这样了:
可以发现,对于每一个 CallExpression,它的实参都是字面量,之前见过,如果函数是个纯函数,且与浏览器环境无关,那我们是可以进行还原的。
这种函数调用还原,就考验大家的抠代码能力了,思路就是先把函数定义抠出来,在node下运行,然后缺啥补啥。最后打印出来的字符串不是乱码,至少可以确认代码成功抠取了。
如下图所示:
代码抠出来后,能在node下正常运行并打印的字符串不是乱码,可以判断抠取对了。
接下来就是还原所有的函数调用了:
const callToString = {
"CallExpression"(path) {
let node = path.node;
let {callee, arguments} = node;
if (!types.isIdentifier(callee, {
"name": "__p_6473343178"
})) {
return;
}
if (arguments.length != 1 || !types.isNumericLiteral(arguments[0])) {
return;
}
let value = __p_6473343178(arguments[0].value);
console.log(path.toString(), "-->", value);
path.replaceWith(types.valueToNode(value));
},
}
这个插件运行后,还原的代码如下:
剩下的部分我相信大部分人都能够还原了,无非就是变量定义的还原,常量折叠,Array类型的还原等。这在星球的置顶二里面都能找到现成的插件。这里不做讲述。
今天的文章就分享到这里,后续分享更多的技巧,敬请期待。