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

本文详细分析了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将新固件刷写到Pokémon Go Plus的Bank 1。此时将有两个固件:我们的固件和pokemon go plus固件使用新固件通过BLE(蓝牙低功耗)提取密钥通过向新固件发送特殊请求恢复原始固件。这是通过读取bank 2上的原始固件并覆盖bank 1中的我们的固件来实现的

实施挑战

不幸的是,我没有时间实现它。我没有DA14580开发板,因此无法通过JTAG进行适当调试。我不想为一个只用于这个项目的板子花40美元。我收到了大约30美元的捐款,我用它买了另一个Pokemon Go Plus克隆品,它有相同的蓝牙MAC地址。(注意钱不是真正的问题,我只是喜欢把它花在我喜欢的东西上,例如,我最近刚买了Nvidia Jetson Nano和立体显微镜)

两个月前,Reddit用户jesus-bamford联系我,提到他将实施我提出的想法。一切似乎都按计划进行: 他可以创建从OTP提取密钥的固件他可以使用SPI编程器写入他的固件他可以通过空中发送他的固件(使用Dialog Semiconductor提供的Android应用)

但好消息到此为止:如果固件是通过OTA写入的,它将无法启动。引导加载程序认为固件无效,它将启动Pokemon Go Plus固件的原始副本。他发现软件更新中添加了一个额外的检查,这在DA14580 SDK提供的源代码中不存在。但他无法弄清楚检查是什么或如何绕过它。

逆向工程发现

在泰国泼水节期间,我有一些空闲时间,所以我尝试逆向工程引导加载程序。他是对的,确实添加了一个额外的检查: 当更新过程开始时,设置一个标志表明此固件镜像尚未有效。如果更新失败,引导加载程序可以启动另一个有效固件。在此过程中,初始化SHA256哈希对于写入SPI闪存的每个传入数据,都会更新哈希在更新结束时,基于SHA256和OTP中的一些数据执行签名检查。如果一切有效,则将固件镜像设置为有效

我没有详细研究签名检查算法,我知道它使用大数计算,可能是RSA,但我没有验证。我也不觉得我会在那里找到bug。重要的是接下来发生的事情:如果签名有效,则设置一个标志表明镜像有效。

所以要明确: 如果我们使用SPI编程器修改固件,我们只是覆盖现有固件,有效标志保持不变如果我们使用OTA修改固件,我们需要在更新过程结束时将其设置为有效

我确定一件事:更新过程还需要OTP区域中的特定密钥。因此,如果Niantic要进行更新,该更新需要连接到他们的服务器以获取特殊密钥。

寻找漏洞

让我们回头看看是否有另一种方法将镜像设置为有效。这是DA14580 SDK中app_spotar_img_hdlr.c的原始源代码:

编译后的二进制文件有一个额外的调用来更新SHA256,但现在这不重要。让我们只关注这一行:

1
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块中的地址),第三个参数是大小。 从视觉上看,我们可以看到mem_base_add指向SPI闪存中的起始地址,suota_img_idx指向当前块。

当写入成功时,我们通过以下方式递增地址:

1
spota_state.suota_img_idx += spota_state.suota_block_idx;

看起来没问题。我寻找可能的缓冲区溢出以进行代码执行,但找不到。但是,如果我们可以修改spota_state.mem_base_add,那么我们将能够写入SPI闪存中的任何位置,包括镜像有效标志。

我找到了一个名为app_spotar_read_mem的函数,它应该在我们完成所有数据写入后调用。这也是在执行最终检查之前写入的地方,

这段代码很奇怪:它不使用临时变量,而是使用spota_state.mem_base_add来存储用于设置spota_state.suota_image_bank的临时值。当spota_state.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代码。当他完成后,任何人都应该能够提取他们的Pokemon Go Plus密钥而无需打开它。

我知道我应该等到他的工作完成后再发布这个,但我希望其他人能帮助他。可能为不同的iOS实现更新,或开始为其他设备实现Pokemon Go Plus。

也可能使用EdXposed(这是适用于Pokemon Go的XPosed分支)或通过向iOS版本添加拦截BLE API调用的库来实现Pokemon Go Plus。

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