为了弄清某服务机器人如何获取软件和系统更新,我沿着原客户端的调用链,做了一轮从 APK 静态分析到远程 ADB 取证的排查。
最初的问题很直接:能不能找到软件、固件和机器人 SDK 的下载地址?真正拆开后才发现,这几个目标走的并不是同一条路。接口地址、设备认证、更新响应和最终安装包之间,还隔着几层需要分别核实的机制。
本文于 2026 年 10 月 27 日整理,主要实机取证发生在 10 月 26 日,并结合此前的 APK 分析。到目前为止,已定位 APK 与 Android 系统的更新接口、认证流程和下载地址字段,也读到了设备缓存中的 token;尚未取得真实更新响应、可验证的下载直链或完整开发 SDK 包。
先看整体流程
身份认证取得 token;服务发现与 TLS 配置决定连接方式。客户端结合设备与版本参数分别查询软件和系统更新,再从真实响应中的包信息提取 URL,交给下载器。图中的虚线表示仍需真实响应验证的环节。

功能与字段已概念化,不包含厂商标识或真实凭证。点击图片可查看大图。
一、先把“签名”拆成几个问题
逆向过程中,“签名”很容易成为一个笼统的说法。这次涉及的机制至少有四种:
| 机制 | 它解决的问题 | 本次确认范围 |
|---|---|---|
| 身份认证与加密证明 | 服务端如何识别设备、签发 token | 从客户端代码追到普通认证与芯片认证两条分支 |
| 客户端 TLS 证书 | HTTPS 连接是否需要客户端提供证书 | 确认特定私有域名选择专门的证书配置 |
| 下载链接签名 | 文件 URL 是否自带访问参数和时效 | 目前没有真实 URL,尚不能判断 |
| APK 签名 | 安装包的签署与安装信任关系 | 本轮没有完成重签名、替换安装或验收 |
后文所说的认证分析,主要指前两项。拿到 token 不等于能重签名安装包,也不代表能够生成服务器的下载链接签名。
二、从原客户端追到两类更新查询
分析对象是设备上的原始客户端。沿更新入口、后台服务与网络请求的调用关系继续追踪,可以看到软件更新和 Android 系统更新使用了不同的查询接口。为避免关联具体厂商,本文省略包名、版本号、内部命名和接口原始路径,以下使用功能描述。
| 更新类型 | 查询接口(功能描述) | 响应中的下载地址位置(概念) |
|---|---|---|
| 应用软件 / APK | 软件版本查询 | 最新适用版本 → 应用包列表 → 各包 URL |
| Android 系统 / ROM | 系统版本查询 | 最新适用版本 → 系统包 → URL |
这里确认的是反编译代码中的声明、数据模型与调用关系,还没有真实响应来验证字段值。
软件响应包含一个包列表,因此一次软件更新可能涉及多个 APK。系统响应则有自己的包信息,除 URL 外,还能看到大小、哈希及元数据等字段。
请求也需要设备上下文。两类模型都包含设备身份、机型、硬件版本和软件主版本;软件查询另外包含系统版本。其中机型结构又有自己的型号和主次版本字段,不能与顶层的软件主版本混为一谈。
所以,单独抄出接口路径还不够。服务器需要根据当前设备与版本信息决定返回什么;即使以后拿到了结果,也应理解为该次请求对应的适用版本,不能直接称为所有设备的全局最新版。
三、为什么在 APK 中找到域名,还不能直接拼请求?
网络层使用了服务发现机制。代码里的更新服务标签是内部路由标识,客户端通过配置获取实际服务地址,再由拦截器替换请求主机。
其中一个容易误读的细节是内部主机选择标记:它参与客户端内部的路由选择,随后会被移除,不能照搬成服务端必需的请求头。
这意味着 APK 中的默认网关、引导配置地址与设备运行时实际使用的 OTA 地址,需要分开核对。设备环境、缓存或配置覆盖,都可能影响最终选择。
本次远程采集后来确实读到了 软件与系统更新服务的实际域名。这推进了定位,但仍然不能据此认定请求已经成功:认证、TLS 和设备参数还需要一起成立。
四、身份认证:普通分支与芯片分支
客户端请求使用 Access-Token,token 的选择还与主机配置有关。代码中至少存在两条机器人认证路线。
普通认证分支
普通分支先获取 secret,再使用 secret 与设备身份等参数获取 token。客户端随后缓存并复用 token,并不是每次检查更新都重新申请一次。
这一分支的代码能够说明协议结构,却不能证明某台设备当前一定适用它,也不能证明离线编写的请求已被服务器接受。
芯片认证分支
另一条分支先获取服务器时间,再将设备 MAC 与经过校正的时间组合,交给设备上的加密实现生成证明,随后请求 token。响应中的 token 还需要经对应解密流程,才能成为有效请求头的值。
静态代码显示,这条路线涉及芯片或 TEE 实现,以及根据硬件条件选择的加密路径。知道 SN 和 MAC,并不足以在另一台电脑上完整复现设备侧能力。
代码里还出现了 120 秒的新鲜度比较。它针对证明材料,不能写成“Access-Token 两分钟过期”。本次未确认 token 的准确有效期,也不能仅凭外观将它认定为 JWT。
五、远程 ADB:最先遇到的是目标选择问题
设备连接在远程 Windows 电脑上,命令需要由对方在 PowerShell 中执行。
最初读取 token 得到数量为零,但同一轮 ADB 已经报出:
more than one device/emulator这时命令没有成功读到目标,零条结果不能证明设备不存在 token。后续连接清单中又出现多条在线记录,可能对应同一设备的不同连接方式;仅凭记录数量也不能断言有几台物理机器人。
解决办法是明确选择目标 transport,再执行读取。transport 编号属于当前连接状态,重连后需要重新确认,不能长期写死。
目标选择正确后,回传结果确认了 root 读取权限;采集程序也成功解码两项 token 缓存,每项为 32 字符。报告只保留“读取成功”和长度,未回传 token 明文。
这里的进展是读到了缓存,token 当前是否有效仍未验证。 十六进制形式也只是该缓存的表示方式;把它还原为文本,不等于破解了设备认证算法。
六、有了 token,为什么普通 curl 还不能直接用?
实际发现的服务域名落在原客户端的私有证书分支中。网络代码会对匹配的域名选择带客户端证书材料的 TLS 配置,其余主机则走另一套连接配置。
因此,请求能否成功至少取决于服务路由、TLS 配置、token 和设备参数。只把路径与 token 填入普通 curl 模板,仍可能在 TLS 阶段就失败,根本没有走到业务认证。
本次没有公开证书私钥,也没有将不完整的模板描述为“已验证可查询”。后续应优先复用原客户端已配置好的连接与身份环境,观察它实际收到的内容。
七、检查本地更新缓存:数据库正常,任务表为空
另一条路线是直接检查已保存的 OTA 任务。
远程采集复制了 更新配置文件,以及 SQLite 的 更新任务主库、-wal 和 -shm 文件。之所以连 WAL 一起取,是因为较新的事务可能尚未写回主库,只拿主文件容易遗漏内容。
这次检查得到:
| 项目 | 结果 |
|---|---|
| SQLite 快速完整性检查 | ok |
| 更新任务表 | 0 条记录 |
| 主库和 WAL 中的 URL 候选 | 0 个 |
| OTA 参数 | 取得主版本与机型参数 |
附件来自运行中数据库文件的顺序复制,并非事务一致的 SQLite 备份。因此,这次能说明的是:所取得的缓存中没有恢复出下载地址。空任务表不能证明服务器没有更新,也不能证明更新查询一定失败。
八、415 字节的 二进制日志:乱码最终被确认是压缩流
缓存没有结果,下一步转向检查更新时的日志。
普通日志采集只得到一条文件位置记录,指向一个 二进制日志 文件。最初将它作为文本读取,得到不可读字符;这种处理方式可能改变二进制数据,于是改成原样复制后再分析。
原始文件只有 415 字节。结合 原生日志库中的压缩流与解码相关代码,最终确认这份样本采用 带两字节小端属性头的 PPMd8 压缩流。
解码结果如下:
| 检查项 | 结果 |
|---|---|
| 原始压缩文件大小 | 415 字节 |
| 解码文本大小 | 730 字节 |
| 压缩流结束标记 | 正常到达 |
| 解码后内容 | 日志头、文件轮换记录 |
| 更新请求、响应或下载 URL | 未出现 |
这一步解决了“文件是什么格式”,却没有取得更新响应。解码成功与找到目标内容是两件事,也不能将这个样本的格式推广为所有 二进制日志 文件都相同。
操作方反馈,点击检查更新后页面提示“已经是最新版本”。但还没有原始 JSON,因此无法判断响应是否带有包信息或 URL,也无法独立核对页面结论。
九、下载地址到底从哪里来?
沿现有更新下载代码继续追踪,能看到更新响应中的包 URL 被复制到下载任务,再交给 Android DownloadManager。
在已恢复的这条调用路径中,没有看到额外添加 Access-Token 或 Cookie,也没有看到下载前再请求一次链接签名接口。
这是一个有用的静态观察:最终 URL 来自更新响应中的包字段。 但它仍不能证明 URL 永久有效或公开可用。服务器可能直接返回带签名参数的 URL;由于本次没有真实地址,其访问策略、有效期和是否可复用都还是未知。
也不能根据 APK 版本名、包名或某次时间接口请求,推算出一个可靠的下载直链。
十、SDK 版本号与 SDK 下载包,要单独处理
排查中还出现了两个容易混淆的版本概念:
- 采集报告里的 Android SDK 等级指系统 API 等级。
- 设备页面还显示了程序引用的机器人 SDK 版本;具体版本字符串在此省略。
它们都不能直接提供完整开发 SDK 的下载地址。设备应用更新、系统固件更新与开发者 SDK 分发是不同目标,后者仍需要另查发布渠道。
十一、下一步:观察原 OTA 进程实际收到的响应
到这里,继续重复查空缓存或同一份日志,能够增加的信息已经很有限。
下一条更直接的路线,是在原 OTA 进程的 HTTP / Retrofit 响应边界观察已解密内容,让原程序继续处理服务发现、客户端证书和 token。可以考虑使用 Frida,但本轮还没有完成设备兼容性验证或实际响应采集。
另一条路线是在电脑上适配完整客户端 TLS 配置,使用已授权的设备参数与有效 token 发起只读查询。目前也没有完成这条路线的服务端验证。
设备调试页的开关则不能随意当作网络日志开关使用:已检查的处理代码涉及 USB 调试、安装模式等系统行为,并未确认它们能打开 HTTP 响应日志。
目前,这轮排查把“找下载地址”缩小成了一个更具体的问题:如何取得原客户端的一次完整更新响应。接口、认证路径和 URL 字段已有证据,最后的响应与下载验证仍需补齐。后续有了新证据,再继续更新这篇记录。