ARTICLE DETAIL

资讯详情

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

jsjiami.com.v7解密工具:基于AST的JS混淆还原方案

jsjiami.com.v7解密工具:基于AST的JS混淆还原方案 简介这是一款专为前端开发者与逆向分析人员设计的JS代码解密工具包聚焦解决jsjiami.com.v7等主流混淆平台如sojson、obfuscator生成的高强度JavaScript加密问题。工具基于AST解析技术依托Babel插件实现字面量还原、死代码清除、控制流扁平化逆转、条件/循环规范化及特殊函数剥离并在全局加密场景中集成VM2沙箱环境执行动态还原显著提升解密准确性与鲁棒性。压缩包共21个文件含12个核心JS源码主入口src/main.js、插件模块src/plugin、4个JSON配置文件package.json、eslint/prettier配置等、1个YML工作流定义以及LICENSE、README.md等工程支撑文件整体仅65KB轻量易部署。已有1682人学习下载开箱即用安装Node.js后执行npm i再通过npm run decode -- -t sojsonv7等指令即可一键解密附带完整使用教程与清晰目录结构特别适合需快速定位混淆逻辑、调试加密脚本或开展JS安全研究的中高级前端工程师。1. jsjiami.com.v7 解密工具不是“一键还原”而是 AST 层面的精准手术刀最近三个项目交接全卡在同一个点上交付的 JS 文件全是jsjiami.com.v7加密后的黑盒——变量名全乱码、控制流被拆成while(![])、字符串用String.fromCharCode(97, 108, 101, 114, 116)拼接、关键逻辑藏在eval(unescape(...))里。试过在线解密网站要么超时失败要么还原后语法报错用传统正则替换刚改完a[b]又冒出c[d][e]越修越乱。直到跑通这个decode-js-main工具链才真正理解什么叫「AST 解密」它不碰字符串、不猜变量名而是把混淆代码喂给 Babel 解析器生成抽象语法树再用插件逐层剥离死代码、还原字面量、规范化条件分支——就像给加密 JS 做 CT 扫描外科手术保留原始逻辑结构只剔除干扰组织。适合前端逆向、安全审计、老系统维护尤其当你面对的是sojsonv7这类强混淆带 VM2 沙箱逃逸检测或obfuscator的多层嵌套时比纯正则/eval 模拟靠谱十倍。别指望它处理混杂未混淆代码的文件——这是它的边界也是你该提前做的预处理。2. 从零启动Node 环境搭建、依赖安装与命令执行全流程2.1 环境准备Node.js 版本与全局依赖检查这个工具链基于现代 JavaScript 生态对 Node.js 版本有明确要求。必须使用 Node.js v16.14.0 或更高版本推荐 v18.18.0 LTS低于 v14 的版本会因babel/parser的 ES2022 语法支持缺失而直接报错SyntaxError: Unexpected token ??。验证方式很简单node -v # 输出应为 v16.14.0 或 v18.x.x npm -v # npm v8.x.x 或 v9.x.xv10 有已知 lockfile 兼容问题提示如果node -v显示 v12 或更低请立即升级。Windows 用户推荐用 nvm-windows 切换版本macOS/Linux 用户用nvm install 18.18.0 nvm use 18.18.0。不要用sudo npm install -g n权限混乱会导致后续npm i失败。2.2 项目初始化解压、安装与脚本映射下载jsjiami.com.v7代码解密工具详细教程.zip后解压到任意目录建议路径不含中文和空格如~/projects/js-decode。进入根目录执行标准 npm 流程cd /path/to/your/extracted/folder npm install这一步会读取package.json中的dependencies和devDependencies安装核心依赖babel/core,babel/parser,babel/traverse,babel/generator构成 AST 处理四件套vm2提供隔离沙箱环境用于安全执行eval类混淆代码sojsonv7类型必需commander解析-t,-i,-o等 CLI 参数fs-extra替代原生fs支持递归创建输出目录。安装完成后package.json的scripts字段定义了预设命令。查看可用指令npm run # 输出类似 # decode-js1.0.0 # Available scripts: # - decode:common → npm run decode -- -t common # - decode:jjencode → npm run decode -- -t jjencode # - decode:sojson → npm run decode -- -t sojson # - decode:sojsonv7 → npm run decode -- -t sojsonv7 # - decode:obfuscator → npm run decode -- -t obfuscator这些decode:*脚本本质是npm run decode -- -t type的快捷方式避免每次敲长参数。2.3 核心命令执行输入/输出路径、类型选择与参数组合工具入口是src/main.js但日常使用绝不直接node src/main.js。正确姿势是通过npm run触发预定义脚本或手动传参。以下三种调用方式等效按场景选用方式一用预定义脚本推荐新手假设你要解密一个sojsonv7类型的login.min.js输出到login.decoded.jsnpm run decode:sojsonv7 -- -i ./input/login.min.js -o ./output/login.decoded.js方式二用通用 decode 脚本推荐批量处理当需要动态切换类型时直接调用主命令npm run decode -- -t sojsonv7 -i ./input/api.js -o ./output/api.js方式三全局安装后直接调用适合高频用户先在项目根目录执行npm link将本地包注册为全局命令之后可在任意目录运行decode-js -t common -i /tmp/obf.js -o /tmp/clean.js注意-t参数必须是common/jjencode/sojson/sojsonv7/obfuscator之一大小写敏感。-i和-o是可选参数默认值为input.js和output.js相对当前工作目录。若input.js不存在程序会报错ENOENT: no such file不会静默跳过。2.4 插件机制解析为什么sojsonv7必须用 VM2而common不需要工具的核心能力来自src/plugin/目录下的插件体系。每个插件是一个独立的 Babel 插件函数接收babel/traverse的path对象通过path.replaceWith()或path.remove()修改 AST。例如src/plugin/common.js处理高频局部混淆如var _0x1234[a,b]; function _0x5678(){return _0x1234[0];}→ 还原为function getA(){return a;}。它只做 AST 静态分析无需执行代码。src/plugin/sojsonv7.js针对jsjiami.com.v7的特有模式包含两阶段第一阶段用babel/parser解析出eval(unescape(...))中的字符串第二阶段必须调用vm2在沙箱中执行该字符串因为其解密逻辑依赖window、document等浏览器全局对象且常含setTimeout等异步操作。若跳过 VM2 直接eval会因ReferenceError: window is not defined崩溃。这就是为什么sojsonv7类型强制依赖vm2而common类型完全离线运行。你在package.json中看到vm2: ^4.1.0正是为此服务——它不是可选依赖是sojsonv7插件的 runtime requirement。3. 输入文件规范为什么你的input.js总是解析失败3.1 单一主加密函数从“整个文件”到“一段代码”的认知转变工具设计哲学是「一次只解密一个加密单元」。这意味着input.js不能是混合体比如你把混淆后的登录逻辑、未混淆的工具函数、HTML 注释、甚至console.log(debug)全塞进一个文件工具会直接报错Error: Multiple top-level statements detected或SyntaxError: Unexpected token。它期望的输入结构极其纯粹// ✅ 正确仅含一段混淆代码无额外内容 eval(function(p,a,c,k,e,d){efunction(c){return c.toString(36)};if(!.replace(/^/,String)){while(c--){d[c.toString(a)]k[c]||c.toString(a)}k[function(e){return d[e]}];efunction(){return\\w};c1};while(c--){if(k[c]){pp.replace(new RegExp(\\be(c)\\b,g),k[c])}}return p}(0 1(2){3 45.6(7);8.9(4)},[],10,return|function|a|var|b|window|atob|dGVzdA|console|log.split(|),0,{})) // ❌ 错误混杂未混淆代码 function utils() { return ok; } // ← 工具会把它当成加密函数的一部分导致 AST 解析失败 eval(function(p,a,c,k,e,d){/*...*/}(0 1...,[],10,....split(|),0,{})) console.log(debug); // ← 任何非混淆代码都会破坏单入口假设提示实际工作中你常需从.html或.js文件中手动提取加密块。推荐用浏览器开发者工具打开混淆页面 → Sources 面板 → 找到目标 script → CtrlF 搜索eval(或function(p,a,c,k,e,d)→ 复制整段eval(...)行含括号粘贴到新建的input.js中。宁可多建几个input.js也不要拼凑一个“全能文件”。3.2 注释的微妙角色允许存在但位置有讲究工具允许input.js包含注释//或/* */但仅限于加密代码外部。例如// 这是合法注释描述来源 // 来源jsjiami.com.v7 加密类型 sojsonv7 eval(function(p,a,c,k,e,d){/*...*/}(0 1...,[],10,....split(|),0,{})) // 这也是合法注释标记结束但如果注释插入到eval内部比如eval(function(p,a,c,k,e,d){/* 这里加注释会破坏字符串结构 */}(0 1...,[],10,....split(|),0,{}))则unescape解密后得到的 JS 字符串会包含非法字符导致babel/parser解析失败。所以注释只能作为“包装纸”不能侵入加密体内部。3.3 文件编码与 BOMUTF-8 without BOM 是唯一安全选项Windows 记事本默认保存为UTF-8 with BOMBOMByte Order Mark是开头的EF BB BF三个字节。当fs.readFileSync(input.js)读取时BOM 会被当作 JS 代码的前缀导致babel/parser.parse()报错SyntaxError: Unexpected character ïBOM 的 UTF-8 编码首字节0xEF被解析为非法字符。解决方案只有两个用 VS Code 打开input.js→ 右下角点击编码如UTF-8 with BOM→ 选择Save with Encoding→UTF-8用命令行批量转换Linux/macOSsed -i 1s/^\xEF\xBB\xBF// input.js # 移除 BOM注意iconv-lite等库虽能自动检测 BOM但本工具未集成硬编码处理反而增加复杂度。坚持UTF-8 without BOM是最省心的约定。3.4 死代码清理的副作用为什么还原后少了alert(hack)common和obfuscator类型插件默认启用「死代码清理」Dead Code Elimination即移除永远不会执行的分支。例如混淆代码中常见的if (false) { alert(hack); } // 工具会直接删除整行 while(0){ console.log(dead loop); } // 删除 while 块这本是优化行为但如果你的加密逻辑故意用if(false)包裹真实代码某些变种混淆会这样绕过静态分析工具就会误删。此时需修改src/plugin/common.js注释掉path.parentPath.remove()相关逻辑或在main.js中传参禁用 DCE当前版本未暴露开关需改源码。这是「安全 vs 完整」的权衡——默认开启 DCE 是为了产出干净代码但逆向时你得知道它删了什么。4. 避坑指南五个血泪经验总结的高频翻车点4.1 现象npm run decode:sojsonv7报错ReferenceError: window is not defined原因sojsonv7插件内部调用vm2执行解密逻辑时代码依赖浏览器全局对象如window,document,location但vm2默认沙箱是 Node.js 环境没有这些对象。解决在src/plugin/sojsonv7.js的vm2创建处显式注入浏览器模拟对象。找到const vm new NodeVM({ ... })改为const vm new NodeVM({ console: redirect, sandbox: { window: {}, // 提供空 window 对象 document: { createElement: () ({}) }, // 最小化 document location: { href: }, setTimeout: global.setTimeout, clearTimeout: global.clearTimeout } });血泪经验jsjiami.com.v7的sojsonv7模式常检查window.location.href是否含特定域名来触发解密不模拟location会导致解密函数返回空字符串。4.2 现象decode:obfuscator运行后output.js为空文件原因obfuscator类型插件依赖babel/preset-env进行语法降级但package.json中未声明该 preset导致babel/core配置缺失generate()时 AST 转 JS 失败。解决在项目根目录创建babel.config.json内容为{ presets: [babel/preset-env] }并确保npm install babel/preset-env --save-dev。否则obfuscator插件的path.replaceWith(t.stringLiteral())等操作无法正确生成目标代码。4.3 现象input.js含中文字符串解密后output.js出现乱码如查询原因混淆代码中unescape(%u67E5%u8BE2)解码为 Unicode但vm2执行时默认编码为ISO-8859-1无法正确处理 UTF-16 编码。解决在src/plugin/sojsonv7.js的vm.run()前强制设置process.env.NODE_ENCODING utf8并在vm选项中添加env: { NODE_ENCODING: utf8 }。更彻底的方案是重写unescape函数const unescapeFix (s) decodeURIComponent(s.replace(/%u([0-9A-F]{4})/gi, (_, hex) \\u hex)); // 在 vm.sandbox 中挂载 unescapeFix 替代原生 unescape4.4 现象npm run decode:common报错TypeError: Cannot read property name of undefined原因混淆代码使用了this或arguments等动态上下文而common插件的字面量还原逻辑假设所有变量都是静态声明var a x遇到function(){return this.a}就崩溃。解决这不是 bug而是能力边界。common插件只处理「确定性字面量」对this/arguments/callee等动态引用无能为力。此时应切换到obfuscator类型它用babel/preset-env更全面地处理上下文或手动补全this绑定如const obj {a:x}; obj.fn function(){return this.a};。4.5 现象output.js生成成功但浏览器运行时报Uncaught TypeError: Cannot set property xxx of undefined原因工具还原了字符串和变量名但未处理with语句或eval动态作用域。例如with(obj){a1}被还原为obj.a1但原始代码中obj可能是window下的属性还原后obj未定义。解决这是 AST 解密的固有局限——它不模拟运行时环境。对策是1在output.js开头手动注入const obj window || {};2用grep -n with( output.js定位问题行人工转译3接受现实with和深度eval是解密的禁区优先用sojsonv7模式在vm2中执行获取结果而非还原源码。5. 进阶技巧定制插件、批量解密与结果验证三板斧5.1 定制插件为私有混淆算法添加专属解密器当遇到jsjiami.com.v7的定制变种如修改了p,a,c,k参数顺序或加入额外的 XOR 层官方插件失效。此时需在src/plugin/下新建文件例如custom-v8.js// src/plugin/custom-v8.js module.exports function customV8Plugin({ types: t }) { return { name: custom-v8, visitor: { CallExpression(path) { const { callee, arguments: args } path.node; // 匹配自定义混淆函数customDecrypt(abc, 0x123) if (t.isIdentifier(callee) callee.name customDecrypt args.length 2 t.isStringLiteral(args[0]) t.isNumericLiteral(args[1])) { const encrypted args[0].value; const key args[1].value; // 实现你的 XOR 解密逻辑 const decrypted encrypted.split().map(c String.fromCharCode(c.charCodeAt(0) ^ (key 0xFF)) ).join(); path.replaceWith(t.stringLiteral(decrypted)); } } } }; };然后在src/main.js的pluginMap中注册const pluginMap { // ...原有映射 custom-v8: require(./plugin/custom-v8) };最后运行npm run decode -- -t custom-v8 -i input.js -o output.js。关键点插件必须导出 Babel 插件函数visitor对象监听 AST 节点path.replaceWith()替换节点。不要试图在插件里require(fs)—— 这违反 Babel 插件设计原则。5.2 批量解密Shell 脚本自动化处理百个文件面对几十个*.min.js文件手动执行npm run效率低下。写一个batch-decode.shmacOS/Linux#!/bin/bash # batch-decode.sh INPUT_DIR./input OUTPUT_DIR./output TYPEsojsonv7 mkdir -p $OUTPUT_DIR for file in $INPUT_DIR/*.js; do if [ -f $file ]; then basename$(basename $file) output_file$OUTPUT_DIR/${basename%.js}.decoded.js echo Processing $basename... npm run decode -- -t $TYPE -i $file -o $output_file 2/dev/null # 检查输出是否成功非空且含有效 JS if [ -s $output_file ] head -n 1 $output_file | grep -q function\|var\|const; then echo ✓ $basename - ${basename%.js}.decoded.js else echo ✗ $basename failed (empty or invalid output) rm $output_file fi fi doneWindows 用户可用 PowerShell$inputDir .\input $outputDir .\output $files Get-ChildItem $inputDir\*.js foreach ($file in $files) { $outputFile Join-Path $outputDir $($file.BaseName).decoded.js Write-Host Processing $($file.Name)... npm run decode -- -t sojsonv7 -i $file.FullName -o $outputFile 2$null if ((Get-Item $outputFile).Length -gt 100 -and (Select-String -Path $outputFile -Pattern function|var|const -Quiet)) { Write-Host ✓ $($file.Name) - $($file.BaseName).decoded.js } else { Write-Host ✗ $($file.Name) failed Remove-Item $outputFile } }注意批量处理时-i和-o必须用绝对路径或相对于当前 shell 的路径避免npm run工作目录混乱。5.3 结果验证三步法确认解密质量不是“能跑就行”解密完成不等于逻辑正确。我习惯用以下三步交叉验证第一步语法校验防低级错误用eslint检查output.js是否有语法错误npx eslint output.js --no-eslintrc --rule no-unused-vars: off, no-undef: off # 若输出空说明语法合格若有 Parsing error说明 AST 生成有缺陷第二步行为比对核心逻辑在浏览器控制台分别执行原始混淆代码和output.js对比关键输出// 原始混淆代码复制到 console eval(...); // 假设它定义了 window.calcToken() console.log(window.calcToken(abc)); // 记录输出如 xyz123 // output.js复制到 console // ...还原后的 calcToken 函数 console.log(calcToken(abc)); // 应输出相同结果第三步AST 差异分析深度可信用astexplorer.net分别粘贴混淆前如有、混淆后、解密后代码对比 AST 结构。重点关注FunctionDeclaration的body是否完整保留StringLiteral的value是否与原始明文一致BinaryExpression如ab是否未被错误拆分为a.concat(b)。从那以后我每次交付解密结果都强制走一遍这三步语法校验扫雷、行为比对保逻辑、AST 分析验结构。少一步就可能把calcToken还原成getToken上线后 token 校验全挂。希望帮到你。本文还有配套的精品资源点击获取
返回列表