【发布时间】:2017-04-02 19:51:32
【问题描述】:
在(32 位)ARM Linux 内核中,如何区分嵌入在代码段中的数据和指令?
最好有一个轻量级的方法,比如位掩码,可以很容易地实现。将反编译器嵌入内核是不明智的。
【问题讨论】:
标签: architecture arm instruction-set disassembly
在(32 位)ARM Linux 内核中,如何区分嵌入在代码段中的数据和指令?
最好有一个轻量级的方法,比如位掩码,可以很容易地实现。将反编译器嵌入内核是不明智的。
【问题讨论】:
标签: architecture arm instruction-set disassembly
一般来说,你要求的是不可能的。
考虑一下这个函数,它碰巧使用了一个太大而无法编码为立即数的数据值:
@ void patch_nop(void *code_addr);
patch_nop:
ldr r1, =0xe1a00000
str r1, [r0]
bx lr
当它通过汇编程序并返回时,它看起来像这样:
$ arm-none-eabi-objdump -d a.out
a.out: file format elf32-littlearm
Disassembly of section .text:
00000000 <patch_nop>:
0: e59f1004 ldr r1, [pc, #4] ; c <patch_nop+0xc>
4: e5801000 str r1, [r0]
8: e12fff1e bx lr
c: e1a00000 .word 0xe1a00000
多亏了 ELF 数据,我们仍然可以确定函数的结束位置和文字池的开始位置,但是 objdump 挖掘部分和符号所做的工作几乎不是“轻量级”的,谁说你有这些?如果您只有代码怎么办?
$ arm-none-eabi-objcopy -Obinary a.out bin
$ arm-none-eabi-objdump -D -marm -bbinary bin
bin: file format binary
Disassembly of section .data:
00000000 <.data>:
0: e59f1004 ldr r1, [pc, #4] ; 0xc
4: e5801000 str r1, [r0]
8: e12fff1e bx lr
c: e1a00000 nop ; (mov r0, r0)
那里。嵌入在您的指令流中,您有数据,这是一条指令。甚至不小心碰巧看起来像指令的数据。实际上,您无法仅从这 32 位中推断出它们不会被执行(嗯,至少不是从 那个 位置)。
有一些启发式方法可能有助于做出有根据的猜测,特别是如果可以假设任何额外的先验知识来缩小范围:
任何可以编码为立即数的东西几乎肯定是一条指令,因为编译器/汇编器一开始不会将它作为文字发出。但是,理想情况下,您至少想知道前面的代码是 ARM 还是 Thumb,以便知道合适的立即范围是什么*。
任何未定义的指令通常都是数据,除非它的代码想要故意引发 undef 异常。而且您基本上必须拥有大部分反汇编程序来检查某些内容是否与任何定义的编码不匹配。在 ARM/Thumb 之上。
紧跟在无条件分支之后的任何内容都可能是文字数据,特别是如果您有符号并且可以判断它非常接近以下函数的开头,或者如果您对要查找的数据有一定的了解它看起来像数据。如果您只是目测反汇编,后一点肯定是相关的 - 实际上,文字数据往往是地址之类的东西,一旦您查看整个代码,它们通常会像拇指一样突出† .
检查某个内容是否为文字的最可靠方法是查看前面的代码(最多 1025 条指令),检查以该地址为目标的 PC 相关负载。您只需要检查文字加载编码(这是您的简单位掩码操作),然后在找到相对偏移量时解码。理想情况下,您希望解决 ARM/Thumb 问题,以避免检查不适当的编码时出现误报,并且在最绝对病态的情况下,您仍然可能在前面的文字池中遇到一些数据,这些数据恰好看起来像文字负载目标你的地址;永远不要说永远。
当然,这仍然是假设编译器/汇编器自动发出的文字池;当谈到完全手写的汇编代码时,所有的赌注都没有了:
patch_nop2:
ldr r1, [pc, #-4]
mov r0, r0
str r1, [r0]
bx lr
是代码吗?是的。是数据吗?是的。
* 顺便说一句,区分 ARM 和 Thumb 代码归结为与这个问题基本相同的问题 - “这个位模式是什么意思?” - 并且在没有外部帮助的情况下同样重要。
† 没有双关语
【讨论】: