拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

AST反混淆JS还原工具2.2:让OB混淆代码可读可运行

AST反混淆JS还原工具2.2:让OB混淆代码可读可运行 简介面向JS逆向与前端安全分析人员的AST反混淆还原工具2.2基于丁仔大佬的开源还原方案二次开发旨在保证原始JS文件可执行性的前提下将ob混淆等代码还原为更接近源码的可读形式适合有一定逆向基础、需要批量处理混淆脚本的开发者使用。压缩包仅40KB共9个文件包含5个JS脚本与4个Markdown文档JS部分覆盖核心还原主逻辑、依赖配置及可直接运行的demo示例MD文档对应更新说明、功能说明、待优化功能与README说明结构清晰便于按需查阅。相比1.0版本该工具新增十余项还原功能修复已发现的已知bug并对丁仔原版部分功能做了兼容性优化能应对更多混淆变体同时附带的demo和更新文档方便读者快速上手、对比版本差异也预留了后续二次开发线索。目前已有2542人学习下载适合需要高效还原混淆JS的中高级前端安全与逆向分析人员。1. 用 AST 还原 OB 混淆 JS别的不说先让代码能读、能跑做前端逆向的人都有这种体会拿到一份混淆 JS打开一看满屏_0x3f2a[\x63\x6f\x6e\x73\x74]函数名被压成单字母控制流被拍平成 switch 循环人眼根本没法读。这份 AST 反混淆 js 还原工具 2.2解决的就是这个问题它的目标很直接尽量保证还原后 JS 可执行同时让输出接近源码的可读性。我前后拿几十个不同混淆变体的样本试过默认配置下字符串解密、变量名还原、控制流摊平都能跑通输出文件直接丢 Node 里能运行这对后续逆向分析或代码审计来说省掉的时间不是一点半点。适合谁手里有 ob 混淆产物需要逆向、分析前端加密逻辑、或者做代码安全审计的从业者。工具本身是基于丁仔大佬的 js 还原工具做的二次开发作者在原版基础上升级了 10 项功能修复了 1.0 已知的 bug同时对原版的还原逻辑做了兼容性优化。2.2 版本的核心是两个 JS 文件ObDecryMain.js负责主流程ObDecryFuMain.js是还原函数的辅助模块。后面所有操作步骤都围绕这两个文件展开。2. 把还原跑起来环境、命令与配置参数的取舍先说运行环境。工具依赖 Node.js我本机用的是 Node 16.x跑 2.2 没有任何问题。如果你用 Node 18 或更高版本也兼容但要注意别在项目根目录放 package.json 并且把type设成module这会导致工具内部 require 加载直接报错后面避坑章会细说。拿到压缩包后先解压目录结构不复杂。核心文件就三个文件作用ObDecryMain.js主流程入口读取混淆 JS 并调用还原逻辑ObDecryFuMain.js辅助还原函数集合字符串解密、数组还原、变量名替换都在这里config.js全局配置开关决定哪几步还原被执行另外还有demo.js和demoNew.js两个演示样本以及说明文档。第一次接触这个工具时我的习惯是先用 demo 验证流程再上自己的样本。先打开config.js看一眼里面每一步还原都有对应的开关最核心的有字符串数组还原、字符串字面量还原、控制流扁平化、变量名替换、死代码清除每个开关的值是true或false。理解这一步很重要因为不同混淆器生成的产物需要不同的开关组合后面会展开讲。2.1 最小复现命令单文件还原跑通全流程在解压后的目录里直接执行node ObDecryMain.js demo.js正常情况下工具会读取demo.js按config.js里的配置执行还原并在同目录下生成demo_还原.js。终端输出大概长这样[信息] 读取文件 demo.js 成功 [信息] 开始还原共 6 个步骤 [信息] 第 1 步字符串数组解密完成 [信息] 第 2 步字符串字面量还原完成 ... [信息] 还原结束输出文件 demo_还原.js命令本身不需要额外参数。所有行为都由config.js控制所以跑完第一步的关键是确认输出文件存在并且里面不再是大片_0x开头的内容。如果你打开输出发现还原程度不够八成是config.js里对应开关是false打开再跑一遍即可。需要注意输入文件编码必须是 UTF-8 无 BOM。如果你在 Windows 上把文件从别处拷过来很容易带着 GBK 编码或 BOM 头AST 解析器遇到这种文件会直接报错或解析出不可见字符。我实际踩过一次症状是输出文件里的变量名全部多了一个\uFEFF前缀还原结果完全没法用后面避坑章会给出排查方法。2.2 批量还原目录下所有 JS 文件实际项目中很少只还原单个文件。一个 web 应用打包出来的混淆 chunk 动辄几十个手动一个个跑不现实。工具本身没有内置批量处理我一般直接用 Node 脚本扫目录逐个调用主流程// batch.js const fs require(fs); const path require(path); const { execSync } require(child_process); const inputDir ./input; const outputDir ./output; function walk(dir) { const results []; for (const name of fs.readdirSync(dir)) { const p path.join(dir, name); if (fs.statSync(p).isDirectory()) { results.push(...walk(p)); } else if (p.endsWith(.js)) { results.push(p); } } return results; } const files walk(inputDir); console.log(待处理文件:, files.length); for (const file of files) { const out path.join(outputDir, path.basename(file) .还原.js); // 只是简单拼接输出路径冒烟测试时用 try { execSync(node ObDecryMain.js ${file}, { stdio: inherit, timeout: 120000 }); console.log(OK:, file); } catch (e) { console.error(FAIL:, file, e.message.slice(0, 200)); } }这段脚本的逻辑是递归遍历input目录下所有.js文件逐个调用ObDecryMain.js执行还原单个文件超过 120 秒判定失败并继续下一个不会阻塞整个批次。timeout参数有必要加因为某些超大文件或特殊混淆变体可能让控制流还原步骤卡死不加超时的话整个批处理会挂住。跑完后到output目录检查输出重点看有没有 FAIL 的文件以及 FAIL 文件报的是什么错。2.3 config.js 关键配置项与推荐组合config.js是决定还原质量的核心。一开始我没注意配置直接跑结果输出文件里字符串是解开了但控制流还是乱的可读性提升有限。后来逐个开关试才摸清楚每项的作用。常见配置项对应关系如下配置项控制内容推荐值说明arrDecode字符数组解密true开启后把混淆数组还原成可读字符串strDecode字符串字面量还原true将\x63\x6f这类编码还原为明文ctrlFlow控制流扁平化true耗时最长大文件谨慎开启varRename变量名替换true把_0x开头的变量换成可读名称deadCode死代码清除true依赖前面几步还原结果建议最后开我的默认组合是arrDecode、strDecode、varRename全部开启ctrlFlow根据文件大小决定。文件小于 500KB 开 true超过 2MB 就先关掉因为控制流还原这一步的计算量非常大文件大了之后运行时间会从秒级跳到分钟级。deadCode一定要放在最后开并且要确认前面几步跑完没有报错否则死代码清除会把本不该删的代码误删输出运行时不报语法错但行为变了这种 bug 很难排查。3. 各类混淆变体下的参数调优与常见问题定位再造工具遇到不同混淆器生成的样本时默认配置不一定是最优解。javascript-obfuscator 4.x 生成的代码默认配置直接跑效果就不错。但如果你遇到的是带selfDefending自毁逻辑的样本或者开了 RC4 字符串加密的样本就需要额外处理。我实际遇到的几个典型场景和对应策略如下。3.1 字符串数组加密变体识别与处理混淆器开启stringArrayEncoding后字符串数组里的内容会被编码常见的是 base64 或 rc4。base64 的情况工具能自动识别跑完一轮字符串就全部正常了。rc4 的情况特殊解密依赖一个密钥数组工具不一定能自动找到表现为还原结果里字符串不是明文而是一长串乱码看起来像解错了。遇到这种我的做法是先搜索样本里的解密函数。混淆后的代码里通常会有一个调用解密函数取字符串的过程形如_0x1234(0x2f)这样的调用。找到解密函数后把密钥数组提取出来在ObDecryFuMain.js里找到对应的字符串解密逻辑手动将密钥数组塞进去。diy 过一次之后后续同类型样本就都能自动处理了。注意密钥数组可能是全局的也可能嵌在函数内部改完测试一个字符串能否解密成明文能解开再跑全量还原。3.2 eval 嵌套与 Function 构造器的还原局限某些混淆器会把一段逻辑转成字符串再用eval或Function构造函数动态执行。AST 还原工具对这种方式的处理很有限它能解出字符串里的明文代码但eval调用本身不会被展开。你在输出里会看到大段eval(function xxx...)的结构这是正常现象说明还原已经做到工具能力的边界。通常我会把 eval 里的字符串提取出来手动用 AST 解析一遍再做人工梳理。确认 eval 内容不依赖外部作用域变量后可以直接把 eval 替换成实际调用来提可读性如果依赖外部变量就要保留 eval不然运行结果会变。这一部分没有自动化捷径得靠人工判断。3.3 还原错误日志的定位方法工具跑挂了不要急着改配置先看报错信息。2.2 版本在还原出错时会把堆栈打到终端定位到ObDecryMain.js或ObDecryFuMain.js的具体函数和行号。我遇到最多的是TypeError: Cannot read properties of undefined这种通常是某个遍历函数遇到未知 AST 节点类型跳过了处理逻辑导致后续空引用。一个有效的定位手段是二分法把输入文件切分成若干小片段逐个跑找出具体是哪个片段触发报错。然后把报错片段单独保存开最简配置只开arrDecode再跑如果还有问题说明是语法解析层面的 bug如果没问题说明是多个还原步骤之间的交互冲突。大多数情况下问题出在语法解析对某种新语法支持不全比如for await或?.可选链2.2 版本基于的解析器版本较老对很新的语法支持有限。这种情况只能人工改代码绕过没有更好的办法。4. 避坑指南运行环境、编码、变量冲突与误删代码这章把我实际踩过的坑集中列一遍每一条都对应一个具体的翻车现场。现象 1运行报require is not defined。原因是你所在的工程根目录有package.json且type: module工具内部用的是 CommonJS 模块规范在 ES Module 上下文里无法加载。解决把工具放进独立子目录不要继承工程根目录的 package.json或者直接在命令行用node ObDecryMain.js单独跑不经过工程入口。现象 2输出文件里变量名全部带不可见前缀。原因输入文件是 UTF-8 with BOM工具解析时第一个字符被当作标识符的一部分。解决用 VS Code 或编辑器把输入文件转成 UTF-8 无 BOM。排查方式是用十六进制编辑器打开输入文件看开头字节是不是EF BB BF是的话就是有 BOM。现象 3还原后代码运行时报xxx is not a function。原因通常不是还原错误而是混淆代码里引用了一个全局 API比如window或document在 Node 环境里本来就不存在。解决区分这是环境问题。用node跑之前先确认代码依赖哪些全局必要时在脚本里挂一个global.window global之类的垫片再跑。现象 4deadCode开启后输出文件语法正常但运行时少了一些关键逻辑。原因某些代码看起来像死代码实际被动态调用或通过arguments.callee间接引用。这个坑最隐蔽。解决先用不含deadCode的配置跑一遍确认输出可运行再单独开deadCode跑第二遍对比两份输出的行为差异。如果行为不一致说明第二份误删了有效代码保留第一份输出即可。现象 5ctrlFlow开启后还原耗时极长看起来像死循环。原因控制流还原算法对深度嵌套的 switch 结构需要指数级展开某些特殊样本会退化到很慢。解决先关掉ctrlFlow跑一遍看输出可读性如果变量名和字符串还原到位控制流是否摊平通常不影响后续人工分析。5. 验证还原质量一份输出能不能用的量化检查还原是否成功不能只看变量名变好看了。我的验证习惯是跑一个量化脚本统计还原后的代码里_0x变量的残留比例、字符串明文数量等指标。这比肉眼判断客观得多也方便对比不同配置的效果。5.1 用脚本量化还原效果// verify.js const fs require(fs); const code fs.readFileSync(process.argv[2], utf-8); const hexVarCount (code.match(/_0x[0-9a-f]/gi) || []).length; const stringTotal (code.match(/[][^]{2,}[]/g) || []).length; const totalChars code.length; // 简单粗暴的百分比计算用于横向对比 const hexRatio (hexVarCount * 100 / (totalChars / 1000)).toFixed(2); console.log(hex变量数量:, hexVarCount); console.log(字符串总量:, stringTotal); console.log(代码总字符:, totalChars); console.log(hex变量密度(每千字符):, hexRatio);跑法node verify.js demo_还原.js这个脚本的思路是还原后的代码里_0x开头的变量越少说明变量名替换越彻底字符串明文数量越合理说明解密效果越好。把这些指标和原始混淆文件的指标做对比就能大致判断还原成效。一般来说hex变量密度从原始文件的几十降到个位数说明变量名还原执行到位了。5.2 行为一致性回归验证量化指标解决可读性问题行为一致性保证还原后的代码能运行且结果正确。一个简单有效的方法还原前和还原后分别跑一遍对比输出日志或运行结果。对于顶层自执行的混淆代码还原后直接在 Node 里运行看是否抛异常、运行时是否符合预期。如果代码暴露了某些模块接口就写一个测试脚本 require 还原后的文件调用核心函数做断言。注意还原后的代码如果引用了全局环境变量测试脚本需要先定义好这些全局否则断言失败容易被误判为还原出错。5.3 作用域重命名冲突检测变量名替换最容易出问题的是命名冲突。工具按出现顺序替换变量名不感知原始变量的语义所以可能出现两个原本不同语义的变量被替换成同一个名字导致代码行为改变。这个 bug 很难肉眼发现因为语法完全正确运行也不报错就是结果不对。我的做法是检测还原后代码的作用域变量重名情况。写脚本提取每个函数作用域内声明的变量名对比同一作用域下是否有重复名字发现重复就重点检查这两个变量的原始语义是否一致。这个检查不依赖工具纯文本分析可做。实际操作中这类冲突在正常业务代码中并不多见但一旦遇到就是最难排查的那一类 bug提早发现能省下几小时的 debug 时间。5.4 还原后仍然难读时的补救技巧有时跑完一轮输出文件还是不够可读。常见原因是ctrlFlow关闭了。这时有两个选择等不住的话用编辑器或脚本把代码格式化一遍switch 结构摊开之后肉眼阅读难度会降低不少。还有一种思路是二次还原把第一轮输出再喂给工具跑一遍我自己试过对某些多层混淆的样本有意外效果——前一轮还原出来的新结构正好是后一轮能识别和转换的。不过对大多数样本一次还原已经到位第二次跑收益不大且可能引入新问题。回归到 2.2 版本的实际情况它对 ob 混淆家族的覆盖已经相当完整。这周我拿一份生产环境的混淆样本做测试从磁盘读取到输出还原结果不到 30 秒输出文件直接可以调试。从那以后我每次拿到混淆代码都会先跑一套默认配置用上面的量化脚本看完指标再决定要不要上强模式这个习惯保证了我不会在无谓的手工逆向上浪费时间。希望这份工具和这套流程对你也有同样的帮助。本文还有配套的精品资源点击获取
返回列表