Python 爬虫逆向实战 4:JS 混淆 AST 解混淆 + webpack 打包代码拆包还原

发布时间:2026/6/5 21:50:31

Python 爬虫逆向实战 4:JS 混淆 AST 解混淆 + webpack 打包代码拆包还原 前言大量加密接口站点使用 JS 混淆、webpack 打包压缩、eval 加密、JJSC/obfuscator 变量乱码前端源码全是随机变量名、控制流平坦化、字符串加密常规抠 JS 加密代码无法直接复用。本章从 AST 抽象语法树原理、webpack 拆包、JS 反混淆落地分手动格式化、简易 AST 解混淆、在线工具原理复刻配套 Python 脚本还原混淆 JS无缝衔接前文 execjs 加密逆向工程。本文所需依赖官方文档超链接esprima-pythonRequests 官方文档estraverse一、JS 混淆常见类型与逆向难点1.1 主流混淆手段变量名混淆变量 / 函数名变为_0x12ab、_0x3f5d无意义十六进制字符字符串数组加密所有明文统一存入数组运行时下标取值解密控制流平坦化大量 switchwhile 打乱代码执行顺序webpack 打包代码被打包成!function(e,t,n){...}闭包模块多接口代码揉在同一个 JSeval/atob 加密代码 base64 编码后 eval 动态执行源码肉眼不可读。1.2 逆向阻碍无法定位加密函数、密钥、AES/RSA 逻辑无法直接复制 JS 用于 execjs 调用。二、环境依赖安装bash运行pip install esprima4.0.1 estraverse5.3.0 requests2.31.0三、模块 1JS 基础预处理去 eval、base64 解码外层加密python运行import base64 import re def decode_eval_b64(js_raw:str): 解析eval(atob(xxx))外层base64加密代码 reg_b64 re.compile(ratob\([\]([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(fatob({b64_str}),f{decode_code}) except: continue return js_raw四、模块 2AST 结构说明 字符串数组解密高频混淆核心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.typeIdentifier and node.property.typeLiteral: 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五、模块 3webpack 打包 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 fvar 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,encodingutf-8) as f: obf_js f.read() clear_code full_deobfuscate(obf_js) with open(clear_code.js,w,encodingutf-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(rfunction\saesEncrypt\(.*?\{.*?\},re.S) func_list reg_encrypt.findall(clear_js) return func_list八、进阶控制流平坦化简易优化思路控制流混淆依靠 whileswitch 调度代码人工优化方案解混淆后格式化 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(headlessTrue) 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 动态运行取值大批量逆向用 PythonAST 自动化解混淆解混淆后的干净 JS 可无缝对接 execjs 实现参数加密爬虫。后续拓展JS 虚拟机还原、obfuscator 全量反混淆、RPC 方式调用浏览器 V8 引擎解密。

相关新闻