AIREITER

逆向代码如何落地:重写、JS 引擎与浏览器桥接的三层阶梯

最后更新: 2026-07-31 07:20:23

逆向工程做到前半程——机械拆分、识别算法家族、差分验证——通常只能得到一个结论:我已经知道它在算什么。但这还不等于能上线。逻辑仍困在原有运行时里,你必须决定它最终是进入 CI 的纯函数,还是一个需要长期照看的外部进程。这个选择一旦错了,前面省下的时间,往往会在运维阶段连本带利地还回去。

四阶段总览里对此只用了一句话概括:“第 4 阶段,按传输层降级。”本文专门展开这一环。核心原则可以浓缩为一句:每往下一级,依赖面、故障模式和部署成本都会增加一个数量级,因此默认策略应当是尽可能往上争取。

三种落地形态,优先级不能倒置

逆向逻辑最终只有三种落地方式,优先级从高到低应直接写进团队规范,而不是每次临时按“哪个先跑通就用哪个”来决定:

  1. 原生重写。用目标语言重写,彻底脱离原始运行时,只依赖标准库。前提是算法家族识别正确:当指纹识别环节通过后,90% 的代码可以直接参考公开实现,剩下的差异点单独处理,最终以纯函数形式落地。

  2. 本地 JS 引擎运行最小片段。有些逻辑短期内不值得完全提纯,可以保留一小段原始 JS,在本地 Node/V8 中执行必要的几十行代码,而不是把整张页面都搬过来。

  3. 被动浏览器桥接。有一类状态只存在于真实且已登录的页面运行时中,例如运行时下发的签名、与会话绑定的动态标识符。静态重建无法复现它们,现阶段只能在浏览器内读取。这只能是临时方案,必须在接口说明中明确标注,绝不能作为默认路径推广。

真正拉开差距的是成本

之所以必须固定这个顺序,是因为三层之间的成本并非线性上升,而是每降一层就跨过一个数量级。

层级

依赖面

故障模式

可进 CI?

原生重写

标准库,无外部进程

输出不一致,一条 assert 就能定位

可以——它是纯函数

本地 JS 引擎

额外引入一个 Node 运行时;V8 context 非线程安全,并发时需要加锁

引擎版本变化,或片段依赖的全局变量缺失

勉强可以——必须先安装引擎

被动浏览器桥接

真实 Chrome + 扩展 + 人工维护的会话 + 本地回环通道

页面未打开、会话过期、页面结构变化、标签页被关闭

不可以——需要活跃的人工操作

第一层的故障由单元测试捕获;第三层的故障则可能是“用户今天把那个标签页关了”。本来能做成纯函数的东西,一旦被降到第三层,就等于把一个人绑到了每一次调用上。一个参考量级是:算法家族确认后,一个混淆过的签名 SDK 可以落成不到 600 行的独立实现,只依赖内置 crypto,运行在第一层。那些你以为只能经由浏览器桥接的能力,很多其实只是算法尚未被完全识别。

被动桥接的边界:只取快照,绝不主动操作

被动桥接失控,通常只因为一件事:它开始“帮忙”了——自动刷新、自动登录、自动等待页面加载。每多一个“自动”,它就离转发器更远一步,离爬虫更近一步。因此边界必须画得极窄。下面这套约束,都是踩过坑后得出的:

只做一次快照探测,绝不改动页面。对已打开页面中的 Cookie、会话状态和页面运行时各探测一次,且仅一次。不要创建标签页,不要刷新,不要跳转,不要聚焦,不要轮询等待。页面不存在就是不存在,不要替用户打开它。

发现缺失状态时,立刻返回明确错误。没有匹配标签页,返回 tab_unavailable;页面存在但未登录,返回 not_logged_in;已登录但运行时尚未就绪,返回 runtime_unavailable。这三个错误码分别对应一种现实状态和下一步动作:等待页面、完成登录或切换目标。不要只丢出一个“失败了”,让调用方自行猜测。

敏感状态绝不能离开浏览器。扩展不申请 cookies / webRequest 权限;它只查询已经打开的匹配标签页,在该页面上下文内完成请求,并在返回前清理字段。Cookie 和页面签名状态不会有任何一步离开 Chrome;通道默认只连接本地回环地址。桥接传递的是结果,不是凭据。

为什么只有几个平台,却要拆成 15 个 scope

被动桥接转发的是经过白名单约束的请求:路径、参数和 referer 都受适配器限制。其中最容易让人意外的是粒度:总共 15 个 scope,却只覆盖了少数几个平台,因为scope 的切分单位不是“平台”,而是“页面上下文”。TikTok 的 Creative Center、Top Ads、Creator platform、influencer library 和 Ads Manager 必须拆成 5 个独立 scope——它们各自拥有独立的会话状态与页面运行时。登录广告后台,并不意味着你能拿到 Creator platform 的运行时。如果一个平台只切一块,第一次遇到“登录了子站 A,却无法服务子站 B”时,就得返工。小红书主站、类 App 路径和创作者市场,同样应划分为 3 个 scope。

调度粒度也要随之匹配:锁只加在平台家族层级。同一家族内的请求,例如抖音家族,需要串行执行,因为它们复用同一个真实标签页;并发向同一页面上下文发请求,会互相踩踏。不同家族,例如抖音和小红书,则可以并行,因为它们对应两个互不相关的标签页。同一家族内还应设置最小请求间隔。粒度太粗,会把本可并行的工作串行化;粒度太细,共享标签页的请求又会冲突。平台家族恰好就是“共享同一页面运行时”的自然边界。

显式登录接管:唯一允许的人为介入

被动桥接不操作页面,但会话终究会过期。解决方式是把人工介入收束成一次明确、单次的动作:交互式命令进入接管流程,程序通过操作系统打开对应业务页面,等待你手动登录,并等待页面运行时就绪,然后重放原始请求。全程中,扩展不点击按钮、不填写表单、不导出 Cookie——登录发生在你亲自操作的真实浏览器内,程序只在你完成后继续处理那条请求。

最关键的约束是:绝不能隐式触发。非交互式命令,例如 CI 或定时任务,绝不应弹出浏览器;它只需清晰返回会话错误,由上层自行决定如何处理。接管流程还要防止误判:登录成功后,运行时最多只能再等待一小段时间,当前实现为 20 秒,之后必须得出结论。如果业务页面已经跳转到账户页,而该页面不存在目标接口所需的上下文,应立即结束当前阶段,而不是把确定性的“到不了”误判成“还在加载”,然后无意义地等待。允许降级的流程应将该阶段记为 unavailable 并继续执行,而不是让整个流程失败。

阶段状态必须可读:完成、主动跳过、需要会话

上述设计背后有一个共同基础:任何阶段都不能只返回“成功”或“失败”。在编排流程中,每个阶段的结果应有六种形态:completed(完成)、empty(执行过但没有数据)、ready(已暂存,等待提交)、skipped(按规则主动跳过)、unavailable(当前不可用,通常意味着需要会话)、blocked(前置条件未满足)。

“主动跳过”“需要会话”和“确实为空”是三种完全不同的信号。如果只返回一个不透明结果,你无法判断空结果究竟是“本来就应该为空”,还是“会话早就失效却没人发现”,流程也就无从运维。将阶段状态建模为有限枚举,上层编排逻辑——无论是脚本还是模型——才能据此决定降级、重新认证还是中止。这与被动桥接的三个错误码遵循同一原则,只是把它提升到了流程层级。

让模型判断能力应落在哪一层

模型只应在这里登场,而且角色必须克制:它不替你提纯逻辑,而是帮助判断是否需要提纯,以及应该提纯到什么程度。这是架构判断,不是破解签名。核心问题只有一个:它依赖的状态能否静态重建,还是只能在运行时获得?接下来再权衡提纯成本与逻辑变化频率。不同步骤对模型能力的要求也不同:

步骤

所需能力

选择

model id

通读整个模块,识别依赖面

长上下文,能一次读懂调用图

Kimi K3

kimi-k3

从正反两面论证分层,反驳“先跑通再说”

强推理能力,能论证为何值得多花一天提纯

Claude Opus 5

claude-opus-5

批量初筛数十到数百项能力

成本低、并发高

Claude Sonnet 5

claude-sonnet-5

降级后的归因分析

中等推理能力,能结合失败日志说明原因

GPT-5.6 Sol

gpt-5.6-sol

第二项最重要。能力分层时最容易犯的错,是模型顺着你“先跑通”的语气,给出“浏览器桥接最简单”的答案——它只是在迎合你,并没有替你核算长期成本。强推理模型则会反驳:“这一段只是标准哈希加上一处常量扰动,值得多花一天落为纯函数,不该放到桥接层。”别只听我说,自己做个测试:挑 3 段逻辑,至少有 1 段是你已经知道正确分层的对照样本;向 claude-opus-5 和 gpt-5.6-sol 输入同一份“给出落地层级、说明理由,并反驳过早进入浏览器桥接”的提示词。只看一件事:它会努力推动你往上提一层,还是懒惰地默认第三层。

真正的阻力是切换成本

来自三家供应商的四个层级,意味着三套 SDK、三种认证方式、三种错误格式。为了在不同步骤间切模型而重写客户端并不划算,于是大多数人最终还是用同一个模型处理全部任务;偏偏在最需要推理能力的分层判断上,使用的是只会附和的模型。

AIReiter 把这一层抹平了:一把 key、一个兼容 OpenAI 的接口,四个层级都在后面;切换模型只需要修改请求体里的 model 字段。

# Placement argument: the reasoning tier
curl https://aireiter.com/api/v1/chat/completions \
  -H "Authorization: Bearer $AIREITER_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "claude-opus-5",
    "messages": [{"role": "user", "content": "<placement prompt + the reversed logic fragment + dependency list>"}]
  }'

# Bulk first-pass triage: change one field
#   "model": "claude-sonnet-5"
# Degradation attribution:
#   "model": "gpt-5.6-sol"

已经在用 OpenAI SDK?将 base_url 指向 https://aireiter.com/api/v1 即可。使用 Anthropic SDK?使用同一把 key 请求 POST /api/v1/messages。价格方面,Claude 模型按标价 7 折计费,GPT 模型半价。这个工作流的成本主要集中在两个位置:一是数十到数百项能力的批量初筛,使用 Sonnet,调用量很高;二是通读完整模块以识别依赖面的长上下文单次输入,使用 Kimi,每次调用的 token 较多。批量初筛跑在 Claude 模型上,折扣正好作用于调用最密集的环节;推理层级的分层论证同样使用 Claude 模型,也享受 7 折。Kimi K3 的长上下文层级则可通过同一把 key 使用。

  • 获取 API key

  • 免注册试用——先手动输入几段逻辑片段,观察两个模型面对“该不该进入浏览器桥接层”时,是在附和你,还是会提出反驳。

结语

逆向逻辑如何落地,归根结底不是技术问题,而是成本问题。原生重写 > 本地 JS 引擎 > 被动浏览器桥接,这条三层阶梯不能倒过来,因为每降一级,都是把纯函数替换成带有外部依赖、人工介入和活跃标签页的进程。被动桥接并非禁区,而是一个边界必须极严的临时组件:只取一次快照、绝不主动操作;页面缺失时立刻明确报错;敏感状态不离开浏览器;登录只能显式接管;阶段状态始终可读。守住这些,它就是可靠的过渡方案;漏掉任何一条,它就会变成没人敢维护的黑箱。模型帮助你判断能力该落在哪一层,也帮助抵抗“先跑通再说”的惯性;至于每次重写是否正确,裁判应当是差分测试,不是模型。