逆向工程Pokémon GO Plus第二部分:OTA签名绕过
距离我发布Pokémon GO Plus发现已近6个月,至今无人公开其密钥。其中一个原因是从OTP(一次性可编程)存储器提取密钥需要精密焊接技术。在我发表文章几周后,我在/r/pokemongodev提出了通过空中升级(OTA)提取Pokémon GO Plus密钥的想法。
该想法基于两个事实:
- 我们可以使用SPI编程器刷写任意镜像,且没有签名检查,只需正确校验和
- SPI闪存包含两份相同固件(存在2个固件库)。这对OTA至关重要:若固件传输失败,引导加载程序(位于OTP中)将启动另一个有效固件
计划如下:
- 创建自定义固件
- 通过OTA将新固件刷写到Bank 1。此时将存在两个固件:我们的固件和官方固件
- 通过BLE(低功耗蓝牙)使用新固件提取密钥
- 通过向新固件发送特殊请求恢复原始固件。通过读取Bank 2的原始固件并覆盖Bank 1的固件实现
实施挑战
遗憾的是我缺乏时间实施该方案。我没有DA14580开发板,无法通过JTAG进行调试。我不愿为单一项目花费40美元购买开发板。获得的30美元捐款被我用于购买另一个Pokémon GO Plus克隆设备(注意:资金不是主要问题,我更愿意购买感兴趣的东西,比如最近购入的Nvidia Jetson Nano和立体显微镜)。
两个月前,Reddit用户jesus-bamford联系我,表示将实施我提出的方案。一切按计划进行:
- 他能创建从OTP提取密钥的固件
- 能使用SPI编程器写入固件
- 能通过OTA发送固件(使用Dialog Semiconductor提供的Android应用)
但好消息到此为止:通过OTA写入的固件无法启动。引导加载程序认为固件无效,会启动原始固件。他发现软件更新中存在SDK源码未包含的额外验证,但无法确定具体检查方式或如何绕过。
深度分析
在泰国泼水节期间,我抽空逆向分析了引导加载程序。他是正确的,确实存在额外验证:
- 更新开始时设置标志位表明固件镜像尚未生效(防止更新失败时无法回退)
- 初始化SHA256哈希值
- 对每个写入SPI闪存的数据更新哈希值
- 更新结束时基于SHA256和OTP数据进行签名验证
- 验证通过后设置镜像有效标志
关键发现:
- 通过SPI编程器修改固件时,有效标志位保持不变
- 通过OTA修改固件时,需要在更新结束时设置有效标志
更新过程还需要OTP区域的特定密钥。这意味着如果Niantic要发布更新,需要连接其服务器获取特殊密钥。
漏洞利用
通过分析DA14580 SDK中的app_spotar_img_hdlr.c源码,发现关键函数:
|
|
该代码存在设计缺陷:直接使用spota_state.mem_base_add存储临时值。当mem_base_add大于2时函数会失败,但这正是我们需要的漏洞——通过修改mem_base_add实现任意地址写入。
攻击方案
- 正常通过OTA发送数据(写入SPI闪存)
- 在发送最后数据前,调用app_spotar_read_mem函数,将mem_base_add设置为固件头部的有效标志位地址
- 发送最后数据段,覆盖固件头部(包含我们想要的验证标志)
成果实现
Jesus-bamford成功实现了该方案。相关固件已开源: https://github.com/Jesus805/PGP_Suota
Android版OTA更新工具尚未发布,他正在基于SDK代码重写。完成后,任何人都无需拆解设备即可提取Pokémon GO Plus密钥。
本文提前发布是希望吸引更多开发者参与,可能实现iOS版工具,或开发其他设备的Pokémon Go Plus实现方案。甚至可能通过EdXposed(兼容Pokémon Go的XPosed分支)或拦截iOS BLE API调用的库来实现。