AIREITER

Frida Hook 全部命中,为什么可用端点依然是 0?

最后更新: 2026-07-31 06:13:43

三个平台的 Native 调用链我都已经定位清楚:设备注册从哪里进入,安全 SDK 在哪一层拦截请求,Native 拦截器如何改写发出的报文,以及 JNI 通过 RegisterNatives 动态注册了哪些运行时方法。Frida 脚本成功附加,日志持续滚动,所有 Hook 都能稳定命中。

但当我打开命令目录,统计这三个平台真正可调用的 Native App 端点时,结果是:0。

同一个项目里的另一个平台则有 11 个端点。这些端点经过真机验证,已经迁入代码,并以一等命令的形式稳定运行。

差距不在 Hook 技术。该挂的点都挂上了,调用链图也画得很完整。真正的分界线,是很多人容易忽略的一道门槛:Hook 命中和能力可用之间,还隔着一个证据分层。本文想讨论的正是如何定义这道门槛、为什么标注“暂不可用”比发布半成品端点代价更低,以及模型在这个过程中究竟能帮上什么忙。

Hook 命中,不代表端点已经可用

做 App 逆向时,很多人会把“Hook 命中了”当成终点。脚本成功挂到目标方法,日志里能看到参数、返回值和调用栈;那种“我已经进去了”的感觉确实很强,也很容易让人误判进度。

但命令目录并不接受“我进去了”这种成果。它只认一件事:给定正常输入,这个命令能否稳定产出非空、结构正确、可供下游消费的数据。两者之间,距离很远。

常见的落差是这样的:所有 Hook 都命中,调用链完整,日志中能看到设备注册请求发出,能看到安全 SDK 算出对应值,也能看到 Native 拦截器补上签名头。表面上一切都对。可一上真机,设备注册返回的是零值 device ID,或者详情接口拿到一个空响应体。链路通了,数据却是空的。

此时你拥有的,只是一组能够观察某个版本 App 内部行为的探针;你还没有一个真正可调用的端点。把前者当成后者交付,等于给后续所有接手的人埋下一颗雷。

11 和 0 的差别,藏在证据门槛里

把两组平台放在一起比较,门槛就一目了然。

对照平台(某视频社区 App)

三个头部内容 App

Hook 调用链

已定位,并经过真机验证

均已定位,所有 Hook 均命中

真实安装态

获得非零设备身份标识

device ID 为零 / 缺少真实设备画像

非空响应

返回结构化详情数据

详情为空 / 响应体为空

进入命令目录

11

0

对照平台上的这 11 个端点,并不是因为 Hook “更漂亮”。每一个端点在迁入一等 Python 命令前,都跨过了下一节的四道证据门槛,并附带结构化的验证证据。

那三个平台也并非没有进展。安全 SDK 的拦截层、Native 请求拦截器、JNI 动态注册的一批方法,都已经摸清,Frida Hook 也仍在正常工作。但只要真实安装态无法通过,只要设备注册拿不到非零身份标识,后续结果就会全部为空。因此,它们的 Native App 命令如实维持在 0;当前真正可用的,是完全独立的 Web 或浏览器传输路径。

从整体账面看,这一批移动端能力共 32 项,其中 23 项已经真正落地为实现,剩余 9 项卡在“等待真实设备画像”阶段,且没有任何一项被提前放进目录。数字 9 不代表失败,而代表纪律:它精确标出了“调用链已经理解,但证据仍然不足”的范围。

能力入库前,必须跨过四道证据门槛

拆开来看,一项 App 能力要进入命令目录,必须同时满足四个条件。缺任何一个,都不能入库。

第一道:真实安装态。 请求必须来自能被上游识别的、非零的安装身份。模拟环境里 Hook 命中,或者设备画像损坏时命中,都不算。设备注册返回零值 ID,就意味着这道门没过;此后调用链再完整,输出也只会是空的。三个平台正是共同卡在这里。

第二道:非空响应。 请求发出并不等于拿到了数据。可能状态码是 200,响应体却为空。这种“成功的空响应”比报错更危险,因为只检查是否抛异常的逻辑,很容易直接放过它。这里要求的是可直接交给下游的、结构完整且非空的数据。

第三道:完整的错误分类。 一项成熟能力失败时,必须能说明失败原因,而不是抛出一个笼统错误。页面不存在、未登录、运行环境未就绪、响应为空,背后的失败路径完全不同,必须映射为可区分的错误码,不能混成一类。一个只能说“失败了”的命令,还不具备入库条件,因为调用方无法判断该重试、重新认证,还是直接跳过。

第四道:闭环且可重复的测试。 一次偶然成功,不是能力。相同输入、相同流程必须可以重复跑通,并且要把成功结果固化为保存下来的结构化验证证据。这次成功、下次为空,说明你还没有真正掌控这条链路,只是某次刚好撞上了匹配的运行时状态。

四道门槛全部闭环,能力才能注册进命令目录。只要有一道未通过,它就仍是一项研究资产,而不是端点。这两个词的区别,正是全文的核心。

Frida 日志爆量后,按模型层级拆分处理

判断一条链路卡在哪道门槛,依据就是 Frida 产出的 trace。而 Frida trace 的规模很容易失控:挂上几十个方法、完整跑一遍链路,出现数万到数十万行日志都很常见;其中绝大多数还是 polyfill、心跳和无关业务模块带来的噪声。

把整份日志直接丢给一个模型,再问“为什么这个 Hook 没拿到数据”,通常只会得到笼统猜测。上下文越大,模型越可能把两段无关调用拼在一起。更合理的做法是先切块,再按任务能力把不同片段交给不同模型层级。

这正是典型的分层协作场景,而不是“挑最强模型包打天下”。这四类工作对模型的要求完全不同:

Frida 日志处理环节

所需能力

选择

model id

一次读完整条调用链的 trace

超长上下文,可同时阅读整条链路

Kimi K3

kimi-k3

给数万行日志打标(设备注册 / 网络 / 加密 / 噪声)

成本低,能高并发处理数千次调用

Claude Sonnet 5

claude-sonnet-5

判断链路卡在四道门槛中的哪一道

推理能力强,敢于给出明确判断

Claude Opus 5

claude-opus-5

对比两份 trace 的分歧点,解释响应为何为空

中等推理强度的归因能力,能针对具体日志行解释

GPT-5.6 Sol

gpt-5.6-sol

尤其值得单独说的是打标环节。这本质上是体力活:在数万行日志中剔除噪声,只保留设备注册、加密和网络相关类别,量级太大,不适合人工完成。这种“简单判断、超高频执行”的任务,正是低成本层最擅长的场景;拿推理层去做,只是在直接浪费预算。等日志被压缩成几百行带标签的摘要,再交给推理层判断“卡在哪道门槛”,成本和准确性才能同时对齐。

分层后的效果不用凭感觉判断,直接跑一轮对照:

  1. 截取一条链路的 trace 片段,规模可从数千行到数万行。

  2. 先用 claude-sonnet-5 分块打标,剔除噪声,仅保留设备注册、加密和网络相关类别。

  3. 把带标记的摘要交给 claude-opus-5,要求输出“这条链路目前卡在四道门槛中的哪一道,以及证据对应哪些日志行”。

  4. 作为对照,把同一份原始 trace 整体丢进单个模型,问同样的问题。

  5. 只看一个结果:它给出的是笼统猜测,还是明确指出具体门槛与具体日志行。这就是模型选型标准。

Hook 是版本绑定的观测探针,不是签名器

为什么这三个平台的 Hook 要保留,却坚决不放入命令目录?因为 Hook 是探针,不是签名器,两者在本质上完全不同。

Hook 绑定的是某一个具体 App 构建版本,例如 32.x。它依赖的符号、偏移和方法布局,都属于这个版本。上游一发布新版本,这些位置一变,Hook 往往立即失效。它天生易过期、强版本绑定,回答的是“这个版本此刻在内部做了什么”——这是观测问题。作为插桩工具,Frida documentation 对 Interceptor 的描述也是在运行时观察和改写调用。它能让你看到函数怎样被调用、参数是什么,但它本身不是一个能“根据输入计算签名”的可交付能力。

能进入端点目录的签名器恰好相反:它必须稳定、可重复、可独立运行,并且能够接入 CI。它回答的是“给我一个输入,我就能计算出正确签名”——这是可复用能力的问题。即便 Hook 已经让你清楚看到签名算法的骨架,那也只是 算法指纹识别 阶段;距离独立签名器,仍然隔着完整的纯化与差分验证流程。

这也解释了纯化阶梯中的优先级:能力应优先争取纯 Python 直连;其次才是在本地 Node/V8 运行最小签名片段;最后才勉强接受被动浏览器桥接。Hook 甚至还不在这条阶梯上。它位于更上游的“研究”阶段,而不是“实现”阶段。把一个版本绑定的观测探针注册为签名器,无异于给一块半成品研究成果挂上“已实现”的牌子。

研究资产怎么归档,才不会两个月后失效

不把 Hook 放入端点目录,不等于把它扔掉。里面沉淀了你真实投入过的工作:调用链知识、样本和验证证据。直接删除是纯损失。正确做法是把它归档好,否则两个月后,可能连你自己都不知道当时究竟推进到了哪一步。

一项 App 研究资产,至少要记录以下四类信息:

  • 传输类型。 链路走的是 Native 协议、本地 Node/V8 签名,还是被动浏览器桥接。这决定了后续能把它纯化到什么程度。

  • 证据等级。 四道门槛通过了几道。是“已定位调用链”,还是“拿到非零安装态但响应为空”,又或者“响应非空但无法重复”。这能让后续接手的人立刻知道,距离可用还差什么。

  • App 版本 / 构建版本。 Hook 绑定的具体版本。缺少这一项,上游更新后你根本无法判断是自己写错了,还是构建版本已经变化。

  • 样本摘要。 保存本次运行的输入和输出形态,并进行脱敏处理。这是重新启动研究时最快的锚点。

记录好这四项,哪怕一条链路当前对应 0 个命令,它仍是一项可以继续推进的资产,而不是一堆过期日志。同时,这也能避免两种最糟糕的处理方式:要么删掉 Hook,假装研究从未发生;要么强行塞进目录,假装它已经可用。后者尤其昂贵。一个“看起来能调用、实际总是返回空数据”的端点,会把成本扩散给每一个信任目录的下游调用方:他们会据此编写集成、补上重试逻辑、被空数据坑过一次,最后连整个目录都不再信任。诚实地留一个“研究资产,0 个命令”的缺口,只需要你在 README 里承担一次、一行的成本。

空壳端点的代价高于能力缺口,这个场景再清楚不过。

一把 Key,降低日志分层的切换成本

回到第四节的日志拆分:超长上下文模型负责通读整份 trace,低成本层负责批量打标,推理层负责分类,中等推理层负责归因。四层模型来自多个供应商,意味着四套 SDK、四种认证方式、四种错误格式。为了给一份 Frida 日志调动四个客户端,很多人算完成本就放弃了,最后仍用一个模型硬啃数十万行 trace——要么烧钱,要么根本处理不完。

AIReiter 把这层摩擦去掉了:一把 Key、一个兼容 OpenAI 的接口,四层模型都在后面;只需修改请求体中的 model 字段即可切换。

# Label the log: the cheap tier, thousands of calls at high concurrency
curl https://aireiter.com/api/v1/chat/completions \
  -H "Authorization: Bearer $AIREITER_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "claude-sonnet-5",
    "messages": [{"role": "user", "content": "<one chunk of trace + labeling instruction>"}]
  }'

# Classify the threshold: switch to the reasoning tier, leave the rest
#   "model": "claude-opus-5"
# Read the whole call chain: the long-context tier
#   "model": "kimi-k3"
# Attribute the response difference:
#   "model": "gpt-5.6-sol"

如果你已经在使用 OpenAI SDK,只需将 base_url 指向 https://aireiter.com/api/v1,其他内容无需修改。使用 Anthropic SDK 时,则用同一把 Key 请求 POST /api/v1/messages。

这篇文章涉及的成本结构并不均衡,优惠恰好覆盖在成本最集中的部分。一条链路的 trace 往往有数万到数十万行,打标需要逐块运行,一个平台就会产生数百到数千次 claude-sonnet-5 调用,这构成了主要成本。读取整份 trace 的超长上下文调用,每次输入也有数十万 token,是另一大块成本。分类和归因的调用次数较少,但单价更高。Claude 七折正好覆盖打标和门槛分类这两个最昂贵的环节;GPT 半价覆盖归因层;超长上下文阅读则使用同一把 Key 可调用的 Kimi K3。

  • 获取 API key

  • 免注册试用:先把一段 trace 交给 claude-sonnet-5 打标,再让 claude-opus-5 做分类,在决定是否接入前,看看它能否直接指出链路卡在哪道门槛。

结语

在 App 逆向中,Hook 命中带来的是“我能看到这个版本内部在做什么”的观测能力;端点目录需要的,则是“给我输入,我能稳定输出非空且正确的数据”的调用能力。两者之间隔着四道证据门槛:真实安装态、非空响应、错误分类和可重复测试。

三个平台的调用链都已定位,所有 Hook 也都命中,但命令数仍然为 0——这不是失败,而是纪律。真实安装态没有通过,就如实停在 0,把 Hook 归档为研究资产;等拿到设备画像后,再继续向前推进。

模型在这套流程中的位置很明确:它负责拆分、打标、分类和归因,帮你处理数十万行 trace,把“这条链路卡在哪里”的判断从读一下午日志压缩到几分钟。但“是否跨过门槛”的最终判断,和差分测试中“假设是否正确”的判断一样,并不该交给模型。它最终取决于你设定的四项标准,以及能够稳定重复跑通的证据。至于如何在完整逆向工作流中把模型放在合适的位置,可以参考四阶段总览。