拿走不谢!固件逆向分析过程中的工具和技巧(上)

luochicun 逆向破解 2019年5月24日发布
Favorite收藏

导语:本文,我将会介绍一些固件逆向分析过程中的工具和技巧,以便让大家更高效地获取有用数据。

firmextractchip.png

将固件逆向分析,然后再将逆向分析后的内容转换为有用的东西,这个过程对于所有人来说都是一个耗时又耗力的过程。有时即使文件出现在你面前,你也无能为力,比如你可能会面临专有(几乎没有文档记录)的文件格式、奇怪的原始数据,甚至被加密的内容。

1.png

本文,我将会介绍一些固件逆向分析过程中的工具和技巧,以便让大家更高效地获取有用数据。

对逆向分析环境的分析和处理

了解正在分析的文件的环境,是一切工作的基础。比如你要搞清楚你要分析的固件运行的是什么芯片?硬件架构是什么?小端还是大端?它是否在运行RTOS?运行Linux吗?……

对逆向分析环境的分析和处理将帮助你找到适合的工具,在SREC编码的RH850固件上运行binwalk(一个文件的分析工具,旨在协助研究人员对文件进行分析,提取及逆向工程)不太可能对分析工作有所帮助;同样,尝试将基于Linux的SoC的ROM文件系统直接加载到IDA中也是一个不恰当地操作。

是二进制编码还是ASCII编码?

找到文件后,你就要看看文件中有什么?它是ASCII字符串吗?还是一大堆二进制数据? 这会决定你是使用head,cat,hexdump还是GUI文本编辑工具。

如果目标设备运行的是裸机MCU,你可能会发现固件文件以文本文件的形式进行转储,且被转化为十六进制 (通常带有一些预先编写的标识符代码或偏移地址/位置,并且可能在每行附加一个校验和)。在处理这些文件之前,首先需要将它们转换成二进制格式。通常,你可能会遇到一些常见的文件格式。

Motorola S-records 文件格式

Motorola S-records 文件格式有时被称为SREC,所有的S-record文件行都以大写s开头。

2.png

Intel HEX格式

与SREC类似,Intel的十六进制行都以冒号开头。

3.png

TI-TXT

TI-TXT是德州仪器的一种格式,通常用于MSP430系列。内存地址以“@”开头,数据以十六进制表示。看起来大致是这样的:

firman4.png

所有Motorola S-record、Intel HEX和TI-TXT文件都可以使用bincopy python库转换为二进制文件。

逆向分析Raw NAND 

通常情况下,要分析的数据以一种奇怪的方式存储在NAND芯片上。如果要分析数据仍然被存储在NAND芯片,则说明它们是有用的。但在得到可用的芯片外(off-chip)数据之前,仍然需要进行一些预处理。

firman5.png

如上所示,带外(Out-of-band, OOB) “备用”段插入每页数据的末尾,或每个数据块的末尾。插入它们的目的是让控制器用来跟踪损坏的数据块、擦除计数器用的,但是,如果你逆向分析整个芯片的原始内容,就将在逆向分析中拥有这些“备用”段。

这意味着,在拥有实际数据的连续文件之前,你必须从原始逆向分析中删除所有这些内容。然后,再使用下面提到的一些策略来开始逆向分析。

在此,我推荐一篇文章《从NAND芯片得到你要的文件》,每当我必须使用Raw NAND逆向分析文件时,我总是会参考其中的一些想法。作者在该文中编写了一个非常有用的工具,称为Nand-dump-tool.py。具体使用方法,请参考《对Nand FLASH详细介绍》一文。

NAND Flash是嵌入式世界里常见的存储器,对于嵌入式开发而言,NAND主要分为两大类:Serial NAND、Raw NAND,这两类NAND的差异是很大的(软件驱动开发角度而言),即使你掌握其中一种,也不代表你能了解另一种。

Raw NAND是相对于Serial NAND而言的,Serial NAND即串行接口的NAND Flash,而Raw NAND是并行接口的NAND FLASH,早期并行接口通信数据率是明显高于串行通信数据率的,但随着串行通信速度越来越快,并行接口速度优势显得不那么重要了,反而因信号线太多导致设计成本较高(PCB走线复杂)显得有点不合潮流。但其实这么说对Raw NAND是不公平的,现在的Serial NOR/NAND信号线其实也不少,比如高速的串行HyperFlash信号线数量已经直逼x8 bit的Raw NAND FLASH,所以Raw NAND市场还是坚挺的,你会发现各大存储厂商都还在不断推出Raw NAND FLASH产品。

一旦你获取二进制格式的固件,你就可以分析它以获取有趣的信息

同样,在分析二进制格式固件的过程中,了解分析环境也是很有用的。如果你知道固件是针对特定的裸机MCU的固件,那么你可能只想获取数据表并将其直接拉入IDA。如果你知道它是裸机,但却没有数据表,那么你可能想做一些字节级的分析工作。如果是针对更复杂的系统,使用像Linux这样的操作系统,你可能会从中获取文件。

以下是我用过的比较拿手的策略,在此推荐给大家。

字符串

字符串对于获取初始布局非常有用,它将返回一个以null结尾的可打印字符字符串列表。它的功能也比大多数人意识到的更全面。基本字符串file.bin将返回所有ASCII/ISO 8859编码的长度是4的字符串或更长的字符串,下面是一些其他有用的字符串标志:

strings -n16 file.bin

由于字符串的默认最小长度是4,-n标志指定要返回的字符串的最小长度,以下这个命令会将任何长度超过16的ASCII字符串打印到stdout。

strings -el file.bin

字符串-e标志指定字符的编码,-el指定16位宽的小端字符(例如UTF-16)。如果逆向分析被编码为big-endian,那么使用-eb。16位宽编码通常可以在嵌入式Windows(或运行Mono的设备)的固件中被找到。

strings -tx file.bin

-t标志将返回文件中字符串的偏移量。 -tx将以十六进制格式返回,T-to以八进制返回,-td以十进制返回。如果你使用十六进制编辑工具进行交叉引用,或者只是想知道字符串在文件中的位置,那么非常有用。

使用-n和-t标志,输出内容可能如下所示:

$ strings -n16 -tx file.bin
de1d73 vl1T-W4m% ]e7 ^")
14b3b12 K,E>$r!!qxc`    a~S
15715a8 [email protected]<-Gb$r+G9[j
19717f0 hg9Dfs[31+.|~#y*4
3a223b5 v?_-=jO ?0n>#@[D
417fec4 s]pD(6X#_tD&-NN-
47667a1 dAsMJjD#=+x'LG4<b7
4d55401 =GKw]I6VCDuTGvsv
511ad94 HelloFirmwareFans
53ef9cc %z.rkn'-z:gVUUl1-i
548b9e0 oelinux123
5d1c7cf P~7^SLD0njEo:ALa+

可以在十六进制格式中找到字符串的偏移量,即每行的第一个字段。

Hexdump

hexdump主要用来查看“二进制”文件的十六进制编码,如果一个文件运行hexdump,你将得到返回到stdout的每个字节的十六进制表示。它实际上是一个“十六进制”的“逆向分析”。

我认识的许多逆向分析的专家都是-C标志的粉丝,它以单字节的形式返回文件字节,并添加一列显示可打印字符(或在不可打印的地方显示句号字符)。通过这种方式,可以轻松挑选字符串并全面了解二进制文件。

hexdump也有助于插入*字符代替重复行,如果你真的想以任何理由查看所有内容,可以使用-v标志将其关闭。

可以使用-n标志来限制返回的字节数,0x200字节的file.bin就是带有-C标志的hexdump。

7.png

File策略

为了以防万一,在固件逆向分析中运行file通常是值得的,以及任何你可能有binwalk(一个文件的分析工具,旨在协助研究人员对文件进行分析,提取及逆向工程)、dd或以其他方式从其中提取的内容。

File策略的工作原理是检查文件的标头是否有魔术字节(就像binwalk一样),尽管魔术字节只负责检查给定文件的前几个字节。

未识别的文件类型将被报告为“data”,但是,但是,任何已识别的文件都将被报告为文件可能是什么,以及有用的元数据(如果已经被解析)。将JPEG图像传递给文件时,你可以看到以下内容。

$ file file.bin
file.bin: data

$ file image.jpg
image.jpg: JPEG image data, JFIF standard 1.01, resolution (DPI), density 1200x1200, segment length 16, baseline, precision 8, 4578x4387, frames 3

固件逆向分析有时可能是包含大量不同文件类型的不规则的二进制大对象,或者它们可能是加密的并且以完全随机的字节开始,这可能与合法的文件魔术字节序列对应。在这种情况下,就像binwalk一样,你最终会得到一个误报。

$ file file2.bin
file2.bin: PDP-11 UNIX/RT ldp

上面出现了一个PDP-11 UNIX/RT ldp文件,至于什么是PDP-11 UNIX/RT ldp?我不知道,但它肯定不是固件文件。所有这些都在告诉我们,文件的前几个字节对应于PDP-11 UNIX/RT ldp文件。

在文件夹环境中运行file *非常快速和有用,例如,在binwalk输出环境中运行,可以很容易地看到你正在处理的文件类型。且binwalk可能已经找到并成功提取了一个JFFS2文件系统,以及其他一些东西。binwalk输出文件夹的内容可能如下所示:

$ file *
2042C4:       data
800000.jffs2: Linux jffs2 filesystem data little endian
jffs2-root:   directory

binwalk

binwalk是一个可靠且流行的工具,用于处理运行某种操作系统的设备的固件。它被谈论的很多,但重要的是要记住,binwalk虽然不是固件分析工具的全部,但却非常有用和简单。

默认情况下, binwalk会在高层级中遍历二进制文件中的所有字节,寻找魔术字节。如果找到一个,它将在打印到stdout的表上进行报告。

它还可以“分割”或“提取”它找到的每个片段,因此你可以单独查看它。使用-e标志指定它应该提取文件,而不是将它发现的所有内容打印到stdout。根据你运行binwalk的文件的文件名,提取的文件全部进入名为_filename.extracted(或_filename- [int] .extracted,如果该文件夹已存在)的目录中。

由于它的性质,你几乎肯定会遇到误报。文件越大,得到误报的可能性就越大。巧合的是,文件将包含给定顺序的魔术字节,以防binwalk的魔术字节解析器把所报告的内容都误认为是有效的。

所以,当你使用binwalk时,你通常可以根据运行的环境估计出你能看到什么文件类型。如果你正在查看的设备运行的是嵌入式Linux,那么就会获得某种ROM文件系统,不过也可能是squashfs、cramfs或jffs2。你还可以假设你将看到zImage或uImage块,通常情况下你可能还希望看到引导加载程序映像。

以下是在大量加密固件文件上运行binwalk的示例,按理说,其中不应该有任何有价值的东西,但是binwalk仍然会找到很多令人期待的内容。

11.png

这些肯定是误报,它们的结果只是基于一个巧合的事实,即这些文件类型的魔术字节最终出现在密文中。另外这些“文件”也可以在任意偏移量处找到,这使得它们更不可能是准确预报的结果。

一个看起来合理的binwalk输出可能是以下这样的:

12.png

有一个uImage文件(大小、入口点和图像名称似乎相同)和一个JFFS2文件系统,由于内核通常被压缩为gzip,所以binwalk也可以在内核标头文件之后找到gzip魔术字节。内核和文件系统都需要被引导至嵌入式Linux,最重要的是,它们都是非常整齐的偏移量(0x200000和0x800000),但情况并不总是如此。

Fdisk

FDISK进行硬盘分区从实质上说就是对硬盘的一种格式化。当我们创建分区时,就已经设置好了硬盘的各项物理参数,指定了硬盘主引导记录(即MasterBootRecord,MBR)和引导记录备份的存放位置。而对于文件系统以及其他操作系统管理硬盘所需要的信息则是通过之后的高级格式化,即Format命令来实现。用一个形象的比喻,分区就好比在一张白纸上画一个大方框。而格式化好比在方框里打上格子。安装各种软件就好比在格子里写上字。分区和格式化就相当于为安装软件打基础,实际上它们为电脑在硬盘上存储数据起到标记定位的作用。

有时,当设备相对高端且固件文件很大时,固件只是一个驱动器映像。这通常是因为设备是如此占用大量资源,因此它是台式计算机的一半。尝试列出带有fdisk -l或fdisk -lu的分区(-u标志以段的形式给出分区大小,这可能会误导你)。

下面是一个文件的fdisk输出,其中没有有效的磁盘映像。

$ fdisk -l file.bin
Disk file.bin: 104.3 MiB, 109407232 bytes, 213686 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes

包含一些有效文件系统的文件可能会返回类似于下面的内容:

$ fdisk -l file.bin
Disk file.bin: 2.6 GiB, 2751447040 bytes, 5373920 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0xcd42b400

Device     Boot       Start      End       Sectors Size Id Type
file.binp1 3892371390 4109164418 216793029 103.4G  72   unknown
file.binp2 3287936629 3304577640 16641012      8G  6    FAT16

如上所示,你可以看到一个有效的FAT16映像被找到,起始位置为3287936629,结束位置为3304577640,最终报告的大小为8GB。

根据这些映像,你可以使用dd从固件二进制文件中提取文件系统。解压缩文件系统后,可以尝试使用适当的mount命令挂载它。根据报告的文件系统类型,通过在网络上广泛搜索正确的命令/工具,你通常能够成功地安装这些文件系统。

在某些情况下,可能需要为非默认文件系统加载内核模块(尤其是QNX)。

如果你有一个包含大量文件系统的文件,并且想要一次性提取它们,你可以使用如下代码:

fdisk -lu file.bin | egrep -i 'file.bin[0-9]' | sed 's/  */ /g' | while read line; do dd if=file.bin of=$(echo $line| cut -d' ' -f1) skip=$(echo $line | cut -d' ' -f2) count=$(echo $line | cut -d' ' -f4); done

dd

为什么那么多人害怕dd?当然,用它来清除整个磁盘非常容易,但这只是在将of=设置到错误位置的时候所发生的事情。

如果使用争取,它仍然是一个非常简单和有效的字节级复制工具。当使用支持“dollar bracket bracket”算术表示法(例如$((0x40/4)) = 16)的shell时,它特别有用。这对于动态地将十六进制转换为十进制非常有用,在处理不同块大小时也可以进行基本的算术运算。

你应该知道的关键dd参数是:

if=[FILE]

输入文件时,dd将从以下这个文件中读取内容。

of=[FILE]

输出文件时,dd将内容输出到这个文件。

bs=[NUMBER]

块大小应该是bs=[NUMBER]大小的倍数,默认块大小为512。如果你不介意分析速度变慢,,请设置bs = 1,此时你不能在命令中进行数学运算。

以下是读取输入文件之前要跳过的数据块的数量:

skip=[NUMBER]

以下是要从输入文件复制到输出文件的总块数:

count=[NUMBER]

所以,假设我们想从firmware.bin中提取一个从0x200到0x400的块,可以运行以下内容:

dd if=firmware.bin of=firmware.chunk bs=1 skip=$((0x200)) count=$((0x400-0x200))
If we wanted to run it a little faster, we could increase the block size:
dd if=firmware.bin of=firmware.chunk bs=$((0x100)) skip=$((0x200/0x100)) count=$(((0x400-0x200)/0x100))

注意:dd是以块的形式工作的。因此,在指定块大小和数量时,必须非常精确地进行计算。如果你想要安全(但是很慢)的进行分析,可以使用一个块大小为1的块。

本文,我们讨论了固件逆向分析过程中的部分工具和策略,下篇我们接着介绍如何分析被加密的固件以及分析策略。

https://www.pentestpartners.com/security-blog/how-to-do-firmware-analysis-tools-tips-and-tricks/

本文翻译自:https://www.pentestpartners.com/security-blog/how-to-do-firmware-analysis-tools-tips-and-tricks/如若转载,请注明原文地址: https://www.4hou.com/reverse/17922.html
点赞 2
  • 分享至
取消

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

扫码支持

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

发表评论