ARM处理器有两个主要的操作状态,ARM模式以及Thumb模式(Jazelle模式先不考虑)。这些模式与特权模式(supervisor mode)并不冲突。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的内容与第二操作数一个立即数相加,将结果存到R0MOVLE R0, #5             - 当满足条件LE(Less and Equal,小于等于0)将第二操作数立即数5移动到R0中,注意这条指令与MOVLE R0, R0, #5相同
MOV   R0, R1, LSL #1     - 将第二操作数R1寄存器中的值逻辑左移1位后存入R0

最后我们总结一下,满足这个模板的一些通用ARM指令集以及其含义:

03_ARM指令集 - 图1