Trasteando con ensamblador y C.

Últimamente me estoy interesando en el mundo del shellcoding. Para ello, es bastante conveniente profundizar en conceptos de arquitectura de computadores, así como en la programación en ensamblador.

La idea del shellcoding es, básicamente, introducir una serie de instrucciones ensamblador en un programa en C, tal que nos permita obtener una shell. Para ello, lo que haremos será obtener los códigos de nuestra serie de instrucciones (en adelante, bytecode) y, a continuación, suministrárselos al programa en C como una cadena, de forma que éste los interprete como instrucciones básicas del computador.

Lógicamente, al empezar no es buena idea meterse de lleno a obtener shells sin haber trasteado un poco antes. Es por ello que mi primer “shellcode” (entrecomillado porque no es un shellcode en sí, ya que no obtiene shell ninguna) solo hace una llamada al syscall exit con el código de salida 0. A continuación el listado en ensamblador:

section .text
global _start
_start:
   xor eax, eax      ; Limpiamos eax
   xor ebx, ebx      ; Limpiamos ebx
   mov al, 0x01      ; Pasamos el argumento para syscall exit
   int 0x80          ; Llamamos al manejador de interrupciones

Lo que hace es una llamada a exit con el argumento 0. Para ello, necesitamos en eax el código del syscall a exit (1) y en ebx el código de salida (0). Usamos la instrucción xor para poner un 0 sobre los registros, ya que al embeber el código ensamblador en nuestro programa C, necesitamos que no exista ningún byte nulo. Esto es así, porque nuestro bytecode lo suministraremos como una cadena, y si existe un byte nulo, se termina la cadena y no se ejecutaría el resto de nuestro código. Prosiguiendo, si hubiéramos puesto mov eax, 0, habríamos obtenido un byte nulo cuando sacáramos los códigos de las instrucciones. Por último, se llama a la interrupción del sistema que nos permite ejecutar el syscall correspondiente.

Hasta aquí, ya tenemos nuestro código en ensamblador. Seguimos el proceso obteniendo el código objeto y enlazando dicho código objeto para que quede montado en un ejecutable. A continuación, hacemos un strace sobre el programa para ver que se efectúa la llamada a exit con el código de salida 0:

$ nasm -f elf exit.asm
$ ld -o exit exit.o
$ ./exit
$ strace ./exit
execve("./exit", ["./exit"], [/* 45 vars */]) = 0
_exit(0)                                = ?

Bien. Ahora, extraemos el bytecode correspondiente a nuestro inútil mini programa:

$ objdump -d exit

exit:     file format elf32-i386

Disassembly of section .text:

08048060 <_start>:
 8048060:	31 c0                	xor    %eax,%eax
 8048062:	31 db                	xor    %ebx,%ebx
 8048064:	b0 01                	mov    $0x1,%al
 8048066:	cd 80                	int    $0x80

Transformado a una cadena de bytes en hexadecimal apta para C, quedaría como sigue:

\x31\xc0\x31\xdb\xb0\x01\xcd\x80

Bien, mostremos de una jodida vez el listado en C que contendrá el bytecode correspondiente:

char code[] =  "\x31\xc0\x31\xdb\xb0\x01\xcd\x80";

int main (int argc, char **argv)
{
    void (*func)();
    func = (void (*)()) code;
    (*func)();
}

Sí, y sin errores de compilación! Bueno, esto necesita un poco de explicación creo yo. Sin olvidar que nuestro objetivo es ejecutar el bytecode en el programa en C, necesitamos buscar una forma de que el compilador nos deje hacerlo sin quejarse. ¿Solución? punteros a funciones (y creías que no servían para nada).

En la primera línea, simplemente alojamos el bytecode en una cadena de caracteres (char*).

Acto seguido, declaramos un puntero a una función que no devuelve ningún valor y que no toma ninguno tampoco, donde colocaremos luego nuestro bytecode.

En la línea siguiente es donde viene el meollo. La idea es “convertir” la ristra de bytecodes a una función. Como un puntero es simplemente un numerito (o numerazo, según se vea) que indica una dirección de memoria, nuestro querido compilador no se quejará al asignarle al puntero a función la dirección de memoria donde estan nuestro bytecode. De la misma forma que si tenemos un puntero a entero podemos asignarle una dirección de memoria de otro tipo, lo mismo podemos hacer con los punteros a funciones. Así que de lo único que nos tenemos que preocupar es de hacer un casting de un puntero a char, a puntero a función-que-no-devuelve-nada-ni-toma-ningún-valor. Esto es, (void (*)()) code.

Por último, llamamos a la función donde hemos encajado nuestro código, y esta se ejecuta sin ningún tipo de problema.

Hay que destacar, que la cadena se alojará en la pila del computador. Por ello, tendremos que habilitar en nuestro binario la ejecución de código desde la pila, si no, nos saldría una asquerosa violación de segmento al no tener permisos de ejecución desde la pila.

La solución para habilitar dichos permisos es simple, podemos pasarle al compilador (gcc) el argumento -z execstack, o también, después de compilar normalmente, pasarle la utilidad execstack al ejecutable: execstack -s ejecutable.

Opción 1:

$ gcc exit.c -o exit2 
$ ./exit2
Violación de segmento
$ execstack -s exit2
$ ./exit2
$

Opción 2:

$ gcc exit.c -o exit2 -z execstack
$ ./exit2
$

La salida del strace es la que sigue:

$ strace ./exit2
execve("./exit2", ["./exit2"], [/* 45 vars */]) = 0
brk(0)                                  = 0x8945000
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
mmap2(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x4f7000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY)      = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=123542, ...}) = 0
mmap2(NULL, 123542, PROT_READ, MAP_PRIVATE, 3, 0) = 0xde9000
close(3)                                = 0
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
open("/lib/i386-linux-gnu/libc.so.6", O_RDONLY) = 3
read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\220o\1\0004\0\0\0"..., 512) = 512
fstat64(3, {st_mode=S_IFREG|0755, st_size=1434180, ...}) = 0
mmap2(NULL, 1444360, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x555000
mprotect(0x6af000, 4096, PROT_NONE)     = 0
mmap2(0x6b0000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x15a) = 0x6b0000
mmap2(0x6b3000, 10760, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x6b3000
close(3)                                = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x6ca000
set_thread_area({entry_number:-1 -> 6, base_addr:0x6ca8d0, limit:1048575, seg_32bit:1, contents:0, read_exec_only:0, limit_in_pages:1, seg_not_present:0, useable:1}) = 0
mprotect(0x6b0000, 8192, PROT_READ)     = 0
mprotect(0x8049000, 4096, PROT_READ)    = 0
mprotect(0x950000, 4096, PROT_READ)     = 0
munmap(0xde9000, 123542)                = 0
_exit(0)                                = ?

¡PERO ESTO QUÉ ES! No hay que alarmarse, la salida es exactamente igual que si ejecutaran un programa en C vacío ( int main () { return 0; } ), con la diferencia de que en el return 0 de C, se llama a la syscall exit_group en vez de exit.

Y bueno, aquí termina todo. Si le sirve a alguien bien, y si no, pues bien también. Ala, saludos y a follar a follar, que el mundo se va a acabar.

Agradecimientos a vlan7 y a NewLog por su ayuda en Wadalbertia.

Anuncios

Un comentario en “Trasteando con ensamblador y C.

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s