样本:https://mooc1-api.chaoxing.com/a ... m/js/enc_js_exam.js
拿到以后先跑了个格式化(记住这个格式化
看一眼 有个数组 _0x47dc 里面有一些看起来像 base64 的东西
直接找引用,定位到一个 IIFE 的参数 _0x430ef9 和函数 _0x1627 里面
前面那个函数里面有些什么 setCookie 什么的,先不管,后面那个函数搜一下可以看到很多形如 _0x1627('0xe', 'VwpN') 的调用,显然是对字符串进行了加密,这个函数用于解密,那么先考虑 AST 变换进行解密
看了一下解密算法,应该是 RC4,但是好像有一些细节改动,考虑直接把字符串解密函数扒出来跑
先直接把整个脚本放到浏览器环境里运行,浏览器直接卡死,看起来像是某处检测未通过造成了死循环
上动态调试,单步定位到死循环在第一个 IIFE 里面,我们仔细看一下
虽然他名字叫做什么 setCookie removeCookie 看起来像个操作 Cookie 的类,但是函数 _0x2fcb74 里为什么在对 removeCookie 序列化?再看,有一个 regex,看起来他在检测 removeCookie 是否被格式化了
再看看何处调用了这个函数:就在几行下面,如果这个函数返回 false,那么会调用 setCookie。看一眼 setCookie 里面,是一段巨长的看起来像是在拼接 cookie 的函数,但是逻辑很奇怪:
它接收 _0x324822(数组)、_0x30597e(名称)、_0x2509dd(值)、_0x2fe755(选项对象)
循环 _0x324822 数组,把每个元素拼接到 _0x2f3d32 字符串后面,并且还会从 _0x324822 中按属性名取值,再 push 回数组,这会造成无限循环(因为循环条件里的 _0x578097 在循环中不断被更新为新的数组长度)。
最后把拼接的字符串赋给 _0x2fe755.cookie(如果 _0x2fe755 是 document 或某个对象,就真的设置 cookie,但这里调用时传入的是 ['*'] 和 { } )。
所以,它实际上的作用是卡死浏览器
我们把 removeCookie 函数还原成没格式化过的样子,再次执行,发现跑了一个无限递归;我们直接搜 .test,发现这样的检测还有四处,都 patch 掉以后,就成功跑起来了
于是我们现在可以直接在 AST 变换里调用它本身的解密函数 _0x1627 来解密,写一个 babel transformer:
[JavaScript] 纯文本查看 复制代码
export default function (babel) {
const { types: t } = babel;
return {
name: "ast-transform",
visitor: {
CallExpression(path) {
if (path.node.callee.name == '_0x1627')
path.replaceWith(t.StringLiteral(decryptStr(Number(path.node.arguments[0].value), path.node.arguments[1].value)))
}
}
};
}}
这样字符串就成功解密了;我们看一下解密后的函数,里面有很多类似 if ("lywZo" === "lywZo") { 的假 if,所以再跑一个死代码移除和假 if 移除,代码就恢复到一个比较可读的地步了。
可以看到实际的业务逻辑是在 _0xc11b2e 里,是一个类似签名的东西,分析大概就到在这里 |