利用GHIDRA逆向Tytera MD380的固件 - 嘶吼 RoarTalk – 网络安全行业综合服务平台,4hou.com

利用GHIDRA逆向Tytera MD380的固件

lucywang 逆向破解 2019-04-22 11:39:34
414634
收藏

导语:本文将详细介绍如何将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中,并且反编译器在二进制文件上做了很多复杂的工作。

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

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

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

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

  • 分享至
取消

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

扫码支持

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

发表评论

 
本站4hou.com,所使用的字体和图片文字等素材部分来源于原作者或互联网共享平台。如使用任何字体和图片文字有侵犯其版权所有方的,嘶吼将配合联系原作者核实,并做出删除处理。
©2022 北京嘶吼文化传媒有限公司 京ICP备16063439号-1 本站由 提供云计算服务