AST 反混淆处理示例

本文章只做技术探讨, 请勿用于非法用途。

引言

爬虫做多了, 见到的混淆想必也不会太少。简单的混淆代码, 我们可以硬顶着调试下去。但是对于过于复杂的混淆代码, 不妨试试用 AST 工具, 反混淆处理后会方便很多。

关于技术本身, 不进行过多的介绍, 感兴趣可以自行百度。

关于 AST 的一些资料

astexplorer.net/ (语法调试)

www.babeljs.cn/docs (nodejs 的 AST 操作库 babel)

github.com/babel/babel... (babel 源码)

对 AST 有了简单的了解之后, 这里我会以 nodejs 的 babel 库为中心, 简单聊一下在实际中如何来使用它。

实战

今天用 AST 来处理一下 Akamai 的加密, 代码混淆程度很高, 部分混淆代码如下。

JavaScript 复制代码
// 部分混淆代码示例
var MZx = new (Fq[K3()[Y7(Rt)](kY, SM, jXc)])(fL,Ns[Nw])[lz()[vg(Gb)](Gb, IR, Mb)](jb()[E6(Zs)](zW, JT, sA));
var Whc = MZx[b8()[r7(tW)](lf, Jl, vt(vt(fL)), vt(vt(fL)))](b8()[r7(j1)](wA, bh, Tz, vt([])));
var wQx = MZx[lz()[vg(St)].apply(null, [pg, P9, rS])](Whc[lz()[vg(YA)].apply(null, [qE, Rs, tN])]);
var RVx = MZx[jt(typeof lz()[vg(CL)], Bk([], [][[]])) ? lz()[vg(St)](MY, P9, rS) : lz()[vg(kL)](vt({}), Axc, Z2)](Whc[kt()[Jz(Rz)](vb, lr, dD, cR)]);
AXx = P8(X, [Eg(typeof kt()[Jz(tW)], Bk([], [][[]])) ? kt()[Jz(Rg)](kpc, rS, Dl, Ob) : kt()[Jz(NB)](N1, wL, lR, BY), wQx, jb()[E6(pg)].call(null, KH, z0, cR), RVx]);
var dQx = new (Fq[K3()[Y7(Rt)].apply(null, [kY, OY, jXc])])(Ns[Nw],fL)[lz()[vg(Gb)](zW, IR, Mb)](Eg(typeof lz()[vg(g8)], 'undefined') ? lz()[vg(kL)](Pmc, r2, fPc) : lz()[vg(Hz)].apply(null, [LB, OS, rb]));
var Qcx = dQx[b8()[r7(tW)](lf, Jl, tI, vL)](Eg(typeof b8()[r7(rW)], Bk([], [][[]])) ? b8()[r7(YB)].call(null, PKc, qnc, Et, vt(fL)) : b8()[r7(j1)].apply(null, [wA, bh, hk, Hz]));
var khc = dQx[lz()[vg(St)].apply(null, [XA, P9, rS])](Qcx[lz()[vg(YA)](vb, Rs, tN)]);
var vEc = dQx[lz()[vg(St)].call(null, Cf, P9, rS)](Qcx[kt()[Jz(Rz)](vb, YA, dD, ZO)]);
x2c = P8(X, [Eg(typeof Vt()[QB(OL)], 'undefined') ? Vt()[QB(vB)](dh, nE, cR, vt(vt({})), D5c, vt(vt(vB))) : Vt()[QB(R7)].apply(null, [wl, x2, BY, vt({}), Rg, LB]), khc, kt()[Jz(lr)].apply(null, [Vz, bh, tXc, vt({})]), vEc]);                

感兴趣的话可以自己去看一下源码, 网站放这里。 www.dhl.com/cn-zh/home....

思路

首先来明确一下目的, 我们最终要得到的是一段明文代码, 如下所示。

JavaScript 复制代码
// 目标代码
var MZx = new Fq["OffscreenCanvas"](fL, Ns[Nw])["getContext"]("webgl");

然后是一些重要的事情。

  • 在解混淆的过程中, 必须保持代码原逻辑不变。
  • 加密代码可能会变化, 通过 overwrited 或是 autoresponse 功能自行固定。

原理

通常情况下, 简单的混淆只有一个解密函数, 我们只需要将解密函数提前放入内存, 处理的时候调用, 就能成功解密。但是对于复杂的混淆, 解密函数并不固定, 我们需要基于 js 的语法特点做一些特殊的处理。

JavaScript 复制代码
// js 语法 A
let x = 1, 2; // 最终 x = 1, 语句返回 2。

原理很简单, 就是利用 js 的逗号表达式, 具体流程如下。

JavaScript 复制代码
// 假定待解混淆表达式(func 中为加密部分)
func[Im()[CY(qj)].apply(null, [NF, cD, rX])];
// 定义一个全局变量
let vars = {};
// 改造原表达式
func[vars["Im()[CY(qj)].apply(null, [NF, cD, rX])"] = Im()[CY(qj)].apply(null, [NF, cD, rX]), vars["Im()[CY(qj)].apply(null, [NF, cD, rX])"]];

// 将加密部分转为字符串作为全局变量 vars 的 key, 将执行结果作为 value, 进行赋值, 同时再次返回该值。
// 这样做是为了不改变原代码逻辑(解密函数只运行一次, 且返回值不变)。
// 用处理后的代码替换原代码(因处理逻辑一样, 理论正常运行), 运行后我们可以得到映射关系 vars。
vars = {"Im()[CY(qj)].apply(null, [NF, cD, rX])": "length"};

// 将映射关系放到本地, 做替换后即可得到最终明文。
func["length"]

希望能理解为什么要这样做, 这很重要。

开整

简单对加密代码进行分析, 可以发现一共四种加密形式。

JavaScript 复制代码
// 第一种, 通过 apply 进行加密
lz()[vg(St)].apply(null, [XA, P9, rS])

// 第二种, 通过 call 进行加密
jb()[E6(pg)].call(null, KH, z0, cR)

// 第三种, 直接调用函数加密
b8()[r7(tW)](lf, Jl, vt(vt(fL)))

// 第四种, 通过恒对或恒错的三元表达式进行加密
jt(typeof lz()[vg(CL)], Bk([], [][[]])) ? lz()[vg(St)](MY, P9, rS) : lz()[vg(kL)](vt({}), Axc, Z2)

我们将四种形式, 放在解析网站上, 根据树的结构来进行捕捉。

树示例

捕捉代码示例

捕捉结果

代码的写法不唯一, 可以自行根据树的结构来找不同, 多打印, 能拿到加密语句即可。成功后开始进行代码替换。

替换代码示例

JavaScript 复制代码
// 将替换处理后的代码写入新文件
var MZx = new Fq[_vars["K3()[Y7(Rt)](kY, SM, jXc)"] = K3()[Y7(Rt)](kY, SM, jXc), _vars["K3()[Y7(Rt)](kY, SM, jXc)"]](fL, Ns[Nw])[_vars["lz()[vg(Gb)](Gb, IR, Mb)"] = lz()[vg(Gb)](Gb, IR, Mb), _vars["lz()[vg(Gb)](Gb, IR, Mb)"]]((_vars["jb()[E6(Zs)](zW, JT, sA)"] = jb()[E6(Zs)](zW, JT, sA), _vars["jb()[E6(Zs)](zW, JT, sA)"]));
var Whc = MZx[_vars["b8()[r7(tW)](lf, Jl, vt(vt(fL)), vt(vt(fL)))"] = b8()[r7(tW)](lf, Jl, vt(vt(fL)), vt(vt(fL))), _vars["b8()[r7(tW)](lf, Jl, vt(vt(fL)), vt(vt(fL)))"]]((_vars["b8()[r7(j1)](wA, bh, Tz, vt([]))"] = b8()[r7(j1)](wA, bh, Tz, vt([])), _vars["b8()[r7(j1)](wA, bh, Tz, vt([]))"]));
var wQx = MZx[_vars["lz()[vg(St)].apply(null, [pg, P9, rS])"] = lz()[vg(St)].apply(null, [pg, P9, rS]), _vars["lz()[vg(St)].apply(null, [pg, P9, rS])"]](Whc[_vars["lz()[vg(YA)].apply(null, [qE, Rs, tN])"] = lz()[vg(YA)].apply(null, [qE, Rs, tN]), _vars["lz()[vg(YA)].apply(null, [qE, Rs, tN])"]]);
var RVx = MZx[_vars["jt(typeof lz()[vg(CL)], Bk([], [][[]])) ? lz()[vg(St)](MY, P9, rS) : lz()[vg(kL)](vt({}), Axc, Z2)"] = jt(typeof lz()[vg(CL)], Bk([], [][[]])) ? lz()[vg(St)](MY, P9, rS) : lz()[vg(kL)](vt({}), Axc, Z2), _vars["jt(typeof lz()[vg(CL)], Bk([], [][[]])) ? lz()[vg(St)](MY, P9, rS) : lz()[vg(kL)](vt({}), Axc, Z2)"]](Whc[_vars["kt()[Jz(Rz)](vb, lr, dD, cR)"] = kt()[Jz(Rz)](vb, lr, dD, cR), _vars["kt()[Jz(Rz)](vb, lr, dD, cR)"]]);
AXx = P8(X, [(_vars["Eg(typeof kt()[Jz(tW)], Bk([], [][[]])) ? kt()[Jz(Rg)](kpc, rS, Dl, Ob) : kt()[Jz(NB)](N1, wL, lR, BY)"] = Eg(typeof kt()[Jz(tW)], Bk([], [][[]])) ? kt()[Jz(Rg)](kpc, rS, Dl, Ob) : kt()[Jz(NB)](N1, wL, lR, BY), _vars["Eg(typeof kt()[Jz(tW)], Bk([], [][[]])) ? kt()[Jz(Rg)](kpc, rS, Dl, Ob) : kt()[Jz(NB)](N1, wL, lR, BY)"]), wQx, (_vars["jb()[E6(pg)].call(null, KH, z0, cR)"] = jb()[E6(pg)].call(null, KH, z0, cR), _vars["jb()[E6(pg)].call(null, KH, z0, cR)"]), RVx]);
var dQx = new Fq[_vars["K3()[Y7(Rt)].apply(null, [kY, OY, jXc])"] = K3()[Y7(Rt)].apply(null, [kY, OY, jXc]), _vars["K3()[Y7(Rt)].apply(null, [kY, OY, jXc])"]](Ns[Nw], fL)[_vars["lz()[vg(Gb)](zW, IR, Mb)"] = lz()[vg(Gb)](zW, IR, Mb), _vars["lz()[vg(Gb)](zW, IR, Mb)"]]((_vars["Eg(typeof lz()[vg(g8)], 'undefined') ? lz()[vg(kL)](Pmc, r2, fPc) : lz()[vg(Hz)].apply(null, [LB, OS, rb])"] = Eg(typeof lz()[vg(g8)], 'undefined') ? lz()[vg(kL)](Pmc, r2, fPc) : lz()[vg(Hz)].apply(null, [LB, OS, rb]), _vars["Eg(typeof lz()[vg(g8)], 'undefined') ? lz()[vg(kL)](Pmc, r2, fPc) : lz()[vg(Hz)].apply(null, [LB, OS, rb])"]));
var Qcx = dQx[_vars["b8()[r7(tW)](lf, Jl, tI, vL)"] = b8()[r7(tW)](lf, Jl, tI, vL), _vars["b8()[r7(tW)](lf, Jl, tI, vL)"]]((_vars["Eg(typeof b8()[r7(rW)], Bk([], [][[]])) ? b8()[r7(YB)].call(null, PKc, qnc, Et, vt(fL)) : b8()[r7(j1)].apply(null, [wA, bh, hk, Hz])"] = Eg(typeof b8()[r7(rW)], Bk([], [][[]])) ? b8()[r7(YB)].call(null, PKc, qnc, Et, vt(fL)) : b8()[r7(j1)].apply(null, [wA, bh, hk, Hz]), _vars["Eg(typeof b8()[r7(rW)], Bk([], [][[]])) ? b8()[r7(YB)].call(null, PKc, qnc, Et, vt(fL)) : b8()[r7(j1)].apply(null, [wA, bh, hk, Hz])"]));
var khc = dQx[_vars["lz()[vg(St)].apply(null, [XA, P9, rS])"] = lz()[vg(St)].apply(null, [XA, P9, rS]), _vars["lz()[vg(St)].apply(null, [XA, P9, rS])"]](Qcx[_vars["lz()[vg(YA)](vb, Rs, tN)"] = lz()[vg(YA)](vb, Rs, tN), _vars["lz()[vg(YA)](vb, Rs, tN)"]]);
var vEc = dQx[_vars["lz()[vg(St)].call(null, Cf, P9, rS)"] = lz()[vg(St)].call(null, Cf, P9, rS), _vars["lz()[vg(St)].call(null, Cf, P9, rS)"]](Qcx[_vars["kt()[Jz(Rz)](vb, YA, dD, ZO)"] = kt()[Jz(Rz)](vb, YA, dD, ZO), _vars["kt()[Jz(Rz)](vb, YA, dD, ZO)"]]);
x2c = P8(X, [(_vars["Eg(typeof Vt()[QB(OL)], 'undefined') ? Vt()[QB(vB)](dh, nE, cR, vt(vt({})), D5c, vt(vt(vB))) : Vt()[QB(R7)].apply(null, [wl, x2, BY, vt({}), Rg, LB])"] = Eg(typeof Vt()[QB(OL)], 'undefined') ? Vt()[QB(vB)](dh, nE, cR, vt(vt({})), D5c, vt(vt(vB))) : Vt()[QB(R7)].apply(null, [wl, x2, BY, vt({}), Rg, LB]), _vars["Eg(typeof Vt()[QB(OL)], 'undefined') ? Vt()[QB(vB)](dh, nE, cR, vt(vt({})), D5c, vt(vt(vB))) : Vt()[QB(R7)].apply(null, [wl, x2, BY, vt({}), Rg, LB])"]), khc, (_vars["kt()[Jz(lr)].apply(null, [Vz, bh, tXc, vt({})])"] = kt()[Jz(lr)].apply(null, [Vz, bh, tXc, vt({})]), _vars["kt()[Jz(lr)].apply(null, [Vz, bh, tXc, vt({})])"]), vEc]);
          

得到处理后的文件, 将其对浏览器的加密文件进行替换, 然后在浏览器上得到变量 _vars。

获取映射关系

Json 复制代码
// 映射数据结构(部分)
{
    "kt()[Jz(fL)].apply(null, [YA, kf, Zd, kY])": "0",
    "jt(typeof K3()[Y7(kL)], Bk([], [][[]])) ? K3()[Y7(SS)](Xw, vt([]), J2) : K3()[Y7(vB)](Pk, jk, k1)": ".",
    "jt(typeof lz()[vg(vB)], 'undefined') ? lz()[vg(D7)](vt(vB), YP, M8) : lz()[vg(kL)].call(null, Hz, MH, TM)": "9",
    "Eg(typeof K3()[Y7(SS)], Bk([], [][[]])) ? K3()[Y7(vB)](V9, kL, gD) : K3()[Y7(D7)](YA, Xw, qh)": "8",
    "b8()[r7(fL)].apply(null, [EV, ml, R7, vt(vt([]))])": "3",
    "jb()[E6(D7)].call(null, bg, rc, R7)": "7",
    "Eg(typeof K3()[Y7(SS)], Bk([], [][[]])) ? K3()[Y7(vB)].call(null, fD, rW, Bh) : K3()[Y7(Nw)](EL, Ik, KC)": "2",
    "lz()[vg(Nw)](Vf, bH, wJ)": "4",
    "jb()[E6(Nw)].call(null, g8, v8, vL)": "5",
    "K3()[Y7(Ik)](SS, SL, lR)": "6",
    "b8()[r7(SS)].call(null, Q5, ct, Vv, g8)": "1",
    "lz()[vg(fL)].apply(null, [vt([]), wO, jS])": "length",
    "Eg(typeof K3()[Y7(fL)], Bk('', [][[]])) ? K3()[Y7(vB)](fFc, YA, Cd) : K3()[Y7(fL)](Ib, GN, rQc)": "JV",
    "lz()[vg(SS)](Tk, Ycc, Vf)": "parseFloat"
}

取得映射关系后, 存入本地, 然后进行替换就能成功解混淆。

解混淆代码示例

JavaScript 复制代码
// 对应的解混淆结果
var MZx = new Fq["OffscreenCanvas"](fL, Ns[Nw])["getContext"]("webgl");
var Whc = MZx["getExtension"]("WEBGL_debug_renderer_info");
var wQx = MZx["getParameter"](Whc["UNMASKED_VENDOR_WEBGL"]);
var RVx = MZx["getParameter"](Whc["UNMASKED_RENDERER_WEBGL"]);
AXx = P8(X, ["vendor", wQx, "renderer", RVx]);
var dQx = new Fq["OffscreenCanvas"](Ns[Nw], fL)["getContext"]("webgl2");
var Qcx = dQx["getExtension"]("WEBGL_debug_renderer_info");
var khc = dQx["getParameter"](Qcx["UNMASKED_VENDOR_WEBGL"]);
var vEc = dQx["getParameter"](Qcx["UNMASKED_RENDERER_WEBGL"]);
x2c = P8(X, ["vendor2", khc, "renderer2", vEc]);

一些值得注意的点

网站格式检测问题?

多数网站都会有压缩检测, 替换代码之前可以找工具进行压缩之后再替换。

像这个网站, 还存在代码检测, 检测你是否修改了部分代码, 可以通过 hook 调试定位到检测位置, 做对应处理即可。

最终代码是否可用?

对于该方案, 其实最终替换后的代码已经不可用, 只能作为调试用的参考了。 原因就是改变了原代码的运行逻辑, 解密函数已经不再被调用了, 可能存在一些计数之类的逻辑, 导致无法通过, 但是对我们的调试还是会方便很多。

总结

算是提供一种思路吧, 当作抛砖引玉, 至于用不用得到, 全看大家了。

做个预告, 也给自己立个 flag, 近期有时间, 会把 Akamai 相关的东西做了, 也就是尽可能系统的聊一下这个网站的坑, 有兴趣的多关注一下吧。

更新时间不固定, 视心情而定了, 周末快乐, 各位 ~。

请洒潘江,各倾陆海云尔。

相关推荐
二川bro3 小时前
第44节:物理引擎进阶:Bullet.js集成与高级物理模拟
开发语言·javascript·ecmascript
越努力越幸运5083 小时前
JavaScript进阶篇垃圾回收、闭包、函数提升、剩余参数、展开运算符、对象解构
开发语言·javascript
程序员ys3 小时前
Vue的响应式系统是怎么实现的
前端·javascript·vue.js
aduzhe3 小时前
关于在嵌入式中打印float类型遇到的bug
前端·javascript·bug
鹏多多4 小时前
vue过滤器filter的详解及和computed的区别
前端·javascript·vue.js
孟陬4 小时前
在浏览器控制台中优雅地安装 npm 包 console.install('lodash')
javascript·node.js
Moment4 小时前
LangChain 1.0 发布:agent 框架正式迈入生产级
前端·javascript·后端
晓得迷路了4 小时前
栗子前端技术周刊第 106 期 - pnpm 10.21、Node.js v25.2.0、Bun v1.3.2...
前端·javascript·html
码上成长4 小时前
<script setup> 实战模式:大型组件怎么拆?
开发语言·javascript·vue.js