ARM 逆向
ARM 中基于 PC 的寻址
最近看 ARM 的反汇编,计算地址时搞得有点头晕,总结了两天,把总结的结果记录下来。还是那句话,网上的资料很零碎,多不可
信。
在 ARM 的代码中,经常会用到相对偏移,换句话说就是基于PC 的寻址,以PC 为基准,加上偏移后找到相对地址。听起来似乎是个
很简单的事情,但由于 ARM 指令的多样性,从 ARM 的指令机器码推算出偏移地址并不是一件简单的事情。
今天用一个例子讨论三种基于 PC 的寻址方式,相关的规则等等。看一段Thumb 指令:
先看 0x 处的指令:BL sub_8B84,对应的机器码是:F90EF000.
第 一个问题来了,Thumb 指令不是 16bit 吗?为何这个指令是 32bit?原因是ARMv7 中引入了新的指令模式Thumb-2,Thumb-2 允
许 16bit 和 32bit 的指令混合使用,添加了一些新指令,即兼顾了 ARM 体积小速度快的优点,又兼顾了 Thumb 低功耗的有点。在
Thumb-2 的模式 下如果指令 bit[15:11]=11101/11110/11111, 那么这条指令就是 32bit,否则就是 16bit. 这个 case 中
bit[15:8] = 0xF0, bit[15:11] = 11110, 属于 32bit 指令。
那么如何通过 0xF90EF000 实现从 0x 到 0x8B84 的偏移的?先要查阅Thumb-2 中 32bit BL 指令的格式。经查询,这条指令符
合如下格式:
套这个格式的时候,左边的 16bit 对应指令的低 16 位,右边的对应高 16 位。这条指令的参数:S=0,
imm10=0,J1=1,J2=1,imm11=0x10E
根 据公式算出 imm32 = 0x10E<<1 = 0x21C,因为 S=0 所以这是一个正数,向后跳转。为何要左移一位?因为 Thumb- 2 指令
的长度是 2 字节或者 4 字节,那么指令的开始地址最后一位一定是 0,所以在指令中把要存储的地址右移一位,节省空间。如果是
表示 ARM 指令的地址,那 么就要左移两位,原理是一样的。
回过头来再看PC 的值和要跳转的地址:0x8B84-0x =0x220,什么?居然不是 0x21C?原因是在执行这条指令的时候PC 的值并
不是 0x . 由于ARM 处理器采用的是流水线技术,使用指令预取,在执行一条指令的时候 PC 的 值是下下条指令的地址。那么
下下条指令地址要加多少呢?在 ARM 模式下是 4*2=8,在 Thumb 模式下是 2*2=4. 那么在 Thumb-2 模式下是多 少?也是 2*2=4,
虽然 Thumb-2 有些指令是 4 字节,但依然按照 2*2=4 来算。
所以 0x21C+4=0x220,一切都能说的通了。
再 看另外一条指令, 0x895E: BEQ loc_8A40, 机器码:0xD06F, 这是一个 16bit 的Thumb-2 指令。CPU 怎么知道这是 个
16bit 的指令的?前面已经说过了,如果这是个 32bit 的指令,那么bit[15:11]=11010,显然不符合标准。因而这是个 16bit 指
令。有了前面的基础,这个就好推算多了。先查 ARM 指令表,符合这个格式:
cond=0, imm8=0x6F
imm32 = 0x6F<<1 = 0xDE == 0x8A40- (0x895E+4)
解释圆满
最 后看 0x895A 处的指令:LDR R3,=0x2711,机器码:0x4B3D, 这也是一个 16bit 的Thumb-2 指令。显而易见的是 0x2711 超过
了 16bit 指令所能忍受的立即数大小,因而 0x2711 直接放在指令里,而是要放在其他地方,用指令里存储的偏移地址来索
引。还是 一样先查 ARM 指令表,找到符合的格式:
Rt=3, imm8=0x3D
imm32=0x3D<<2=0xF4
按此推论存放 0x2711 的地址:0x895A+4+0xF4=0x8A52
查看一下具体地址:
居然是 0x8A50,差了两个字节,怎么回事?
再仔细查看 ARM 指令说明,找到这么一行:
原来这个指令在寻址的时候要求字对齐(4 字节),因而会强制把 bit[1]设为 0,不巧的是我们这个PC 原来的值 bit[1]刚好是
1,所以PC 的值被减去了 2,又一次 大白了。
总的来说 ARM 指令的寻址多种多样,对于每一种寻址方