因为我不熟悉ARM,但是现在问这个问题还为时过早,但是它已经困扰了我一阵子,所以我还是要问。
上下文和背景信息
第1部分:与其他体系结构的固件相比,ARM固件
我在以下两个问题中考察了ARM固件:

运行被binwalk --disasm
标识为可执行的ARM二进制文件从二进制文件中提取有用的信息

,并且对代码和数据的混合感到困惑,因为我查看了Ubicom,MIPSEL和MIPS等其他体系结构的固件,却没有观察到相同的代码和数据的混合,就像在ARM固件中一样。
以下是上下文中各种二进制文件的可视化:
<------------------------ code -------------------------><---- images, ASCII---->
未知的指令集,来自lzma的uC / OS固件:无法识别文件格式[包含的详细信息]
q431 2010q


<-------------------------- code??? ------------------------><-- ASCII -->
Ubicom32 SlingBox固件来自需要帮助从固件.bin文件中提取YAFFS



<--------------------------- code -----------------------> <- data ->< code >
出现一种模式,不是吗?此处显示的固件映像中的可执行代码并不难与数据区分开,因为它很大程度上位于1或2个连续的大块中。来自运行binwalk --disasm标识为ARM可执行文件的二进制文件的32位ARM固件。代码在哪里?数据在哪里?
其他固件二进制文件之间没有明显的区别。 >这里的情况类似。
当可视化并置时,差异变得非常明显:





此样本中的非ARM固件二进制文件具有相当清晰的代码和数据分隔,如果我们要反汇编,则将很有帮助编码。但是,对于ARM固件二进制文件,代码和数据的标识似乎非常麻烦。
第2部分:ARM文字池
那么,为什么ARM固件二进制文件看起来与其他二进制文件有很大不同?为什么没有清晰可辨的代码和数据分离?答案似乎与ARM文字池有关。
我在这里包括有关ARM文字池的信息,因为它似乎很相关,而且因为我自己花了很长时间才自行找到它们,因此希望这可以节省其他时间。
根据Ian Cook(强调我的观点),

...代码和数据的混合在ARM代码中实际上很常见。
ARM指令是固定大小的(4个字节在ARM上为THUMB为2个字节),并且没有足够的空间来编码32位立即数。取而代之的是,ARM编译器通常会做两件事-


它们将32位立即数放置在需要常量的函数的指令之后,以及


它们在函数中使用PC相对加载指令将这些常量放入寄存器中。带文字池:

那么,什么是文字池?我很高兴你问。文字池是内存区域(在文本段中),用于存储常量。这些常数可以是普通的数字常数,但是它们的主要用途是将变量的地址存储在系统中。这些地址是必需的,因为ARM指令没有任何直接在内存中加载(或存储)地址的指令。相反,ldr和str只能与寄存器以±12位的偏移量存储。现在有很多方法可以生成具有此限制的代码,例如,可以确保数据段的大小小于8KiB,并保留一个寄存器以用作所有数据查找的基础。但是,只有在数据段大小有限的情况下,这种方法才有效。采用的标准方法是,使用变量时,将其地址写出到文字池中。然后,编译器生成两条指令,第一条指令从该文字池中读取地址,第二条指令是访问变量的指令。好吧,这样一来,就不需要指向文字池的特殊寄存器,编译器便将程序计数器(PC)寄存器用作基本寄存器。生成的代码类似于:ldr r3, [pc, #28]。该代码将与PC当前值相距28字节偏移的值加载到寄存器r3中。然后r3包含我们要访问的变量的地址,并且可以像ldr r1, [r3, #0]这样使用,它将变量的值(而不是地址)加载到r1中。现在,由于将PC用作文字池访问的基础,因此应该清楚,文字池的存储位置与需要使用它的代码足够近。
要确保文字池足够靠近对于使用它的代码,编译器在每个函数的末尾存储一个文字池。这种方法效果很好(除非您具有4KiB +功能,无论如何还是很愚蠢的),但可能有点浪费。

但是,在ARM文档中的文字池中,声明了

,汇编器使用文字池来保存某些要加载到寄存器中的常数值。汇编器在每个部分的末尾放置一个文字池。节的结尾是由程序集结尾处的END指令定义的,也可以由下一节开头的AREA指令定义的。包含文件末尾的END指令并不表示节的末尾。

我不知道关于ARM的杰克,所以这令人困惑。我可能将其作为一个单独的问题。无论如何,关键是文字池似乎是代码和数据在先前可视化的ARM固件中混合在一起的原因。还有一种叫做分散加载的东西,但是我不知道这是否与这里描述的问题有关。
问题
如何混合使用可执行代码和文字池来反汇编问题能否克服ARM固件中的问题?
根据Ian Cook(与以前的回答相同)(强调我的意思),

通常会看到函数后存储的立即数按访问它们的函数的地址。因此,在这种情况下,该函数以0x00000F5C处的指令结尾,并且立即数常量似乎跨越了地址范围0x00000F60到0x00000FFF,我通常希望以下函数从地址0x00001000开始。反汇编程序可以识别此模式并自动跳过关联的数据。

是的,如何?我尝试使用radare2拆解ARM 32位Panasonic相框固件和ARM Thumb-2 SMOK-X多维数据集设备固件,但似乎数据和代码都在反汇编。由于函数后的文字池的位置似乎是ARM二进制文件的常见功能,即使二进制文件中没有任何符号,现有的反汇编程序是否能够区分函数中的代码和其相邻文字池中的数据?据我所知,Capstone并非如此。

评论

顺便说一句,另一个问题是诸如TBB和TBH之类的指令所使用的交换表。这些也嵌入在代码中。

@IgorSkochinsky我不知道这个。感谢您指出这一点

#1 楼

对于线性扫描拆卸器来说,这确实是一个问题。递归下降序列大多数不受影响,因为如果没有代码流流入,它们将不会尝试反汇编数据。但是,可能会有一些启发式/廉价的技巧,甚至可以帮助线性扫描反汇编程序。我想到以下几点:在拆卸指令时,寻找带有PC相对操作数的LDR(以及ADR指令)并记住目标地址。到达这些地址时,请停止反汇编。
在ARM模式下,当解码的指令具有不合理的条件时,尤其是在没有前面的标志设置指令的情况下,请停止反汇编。

从确实具有符号的二进制文件中收集一些统计信息(例如n-gram频率),并使用它们从数据中区分代码。

最后,我认为没有一个很好的通用解决方案可以解决这个问题,因此,针对每个特定目标调整方法可能更加容易。



评论


谢谢。我在该站点上找到了与第一段中的陈述相关的一些有用信息。对于与无意义的条件有关的第二个要点,反汇编程序是否能够做出这种确定,或者通常需要人工解释条件?

– julian♦
17年5月13日在14:35

我认为应该可以提出一些简单的规则来检测大多数误报。

–伊戈尔·斯科钦斯基♦
17年5月14日在17:43