逆向工程破解Pokémon GO Plus的OTA签名验证机制

本文详细分析了Pokémon GO Plus设备的OTA固件更新机制,发现并利用内存地址修改漏洞绕过SHA256签名验证,实现无需物理拆解即可提取设备密钥的技术方案。

逆向工程Pokémon GO Plus第二部分:OTA签名绕过

距离我发布Pokémon GO Plus发现已近6个月,至今无人公开其密钥。其中一个原因是从OTP(一次性可编程)存储器提取密钥需要精密焊接技术。在我发表文章几周后,我在/r/pokemongodev提出了通过空中升级(OTA)提取Pokémon GO Plus密钥的想法。

该想法基于两个事实:

  1. 我们可以使用SPI编程器刷写任意镜像,且没有签名检查,只需正确校验和
  2. 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源码未包含的额外验证,但无法确定具体检查方式或如何绕过。

深度分析

在泰国泼水节期间,我抽空逆向分析了引导加载程序。他是正确的,确实存在额外验证:

  1. 更新开始时设置标志位表明固件镜像尚未生效(防止更新失败时无法回退)
  2. 初始化SHA256哈希值
  3. 对每个写入SPI闪存的数据更新哈希值
  4. 更新结束时基于SHA256和OTP数据进行签名验证
  5. 验证通过后设置镜像有效标志

关键发现:

  • 通过SPI编程器修改固件时,有效标志位保持不变
  • 通过OTA修改固件时,需要在更新结束时设置有效标志

更新过程还需要OTP区域的特定密钥。这意味着如果Niantic要发布更新,需要连接其服务器获取特殊密钥。

漏洞利用

通过分析DA14580 SDK中的app_spotar_img_hdlr.c源码,发现关键函数:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
void app_spotar_read_mem(uint32_t mem_dev, uint32_t* mem_info)
{
    *mem_info = 0;
    uint8_t mem_dev_cmd = (uint8_t)((mem_dev & SPOTAR_READ_MEM_DEV_TYPE) >> 24);
    
    if(mem_dev_cmd < SPOTAR_MEM_INVAL_DEV)
    {
        spota_state.mem_dev = (uint8_t)((mem_dev & SPOTAR_READ_MEM_DEV_TYPE) >> 24);
        spota_state.mem_base_add = mem_dev & SPOTAR_READ_MEM_BASE_ADD;
        
        // 设置镜像库编号
        if(spota_state.mem_base_add <= 2)
        {
            spota_state.suota_image_bank = spota_state.mem_base_add;
        }
        else
        {
            // 无效镜像库处理
        }
    }
}

该代码存在设计缺陷:直接使用spota_state.mem_base_add存储临时值。当mem_base_add大于2时函数会失败,但这正是我们需要的漏洞——通过修改mem_base_add实现任意地址写入。

攻击方案

  1. 正常通过OTA发送数据(写入SPI闪存)
  2. 在发送最后数据前,调用app_spotar_read_mem函数,将mem_base_add设置为固件头部的有效标志位地址
  3. 发送最后数据段,覆盖固件头部(包含我们想要的验证标志)

成果实现

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调用的库来实现。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计