With the increasing growth in Internet-Of-Things (IoT) devices, it is an absolute necessity to scrutinize the security of these devices too, especially when they’re going to be right in our homes.
What better way to start, than at the very instruction set architecture (ISA) that’s most commonly found on these devices — ARM.
ARM, or what was previously known as Advanced RISC Machine is a reduced instruction set computing architecture (RISC). It’s found in the aforementioned IoT devices, along with mobile computing devices like smartphones and tablets.
The instruction set
The ARM instruction set is very unlike the traditional x86 instruction set. The first obvious difference being that ARM is RISC and x86 is CISC (Complex Instruction Set Computing)
ARM instructions operate only on registers with a few instructions for loading and saving data from / to memory while x86 can operate on directly memory as well. Up until v8 ARM was a native 32 bit architecture, favoring four byte operations over others.
With that out of the way, let’s look at a comparison table for the registers used ARM with x86. Both are for 32-bit.
ARM | Description | X86 |
r0 – r3 | General purpose. Often used to pass arguments to functions. | eax |
r4 – r10 | General purpose. | ebx , ecx ,edx , esi , edi |
r11 | The frame pointer (fp ). | ebp |
r12 | Intra-procedural call (ip ). | – |
r13 | Stack pointer (sp ). | esp |
r14 | Link register (lr ). | – |
r15 | Program counter/instruction pointer. | eip |
CPSR | Current Program State Register/flags. | EFLAGS |
An interesting thing to note, is the lr
, or the link register. Unlike in x86, where the return address is automatically pushed onto the stack by the call
instruction, here, the return address is stored in the link register lr
, which is then pushed onto the stack. Here’s an example of how a function is defined in ARM assembly.
1 push {lr}
2 add lr, sp, #0
3 pop {r0, pc}
4 nop
5 add sp, r11, #0
6 pop {lr}
7 bx lr
This is a fairly simple function definition. It starts with a push {lr}
instruction, which pushes the return address onto the stack, so program execution can return correctly. Note that ARM 32-bit doesn’t actually have a ret
, or a return instruction.
And then there’s an add
instruction, which adds the number 0 to the stack pointer sp
and stores that value in the lr
. In x86, it would look like this: add eax, [eip + 0]
.
And at line 6, the lr
is popped off the stack, and the function returns using a bx
or a branch and exchange.
Let’s look at some common instructions that you’ll come across when reading ARM (dis)assembly.
pop
/push
— standard instructions to pop off and push to the stackb
/bx
/bl
etc. — branching instructions, used to change control flow-
ldr
/str
— load and store, single data transfer ldm
/stm
— load and store, block data transferadd
,sub
,mul
,sdiv
/udiv
— add, subtract, multiply and signed and unsigned division
With the basics out of the way, let’s perform the environment setup for the future installments in this series.
Environment setup
Since we’re using a non-native architecture, we will have to emulate it. Unless, of course, you have an ARM device with you, like the Raspberry Pi.
This guide assumes that the host system is GNU/Linux and preferably, a variant of Ubuntu/Debian.
For our emulator, we’ll be using QEMU. Click on the link to find your OS specific download links. Ubuntu/Debian users can do this:
$ sudo apt install qemu
Download the ARMv6 Stretch image from here. Once extracted, run the start.sh
script, and wait for it to boot. You can then ssh into the VM using ssh pi@localhost -p 5022
and login with the password raspberry
. This is an image based on Raspbian.
Getting familiar
If all went well, you should now have a shell on your ARM based VM! Go ahead and compile a simple C program and analyze it gdb.
#include <stdio.h>
#include <stdlib.h>
int main() {
printf("hello");
return 0;
}
You can do that by using gcc
$ gcc hello.c -o hello
and then
$ gdb hello
to drop into a gdb shell. From here, you can use set breakpoints, disassemble functions, single-step through instructions… etc. Learning gdb is immensely useful and I highly recommend reading up on it, if you haven’t already.
Conclusion
With this, we’re done with the first installment of the ARM Exploitation Series. In the next post, we’ll be looking at basic stack based exploits and buffer overflows. See you then.