上周直播讲解了第16题混淆js的还原,本文再做个补充,方便星友们学习AST插件的编写技巧。
https://match.yuanrenxue.com/match/16
抓包,分析接口数据:
点击去,即可发现加密参数 m 及 t 赋值的地方:
在 r.m = n[e(528)](btoa, p_s) 这行代码打上断点,再次请求,断住后,控制台输入 btoa并回车,双击下面的代码,来到这里:
代码往上翻,把整个 732 相关的代码抠下来,并保存到js文件中。
代码扣下来格式化,看着有点像 ob 混淆,但与常规的还是有点区别,用我那个一键还原无法还原,不过没关系,见招拆招就好。
分析混淆文件,看到了这些函数调用:
之前的文章也讲过,像这种 函数调用,其实参为字面量,大概率是可以进行还原的,因此我们先进行还原?
不慌,我们看看它的函数定义在哪里:
可以看到,函数定义 l ,然后 l 又赋值给了e,看到这种情况,我们可以先将e替换成l,即还原重复定义的变量。
AST源代码如下:
//删除重复定义且未被改变初始值的变量
const deleteRepeatDefine = {
"VariableDeclarator|FunctionDeclaration"(path)
{
let {node,scope,parentPath} = path;
if (path.isFunctionDeclaration())
{
scope = parentPath.scope;
}
let name = node.id.name;
const binding = scope.getBinding(name);
if (path.isFunctionDeclaration())
{
if(!binding || binding.constantViolations.length > 1)
{
return;
}
}
else
{
if(!binding || !binding.constant) return;
}
scope.traverse(scope.block,{
VariableDeclarator(path)
{
let {node,scope} = path;
let {id,init} = node;
if (!types.isIdentifier(init,{name:name})) return;
const binding = scope.getBinding(id.name);
if (!binding || !binding.constant) return;
scope.rename(id.name,name);
path.remove();
},
})
scope.crawl();
},
}
还原后效果如下:
这样,就把之前的那些不同的函数名全部变成 l了,方便我们进一步分析。
这个时候,我们把l的函数声明 及其环境依赖复制到还原的ast代码中,注意,这里需要进行代码压缩,因为进行格式化后运行,容易导致内存溢出。
PS:不用管ob混淆检测了什么,直接对其检测的代码进行格式万事大吉。
压缩后的代码如下:
var r,o,a,s;_0x34e7=["AqLWq","0zyxwvutsr","TKgNw","eMnqD","thjIz","btoa","MNPQRSTWXY","oPsqh","niIlq","evetF","LVZVH","fYWEX","kmnprstwxy","aYkvo","tsrqpomnlk","HfLqY","aQCDK","lGBLj","test","3210zyxwvu","QWK2Fi",'return /" ',"hsJtK","jdwcO","SlFsj","OWUOc","LCaAn","[^ ]+)+)+[","FAVYf","2Fi+987654","floor","join","EuwBW","OXYrZ","charCodeAt","SkkHG","iYuJr","GwoYF","kPdGe","cVCcp","INQRH","INVALID_CH","charAt","push","apply","lalCJ","kTcRS",'+ this + "',"ykpOn","gLnjm","gmBaq","kukBH","dvEWE","SFKLi","^([^ ]+( +","qpomnlkjih","^ ]}","pHtmC","length","split","ABHICESQWK","FKByN","U987654321","lmHcG","dICfr","Szksx","Bgrij","iwnNJ","jihgfdecba","GfTek","gfdecbaZXY","constructo","QIoXW","jLRMs"],a=_0x34e7,s=function(e){for(;--e;)a.push(a.shift())},(o=(r={data:{key:"cookie",value:"timeout"},setCookie:function(e,o,t,r){r=r||{};for(var n=o+"="+t,i=0,u=e.length;i
现在就可以愉快的进行 函数表达式的还原了,代码如下:
const callToLiteral =
{
CallExpression(path)
{
let {scope,node} = path;
let {callee,arguments} = node;
if (!types.isIdentifier(callee,{name:"l"}) ||
arguments.length != 1 || !types.isNumericLiteral(arguments[0]))
{
return;
}
let arg = arguments[0].value;
let value = l(arg);
path.replaceWith(types.valueToNode(value));
scope.crawl();
},
}
运行上面的插件后,混淆代码变成了这样:
字面量全部还原了。
熟悉ob混淆的朋友都知道,在还原函数调用后,接下来就是还原object对象与控制流,这里发现控制流是for循环:
而一般的 ob混淆都是while 混淆,因此想用我的一键还原,还需要把这里的控制流改一下,即for循环改为 符合ob混淆的while循环。
AST代码如下:
const obForToObWhile =
{
ForStatement(path)
{
let {scope,node} = path;
let {init,test,update,body} = node;
if (!types.isVariableDeclaration(init) ||
test != null || update != null)
{
return;
}
path.insertBefore(init);
let whileNode = types.WhileStatement(types.BooleanLiteral(true),body);
path.replaceWith(whileNode);
scope.crawl();
}
}
这个插件运行后,就可以愉快的使用星球里的一键还原脚本了,还原结果如下:
可以看到btoa函数定义在这里,在删除一些垃圾代码后,723 这个代码最终变成了这样:
var f = "U9876543210zyxwvutsrqpomnlkjihgfdecbaZXYWVUTSRQPONABHICESQWK2Fi+9876543210zyxwvutsrqpomnlkjihgfdecbaZXYWVUTSRQPONABHICESQWK2Fi";
function d(e) {
e = e || 32;
var l = "ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678";
var s = l["length"];
var c = "";
for (i = 0; i < e; i++) {
c += l["charAt"](Math["floor"](Math["random"]() * s));
}
return c;
}
function btoa(e) {
if (/([^\u0000-\u00ff])/["test"](e)) {
throw new Error("INVALID_CHARACTER_ERR");
}
for (var o, a, s, l = 0, c = []; l < e["length"]; ) {
switch (a = e["charCodeAt"](l),
s = l % 6) {
case 0:
delete window;
delete document;
c["push"](f["charAt"](a >> 2));
break;
case 1:
c["push"](f["charAt"]((2 & o) << 3 | a >> 4));
break;
case 2:
c["push"](f["charAt"]((15 & o) << 2 | a >> 6));
c["push"](f["charAt"](a & 63));
break;
case 3:
c["push"](f["charAt"](a >> 3));
break;
case 4:
c["push"](f["charAt"]((o & 4) << 6 | a >> 6));
break;
case 5:
c["push"](f["charAt"]((o & 15) << 4 | a >> 8));
c["push"](f["charAt"](a & 63));
}
o = a;
l++;
}
0 == s ? false || (c["push"](f["charAt"]((o & 3) << 4)),
c["push"]("FM")) : s == 1 && (c["push"](f["charAt"]((15 & o) << 2)),
c["push"]("K"));
return d(15) + window["md5"](c["join"]("")) + d(10);
}
;
整个代码,就剩md5这个函数未定义了,因此在网站上把md5所在的代码也扣下来,按照上面的方式进行还原,也可以得到一个十分清爽的代码,再次不作重复讲解。
好了,文章就讲解到这里,不懂的随时群里问,反混淆后的代码我放在星球里了,需要的自取:
https://t.zsxq.com/BIEQbqZ
感谢阅读,再见!