AST实战|遇到混淆的代码怎么下手(初级混淆)
看到群里的徐大哥被 webpack 折磨许久,写下此混淆还原入门篇,仅供学习参考。
实战地址:
https://match.yuanrenxue.com/match/16
怎么分析就不讲了,直接将 732 这个函数抠下来,去头去尾,形成一个AST可解析的完整代码。
格式化后的代码如下:
通过分析代码,可以发现,有很多函数调用的地方:
以及:
它们有一个很明显的特征,就是实参都是字面量。那就好办啊,直接把它们全部都计算出来,然后再把计算出来的值全部替换这些函数调用表达式,不就更直观了么。
混淆还原的通用法则一:实参全部为字面量的函数可还原。
下面就是写插件了,仔细观察后发现,这些 e, t, o,u 函数名,都是重复赋值,其实都是 l 函数:
因此,我们把 l 函数 及其环境都扣取出来,浏览器运行后发现卡死,肯定有代码展开检测,那就压缩代码吧。使用之前文章 工具推荐|一款爬虫界的神兵利器,值得拥有 介绍的压缩工具,效果还挺好:
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,t,n,r){r=r||{};for(var i=t+"="+n,o=0,a=e.length;o<a;o++){var s=e[o];i+="; "+s;var l=e[s];e.push(l),a=e.length,!0!==l&&(i+="="+l)}r.cookie=i},removeCookie:function(){return"dev"},getCookie:function(e,t){var n,r=(e=e||function(e){return e})(new RegExp("(?:^|; )"+t.replace(/([.$?*|{}()[]\/+^])/g,"$1")+"=([^;]*)"));return n=133,s(++n),r?decodeURIComponent(r[1]):void 0},updateCookie:function(){return new RegExp("\\w+ *\\(\\) *{\\w+ *['|\"].+['|\"];? *}").test(r.removeCookie.toString())}}).updateCookie())?o?r.getCookie(null,"counter"):r.removeCookie():r.setCookie(["*"],"counter",1);var l=function(e,t){return _0x34e7[e-=188]};
浏览器再次运行,可以得到正确的结果:
再就是编写插件了,这里我们遍历函数表达式,代码如下:
-
const callToLiteral=
-
{
-
CallExpression(path)
-
{
-
}
-
}
然后把上面的环境代码补到插件上面,使它可以进行调用。
再就是在线解析(有个小坑,自己解决),对照分析:
写出判断规则:
-
const callToLiteral=
-
{
-
CallExpression(path)
-
{
-
let {callee,arguments} = path.node;
-
if (!types.isIdentifier(callee) || arguments.length != 1)
-
{
-
return;
-
}
-
-
let name = callee.name;
-
-
if (!['e','t','o','u'].includes(name) || !types.isNumericLiteral(arguments[0]))
-
{
-
return 0;
-
}
-
-
}
-
}
判断后,计算出结果,并进行替换:
-
const callToLiteral=
-
{
-
CallExpression(path)
-
{
-
let {callee,arguments} = path.node;
-
if (!types.isIdentifier(callee) || arguments.length != 1)
-
{
-
return;
-
}
-
-
let name = callee.name;
-
-
if (!['e','t','o','u'].includes(name) || !types.isNumericLiteral(arguments[0]))
-
{
-
return 0;
-
}
-
-
let value = l(arguments[0].value);
-
path.replaceWith(types.valueToNode(value));
-
}
-
}
这样,一个简单的替换插件就完成了,这只是插件,还要记得调用哈。
traverse(ast, callToLiteral);
还原后,在混淆代码中可以把 l函数 及其环境删除,因为代码中没有地方再用到它了。
部分还原后的代码截图,效果如下:
可以看到,这比之前的代码清爽多了。代码当然还是可以继续还原的,在这里不多讲。感谢阅读。
插件代码已发在星球,需要学习的自取:
https://t.zsxq.com/jiMb23J
文章来源: blog.csdn.net,作者:悦来客栈的老板,版权归原作者所有,如需转载,请联系作者。
原文链接:blog.csdn.net/qq523176585/article/details/121847865
- 点赞
- 收藏
- 关注作者
评论(0)