kita / notes

约 3 分钟

在模拟器里抓包,抓住的全是密文

想看看某个 App 的接口数据结构。手机抓包太折腾(证书、代理、各种限制),我改用电脑:安卓模拟器跑 App,Frida 挂在进程上。

抓包本身不难,装好代理证书,请求都出来了。

问题是请求体是密文。

密文抓包没用

抓到的请求长这样:body 是一串二进制,响应也是。能看到的只有 URL、状态码、几个明文头。想知道接口到底传了什么参数、返回什么结构,光有密文没用。

这时候方向就变了:不去解前面的密文,而是去截解密之后的数据。

App 拿到响应总得解密才能用,解密之后的那一刻它一定是明文。把这个时刻抠出来就行。

先把目标进程找对

模拟器里看到的进程号不一定能直接用。雷电模拟器有自己的 adb:

D:\leidian\LDPlayer9\adb.exe shell "pidof com.tencent.gamehelper.smoba"

返回一个 PID,比如 12308。然后把这个 PID 交给 Frida:

frida -U 12308 -l hook.js

这里踩了一个坑。 第一次跑的时候正常,第二次报进程不存在——因为 App 重启后 PID 变了。

所以流程得改成"每次先取 PID,再 attach"。我一开始想着记下 PID 反复用,白白浪费了几轮。写法上应该是:

PID=$(adb shell "pidof <包名>")
frida -U $PID -l hook.js

第一个脚本报的错很有意思

写第一个探测脚本的时候,Frida 报:

ReferenceError: 'java' is not defined at <anonymous>

java 这个对象在 Frida 的某些环境下不可用——我在用的这个版本(17.x)里,安卓侧的 Java 桥接和早期写法不一样,直接调 Java.perform 那套会挂。

这类错误没什么深意,就是脚本 API 用错了版本。但报错信息本身很干,只说 java is not defined,不告诉你在哪一行、怎么改。是去翻 Frida 的版本说明才对上的。

把 hook 打在解密函数上

搞清楚 App 用的什么加密之后,就去找解密入口。

先用一个探测脚本把候选函数列出来(就是前面报 java is not defined 那个,改完版本问题之后跑通了)。找到目标之后,写 hook 脚本挂上去,等它被调用时把入参和返回值都 dump 出来。

第一次拿到的输出长这样:

[decrypt(I,I)] inlen=3663 outlen=3663
IN : 6572227b 6e727574 65646f43 2c303a22 74657222 4d6e7275
KEY: 62326130 65366462 32326462 35633532
toByteArray err: Error: toByteArray(): specified array is not of type byte[]

6272 6e72 7565 7443 6f64 653a 3022 ... —— 把这串十六进制按 ASCII 读出来,开头是 erb"nrutCod...,是字节序反了的 JSON({"userCode":0,"return... 这种)。

也就是说:函数找对了,但取数据的姿势不对。 toByteArray 要求的是 byte[] 类型,而传进去的是别的东西,所以 dump 失败——但从入参的长度和内容能看出,里面就是我要的那份解密数据。

后来换了 hook 点,改成挂 XXTEA 那层:

[XXTEA FULL] key=0a2bbd6ebd2225c5 inlen=14652 outlen=14652

这次拿到完整明文,直接就是 JSON 结构。要的是这个。

三条经验

抓包只是第一步。 现代 App 的请求体基本都加密,抓到不等于看懂。真正的位置在"解密之后的那个函数",而不是网络层。

Frida 的 hook 点要试。 我前后试了好几个位置:最外层解密、中间层、底层算法。每个的入参格式不一样,有的要类型转换才能读。找到"直接吐出明文 JSON"的那个,就不用再折腾了。

版本差异会以最没信息量的方式报出来。 java is not defined 这种错误,靠读报错本身解决不了,得知道 Frida 在哪个版本改了什么。

← 回到全部笔记