ARM processors have two main states they can operate in (let’s not count Jazelle here), ARM and Thumb. These states have nothing to do with privilege levels. For example, code running in SVC mode can be either ARM or Thumb. The main difference between these two states is the instruction set, where instructions in ARM state are always 32-bit, and instructions in Thumb state are 16-bit (but can be 32-bit). Knowing when and how to use Thumb is especially important for our ARM exploit development purposes. When writing ARM shellcode, we need to get rid of NULL bytes and using 16-bit Thumb instructions instead of 32-bit ARM instructions reduces the chance of having them.
The calling conventions of ARM versions is more than confusing and not all ARM versions support the same Thumb instruction sets. At some point, ARM introduced an enhanced Thumb instruction set (pseudo name: Thumbv2) which allows 32-bit Thumb instructions and even conditional execution, which was not possible in the versions prior to that. In order to use conditional execution in Thumb state, the “it” instruction was introduced. However, this instruction got then removed in a later version and exchanged with something that was supposed to make things less complicated, but achieved the opposite. I don’t know all the different variations of ARM/Thumb instruction sets across all the different ARM versions, and I honestly don’t care. Neither should you. The only thing that you need to know is the ARM version of your target device and its specific Thumb support so that you can adjust your code. The ARM Infocenter should help you figure out the specifics of your ARM version (http://infocenter.arm.com/help/index.jsp).
As mentioned before, there are different Thumb versions. The different naming is just for the sake of differentiating them from each other (the processor itself will always refer to it as Thumb).
Thumb-1 (16-bit instructions): was used in ARMv6 and earlier architectures.
Thumb-2 (16-bit and 32-bit instructions): extents Thumb-1 by adding more instructions and allowing them to be either 16-bit or 32-bit wide (ARMv6T2, ARMv7).
ThumbEE: includes some changes and additions aimed for dynamically generated code (code compiled on the device either shortly before or during execution).
Conditional execution: All instructions in ARM state support conditional execution. Some ARM processor versions allow conditional execution in Thumb by using the IT instruction. Conditional execution leads to higher code density because it reduces the number of instructions to be executed and reduces the number of expensive branch instructions.
32-bit ARM and Thumb instructions: 32-bit Thumb instructions have a .w suffix.
The barrel shifter is another unique ARM mode feature. It can be used to shrink multiple instructions into one. For example, instead of using two instructions for a multiply (multiplying register by 2 and using MOV to store result into another register), you can include the multiply inside a MOV instruction by using shift left by 1 -> Mov R1, R0, LSL #1 ; R1 = R0 * 2
To switch the state in which the processor executes in, one of two conditions have to be met:
We can use the branch instruction BX (branch and exchange) or BLX (branch, link, and exchange) and set the destination register’s least significant bit to 1. This can be achieved by adding 1 to an offset, like 0x5530 + 1. You might think that this would cause alignment issues, since instructions are either 2- or 4-byte aligned. This is not a problem because the processor will ignore the least significant bit. More details in Part 6: Conditional Execution and Branching.
We know that we are in Thumb mode if the T bit in the current program status register is set.
The purpose of this part is to briefly introduce into the ARM’s instruction set and it’s general use. It is crucial for us to understand how the smallest piece of the Assembly language operates, how they connect to each other, and what can be achieved by combining them.
As mentioned earlier, Assembly language is composed of instructions which are the main building blocks. ARM instructions are usually followed by one or two operands and generally use the following template:
Due to flexibility of the ARM instruction set, not all instructions use all of the fields provided in the template. Nevertheless, the purpose of fields in the template are described as follows:
由于ARM指令集的灵活性,并非所有指令都使用模板中提供的所有字段。然而,模板中字段的目的如下:
MNEMONIC - Short name (mnemonic) of the instruction
//指令集的短名字(助记符)
{S} - An optional suffix. If S is specified, the condition flags are updated on the result of the operation
//一个可选的后缀。如果指定了S,则基于操作结果更新条件标志位S。
{condition} - Condition that is needed to be met in order for the instruction to be executed
//为了执行指令而需要满足的那些条件
{Rd} - Register (destination) for storing the result of the instruction
//存储运算结果的寄存器(目的地)
Operand1 - First operand. Either a register or an immediate value
//第一操作数,既可以是寄存器也可以是立即数
Operand2 - Second (flexible) operand. Can be an immediate value (number) or a register with an optional shift
//第二操作数(可选的),可以是立即数或具有可选移位的寄存器。
While the MNEMONIC, S, Rd and Operand1 fields are straight forward, the condition and Operand2 fields require a bit more clarification. The condition field is closely tied to the CPSR register’s value, or to be precise, values of specific bits within the register. Operand2 is called a flexible operand, because we can use it in various forms – as immediate value (with limited set of values), register or register with a shift. For example, we can use these expressions as the Operand2:
#123 - Immediate value (with limited set of values).
//立即数(有限的取值范围)
Rx - Register x (like R1, R2, R3 ...)
//某一寄存器
Rx, ASR n - Register x with arithmetic shift right by n bits (1 = n = 32)
//右移n位的算数运算寄存器(n从1-32)
Rx, LSL n - Register x with logical shift left by n bits (0 = n = 31)
//左移n位的逻辑运算寄存器(n从1-31)
Rx, LSR n - Register x with logical shift right by n bits (1 = n = 32)
//右移n位的逻辑运算寄存器(n从1-32)
Rx, ROR n - Register x with rotate right by n bits (1 = n = 31)
//循环右移n位的寄存器(n从1-31)
Rx, RRX - Register x with rotate right by one bit, with extend
//循环右移1位的寄存器,带扩展??(这里不确定)
As a quick example of how different kind of instructions look like, let’s take a look at the following list.
下面的例子可以帮助我们快速浏览不同类型指令集看上去有什么不同。我们来看看下表:
ADD R0, R1, R2 - Adds contents of R1 (Operand1) and R2 (Operand2 in a form of register) and stores the result into R0 (Rd)
//将R1里的内容(第一操作数)和R2里的内容相加(寄存器形式的第二操作数)相加,并将结果存储在R0里
ADD R0, R1, #2 - Adds contents of R1 (Operand1) and the value 2 (Operand2 in a form of an immediate value) and stores the result into R0 (Rd)
//将R1里的内容(第一操作数)和立即数2(立即数形式的第二操作数)相加,并将结果存储在R0(Rd)里
MOVLE R0, #5 - Moves number 5 (Operand2, because the compiler treats it as MOVLE R0, R0, #5) to R0 (Rd) ONLY if the condition LE (Less Than or Equal) is satisfied
//将数字5(第二操作数,因为编译器将它处理为MOVLE R0, R0, #5)传送到R0(Rd),只有LE条件(大于或等于)满足时。
MOV R0, R1, LSL #1 - Moves the contents of R1 (Operand2 in a form of register with logical shift left) shifted left by one bit to R0 (Rd). So if R1 had value 2, it gets shifted left by one bit and becomes 4. 4 is then moved to R0.
//将R1的值(第二操作数,逻辑左移寄存器的形式)左移一位后传送到R0(Rd),所以如果R1里的值是2,它左移一位就变成了4,接下来把4传送到R0里
As a quick summary, let’s take a look at the most common instructions which we will use in future examples.