原文链接 https://azeria-labs.com/arm-instruction-set-part-3/
ARM模式与THUMB模式
ARM处理器有两个主要的操作状态,ARM模式以及Thumb模式(Jazelle模式先不考虑)。这些模式与特权模式并不冲突。SVC模式既可以在ARM下调用也可以在Thumb下调用。只不过两种状态的主要不同是指令集的不同,ARM模式的指令集宽度是32位而Thumb是16位宽度(但也可以是32位)。知道何时以及如何使用Thumb模式对于ARM漏洞利用的开发尤其重要。当我们写ARM的shellcode时候,我们需要尽可能的少用NULL以及使用16位宽度的Thumb指令以精简代码。
不同版本ARM,其调用约定不完全相同,而且支持的Thumb指令集也是不完全相同。在某些版本山,ARM提出了扩展型Thumb指令集(也叫Thumbv2),允许执行32位宽的Thumb指令以及之前版本不支持的条件执行。为了在Thumb模式下使用条件执行指令,Thumb提出了"IT"分支指令。然而,这条指令在之后的版本又被更改移除了,说是为了让一些事情变得更加简单方便。我并不清楚各个版本的ARM架构所支持的具体的ARM/Thumb指令集,而且我也的确不想知道。我觉得你也应该不用深究这个问题。因为你只需要知道你设备上的关键ARM版本所支持的Thumb指令集就可以了。以及ARM信息中心可以帮你弄清楚你的ARM版本到底是多少。
就像之前说到的,Thumb也有很多不同的版本。不过不同的名字仅仅是为了区分不同版本的Thumb指令集而已(也就是对于处理器来说,这些指令永远都是Thumb指令)。
- Thumb-1(16位宽指令集):在ARMv6以及更早期的版本上使用。
- Thumb-2(16位/32位宽指令集):在Thumb-1基础上扩展的更多的指令集(在ARMv6T2以及ARMv7即很多32位Android手机所支持的架构上使用)
- Thumb-EE:包括一些改变以及对于动态生成代码的补充(即那些在设备上执行前或者运行时编译的代码)
ARM与Thumb的不同之处在于:
- 对于条件执行指令(不是条件跳转指令):所有的ARM状态指令都支持条件执行。一些版本的ARM处理器上允许在Thumb模式下通过IT汇编指令进行条件执行。条件执行减少了要被执行的指令数量,以及用来做分支跳转的语句,所以具有更高的代码密度。
- ARM模式与Thumb模式的32位指令:Thumb的32位汇编指令都有类似于
a.w
的扩展后缀。 - 桶型移位是另一种独特的ARM模式特性。它可以被用来减少指令数量。比如说,为了减少使用乘法所需的两条指令(乘法操作需要先乘2然后再把结果用MOV存储到另一个寄存器中),就可以使用在MOV中自带移位乘法操作的左移指令(
Mov R1, R0, LSL #1
)。
在ARM模式与Thumb模式间切换的话,以下两个条件之一必须满足:
- 我们可以在使用分支跳转指令BX(branch and exchange)或者分支链接跳转指令BLX(branch,link and exchange)时,将目的寄存器的最低位置为1。之后的代码执行就会在Thumb模式下进行。你也许会好奇这样做目标跳转地址不就有对齐问题了么,因为代码都是2字节或者4字节对齐的?但事实上这并不会造成问题,因为处理器会直接忽略最低比特位的标识。更多的细节我们会在第6篇中解释。
- 我们之前有说过,在CPSR当前程序状态寄存器中,T标志位用来代表当前程序是不是在Thumb模式下运行的。
ARM指令集规律含义
这一节的目的是简要的介绍ARM的通用指令集。知道每一句汇编指令是怎么操作使用,相互关联,最终组成程序是很重要的。之前说过,汇编语言是由构建机器码块的指令组成。所以ARM指令通常由助记符外加一到两个跟在后面的操作符组成,如下面的模板所示:
MNEMONIC{S}{condition} {Rd}, Operand1, Operand2
助记符{是否使用CPSR}{是否条件执行以及条件} {目的寄存器}, 操作符1, 操作符2
由于ARM指令的灵活性,不是全部的指令都满足这个模板,不过大部分都满足了。下面来说说模板中的含义:
MNEMONIC - 指令的助记符如ADD
{S} - 可选的扩展位,如果指令后加了S,则需要依据计算结果更新CPSR寄存器中的条件跳转相关的FLAG
{condition} - 如果机器码要被条件执行,那它需要满足的条件标示
{Rd} - 存储结果的目的寄存器
Operand1 - 第一个操作数,寄存器或者是一个立即数
Operand2 - 第二个(可变的)操作数,可以是一个立即数或者寄存器或者有偏移量的寄存器
当助记符,S,目的寄存器以及第一个操作数都被声明的时候,条件执行以及第二操作数需要一些声明。因为条件执行是依赖于CPSR寄存器的值的,更精确的说是寄存器中的一些比特位。第二操作数是一个可变操作数,因为我们可以以各种形式来使用它,立即数,寄存器,或者有偏移量的寄存器。举例来说,第二操作数还有如下操作:
#123 - 立即数
Rx - 寄存器比如R1
Rx, ASR n - 对寄存器中的值进行算术右移n位后的值
Rx, LSL n - 对寄存器中的值进行逻辑左移n位后的值
Rx, LSR n - 对寄存器中的值进行逻辑右移n位后的值
Rx, ROR n - 对寄存器中的值进行循环右移n位后的值
Rx, RRX - 对寄存器中的值进行带扩展的循环右移1位后的值
在知道了这个机器码模板后,然我们试着去理解这些指令:
ADD R0, R1, R2 - 将第一操作数R1的内容与第二操作数R2的内容相加,将结果存储到R0中。
ADD R0, R1, #2 - 将第一操作数R1的内容与第二操作数一个立即数相加,将结果存到R0中
MOVLE R0, #5 - 当满足条件LE(Less and Equal,小于等于0)将第二操作数立即数5移动到R0中,注意这条指令与MOVLE R0, R0, #5相同
MOV R0, R1, LSL #1 - 将第二操作数R1寄存器中的值逻辑左移1位后存入R0
指令 | 含义 | 指令 | 含义 |
---|---|---|---|
MOV | 移动数据 | EOR | 比特位异或 |
MVN | 取反码移动数据 | LDR | 加载数据 |
ADD | 数据相加 | STR | 存储数据 |
SUB | 数据相减 | LDM | 多次加载 |
MUL | 数据相乘 | STM | 多次存储 |
LSL | 逻辑左移 | PUSH | 压栈 |
LSR | 逻辑右移 | POP | 出栈 |
ASR | 算术右移 | B | 分支跳转 |
ROR | 循环右移 | BL | 链接分支跳转 |
CMP | 比较操作 | BX | 分支跳转切换 |
AND | 比特位与 | BLX | 链接分支跳转切换 |
ORR | 比特位或 | SWI/SVC | 系统调用 |
最后我们总结一下,满足这个模板的一些通用ARM指令集以及其含义:
指令 | 含义 | 指令 | 含义 |
---|---|---|---|
MOV | 移动数据 | EOR | 比特位异或 |
MVN | 取反码移动数据 | LDR | 加载数据 |
ADD | 数据相加 | STR | 存储数据 |
SUB | 数据相减 | LDM | 多次加载 |
MUL | 数据相乘 | STM | 多次存储 |
LSL | 逻辑左移 | PUSH | 压栈 |
LSR | 逻辑右移 | POP | 出栈 |
ASR | 算术右移 | B | 分支跳转 |
ROR | 循环右移 | BL | 链接分支跳转 |
CMP | 比较操作 | BX | 分支跳转切换 |
AND | 比特位与 | BLX | 链接分支跳转切换 |
ORR | 比特位或 | SWI/SVC | 系统调用 |