Python 爬虫逆向实战 4:JS 混淆 AST 解混淆 + webpack 打包代码拆包还原
前言
大量加密接口站点使用 JS 混淆、webpack 打包压缩、eval 加密、JJSC/obfuscator 变量乱码,前端源码全是随机变量名、控制流平坦化、字符串加密,常规抠 JS 加密代码无法直接复用。本章从 AST 抽象语法树原理、webpack 拆包、JS 反混淆落地,分手动格式化、简易 AST 解混淆、在线工具原理复刻,配套 Python 脚本还原混淆 JS,无缝衔接前文 execjs 加密逆向工程。
本文所需依赖官方文档超链接:
- esprima-python
- Requests 官方文档
- estraverse
一、JS 混淆常见类型与逆向难点
1.1 主流混淆手段
- 变量名混淆:变量 / 函数名变为
_0x12ab、_0x3f5d无意义十六进制字符; - 字符串数组加密:所有明文统一存入数组,运行时下标取值解密;
- 控制流平坦化:大量 switch+while 打乱代码执行顺序;
- webpack 打包:代码被打包成
!function(e,t,n){...}闭包模块,多接口代码揉在同一个 JS; - eval/atob 加密:代码 base64 编码后 eval 动态执行,源码肉眼不可读。
1.2 逆向阻碍
无法定位加密函数、密钥、AES/RSA 逻辑,无法直接复制 JS 用于 execjs 调用。
二、环境依赖安装
bash
运行
pip install esprima==4.0.1 estraverse==5.3.0 requests==2.31.0三、模块 1:JS 基础预处理(去 eval、base64 解码外层加密)
python
运行
import base64 import re def decode_eval_b64(js_raw:str): """解析eval(atob('xxx'))外层base64加密代码""" reg_b64 = re.compile(r'atob\(["\']([A-Za-z0-9+/=]+)["\']\)') res_list = reg_b64.findall(js_raw) for b64_str in res_list: try: decode_code = base64.b64decode(b64_str).decode("utf-8") js_raw = js_raw.replace(f"atob('{b64_str}')",f'"{decode_code}"') except: continue return js_raw四、模块 2:AST 结构说明 + 字符串数组解密(高频混淆核心)
4.1 AST 解混淆逻辑
- esprima 将 JS 转为 AST 抽象语法树;
- 遍历 AST 提取加密字符串数组;
- 替换代码中数组下标取值为原始明文;
- 生成还原后可读 JS 源码。
python
运行
import esprima import estraverse def str_array_deobfuscate(js_code): # 解析生成AST树 ast = esprima.parseScript(js_code) str_dict = {} # 第一步:遍历提取全局字符串数组 def collect_str(node,parent): nonlocal str_dict # 匹配var _0xabc=["xxx","yyy"]数组定义 if node.type == "VariableDeclaration": for dec in node.declarations: if dec.init and dec.init.type == "ArrayExpression": arr_val = [] for elem in dec.init.elements: if elem.type == "Literal" and isinstance(elem.value,str): arr_val.append(elem.value) if arr_val: var_name = dec.id.name str_dict[var_name] = arr_val estraverse.traverse(ast,{"enter":collect_str}) # 第二步:替换下标取值 _0xabc[0] → "xxx" def replace_index(node,parent): if node.type == "MemberExpression": if node.object.type=="Identifier" and node.property.type=="Literal": var_n = node.object.name idx = node.property.value if var_n in str_dict and idx < len(str_dict[var_n]): node.type = "Literal" node.value = str_dict[var_n][idx] del node.object del node.property estraverse.traverse(ast,{"enter":replace_index}) # AST转回JS字符串 from escodegen import generate clean_js = generate(ast) return clean_js五、模块 3:webpack 打包 JS 拆包提取指定模块
webpack 打包代码采用模块 ID 映射,通过 ID 分离单个接口加密模块:
python
运行
def split_webpack_js(js_all:str,target_module_id:int): """拆分webpack,根据模块ID截取单独JS代码""" # 简易正则匹配模块包裹结构 reg_mod = re.compile(r',(\d+):\[(function.*?)\],',re.S) mod_map = {} for mid,code in reg_mod.findall(js_all): mod_map[int(mid)] = code if target_module_id in mod_map: return f"var module={{}};var exports={{}};{mod_map[target_module_id]}" return ""六、完整一键解混淆调用链路
python
运行
def full_deobfuscate(raw_js): # 1.解码外层base64 eval step1 = decode_eval_b64(raw_js) # 2.AST字符串数组解密 step2 = str_array_deobfuscate(step1) return step2 # 使用示例 if __name__ == "__main__": with open("obf_code.js","r",encoding="utf-8") as f: obf_js = f.read() clear_code = full_deobfuscate(obf_js) with open("clear_code.js","w",encoding="utf-8") as fw: fw.write(clear_code) print("解混淆完成,已输出clear_code.js")七、配合前文 AES 逆向实战:解混淆后提取加密函数
python
运行
def get_encrypt_func_from_deob(clear_js): """解混淆完成后正则提取AES加密函数""" reg_encrypt = re.compile(r'function\s+aesEncrypt\(.*?\{.*?\}',re.S) func_list = reg_encrypt.findall(clear_js) return func_list八、进阶:控制流平坦化简易优化思路
控制流混淆依靠 while+switch 调度代码,人工优化方案:
- 解混淆后格式化 JS,标注 case 对应执行逻辑;
- 删除无用 switch 调度代码,按执行顺序重写原生代码;
- 精简后代码直接用于 execjs 加密。
九、Playwright 动态运行提取明文(懒人免 AST 方案)
python
运行
from playwright.sync_api import sync_playwright def auto_get_decrypt_str(ob_js): with sync_playwright() as pw: browser = pw.chromium.launch(headless=True) page = browser.new_page() # 在页面注入混淆JS,运行后导出解密后的全局变量 page.evaluate(ob_js) # 打印全局解密后的加密函数 res = page.evaluate("JSON.stringify(aesEncrypt)") browser.close() return res十、常见故障与优化表
表格
| 异常现象 | 解决方案 |
|---|---|
| AST 解析报错语法错误 | 剔除注释、补全缺失分号,预处理脏字符 |
| 部分下标无法替换 | 数组为动态运行生成,改用浏览器运行提取 |
| webpack 多依赖报错 | 拆分依赖模块,补齐 require 模拟环境 |
| 多层嵌套混淆 | 循环多次执行解混淆脚本逐层解密 |
十一、本章总结
JS 混淆逆向标准流程:外层 Base64/Eval 解码 → AST 字符串解密 → webpack 拆包 → 格式化精简源码 → 提取加密逻辑;小规模调试用 Playwright 动态运行取值,大批量逆向用 Python+AST 自动化解混淆,解混淆后的干净 JS 可无缝对接 execjs 实现参数加密爬虫。后续拓展:JS 虚拟机还原、obfuscator 全量反混淆、RPC 方式调用浏览器 V8 引擎解密。
