首页
社区
课程
招聘
[原创]JSVMP 类型 a_bogus 本地化复现实践:基于 Claude Code 与 DeepSeek V4 的提效方法
发表于: 12小时前 202

[原创]JSVMP 类型 a_bogus 本地化复现实践:基于 Claude Code 与 DeepSeek V4 的提效方法

12小时前
202

爬虫逆向工程中,最棘手的场景莫过于JSVMP(JavaScript Virtual Machine Protection)保护的签名字段。这类保护方案将核心签名逻辑编译为自定义字节码,由内嵌在 JS 文件中的栈式虚拟机解释执行。传统的"读混淆 JS → 翻译为 Python"路线在此基本失效——面对 100+ KB 的 opcode 表和上千条虚拟机指令,算法提取的时间成本以周甚至月计。

**补环境(Environment Patching)**提供了一条替代路径:不提取算法,而是在 Node.js 的 vm 沙箱中搭建一个足以"欺骗"签名 SDK 的假浏览器环境,将目标站点原样的 JS 文件丢进去直接执行,从而离线产出签名。

本文以某音系签名字段 a_bogus 为案例,记录从零到完整出参的 7 阶段工作流,重点讨论如何借助 Claude CodeDeepSeek V4 的能力组合将传统需要 3-5 天的手动迭代压缩到数小时内完成。

a_bogus 的签名由 3 个 JS 文件协作完成,加载顺序严格不可变:

SDK 通过 window.bdms.init({ aid: 6383, paths: ['^/aweme/v1/web/'] }) 激活。paths 是一个正则前缀数组——只有匹配这些前缀的 API 请求才会被 XHR hook 拦截并自动附加 a_bogus 参数。这是整个流程中最高频的静默故障点:init() 不报错、签名长度正确、字母表正确,但服务端拒绝——只因 paths 未配置或配置错误。

在实验过程中发现,同一个 SDK 在不同浏览器会话中会产出不同长度的 a_bogus:最常见的是 172 字符,但也会出现 192 字符。初步假设是 Math.random 种子或设备指纹(hardwareConcurrencydeviceMemory)影响,但对 200 个随机种子、多种硬件指纹组合的遍历测试结果显示——所有组合都输出 172 字符。这与 platform: 'Linux aarch64_64' 的特殊设置有关,但本质上 192 更可能是会话级随机波动,而非可稳定复现的分支。

这个探索过程本身很有价值:它展示了补环境方法论在"不理解算法细节"的前提下,如何通过受控实验(遍历种子、替换环境变量、注入不同轨迹数据)来缩小问题空间。

使用 Playwright 驱动的 capture_sdk.js 打开目标页面,自动按文件大小(>20KB 阈值)和 URL 正则(匹配 vmp|protect|crawler|risk|sec|sdk|runtime|bundler|glue|loader|sign|guard|shield)过滤 JS 响应,保存到 bundles/ 并生成含 SHA-256 校验的 manifest.json

关键原则:永远从目标站点线上实时抓取,不要从 GitHub 镜像或博客文章复制。SDK 版本以周为单位变化,过期文件会导致签名被服务端静默拒绝。

这是整套工具链中最具创新性的环节。传统补环境流程中,这一步需要人工反复:加载 SDK → 观察报错 → 补齐缺失属性 → 重新加载 → 再观察报错,通常需要 3-5 轮手动迭代。

trace_env.js 将此过程全自动化。核心机制:

启发式猜值函数 guessDefault():内置约 80 个属性名模式匹配规则。例如以 hardwareConcurrency 结尾的属性返回 8,以 onLine 结尾返回 false,以 get/set/create 等动词开头的属性返回空函数,首字母大写的标识符返回空构造函数。

递归自愈 Proxy makeHealer():对每个对象包裹深度为 2 的 Proxy,当 SDK 访问不存在的属性时自动调用 guessDefault() 创建桩值,并记录访问路径到 traceLog

自动重跑机制:如果 Proxy 的自动补全仍然导致 SDK 崩溃(例如函数返回值被链式访问了更深层属性),脚本解析错误信息中的属性路径、注入缺失桩、重新加载 SDK。默认最多 8 轮自愈。

日志输出示例:

最终输出的 fake_env.js 是一个可直接 require 的完整环境桩模块,覆盖了 SDK 实际触碰的所有属性。

基于 Phase 2 的追踪输出或手动编写的 fake_env.js,构建完整的假浏览器表面。a_bogus_192/core/fake_env.js 覆盖了以下核心表面:

navigator 层(~40 属性):UA、platform、hardwareConcurrency=8、deviceMemory=8、webdriver=false、connection(effectiveType/rtt/downlink)、userAgentData(brands 含 Chromium 146)、clipboard、permissions、plugins/mimeTypes。

DOM 层:document.createElement 返回带完整方法集的伪元素,canvas 的 2D/webgl 上下文返回稳定的假渲染结果。addEventListener 必须捕获 handler 引用(存入 __eventHandlers 数组)——这是 Phase 5.5 轨迹注入的前提。

存储层:localStorage/sessionStorage 通过 Storage 原型类实现,带正确的 Symbol.toStringTag 和原型链。这是 SDK 检测 instanceof Storage 的关键。

平台类:Headers、Request、Response、FormData、Blob、AbortController、URL、URLSearchParams——全部 typeof === 'function'。Observer 系列(IntersectionObserver、MutationObserver、ResizeObserver)为合法的空类。

反重放检测processDenorequireglobalmodule 全部显式设为 undefined,防止 SDK 检测到 Node 运行时环境。

签名 SDK 不暴露 sign(url) 这样的公开 API——它透明地 hook 了 XMLHttpRequest.prototype.send,在请求发出前改写 URL。因此离线捕获签名的核心技巧是构建一个假的 XHR 对象

几个关键要点:

触发签名只需一行:

window.bdms.init() 的配置参数直接决定签名分支。错误的 aid(应用 ID)或 paths(路由匹配)会导致签名产出长度正确、字母表正确、Base64 解码干净的字符串——但服务端拒绝。

paths 发现技巧:在浏览器 DevTools Sources 面板全文搜索 .init(,复制线上完整配置对象。paths 字段是正则前缀,不是字面量路径字符串。例如 ['^/aweme/v1/web/'] 匹配所有 /aweme/v1/web/* 路径,而字面量 '/aweme/v1/web/aweme/post/' 只匹配精确路径。

签名 SDK 内嵌 Date.now()Math.random() 调用,每次输出不同。为方便调试和版本对比,可在 SDK 加载前锁死随机源:

注意:锁定必须在 SDK 加载前完成——SDK 在加载时缓存 Date.nowMath.random 的引用。此外,生产环境不应锁定随机性——服务端可能校验时间戳新鲜度。

这是唯一能证明签名正确的步骤。长度检查、格式检查、Base64 解码检查只是必要条件,不是充分条件。必须通过一次真实的、TLS 指纹模拟的 HTTP 往返来验证。

TLS 指纹模拟是强制要求。同样的签名 URL 和 cookie:

反爬系统在 TLS 握手阶段检测 ClientHello、JA3 指纹、HTTP/2 SETTINGS 帧——这些与真实 Chrome 的差异导致静默拦截。

回顾整个 a_bogus 复现过程,AI 辅助在以下几个环节起到了决定性加速作用:

传统补环境工作流中,Phase 2-3 是最耗时的环节。手动迭代的典型流程:在 fake_env.js 中写几十行 stub → 加载 SDK → 看报错信息 → 补齐缺失属性 → 重新加载。遇到 SDK 对某属性的深层链式访问时,往往需要多轮才能定位到根因。

trace_env.js 的自愈 Proxy 机制本身就是为"减少人工迭代"而设计的。但在实际运行中,guessDefault() 的启发式规则可能产出类型错误的桩值(例如 SDK 期望一个字符串,但 guessDefault() 返回了空函数)。此时 AI 介入:读取追踪日志 → 识别类型不匹配的模式 → 在生成的 fake_env.js 中修正对应属性的类型。这比从头手写快一个数量级。

a_bogus 的输出不是标准 Base64,而是使用了自定义字母表:

直接在浏览器 DevTools 中搜索 a_bogus 赋值位置 → 向上追溯编码函数 → 发现自定义字母表,这是纯人工操作。但当需要批量验证多个签名是否使用相同字母表时,AI 可以快速编写和运行字母表匹配脚本。

"为什么有时候是 172 字符,有时候是 192 字符?" —— 这是一个典型的"需要系统化实验"的问题。在传统工作流中,工程师需要手动修改变量、反复运行、记录结果。而借助 AI:

这种"提出假设 → AI 快速生成实验脚本 → 批量运行 → 分析结果 → 排除或确认假设"的循环,将原本以天计的探索过程压缩到了 1-2 小时内。

debug_trajectory.js 是一个关键的诊断脚本:在 SDK 加载并注入轨迹后,检查 moveList/clickList/keyList 数组的实际内容和长度。通过对比"注入前"和"注入后"的数组状态,确认:

这种诊断逻辑写起来简单,但想到要做这个检查才是关键——而这是 AI 在阅读 SDK 源码时自然产生的假设,继而自动生成了验证脚本。

core/ 目录下的模板文件(sign.jsfake_env.jspersistent_signer.py)是通用框架。每个新站点都需要定制:SDK 文件名、init() 配置、UA 字符串、目标域名、假响应 JSON 结构。AI 在已知站点上下文中,可以一次性完成所有 TODO 占位符的替换,并生成适配后的完整文件。这比人工 grep-then-edit 快 5-10 倍,且不容易遗漏。

对于需要高频调用的生产场景,每次都重新加载 SDK 是不可接受的(首次签名 ~1500ms)。sign.js--server 模式配合 persistent_signer.py 实现了一次加载、持续复用的架构:

凭记忆写 stub 是最大的时间陷阱。SDK 实际探测的属性往往包括 navigator.connection.effectiveTypedocument.characterSetscreen.colorDepth 等冷门属性。错过任何一个,签名静默失败。始终先追踪、再补全。

当签名"看起来正确"但服务端返回 200 空 body 时,第一个排查点就是 TLS 指纹,而非签名本身。requests/urllib/https.request 在 TLS/HTTP-2 层就被识别为非浏览器流量,被静默拦截。必须使用 curl_cffi(Python)或 cycletls(Node)。

签名 SDK 对原始 query string 做哈希。URLSearchParams.toString() 会按字母序重排参数,破坏签名。务必保持原始 URL 的参数顺序。

SDK 初始化时可能 POST 请求运行时 token。如果 fake XHR 的 responseText 返回的不是合法 JSON 或缺少必要字段(如 data.d),初始化会在不抛异常的情况下跳过关键步骤,导致后续签名缺失数据。

SDK 安装了内部心跳定时器和 token 刷新定时器,维持了 Node 事件循环。在所有模式下,签名完成后必须显式调用 process.exit(0)

a_bogus 的补环境复现展示了一种在无法提取算法时行之有效的方法论

Claude Code 与 DeepSeek V4 的组合在此流程中的核心价值不是"替代人类写代码",而是加速探索循环:快速生成实验脚本 → 批量运行 → 分析结果 → 调整假设。在 172 vs 192 的排查中,这个循环被压缩到了分钟级,而不是传统的"改一行 → 跑一次 → 看结果 → 再改一行"的小时级节奏。


[培训]《冰与火的战歌:Windows内核攻防实战》!从零到实战,融合AI与Windows内核攻防全技术栈,打造具备自动化能力的内核开发高手。

最后于 11小时前 被红皮西瓜编辑 ,原因:
收藏
免费 6
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回