在本文中,我们将以ANYTONE 878UVII对讲机中的固件为例,为大家演示如何对ARM固件映像进行逆向分析。不过,本文中的大部分内容,对于ARM架构来说都是通用的。
本文假设读者已经熟悉IDA Pro,并且至少分析过一些普通的二进制文件。如果您还不熟悉IDA,只需在网上搜索一下,就能找到许多非常优秀的入门教程,大家可以先通过它们来掌握相关的基础知识。
固件映像就本文来说,我们只需IDA Pro和ANYTONE 878UVII对讲机的固件映像就能搞定我们的实验。并且,所需的映像还可以从分销商网站下载。实际上,下载哪个版本并不重要,但本文是将以2.04版本为例进行介绍。
在下载的更新包中,我们可以找到FW文件夹,其中包含三个文件:CDI、SPI和CDD文件。其中,CDD是最大的文件,它实际上就是我们要分析的固件映像。
这次我们的运气不错,因为这个固件映像并没有加密,否则,事情就会麻烦一些。它只是内部闪存中的映像,甚至连文件头都没有。并且,该文件的元数据被拆分为单独的文件。所以,我们可以直接在IDA Pro中加载CDD文件。
技术背景ANYTONE 878系列对讲机使用的是GigaDevice GD32 ARM Cortex-M4微控制器:通过拆开对讲机,我们就能看到这些芯片的型号。
除了拆对讲机外,实际上还有另一种更方便的方法:查询FCC。如果您的设备符合FCC的要求,网上应该有关于它的公开信息。这时,我们可以直接在FCC或独立的数据库中搜索制造商的信息。大多数情况下,我们会找到一份带有“内部照片”的文件。这个文件通常能够提供我们感兴趣的信息,比如芯片型号等,这样,我们就不用拆机了。
重要的是,我们建议大家下载CPU的数据表,并保存起来供后面使用:后面步骤中需要设置的参数,都可以从中找到。
关于CPU的相关设置首先要做的是,把CDD拖到打开的IDA Pro窗口中,或者通过文件菜单打开它。IDA会检测出这是一个二进制文件。然后,将“Processor type”指定为 “ARM little-endian”,具体如下图所示。
现在,先别按“Ok”按钮,因为还要对处理器选项进行一些设置。我们知道,这种设备使用的处理器是基于ARMv7E-M架构的。因此,我们必须对处理器选项做相应的修改。最佳设置如下图所示;为此,需要按下“Processor options”菜单中的“Edit ARM architecture Options”按钮,这样就可以找到中间的窗格了。
由于这个项目与Thumb指令集高度相关,所以也建议在“ARM specific options”中勾选“No automatic ARM THUMB switching”选项。虽然这一点并没有显示在上面的截图中,但对本项目来说的确是一个非常有用的设置。
加载映像现在,我们已经完成了基本的CPU设置。接下来,我们需要将加载的固件映像重新定位到正确的偏移量处。这个固件映像将被加载到IDA数据库的ROM部分。由于CPU不会从文件中的0x00处开始加载映像,所以,我们必须重新定位。如果跳过这一步,交叉引用将被破坏,反汇编文件将无法正常工作。我们的目标设备中使用的ARM CPU将要求映像从偏移量0x8004000处开始。这里其实就是映射到物理ROM的内存位置,所以,我们需要将文件映射到这个地址。
在单击“Load new file”对话框中的Ok按钮之后,将会出现如下所示的对话框。通常情况下,RAM的大小和ROM的大小并不需要调整。它们现在已经正确地自动填充好了。
接下来要做的事情,就是创建一个RAM分区。为此,可以勾选“Create RAM section”,分配的RAM将从0x20000000位置开始,长度为0x17FFF。
如何找到正确的内存偏移量如果读者是第一次接触这方面的内容,通常会有这样的疑问:这些值是如何确定的?答案很简单,我们可以从之前下载的数据手册中找到它们。
从第17页的内存映射部分,我们可以找到主闪存(固件文件)的加载地址。而在第16页中,我们可以找到SRAM偏移量和这段内存的长度。
很简单吧?上面所做的只是将文件/映像重新定位到从我们的数据表中获取的正确位置。关于主闪存有一个小技巧,第一个0x4000似乎是由引导程序获取的,所以,我们的二进制文件必须位于0x8004000处。
二进制文件的结构对于第一次使用IDA的读者来说,感觉可能非常奇怪:它并没有像其他软件一样进行自动分析,也没有展示程序代码,相反,它只是给出了大量的十六进制字符。难道是我们哪里做错了吗?很可能不是。如果您正在使用IDA Pro分析固件映像,这是非常正常的现象。这里的难点在于,我们必须自己从头开始进行分析。
但这也没有想象的那么难。首先,让我们考察文件的开头位置。这是ARM CPU开始执行代码的地方。在这个偏移量处,一个被称为向量表的结构被定位,它在ARM Cortex通用用户指南中有很好的详细描述。
正如我们在用户指南的图形中所看到的,偏移量0x0000(0x08004000)处包含初始堆栈指针。CPU将在这个地址加载接下来的四个字节,并将其用作指向未来堆栈的指针。
复位处理程序接下来的字节是各种处理程序,最重要的是复位处理程序(reset handler)。它正是CPU要启动或重新启动时将会跳转到的地方。
它又是一个4字节的地址,对于我们的映像来说,这个地址很容易解析。正如链接的ARM用户指南文章所告诉我们的,如果地址的最低有效位为1,则处理程序为Thumb。
在我们的例子中,该地址的最后一个字节是0xF9,二进制形式为11111001B。我们可以看到,这里的最低有效位确实是1。因此,我们需要将复位处理程序的入口点改为Thumb。实际上,复位处理程序的实际偏移量也由于该位的值而移动了一个字节。
0xF9 = 11111001b (with Thumb indicator)
0xF8 = 11111000b (without)
单击这个偏移量,就会跳转到复位处理程序的地址减1个字节的地方。现在,请按“Alt+G”,这时会打开一个对话框,我们需要将下面的部分定义为Thumb(CODE16)。
这个项目主要涉及Thumb指令集,因此,您也可以从ROM段的第一个字节开始使用Thumb代码。请记住,这一点并非适用于所有的ARM项目。但对于这个项目来说,这是没问题的。
将当前偏移量改为CODE16后,只需按“C”,就能在该偏移量处创建代码了。现在,我们就应该可以看到复位处理程序的代码了。
查找其他代码和字符串上面介绍的方法虽然能用,但是通过手动方式来创建所有的代码是相对繁琐的。别担心,我们可以借助于脚本来完成这些任务。实际上,Maddie Stone已经为IDA Pro创建了许多非常方便的脚本,能够给我们带来极大的便利。
由于她的脚本不能用于较新的IDA版本(在写这篇文章时,最新的版本为7.7),所以,我们专门把适用于IDA 7.x版本的脚本上传到了Github上,读者可以从https://github.com/alexander-pick/IDAPythonEmbeddedToolkit下载。为了支持基于ARM的项目,我已经对这些代码做了相应的处理。
首先,我们可以使用脚本define_code_functions.py,在0x08004000到0x080963DC大致范围内创建代码。如果脚本询问是否要撤销现有的代码,请选择No。
IDA Pro应该可以正常工作了,此时的ROM部分应该开始变得更有趣了。
接下来,我们可以使用make_strings.py脚本,在ROM的其余部分创建字符串。这时,你会在其中发现许多我们感兴趣的字符串。
关于字符串引用分析这个固件时,我们会发现一个奇怪的现象。由于ANYTONE的开发人员为多国语言创建了固件,所以,他们使用了引用表。因此,这可能导致我们会遗漏某些字符串的引用。之所以会发生这种情况,是因为这些字符串是根据选择的语言来动态加载的。遗憾的是,基于IDA的静态分析是无法解决这个问题的。
不过,引导过程中的一些字符串是直接嵌入的,所以它们解析起来问题不大。因此,我们可以从这些字符串开始下手。
为了做进一步的分析,我们需要能够识别一些基本的OS函数,即操作嵌入字符串的函数,比如“print”或“read”函数等。当然,类似“memcpy”这样的函数在各种操作系统中都是非常常见的。
非常值得注意的是“print_string”函数(一旦识别出来,我就把它重命名为这个名字)。它接受一些坐标和一个字符串作为参数,并将字符串显示在屏幕上给定的位置处。这个函数在启动菜单中被大量使用。
从固件镜像中的字符串可以识别出设备使用的RTOS(实时操作系统)是μC/OS-II。μC/OS-II是一个用ANSI C编写的免费实时操作系统。关于该系统的进一步介绍,以及相关文档,读者可以在这里找到;而相关代码则可以从这里下载。感兴趣的读者可以参考这些资料,它们应该对您有很大的帮助。
I/O和外围设备像这样基于ARM的CPU通常使用特殊的内存区域来处理地址总线、GPIO、I/O或简单的定时器和时钟,具体请参阅数据表。实际上,在第14页中,大家可以找到我们用来指定加载偏移量的内存映射。该映射还包含要添加到数据库中的特殊内存区域。
打开IDA中的内存区域视图(segments view)并将它们添加到数据库中,结果应该与下图类似。如果你想偷懒,则可以使用这个IDC(https://github.com/alexander-pick/useful-script-and-code/blob/master/GD32F303xx_segments.idc)来完成这个过程。
现在,请重新运行自动分析(Options -> General -> Reanalyse program)以创建交叉引用。
一旦你完成了上面的步骤,就可以查看感兴趣的内存区域,看看是否有对它们的交叉引用。这些可以帮助您找到使用特定总线、GPIO或I/O的函数。
如果您查找操作UART的函数,只需检查UART区域,就会找到对它的引用。这在没有或只有很少字符串作为引用的情况下是特别有用的。
小结我认为,到目前为止,您应该已经具备了自己研究这一主题所需的一切。
审核编辑:汤梓红
全部0条评论
快来发表一下你的评论吧 !