大家好,我是威哥。做爬虫快7年,JS逆向这块踩的坑,能装满我那台1T的旧硬盘——obfuscator重度混淆、控制流平坦化、动态加载加密模块、AST动态生成代码、甚至还有把加密逻辑藏在WebAssembly里的。
前两年接了个跨境电商竞品数据的长期单,每周要爬10个不同的平台,每个平台的加密逻辑每周都微调,要么换个混淆参数,要么改个拼接顺序,要么换个动态盐值的获取位置。那段时间我每天凌晨3点爬起来改逆向代码,改到吐,差点把项目推了。
被逼无奈之下,我花了整整3个月,踩了无数坑,终于用Python+Node.js写了个全自动JS逆向工具链——不用抠一行混淆后的核心代码,不用手动断点调试,不用自己写AST还原脚本,只要输入目标接口的URL和加密参数名,3分钟就能生成可直接复用的Python加密函数,实测通解了我手里200+平台里的90%动态加密(剩下的10%是WebAssembly和硬件绑定的,暂时搞不定)。
今天把这套工具链的核心思路和最简实现分享出来,没有网上抄烂的无效理论,全是我踩坑踩出来的硬货,看完你就能自己搭一个简化版的。
先戳破真相:90%的动态加密,根本不需要你抠核心代码
很多人一碰到混淆JS,第一反应就是“找入口→断点调试→AST还原→抠代码→Python复现”,这套流程虽然经典,但效率太低了——碰到重度混淆的,光找入口就要1小时,断点调试要2小时,AST还原要3小时,最后复现还要1小时,一个平台半天就没了。
其实我踩了无数坑才发现:90%的动态加密,核心逻辑都是“参数拼接+哈希/对称加密”,真正复杂的混淆,只是为了隐藏拼接规则和固定盐值,而不是加密算法本身。既然如此,我们为什么不直接绕过混淆,让浏览器帮我们执行加密逻辑,然后把结果和入参吐出来?
这就是我这套工具链的核心思路:用Playwright控制真实浏览器加载目标页面,注入Node.js写的Hook脚本,拦截加密函数的调用,把入参、出参、甚至拼接好的中间字符串全部吐出来,然后用Python收集这些数据,训练一个简单的规则模型,或者直接生成一个调用浏览器加密逻辑的Python封装函数。
第一部分:工具链的核心组件,3个模块搞定一切
这套工具链非常简单,只有3个核心模块,每个模块都可以单独使用,也可以组合起来用:
第二部分:最简实现,100行代码搭一个简化版工具链
我给大家写了一个最简实现,只有100行左右的代码,就能搞定大部分简单的动态加密,比如MD5签名、SHA256签名、固定盐值的AES加密。
1. 环境搭建
一行命令搞定所有依赖:
pip install playwright websockets
playwright install chrome
2. JS Hook脚本(hook.js)
这个脚本会Hook所有常见的加密函数,以及自定义的getSign、getToken、getClientSign函数,把入参、出参、中间字符串全部通过WebSocket吐出来:
// hook.js
const WebSocket = require('ws');
const ws = new WebSocket('ws://localhost:8765');
// 等待WebSocket连接成功
ws.on('open', () => {
console.log('WebSocket连接成功,开始Hook加密函数');
});
// 通用Hook函数
function hookFunction(obj, funcName, tag) {
const originalFunc = obj[funcName];
obj[funcName] = function(…args) {
// 调用原始函数
const result = originalFunc.apply(this, args);
// 把入参、出参、标签通过WebSocket吐出来
ws.send(JSON.stringify({
tag: tag,
funcName: funcName,
args: args.map(arg => {
if (typeof arg === 'object') {
return JSON.stringify(arg);
}
return arg;
}),
result: result
}));
return result;
};
}
// Hook常见的哈希函数
if (typeof CryptoJS !== 'undefined') {
hookFunction(CryptoJS, 'MD5', 'CryptoJS_MD5');
hookFunction(CryptoJS, 'SHA1', 'CryptoJS_SHA1');
hookFunction(CryptoJS, 'SHA256', 'CryptoJS_SHA256');
hookFunction(CryptoJS, 'AES', 'CryptoJS_AES');
}
if (typeof window !== 'undefined' && window.crypto && window.crypto.subtle) {
// 这里可以Hook Web Crypto API,稍微复杂一点,简化版就不写了
}
// Hook自定义的加密函数,这里可以根据目标平台修改
const customFuncNames = ['getSign', 'getToken', 'getClientSign', 'generateSign', 'generateToken'];
customFuncNames.forEach(funcName => {
// 遍历window对象的所有属性,找到自定义的加密函数
for (let key in window) {
if (key.toLowerCase().includes(funcName.toLowerCase()) && typeof window[key] === 'function') {
hookFunction(window, key, 'Custom_' + key);
}
}
// 遍历document对象的所有属性
for (let key in document) {
if (key.toLowerCase().includes(funcName.toLowerCase()) && typeof document[key] === 'function') {
hookFunction(document, key, 'Custom_' + key);
}
}
});
3. Python控制模块(main.py)
这个模块会启动WebSocket服务器,启动Playwright浏览器,加载目标页面,注入Hook脚本,收集入参和出参,然后生成一个调用浏览器加密逻辑的Python封装函数:
import asyncio
import websockets
import json
from playwright.async_api import async_playwright
# 存储收集到的加密函数信息
collected_funcs = {}
async def websocket_handler(websocket, path):
"""WebSocket服务器,接收Hook脚本吐出来的加密函数信息"""
async for message in websocket:
data = json.loads(message)
func_name = data['funcName']
tag = data['tag']
if func_name not in collected_funcs:
collected_funcs[func_name] = {
'tag': tag,
'args_count': len(data['args']),
'examples': []
}
# 只存储前5个例子,避免数据太多
if len(collected_funcs[func_name]['examples']) < 5:
collected_funcs[func_name]['examples'].append({
'args': data['args'],
'result': data['result']
})
print(f"收到加密函数调用:{tag} – {func_name}")
print(f"入参:{data['args']}")
print(f"出参:{data['result']}")
print("-" * 50)
async def start_websocket_server():
"""启动WebSocket服务器"""
async with websockets.serve(websocket_handler, "localhost", 8765):
await asyncio.Future() # 永久运行
async def start_playwright_browser(target_url, trigger_selector):
"""启动Playwright浏览器,加载目标页面,注入Hook脚本,触发加密函数调用"""
async with async_playwright() as p:
# 启动Chrome浏览器,用系统安装的Chrome,指纹更真实
browser = await p.chromium.launch(
headless=False,
channel="chrome",
args=["–disable-blink-features=AutomationControlled"]
)
# 新建上下文,注入stealth脚本,绕过自动化检测
context = await browser.new_context(
viewport={"width": 1920, "height": 1080},
user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36"
)
# 注入stealth脚本,这里可以替换成你自己的stealth.min.js路径
await context.add_init_script(path="stealth.min.js")
# 注入我们的Hook脚本
await context.add_init_script(path="hook.js")
# 打开目标页面
page = await context.new_page()
await page.goto(target_url, wait_until="networkidle")
print("目标页面加载完成,请手动触发加密函数调用(比如点击搜索按钮、翻页按钮)")
print("或者输入触发选择器,自动触发:")
if trigger_selector:
await page.click(trigger_selector)
print(f"已自动触发选择器:{trigger_selector}")
# 等待用户手动操作,或者等待30秒
await asyncio.sleep(30)
# 关闭资源
await context.close()
await browser.close()
# 生成调用浏览器加密逻辑的Python封装函数
generate_wrapper_func()
def generate_wrapper_func():
"""生成调用浏览器加密逻辑的Python封装函数"""
if not collected_funcs:
print("未收集到任何加密函数信息")
return
print("\\n" + "=" * 50)
print("收集到的加密函数信息:")
for func_name, info in collected_funcs.items():
print(f"\\n{info['tag']} – {func_name}")
print(f"入参数量:{info['args_count']}")
print(f"示例:")
for i, example in enumerate(info['examples']):
print(f" 示例{i+1}:")
print(f" 入参:{example['args']}")
print(f" 出参:{example['result']}")
print("\\n" + "=" * 50)
print("生成的Python封装函数(简化版):")
print("""
import asyncio
from playwright.async_api import async_playwright
async def call_encrypt_func(func_name, *args):
async with async_playwright() as p:
browser = await p.chromium.launch(headless=True, channel="chrome")
context = await browser.new_context()
await context.add_init_script(path="stealth.min.js")
await context.add_init_script(path="hook.js")
page = await context.new_page()
# 这里需要替换成你目标页面的URL
await page.goto("https://www.example.com", wait_until="networkidle")
# 调用加密函数
result = await page.evaluate(f"() => window.{func_name}(…{json.dumps(args)})")
await context.close()
await browser.close()
return result
# 测试调用
if __name__ == "__main__":
# 这里需要替换成你收集到的加密函数名和入参
result = asyncio.run(call_encrypt_func("getSign", "param1", "param2"))
print(result)
""")
async def main():
"""主函数,同时启动WebSocket服务器和Playwright浏览器"""
# 启动WebSocket服务器
websocket_task = asyncio.create_task(start_websocket_server())
# 启动Playwright浏览器,这里可以替换成你目标页面的URL和触发选择器
target_url = "https://www.example.com"
trigger_selector = "#search_button" # 可以为空,手动触发
playwright_task = asyncio.create_task(start_playwright_browser(target_url, trigger_selector))
# 等待两个任务完成
await asyncio.gather(websocket_task, playwright_task)
if __name__ == "__main__":
asyncio.run(main())
第三部分:使用方法,3分钟搞定一个平台
使用方法非常简单,3分钟就能搞定一个平台:
踩了无数坑总结的5条避坑指南
最后说几句
这套工具链虽然不是万能的(剩下的10%是WebAssembly和硬件绑定的,暂时搞不定),但已经能解决我手里90%的动态加密问题了,每周爬10个平台,再也不用凌晨3点爬起来改代码了。
当然,还是要郑重提醒大家:爬虫技术是一把双刃剑,一定要严格遵守《网络安全法》《数据安全法》,遵守目标平台的用户协议和robots协议,只爬取公开的、合规的数据,不要爬取用户隐私、商业机密等敏感信息,更不要用这个技术做任何违法违规的事情。


