这篇是一次匿名化的排查记录,只保留方法和判断过程。真实包名、域名、接口路径和业务背景都做了脱敏,统一记成某 App、某网关、登录接口。

我这次的目标很简单:不是研究整个 APK,而是把某 App 的手机号登录链路摸清楚,最后拿到真实请求参数,方便和后台联调。

环境先定死,不然后面会一直怀疑链路

我这次用的是:

  • macOS
  • Android Studio 自带模拟器,机型是 Pixel 8a
  • adb
  • Proxyman
  • 一份 APK 安装包

先把 APK 装进模拟器,确认 App 至少能正常拉起:

adb -s emulator-5554 install -r base.apk
adb -s emulator-5554 shell monkey -p 某.app.package -c android.intent.category.LAUNCHER 1

这一步看着没技术含量,但很关键。后面如果抓不到流量,得先排除是不是 APK 根本没跑起来,或者其实还停留在 splash / 隐私页 / 历史登录态。

第一轮先做静态分析,但不要太相信静态结果

我一开始先把 APK 反编译了一遍,主要看三类东西:

  • 包名、版本号、启动页
  • Retrofit / OkHttp 这类网络定义
  • dex 里的接口路径和动作名字符串

静态分析的价值主要有两个:

  1. 先知道它大概打向哪些后端
  2. 先知道它是 REST 风格,还是网关风格

我这次遇到的就是典型网关风格。URL 本身信息很少,真正的业务动作不在路径里,而是在请求体里的两个字段里:

A=业务动作
P=业务参数

也就是说,哪怕你在抓包工具里看到几十条长得一模一样的 POST,它们也可能分别对应“发验证码”“登录”“拉用户信息”“拉首页数据”。

静态分析里我还看到过一套看起来像旧登录服务的定义,一开始差点以为那就是登录入口。后来事实证明,这种判断很容易错。包里存在,不等于现在运行真的走它。

第二轮开始抓运行时流量,先踩的坑不是证书,是代理根本没进

我本机最开始开的是别的代理工具,模拟器里也把全局代理指过去了,但流量面板完全没有记录。

后来换成 Proxyman,问题开始清楚了。

Android 模拟器访问宿主机,不该填 127.0.0.1,而是:

10.0.2.2

所以模拟器里的代理要这么设:

adb -s emulator-5554 shell settings put global http_proxy 10.0.2.2:9090
adb -s emulator-5554 shell settings get global http_proxy
# 取消代理
adb -s 172.20.130.185:5555 shell settings put global http_proxy :0

我当时专门验证了三件事:

  • 模拟器能 ping 10.0.2.2
  • 宿主机上的 Proxyman 确实监听了 9090
  • 模拟器能直接和 10.0.2.2:9090 建立 TCP 连接

这一步非常重要。很多时候你以为是 App 绕过了代理,结果实际上是模拟器根本没打到宿主机代理端口。

浏览器能走代理,只说明链路通,不代表 App 一定会老实走

我排查代理是否生效时,用了一个很土但很有效的方法:先不碰 App,直接让模拟器浏览器访问一个纯 HTTP 页面。

如果浏览器请求能在 Proxyman 里看到,说明至少这条链路是通的:

模拟器 -> 10.0.2.2:代理端口 -> Proxyman

这一步只解决了“代理通不通”,没有解决“某 App 会不会按系统代理走”的问题。

两者不是一回事。

有些 App 会老老实实走系统代理,有些不会。有些即使走了代理,到了 HTTPS 阶段还会因为证书校验、TLS 握手或更严格的校验策略直接失败。

日志里出现握手失败,不等于前面的判断全白做了

我当时在 logcat 里能看到明显的 TLS 相关调用栈,比如:

  • startHandshake
  • connectTls
  • getOutputStream

这说明有部分网络请求确实已经走到了 HTTPS 连接阶段,只是后面没有顺利完成。

这类现象很容易把人带偏,误以为“代理完全没生效”。其实不一定。

更准确的理解应该是:

  • 代理链路可能是通的
  • 请求也可能已经发起了
  • 但某些 HTTPS 流量在客户端校验阶段被拦下来了

所以这一步我没有再继续怀疑代理端口,而是转头去看另一件更关键的事:这次抓到的流量,到底是不是登录时刻的流量。

第一次导出的 CSV 看着很多,其实没法直接定位登录请求

我后面导出过一份 CSV,请求数量并不少,但问题也很明显:

  • 能看到某统计域名
  • 能看到某网关入口被多次调用
  • 能看到若干初始化接口
  • 但看不到 POST body

这就导致一个典型误判:表面上像“没抓到登录接口”,其实更可能只是导出格式不对。

因为这个 App 的关键数据根本不在 URL 上,而是在 body 里。例如同样是某个统一网关入口:

  • 一条可能是发验证码
  • 一条可能是提交手机号和密码
  • 一条可能是登录成功后拉用户信息

只看 CSV 里的路径和状态码,根本分不出来。

后来我回头看那份导出,还能发现一个反证:请求里已经带上了登录后才常见的一些上下文字段。这说明那次抓到的不是“第一次登录动作”,而是已经进首页之后的初始化流量。

清数据重新进一次,才有资格谈登录链路

这次排查里最容易被忽略的一步,就是清空 App 数据。

如果模拟器里残留了历史状态,App 很可能:

  • 直接跳过登录页
  • 先落到主页
  • 再补发一堆首页初始化请求

这样抓出来的数据再多,也不是你要的那条登录链路。

所以我后面每次重新验证前,都会先做:

adb -s emulator-5554 shell pm clear 某.app.package
adb -s emulator-5554 shell monkey -p 某.app.package -c android.intent.category.LAUNCHER 1

只有保证自己从冷启动、未登录状态进 App,后面看到的验证码请求、登录请求、登录后请求,时间顺序才可信。

真正有用的结论,不是“抓到了一个 URL”,而是确认了它的调用模型

最后把静态分析和运行时抓包对起来,最有价值的不是某一条具体路径,而是下面这几个判断被坐实了:

  1. 这个 App 的主业务请求走统一网关,不是每个功能一个独立 URL。
  2. 真实业务动作藏在 body 里的 AP,不是藏在 path 里。
  3. 静态分析里看到的某些旧接口定义,和当前实际登录链路不是一回事。
  4. 只导出 CSV 摘要不够,必须能看到 request body,才能判断哪一条是登录。
  5. 如果不先清数据,抓到的大概率是首页初始化流量,不是登录流量。

也正是因为这几个判断明确了,后面我才能在代理工具里缩小范围,最终拿到真实的登录请求参数。

这次排查里最值钱的其实是三个经验

1. 静态分析负责缩小范围,动态抓包负责确认事实

只看静态分析,很容易被包里的历史代码误导。只看动态抓包,如果没有上下文,又容易把网关型请求看成一堆无意义的重复调用。

两者一定要结合着看。

2. 模拟器代理最容易错在宿主机地址

只要是 Android Emulator,访问宿主机代理,先想 10.0.2.2,别先想 127.0.0.1

3. 导出格式决定你能不能定位登录动作

如果导出的只是 URL、状态码、请求体大小,那基本只能做流量概览。真正要定位登录,至少要能看到:

  • 请求时间顺序
  • Request Body
  • Response Body

否则你只能看到“网关被调用了”,看不到“登录到底怎么调的”。

收尾

这次过程看起来像是在折腾反编译、代理、模拟器和日志,其实最后真正解决问题的,是把每一层的职责分清楚:

  • 反编译用来判断请求模型
  • 代理用来拿运行时真实参数
  • logcat 用来判断代理阶段卡在哪
  • 清数据用来保证时序可信

把这些顺序理顺之后,登录链路其实没有想象中那么玄学。难的不是工具本身,而是别在错误的样本上做判断。 proxyman.png

ps: 对于https 需要安装proxyman证书才能解密(如果是抓包设备应该把proxy证书安装在设备上),对于http明文可以看到