逆向工程破解 Pokémon GO Plus 的 OTA 签名验证机制
逆向工程 Pokémon GO Plus 第二部分:OTA 签名绕过
距离我发布Pokemon Go Plus研究发现已近6个月,至今无人公开其Pokemon Go Plus密钥。其中一个原因是从OTP(一次性可编程)存储器提取密钥需要精密焊接技术。在我发表文章几周后,我在/r/pokemongodev提出了通过空中下载(OTA)更新提取Pokemon Go Plus密钥的设想。
该设想基于两个事实:
- 我们可以使用SPI编程器刷入任意镜像,且不存在签名验证,只需正确校验和
- SPI闪存包含两份相同固件副本(存在2个固件库),这对OTA至关重要:若固件传输失败,引导程序(位于OTP中)将启动另一份有效固件
计划方案如下:
- 创建自定义固件
- 通过OTA将新固件刷入Bank 1。此时设备将存在两个固件:我们的固件和官方固件
- 通过新固件的BLE(低功耗蓝牙)功能提取密钥
- 向新固件发送特殊请求恢复原始固件(通过读取Bank 2的原始固件覆盖Bank 1的固件)
实施挑战
两个月前,Reddit用户jesus-bamford联系我并表示将实施该方案。进展看似顺利:
- 他能创建从OTP提取密钥的固件
- 能使用SPI编程器写入固件
- 能通过OTA发送固件(使用Dialog Semiconductor提供的Android应用)
但好消息至此为止:通过OTA写入的固件无法启动。引导程序认为固件无效,会启动Pokemon Go Plus的原始固件。他发现软件更新过程中存在SDK源码未提及的额外验证机制,但无法确定具体验证方式或绕过方法。
深度逆向分析
在泰国泼水节假期期间,我通过逆向工程引导程序确认了额外验证机制的存在:
- 更新启动时设置标志位表明固件镜像尚未生效(确保更新失败时能启动其他有效固件)
- 初始化SHA256哈希值
- 每个写入SPI闪存的数据块都会更新哈希值
- 更新结束时基于SHA256和OTP数据进行签名验证,验证通过后设置镜像有效标志
关键发现:
- 通过SPI编程器修改固件时,有效标志位会保留
- 通过OTA修改固件时,需要在更新结束时通过验证才能设置有效标志
- 更新过程需要OTP区域的特定密钥(意味着Niantic的官方更新需要服务器连接获取特殊密钥)
突破性漏洞发现
通过分析DA14580 SDK中的app_spotar_img_hdlr.c源码,发现关键函数:
ret = spi_flash_write_data(spota_all_pd,
(spota_state.mem_base_add + spota_state.suota_img_idx),
spota_state.suota_block_idx);
其中spota_all_pd包含来自更新应用的数据,第二个参数指定SPI闪存写入地址,第三个参数为数据大小。
更重要的是发现app_spotar_read_mem函数存在设计缺陷:该函数使用spota_state.mem_base_add存储临时值而非使用临时变量。当spota_state.mem_base_add大于2时函数会失败。
漏洞利用方案
利用流程:
- 正常通过OTA发送数据(数据将按预期写入SPI闪存)
- 在发送最后数据块前,触发app_spotar_read_mem调用,将mem_base_add修改指向固件头部的有效标志位地址
- 发送最终数据块覆盖固件头部(包含我们设计的有效头部数据)
成功实践
Jesus-bamford出色地实现了该方案并取得成功。相关固件已在GitHub开源:
https://github.com/Jesus805/PGP_Suota
目前Android版更新工具尚未发布(正在基于SDK代码重写)。完成后用户将无需拆机即可提取Pokemon Go Plus密钥。
虽然本应等待其工作完成再发布本文,但我希望更多人能参与开发:包括iOS版本实现、其他设备的Pokemon Go Plus适配,甚至通过EdXposed(兼容Pokemon Go的XPosed分支)或iOS BLE API拦截库实现相同功能。
更多精彩内容 请关注我的个人公众号 公众号(办公AI智能小助手)
公众号二维码