利用GHIDRA逆向Tytera MD380的固件

lucywang 逆向破解 2019年4月22日发布
Favorite收藏

导语:本文将详细介绍如何将MD380对讲机固件加载到GHIDRA,然后从md380tools逆向工程项目中导入符号。

背景知识介绍

2019年1月,美国国家安全局(NSA)宣布,它将免费向公众开放其逆向工程工具GHIDRA,源码已经于今年3月登陆代码托管平台GitHub 。NSA指出,GHIDRA框架的本质,是一款适用于 Windows、Mac 和 Linux平台的反汇编程序。它能够将可执行文件分解为汇编代码,以进行分析。对于希望深入了解恶意软件,以查看其工作原理的安全研究人员来说,反汇编工具是相当实用的。实际上,NSA早已向其它政府机构分享过该工具。2017年的时候,维基解密在Vault 7中首次曝光了GHIDRA,消息来自于中央情报局(CIA)的内部文件。

用过Tytera MD380对讲机的朋友很多,其较高的性价比让大家轻松无压力的从纯模拟电台跨步到了DMR数字时代,从而感受到DMR技术的魅力。

不过几年前,我们就升级了这个对讲机的固件了,并对其进行了逆向工程。为此我们专门构建了md380tools项目,以便通过其他功能例如全局用户目录,混杂模式和其他USB功能升级固件的功能。

所谓的混杂模式(Promiscuous Mode)是指一台机器能够接收所有经过它的数据流,而不论其目的地址是否是他。它是相对于通常模式(又称“非混杂模式”)而言的。这被网络管理员使用来诊断网络问题,但是也被无认证的想偷听网络通信(其可能包括密码和其它敏感的信息)的人利用。

以下,我会向你详细介绍如何将MD380对讲机固件加载到GHIDRA,然后从md380tools逆向工程项目中导入符号。

配置介绍

首先,你应该拥有MD380Tools源代码,将其进行编译以生成中间文件,例如符号和解密的固件映像。如果你还没有嵌入式ARM开发工具链,请参阅以下内容。

% git clone https://github.com/travisgoodspeed/md380tools
% cd md380tools
% make release

加载固件

GHIDRA似乎在设计时考虑了固件,并且它很乐意支持加载原始二进制文件。

让我们从File/New项目开始,创建一个新的非共享项目。

打开项目后,加载md380tools / firmware / unwrapped / D013.020.img或任何其他未封装的固件映像,这些脚本是由Makefile脚本通过互联网上加密不良的固件中更新生成的。

我们必须将语言设置为little-endian ARM Cortex (ARM:LE:32:Cortex),并使用选项窗口设置正确的加载地址。这些映像包含从48k引导加载程序开始的闪存,因此我们需要加载地址0x0800C000。

1.png

2.png

加载映像后,我们现在将Flash加载到正确的地址,但是我们没有RAM,也没有IO和函数符号。让我们首先使用自动分析器找到函数入口点,然后导入它们的专有名称。

初步分析

当你在加载后首次双击D013.020.img映像时,GHIDRA将提示你运行具有任意数量默认选项的自动分析器。选择默认选项后,再等待一分钟,你将发现数百个函数被准确的标识出来。

不幸的是,并不是所有的内容都能立即被识别出来。例如,位于开头(0x0800C000)的表是中断向量表,但它不位于默认地址。 GHIDRA非常聪明,能够识别其中的许多条目。但有一些例外,比如应用程序自己的代码从未调用的初始堆栈地址(0x0800C000)和重置向量(0x0800C004)。为了了解自动分析器是如何失败的,有必要研究其中的一些问题,但是在本教程中我不会关注它们。

与IDA Pro(一个静态反编译软件)和Binary Ninja(一个逆向平台)不同,GHIDRA的语言设置概念允许它提前知道这个二进制文件完全是Thumb2,没有经典的ARM指令,也不需要设置虚拟寄存器。

在继续从MD380Tools项目中导入其他构件之前,让我们先看一下一些简单的函数,并使用它们来查找其他函数。

让我们从SPI Flash驱动程序的最低级别开始。SPI Flash是一个外部闪存芯片,包含对讲机的代码插头、定义频率和配置设置。在逆向工程中,观察它们如何从SPI Flash芯片中读取设置是非常有帮助的。

首先导航到0x080314bd处的md380_spi_sendrecv函数。通过在反汇编视图中按G来执行此操作,然后向GHIDRA提供地址,它将四舍五入到0x080314bc。在内部,ARM具有所有Thumb函数的奇数地址,但GHIDRA不遵循此约定。之后,选择函数的名称并按F键编辑函数定义,将名称更改为md380_spi_sendrecv。我们可以把这个函数想象成getchar()和putchar(char)封装成的一个函数。它会从SPI漏洞中发送一个字节,同时返回相应的字节。

接下来,导航到0x080314bd并查看其反编译视图。你可以看到,这会发送字节0x03,然后是第二个参数的三个字节。以最重要的开始,然后再是第一个参数指向的所有字节,用于第三个参数的计数。

void FUN_080314bc(undefined *puParm1,uint uParm2,short sParm3)

{
  undefined uVar1;  
  FUN_0803152a();  md380_spi_sendrecv(3);  md380_spi_sendrecv(uParm2 >> 0x10 & 0xff);  md380_spi_sendrecv(uParm2 >> 8 & 0xff);  md380_spi_sendrecv(uParm2 & 0xff);  while( true ) {    if (sParm3 == 0) break;
    uVar1 = md380_spi_sendrecv(0xa5);
    *puParm1 = uVar1;
    puParm1 = puParm1 + 1;    sParm3 = sParm3 + -1;
  }  FUN_08031546();  return;
}

从SPI Flash的数据表中,我们知道0x03是从芯片读取数据字节的命令。这个函数的真实名称是md380_spiflash_read,跟踪它读取的地址允许我们将固件中的函数与其在codeplug中的含义匹配起来。由于我们知道必须在读取之前选择芯片,然后在读取之后取消选择,所以我们可以猜测FUN_0803152a实际上是md380_spiflash_enable而FUN_8031546实际上是md380_spiflash_disable,甚至不需要读取它们的代码。

4.png

使用反编译视图中的L键来定义参数、函数和变量名称,我们可以在去除反编译后得到很多有用的东西,进行很多清理反编译。你可以使用;注释标记。

void md380_spiflash_read(undefined *buffer,uint adr,short length)

{
  undefined currbyte;  
  md380_spiflash_enable();                    /* 0x03 = READ DATA BYTES command */
  md380_spi_sendrecv(3);  md380_spi_sendrecv(adr >> 0x10 & 0xff);  md380_spi_sendrecv(adr >> 8 & 0xff);  md380_spi_sendrecv(adr & 0xff);  while( true ) {    if (length == 0) break;
    currbyte = md380_spi_sendrecv(0xa5);
    *buffer = currbyte;
    buffer = buffer + 1;
    length = length + -1;
  }  md380_spiflash_disable();  return;
}

通过这些方法,我们就能找到相关的功能。在反汇编视图中右键单击函数名称,然后选择“显示引用”以显示函数的每个调用者。在md380_spiflash_enable()上使用Ctrl + Shift + F执行此操作,我们可以得到六个与SPI Flash交互的函数,例如md380_spiflash_write(),md380_spiflash_sektor_erase4k()和md380_spiflash_block_erase64k()。对md380_spiflash_read()执行此操作将为我们提供从SPI Flash中读取的每个函数,并且从这些地址中我们可以知道它们正在读取什么。

例如,考虑这个从0x2040读取20个字节的未知函数。

undefined4 FUN_080226c0(void)

{
  undefined4 unaff_r7;  
  md380_spiflash_read(DAT_08023398,0x2040,0x14);  return unaff_r7;
}

在md380tools/chirp/md380.py的不完整CHIRP驱动程序中,我们看到0x2000是一个配置结构,包含两个用于启动文本的二十字节字符串。这个神秘函数是Get_Welcome_Line1_from_spi_flash,我们可以钩住或修补它来更改在启动时显示的文本!

#seekto 0x2000;
struct {
    u8 unknownff;
    bbcd prog_yr[2];
    bbcd prog_mon;
    bbcd prog_day;
    bbcd prog_hour;
    bbcd prog_min;
    bbcd prog_sec;
    u8 unknownver[4];       //Probably version numbers.
    u8 unknownff2[52];      //Maybe unused?  All FF.
    char line1[20];         //Top line of text at startup.
    char line2[20];         //Bottom line of text at startup.
...

加载RAM的核心转储

现在,我们可以从SPI Flash映像中跟踪这些设置,这听起来很酷。但是如果我们通过查看RAM中的0x08023398来查看其中加载了哪些字节,效果就更好了。为此,我们需要在0x20000000处创建一个128k的区域。STM32F405在0x10000000处也有64k,但链接器很少使用静态缓冲区。

从“文件”菜单中选择“添加到程序”以添加第二个映像。选择md380tools/cores/d13020-core.img,这是一个通过USB从D13.020的启动副本生成的实时RAM转储。在选项窗口中,我们必须将加载地址设置为0x20000000。

8.png

现在我们可以导航到存储字符串的0x2001E3FC。将第一个类型定义为wchar16,然后按[创建一个包含10个元素的数组。在0x2001e410中对第二行执行相同的操作,你可以在SRAM中看到我的对讲机配置的两条启动线!

   2001e3fa 00              ??         00h
        2001e3fb 00              ??         00h
        2001e3fc 4b 00 4b        wchar16[   u"KK4VCZ    "
                 00 34 00 
                 56 00 43 
        2001e410 33 00 31        wchar16[   u"3147092   "
                 00 34 00 
                 37 00 30

修改文字池

最后一个小麻烦是,反编译器对ARM文字池的理解不是很到位。ARM并没有32位的即时信息,相反,它通过在函数之间存在数据池来伪造它们,这些数据池是相对于程序计数器引用的。

此时GHIDRA的逆向进程就会受阻,因为从理论上讲,这些文字池可能会被新的32位值覆盖。由于这种混淆,反编译器表面上会告诉你传递的值是DAT_080233b0,而实际上0x2001E410是0x080233b0处的惟一值。

void Get_Welcome_Line2_from_spi_flash(void){
  md380_spiflash_read(DAT_080233b0,0x2054,0x14);
  return;
}
DAT_080233b0             XREF[1]:     Get_Welcome_Line2_from_spi_flash
080233b0 10 e4 01 20     undefined4 2001E410h

实际上,这种闪存修改起来相当复杂,并且代码不能直接被写入。因此,我们需要告诉反编译器,Flash页面不能在窗口/内存映射中写入。简单地取消复选框并保存新的内存映射将纠正反编译过程,向我们显示正在传递的字符串是“3147092”。当然,我们可以给它一个更清晰的名称,并检查交叉引用,以便了解哪些其他函数使用第二个欢迎行。

11.png

void Get_Welcome_Line2_from_spi_flash(void){
  md380_spiflash_read((char *)u_3147092_2001e410,0x2054,0x14);
  return;
}

从GNU LD加载符号

既然我们已经学会了寻找自己的函数,现在可能是导入其他人已经找到的函数的好时机。

在GHIDRA包含的许多优秀示例脚本中,有一个是ImportSymbolsScript.py,它采用符号名称和地址的平面文本文件在打开的项目中创建标签。

#Imports a file with lines in the form "symbolName 0xADDRESS"
#@category Data
#@author 
 
f = askFile("Give me a file to open", "Go baby go!")

for line in file(f.absolutePath):  # note, cannot use open(), since that is in GhidraScript
    pieces = line.split()
    address = toAddr(long(pieces[1], 16))
    print "creating symbol", pieces[0], "at address", address
    createLabel(address, pieces[0], False)

在md380tools项目中,我们使用Radare2符号来标记我们对映像和GNU LD脚本的看法,以标记我们实际链接的那些部分。使用symbols2ghidra.py <symbols_d03_020> ghidrasyms.txt转换GNU LD脚本非常简单。

14.png

15.png

通过上文,我们可以很容易将MD380固件和符号加载到GHIDRA中,并且反编译器在二进制文件上做了很多复杂的工作。

你还应该注意正确地标记内存页,因为它们是反编译器生成干净、紧凑代码所必需的。处理共享项目的用户,必须确保在更改内存映射或加载新区域之前只签出一个文件。

译者注:经常有用户问该怎么修改文件。其实除了点击菜单栏上的“修改”来修改文件,菜单“版本”里的“签出”也能用来修改文件。

当你要修改文件时,先选中文件,然后点“签出”,文件自动签出并打开后,你修改文件,并保存修改内容。再点“签入”,文件就会保存到多可系统里。

简单说来,该流程就是:签出 -> 修改并保存 -> 签入。

本文翻译自:https://github.com/travisgoodspeed/md380tools/wiki/GHIDRA如若转载,请注明原文地址: https://www.4hou.com/reverse/17464.html
点赞 4
  • 分享至
取消

感谢您的支持,我会继续努力的!

扫码支持

打开微信扫一扫后点击右上角即可分享哟

发表评论