SuperAPI AI Directory

ast-deobfuscate

Coding & development

View repository

Updated 10-15 09:43

About this skill

ast-deobfuscate

For $ARGUMENTS perform AST deobfuscation, maximizing restoration of readable code.

Toolchain: @babel/parser + @babel/traverse + @babel/generator + @babel/types


Working Directory Conventions

project/
├── source/
│   └── original/          # 原始混淆文件(只读,不修改)
│       └── target.js
├── scripts/               # 解混淆脚本(每步一个,一次性,针对目标定制)
│   ├── step1_string_decrypt.js
│   ├── step2_expr_simplify.js
│   └── ...
├── intermediate/          # 中间产物(每步输出,可回退)
│   ├── target_step0.js    # 格式化 + 去反调试
│   ├── target_step1.js    # 字符串解密后
│   ├── target_step2.js    # 表达式简化后
│   └── ...
└── source/
    └── deobfuscated/      # 最终输出
        └── target_deobf.js

Principles: Each step reads the intermediate file from the previous step and outputs a new intermediate file. When an error occurs, you can restart from any intermediate file.

Environment Initialization

The deobfuscation scripts run in Node.js based on the Babel toolchain. Before the first execution, check and install dependencies:

# 在项目根目录检查 package.json,没有则初始化
npm init -y
npm install @babel/parser @babel/traverse @babel/generator @babel/types
# 如需格式化
npm install prettier

Execution Strategy

Overall Principles

This skill is anencyclopedia, not a fixed pipeline. The executor flexibly selects steps based on the actual code:

  1. Enter plan mode: First complete the Step 0 analysis, then enter plan mode, show the user the analysis results and the suggested combination of steps, and execute only after confirmation
  2. Skip steps as needed: If there is no string array, skip Step 1; if there is no control flow flattening, skip Step 4
  3. Adjust at any time: After executing any step, if new situations are discovered, you can roll back or insert additional steps
  4. MCP is used for analysis and verification: js-reverse MCP is used to obtain source code, search for features, and verify the feasibility of a solution on a small scale; large-scale conversions and verifications are executed through Node.js scripts

When to Use MCP

The role of MCP is ananalysis tool and verification probe, not a batch execution engine.

Stage MCP Usage
Analysis stage search_in_sources search for obfuscation features;get_script_source obtain the target script
Solution verification evaluate_script execute a few decryption calls to confirm the solution is feasible before writing batch scripts
Intermediate verification evaluate_script sample and compare function outputs before and after deobfuscation
Control flow analysis set_breakpoint + step_over dynamically track the switch-case execution order
Final verification In the browser, replace the original code with the deobfuscated code and verify that the functionality works normally

Large-scale conversion work such as batch string decryption, expression simplification, and dead code removal is all done through Node.js scripts + Babel AST.

Subagent Parallelization Strategy

Task combinations that can be parallelized:

  • In Step 0: feature identification + anti-debugging code search
  • In Step 2: Proxy function identification + object dictionary identification (independent of each other)
  • Verification stage: multiple sets of test data can be verified in parallel

Technical Manual

The following is a detailed reference for various deobfuscation techniques.Based on the analysis results of Step 0, select the chapters you need to execute.


Step 0: Pre-analysis and Feature Identification

Goal: Understand the obfuscation type and degree, to provide reference for subsequent steps (not mandatory routing).

0.1 Obtain Source Code

本地文件: 直接读取 $ARGUMENTS
URL: 下载后保存到 source/original/
浏览器中(代码尚未下载到本地):
  - 用 MCP list_scripts → get_script_source 获取
  - 可用 search_in_sources 做关键词快速扫描:
    "_0x" → 确认混淆前缀    "debugger" → 定位反调试
    "push" + "shift" → 旋转 IIFE    "split('|')" → 控制流平坦化

0.2 Formatting: Use prettier or babel generator to unify indentation, save as intermediate/target_step0.js

0.3 AST Health Check Report(optional)

When the file is small, reading the full text directly is the fastest and most comprehensive way. But even if you can read the full text, the value of the statistics script lies inTransform unstructured code into structured data — Information such as "Top 5 callee frequencies" is difficult to accurately tally by reading code manually. Write a script to perform a global scan of the AST:

统计项:
  基础指标:
    - 文件大小、总行数、AST 总节点数
    - FunctionDeclaration / FunctionExpression 数量
    - VariableDeclaration 数量

  字符串数组特征:
    - 最大的 ArrayExpression 及其元素数量(>50 个元素大概率是字符串数组)
    - 该数组所在的行号范围

  访问器函数特征:
    - CallExpression 中 callee 名称频次 Top 5(高频调用的大概率是访问器函数)
    - 这些函数的参数模式(单参数数字?双参数?)

  控制流特征:
    - WhileStatement 内嵌 SwitchStatement 的数量(>0 说明有控制流平坦化)
    - SwitchCase 总数量

  命名模式:
    - 标识符前缀分布统计(如 _0x、_0X、a0_、_$、__x 等,取 Top 3 前缀及占比)
    - 单字符变量占比
    - 平均标识符长度

  混淆强度:
    - StringLiteral 数量 vs CallExpression 取字符串的数量(比值越低,字符串混淆越重)
    - UnaryExpression 中 ! 运算符数量(大量 ![] !![] 说明布尔值混淆)

0.4 Feature Fingerprints (for reference only)

Feature Obfuscation Type Common Handling
High-frequency prefix (such as _0x/a0_) + large string array + rotating IIFE Obfuscator.io and variants Step 1→2→3→4→5→6
High-frequency prefix but no string array Light obfuscation Step 2→3→5→6
while(true){switch} + sequential array Control flow flattening Step 2→4→5→6
jsjiami.com marker sojson Unpack first, then choose steps according to the actual situation
Webpack/Parcel module wrapper Bundling tool First unpack to extract modules, then deobfuscate individual modules
Large amount of eval / Function() code encryption Execute the decryption layer in a sandbox, then choose steps according to the actual situation

0.5 Remove Anti-Debugging(if present)

Search for and delete:

  • debugger statements (especially in timers/loops)
  • setInterval(() => { debugger }, ...)
  • constructor("debu") anti-formatting detection
  • console rewriting/disabling code

0.6 Enter Plan Mode

Show the user:

  1. Identified obfuscation features
  2. Recommended combination of steps to execute
  3. Estimated complexity

After the user confirms, begin execution. If new situations are discovered during execution, the plan can be adjusted at any time.

Output: intermediate/target_step0.js + analysis report


Step 0.5: Webpack/Bundling Tool Unpack (optional)

Applicable conditions: The code is a bundle packaged by Webpack/Browserify/Parcel. Identification features:

Webpack 特征:
  - 外层 IIFE 接收一个模块数组或对象作为参数
  - 内部有 __webpack_require__ 或类似的模块加载函数:
    function(modules) {
      function __webpack_require__(moduleId) {
        if(installedModules[moduleId]) return installedModules[moduleId].exports;
        var module = installedModules[moduleId] = { exports: {} };
        modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
        ...
      }
    }
  - 每个模块是 function(module, exports, __webpack_require__) { ... }

Handling direction

1. 识别模块数组/对象(IIFE 的参数)
2. 提取每个模块函数,保存为独立文件:
   intermediate/modules/module_0.js
   intermediate/modules/module_1.js
   ...
3. 对 __webpack_require__(N) 调用,添加注释标注引用了哪个模块
4. 识别入口模块(通常是 __webpack_require__(0))
5. 对每个模块独立执行后续解混淆步骤

Webpack 固定参数重命名(确定性的,不需要猜测):
  - 第 1 个参数 → module
  - 第 2 个参数 → exports
  - 第 3 个参数 → __webpack_require__
  - __webpack_require__.p → __webpack_public_path__
  - __webpack_require__.m → __webpack_modules__
  - __webpack_require__.c → __webpack_module_cache__

Output: intermediate/modules/module_N.js(each module as an independent file)

⚠️ After unpacking, subsequent steps are executed independently on each module file, rather than on the entire bundle.


Step 1: String Decryption and Large Array Backfilling

Applicable conditions: The code contains a string array + accessor function pattern. If this pattern is absent, skip.

Goal: Restore all encrypted/encoded strings to plaintext. This is the foundation for subsequent steps.

1.1 Identify the String Array Structure

The typical pattern consists of three parts (the function name prefix is not fixed; it may be _0x、a0_、_$ or any obfuscated name):

  • String array function: Returns an array containing all the strings
  • Rotating IIFE: Performs push/shift rotation on the array, scrambling the original order
  • Accessor function: Accepts an index parameter and returns the corresponding string in the array (usually with offset calculation)

Identification method: Locate via the "largest ArrayExpression" and "CallExpression callee frequency Top 5" in the AST inspection report

1.2 Handling direction

流程: 先用 MCP 小规模验证方案,再写 Node.js 脚本批量执行

第一步: 用 MCP 验证方案
  1. 用 MCP evaluate_script 调用几个访问器函数,确认能正确返回明文
  2. 确认参数模式(单参数?双参数?偏移量?)
  3. 方案验证通过后,再写批量脚本

第二步: 批量执行(二选一)

方案 A(推荐): Node.js vm 沙箱
  1. 从 AST 中提取字符串数组 + 旋转 IIFE + 访问器函数的源码
  2. 在 vm.createContext() 沙箱中执行
  3. 遍历 CallExpression,用沙箱函数计算返回值并替换
  4. 删除已无用的数组函数、旋转 IIFE、访问器函数

方案 B: 纯静态分析(旋转逻辑简单时)
  1. 解析数组元素,静态计算旋转次数
  2. 手动应用旋转得到最终数组
  3. 直接用索引查表替换

1.3 Escaped String Restoration

遍历所有 StringLiteral: 删除 node.extra(强制用已解析的 value)
  \x48\x65\x6c\x6c\x6f → "Hello"
  \u0048\u0065 → "He"

1.4 Numeric Restoration

遍历所有 NumericLiteral: 删除 node.extra
  0x12 → 18, 0b1010 → 10

Verification: Use MCP evaluate_script to sample and compare whether several decryption results are correct

Output: intermediate/target_step1.js


Step 2: Expression Simplification

Applicable conditions: Almost all obfuscated code requires this step. Works best when executed in a loop with Step 5.

2.1 Constant Folding

遍历 BinaryExpression / UnaryExpression / LogicalExpression / ConditionalExpression:
  1. 调用 path.evaluate()
  2. 若 confident === true:
     - 用 t.valueToNode(value) 生成新节点
     - 验证结果是 Literal 类型(排除 Infinity/undefined 等边界值)
     - 所有类型都要处理: string, number, boolean(不要只处理部分类型)
     - 替换原节点
  3. 若 confident === false:
     - 检查是否为连续字符串拼接(左子树右叶 + 右叶都是 StringLiteral)
     - 若是,合并相邻字面量

2.2 Boolean Restoration

遍历 UnaryExpression:
  ![]  → false    !![] → true
  !0   → true     !1   → false
  !""  → true     !"x" → false
  void 0 → undefined

2.3 Proxy Function Inlining

识别条件: 函数体只有一条 return 语句,且 return 的是:
  - 二元运算: function(a,b){ return a + b }
  - 函数调用: function(a,b){ return a(b) }
  - 属性访问: function(a,b){ return a[b] }
  - 逻辑运算: function(a,b){ return a && b }

处理:
  1. 找到所有符合条件的 FunctionDeclaration
  2. 确认 binding.constant === true(未被重新赋值)
  3. 找到所有 CallExpression 引用
  4. 将实参代入函数体表达式,替换调用处
  5. 引用归零后删除函数声明

2.4 Object Property Dictionary Inlining

识别条件: 对象仅作为属性字典使用
  var map = { 'a': function(x,y){return x+y}, 'b': 'hello' }
  map['a'](1, 2)  →  1 + 2
  map['b']        →  'hello'

处理:
  1. 找到对象声明,确认所有属性值为 Literal 或简单函数
  2. 确认 binding.constant === true
  3. 将所有 MemberExpression 访问替换为对应属性值
  4. 对函数类属性,同时执行 Proxy 函数内联逻辑
  5. 引用归零后删除对象声明

Output: intermediate/target_step2.js


Step 3: Reference and Property Access Normalization

Applicable Conditions: The code makes heavy use of obj['prop'] bracket access. Must be executed after string decryption (Step 1).

3.1 Bracket to Dot Notation

遍历 MemberExpression:
  条件: computed === true 且 property 是 StringLiteral
  且属性名是合法标识符(匹配 /^[a-zA-Z_$][a-zA-Z0-9_$]*$/)
  obj['prop']     → obj.prop
  obj['forEach']  → obj.forEach
  保留: obj['class'](保留字), obj['data-id'](含连字符), obj[variable](变量)

3.2 Comma Expression Splitting

逗号表达式不只出现在 ExpressionStatement 中,需覆盖所有位置:

1. ExpressionStatement:
   (a = 1, b = 2, c())  →  a = 1; b = 2; c();

2. ReturnStatement:
   return (a = 1, b = 2, result)  →  a = 1; b = 2; return result;

3. IfStatement test / for init / for update 中的逗号表达式也需拆分

4. 赋值右侧:
   x = (a(), b(), c)  →  a(); b(); x = c;

Output: intermediate/target_step3.js


Step 4: Control Flow Flattening Restoration

Applicable Conditions: The code contains the while(true){ switch(...) { case... } } structure. Strongly depends on Step 2's constant folding.

4.1 Analyze the Actual Structure

Do not presuppose a fixed pattern; analyze the control flow structure based on the actual code:

常见特征(仅供参考,实际变种很多):
  - WhileStatement,test 恒为 true
  - 循环体内是 SwitchStatement
  - 存在某种顺序控制机制(数组索引、状态变量等)
  - 每个 case 以 continue/break 结尾

分析方法:
  1. 阅读具体的 while-switch 代码,理解其控制流逻辑
  2. 用 MCP set_breakpoint + step_over 动态跟踪实际执行顺序
  3. 判断顺序控制机制是静态可解的还是依赖运行时值

4.2 Restoration Direction

根据分析结果制定还原策略。核心思路:
  1. 确定代码块的执行顺序
  2. 按顺序提取各 case 的代码块
  3. 移除控制流包裹(while/switch/continue)
  4. 用 path.replaceWithMultiple() 替换整个结构

⚠️ 复杂情况(状态变量动态计算、嵌套多层等):
  - 向用户展示分析结果,请求辅助判断
  - 可用 MCP 动态跟踪辅助理解执行流程
  - 宁可保留未还原的结构,也不要错误还原

Verification: Use MCP evaluate_script to execute the restored function and compare with the original output

Output: intermediate/target_step4.js


Step 5: Dead Code Removal

Applicable Conditions: Required for almost all obfuscated code. Works best when executed in a loop with Step 2.

5.1 Unreachable Branch Cleanup

遍历 IfStatement / ConditionalExpression:
  1. 用 path.get("test").evaluateTruthy() 求值条件
  2. 若结果 === true:  用 consequent 替换整个节点
  3. 若结果 === false: 用 alternate 替换(无 alternate 则删除)
  4. 注意: BlockStatement 需展开为语句数组再替换

5.2 Removal of Useless Variables/Functions

遍历 VariableDeclarator / FunctionDeclaration:
  1. 获取 binding = scope.getBinding(name)
  2. 若 binding.constant === true 且 binding.referenced === false:
     - 确认 init 无副作用后删除
  3. 若 init 有副作用(函数调用等): 保留

5.3 Empty Statement Cleanup

遍历 EmptyStatement: 直接删除
遍历 BlockStatement: 若 body 为空且不是函数体,删除

5.4 Loop Strategy (Step 2 + Step 5) — Must Be Implemented, Cannot Be Skipped

Executing Step 2 and Step 5 only once will miss a large amount of simplifiable code. Reason: dead code removal exposes new constant expressions, and constant folding in turn exposes new dead code.

实现方式: 在脚本中用循环包裹 Step 2 和 Step 5 的 visitor

let round = 0;
do {
  changed = false
  执行 Step 2 全部 visitor(常量折叠 + 布尔值 + Proxy + 对象字典)
  执行 Step 5 全部 visitor(不可达分支 + 无用变量 + 空语句)
  若本轮有任何节点被替换或删除 → changed = true
  round++
  保存中间文件: intermediate/target_step5_round{round}.js
} while (changed && round < 50)

Save intermediate files at each loop iteration: intermediate/target_step5_round{N}.js

Output: intermediate/target_step5.js


Step 6: Variable Renaming and Final Formatting

Applicable Conditions: Execute when meaningless obfuscated variable names exist in the code. Only perform deterministic pattern-based renaming; do not perform semantic inference.

6.1 Pattern-based Renaming (Deterministic)

某些变量名可以根据代码模式确定性地重命名,不需要猜测:

Webpack 模块参数(不管原始名是什么前缀):
  function(xx1, xx2, xx3) { xx3(69) }
  → function(module, exports, __webpack_require__) { __webpack_require__(69) }
  判断依据: 三参数函数,第三个参数被当作函数调用且参数是数字

常见回调模式:
  .then(function(xx) { ... })  → .then(function(response) { ... })
  .catch(function(xx) { ... }) → .catch(function(error) { ... })

事件处理:
  .addEventListener("click", function(xx) { ... })
  → .addEventListener("click", function(event) { ... })

Variables with uncertain semantics retain their original obfuscated names; do not perform rule-based numbering (such as var_1) or semantic inference, to avoid introducing errors.

6.2 Code Formatting

1. var 声明拆分: 将单行多声明拆为每行一个
2. return 简化: if(x) return a; else return b; → return x ? a : b
3. 用 babel generator 输出,配置 compact: false, concise: false

6.3 Final Verification

用 MCP evaluate_script:
  1. 选取关键函数,用相同输入对比原始代码和解混淆代码的输出
  2. 若有差异,定位出错的步骤,从对应中间文件回退修复

Output: source/deobfuscated/target_deobf.js + scripts/deobfuscate_target.js


Safety Boundaries

  1. Sandbox Isolation: Executing obfuscated code must be done in the vm module,isolated-vm or a browser (MCP); direct eval
  2. Iteration Limit: Set a hard limit (50) for the Step 2↔5 loop to prevent infinite loops
  3. Side Effect Protection: Before deleting code, check whether there are side effects (network requests, DOM operations, assignments)
  4. Scope Safety: Renaming must go through path.scope.rename(), cannot directly modify node.name
  5. Verification: After each step is completed, use @babel/parser to re-parse and confirm the syntax is valid

Babel Key API Quick Reference

Purpose API
Static Evaluation path.evaluate() → { confident, value }
Boolean Evaluation path.get("test").evaluateTruthy() → true/false/undefined
Replace Node path.replaceWith(node) / path.replaceWithMultiple([nodes])
Remove Node path.remove()
Get Binding path.scope.getBinding(name) → { constant, referenced, references, referencePaths }
Safe Renaming path.scope.rename(oldName, newName)
Refresh Scope path.scope.crawl() — must be called after removing/replacing nodes
Value to Node t.valueToNode(value) — Note that Infinity/undefined returns non-Literal

Notes

  1. The Step 2↔5 loop cannot be omitted — This is the step most easily skipped but with the greatest impact; a single pass will miss a large amount of simplifiable code
  2. The Webpack bundle must be unpacked first — Deobfuscating directly without splitting modules leaves 9000 lines mixed together, which is unfriendly to both the LLM and debugging
  3. Comma expressions are not only in ExpressionStatement — They also exist in return, if, and for; all must be covered
  4. Constant folding must cover all types — string, number, and boolean must all be handled; do not handle only some
  5. Prioritize pattern-based renaming — Handle deterministic patterns such as Webpack parameters first; keep original names for uncertain ones
  6. Obfuscation prefixes are not only _0x — They may also be a0_、_$、__x etc.; use AST statistics of high-frequency prefixes to determine them
  7. Write an independent visitor for each step — Do not mix multiple transformations in one traverse; this easily causes invalid nodes
  8. Refresh after scope changes — After deleting/replacing nodes, subsequent traversals need to call scope.crawl()
  9. Keep uncertain code — Better to keep code you do not understand than to mistakenly delete useful logic
  10. Intermediate files are a safety net — If any step goes wrong, you can restart from the intermediate file of the previous step
  11. Adjust steps flexibly — Real-world obfuscation is ever-changing; this manual is a reference, not dogma