欢迎光临
我们一直在努力

踩了200+平台混淆JS坑!我用Python+Node.js写了个全自动逆向工具,3分钟通解90%动态加密,不用抠一行核心代码

大家好,我是威哥。做爬虫快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个核心模块,每个模块都可以单独使用,也可以组合起来用:

  • Playwright浏览器控制模块(Python):负责启动真实浏览器,加载目标页面,注入Hook脚本,拦截加密函数的调用,收集入参和出参;
  • JS Hook脚本模块(Node.js/JavaScript):负责在目标页面加载前注入,Hook所有可能的加密函数(比如MD5、SHA1、SHA256、AES、RSA,以及自定义的getSign、getToken函数),把入参、出参、中间字符串全部通过WebSocket或者Console吐出来;
  • 规则生成/封装模块(Python):负责收集Playwright吐出来的入参和出参,分析拼接规则和固定盐值,生成可直接复用的Python加密函数,或者生成一个调用Playwright浏览器加密逻辑的封装函数(适合规则太复杂,分析不出来的情况)。

  • 第二部分:最简实现,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分钟就能搞定一个平台:

  • 修改hook.js里的自定义加密函数名:如果目标平台的加密函数名不在默认的列表里,就手动加进去;
  • 修改main.py里的目标URL和触发选择器:把目标URL换成你要爬的平台的URL,把触发选择器换成能触发加密函数调用的元素选择器(比如搜索按钮、翻页按钮),如果触发选择器不好找,就留空,手动触发;
  • 运行main.py:运行之后,会自动启动WebSocket服务器和Playwright浏览器,加载目标页面,注入Hook脚本,然后你手动或者自动触发加密函数调用,收集到的信息会自动打印出来,最后会生成一个调用浏览器加密逻辑的Python封装函数;
  • 测试封装函数:把生成的封装函数里的目标URL和加密函数名、入参替换成你收集到的,然后运行测试,看看能不能得到正确的结果。

  • 踩了无数坑总结的5条避坑指南

  • 一定要用真实浏览器,不要用无头模式:虽然简化版里的封装函数用了无头模式,但收集信息的时候一定要用有头模式,不然很多动态加载的加密模块不会加载,Hook不到;
  • 一定要注入stealth脚本:不然目标平台会检测到你用的是自动化浏览器,直接拒绝加载页面,或者加载假的加密模块;
  • WebSocket服务器一定要先启动:不然Hook脚本连接不上,吐不出来信息;
  • 收集信息的时候,尽量多触发几次加密函数调用:比如换不同的参数、换不同的页面,这样收集到的例子更多,更容易分析拼接规则;
  • 如果规则太复杂,分析不出来,就直接用封装函数:虽然封装函数的效率比直接复现低一点,但胜在稳定,不用每周改代码。

  • 最后说几句

    这套工具链虽然不是万能的(剩下的10%是WebAssembly和硬件绑定的,暂时搞不定),但已经能解决我手里90%的动态加密问题了,每周爬10个平台,再也不用凌晨3点爬起来改代码了。

    当然,还是要郑重提醒大家:爬虫技术是一把双刃剑,一定要严格遵守《网络安全法》《数据安全法》,遵守目标平台的用户协议和robots协议,只爬取公开的、合规的数据,不要爬取用户隐私、商业机密等敏感信息,更不要用这个技术做任何违法违规的事情。

    赞(0)
    未经允许不得转载:171主机测评 » 踩了200+平台混淆JS坑!我用Python+Node.js写了个全自动逆向工具,3分钟通解90%动态加密,不用抠一行核心代码
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址