前言
这次做的是一个移动端接口签名还原项目:目标不是把 native 当黑盒服务一直挂着,而是把核心签名逻辑拆出来,做到本地纯算。
最终结果:sig 和 __NS_sig3 都完成了本地计算,文档中的 17 个未登录接口用纯算签名实际请求,全部返回 HTTP 200,响应 body 都是 result=1。
这篇文章只复盘思路和关键判断,不展开可直接复现的代码、常量和完整参数。
一、目标与工具
1.1 目标
项目开始时,请求里主要卡在两个字段:
这类项目最重要的不是“能不能跑一次”,而是要弄清楚每个字段到底由什么决定。
1.2 工具链
整体路线是:
Java 调用链定位
|
v
UniDbg 跑通 native 输出
|
v
trace 中间字段
|
v
Python 复现算法
|
v
样本回归 + 接口验证
二、请求流程分析
2.1 签名链路
请求大致分成两层签名:
query + form body
|
v
按客户端规则拼接明文
|
v
生成 sig
|
v
path + sig
|
v
生成 __NS_sig3
|
v
发送接口请求
几个关键点:
2.2 参数类型要分清
逆向时很容易把“测试参数”和“算法参数”混在一起。这里我把它拆成三类:
| | |
|---|
| | |
| | |
| photoId、userId、keyword、page-code | |
后面实际验证时,业务参数可以替换;签名算法会根据新请求重新计算。
三、核心逆向过程
3.1 sig 的还原
sig 的入口在 Java 层比较容易定位,最终会走到 native 方法。
通过 UniDbg trace 可以看到它内部是一个标准摘要计算流程,并且 update 输入由两部分组成:
请求明文 + native 内固定补充片段
这一步还原完成后,sig 就不需要再调用 UniDbg。
验证方式是多组输入输出回归:
3.2 __NS_sig3 的结构
__NS_sig3 比 sig 复杂很多。它不是简单 hash,同一个输入多次调用,输出也会变化。
trace 后拆成几层:
path + sig
|
v
输入相关摘要
|
v
白盒变换
|
v
CRC 类压缩字段
|
v
拼 raw payload
|
v
checksum + 编码
最终输出是固定长度的 hex 字符串。
我把 raw payload 拆成几段:
这里最关键的是区分两类字段:
3.3 输入相关 hash 的还原
输入相关部分经过了几步:
input bytes
|
v
HMAC/SHA 类摘要
|
v
按 block 分组
|
v
whitebox transform
|
v
压缩成 4 字节字段
这部分是 __NS_sig3 能否纯算的核心。因为只要输入相关字段错,服务端就会直接判签名失败。
我的做法是:
- 用 UniDbg trace 抽出中间 block。
最终可以做到:
path + sig -> input hash
完全本地计算。
3.4 nonce 和 counter 的定位
一开始 nonce/counter 很容易被误判为时间戳。后来通过内存读写确认:
这两个字段不是硬编码。
默认请求时:
nonce:本地随机初始化
counter:每次签名递增
只有在做 trace 回归时,才会固定这两个值,用来复现某一条 native 输出。
四、其他技术点
4.1 为什么有些接口返回数据少
17 个接口都能签名通过,但不是每个接口都会返回丰富数据。有些接口只返回轻量字段或空列表。
为了确认这不是纯算签名的问题,我做了纯算和 UniDbg 对比:
如果纯算和 UniDbg 同参数返回一致,就说明问题不在签名,而在业务上下文。
这类上下文包括:
| |
|---|
ussid | |
llsid | |
recoReportContext | |
| |
| |
4.2 设备字段不是算法硬编码
请求里有大量设备字段。它们会参与签名明文,但它们不是算法本身。
可以这样理解:
设备字段变化
-> sig_plain 变化
-> sig 变化
-> __NS_sig3 变化
这说明签名是跟请求内容绑定的,不代表算法里硬编码了某个设备。
项目里也做过设备随机化测试:保留身份字段,只随机硬件字段时,常用接口可以稳定返回。
五、完整实现流程
5.1 工程结构
最后落地时,我把签名封装成统一入口:
调用流程:
build request
|
v
build sig_plain
|
v
calc sig
|
v
calc sig3(path + sig)
|
v
send request
5.2 回归策略
我没有只看最终接口能不能过,而是做了三层回归:
这能避免一种常见问题:接口偶然过了,但算法其实没有完全对齐。
六、实测结果
6.1 单接口结果
评论首屏接口纯算签名请求结果:
6.2 17 个接口结果
总结果:
七、AI 辅助心得
7.1 AI 做了什么
这次 AI 主要帮在三个地方:
- trace 整理:从大量日志里归纳寄存器、栈字段和内存读写关系。
- 假设验证:快速区分字段是输入相关、状态相关,还是固定协议字段。
效率提升最大的地方是日志阅读。native trace 很容易陷入细节,AI 适合先把证据链梳成结构,再由人判断关键路径。
7.2 正确用法
这类项目里,我不会让 AI 直接猜算法,而是这样用:
AI 的价值不是替代逆向判断,而是把证据整理速度拉起来。
总结
这次逆向的核心收获:
__NS_sig3 已拆成输入 hash、白盒变换、动态状态、外层编码四部分。nonce/counter 不是硬编码,分别来自初始化状态和进程内自增。- 纯算签名实际请求 17 个接口全部返回
result=1。 - 数据少的接口和 UniDbg 对比一致,问题在业务上下文,不在签名。
真正有价值的不是某一段代码,而是完整的验证链路:定位、trace、还原、回归、请求验证。只要这条链路闭环,后面扩展接口就不会靠猜。