lunes, 13 de agosto de 2012

Minicurso de exploiting (parte 5): Hardcoding the flag

Hardcoding

Esta entrada es un cambio radical comparada con las anteriores. Aquí ya nos podremos con las manos en la masa y empezaremos a hacer cosas (las más sencillitas claro).

Bueno, ¿qué es eso de "hardcoding"? Pues hardcodear (conocido verbo español) no es más que "ponerlo a fuego" en el código de un programa. Algo del estilo:

if (variable == "CADENA_HARDCODEADA") {

Esa "CADENA_HARDCODEADA" estará dentro de los datos del programa y si sabemos buscarla la encontraremos muy rápido. Hacer esto de por si no es malo, es algo que todos los lenguajes permiten y que hacemos muchísimo cuando programamos, pero cuando el dato que se hardcodea es sensible en el sentido de que no cualquiera debería conocerlo, es cuando empieza la mala práctica. Si resultara que ese dato hardcodeado es un serial key, una contraseña, una respuesta a una pregunta secreta o cosas por el estilo ya vemos claramente que habría que añadir alguna capa de protección para que ese dato no sea accedido.

Esto del hardcoding no sólo tiene por qué aplicarse a datos, el código también puede ser algo que queremos ocultar (son datos a fin de cuentas), así que poner una función de "cifrado" inventada por el programador dentro del binario también es hardcoding, cualquiera podrá coger ese código, analizarlo, comprenderlo y probablemente hacerle la función inversa (suponiendo que sea una función reversible, pero si te programas tus propias funciones criptográficas... o eres un pro y lo sabes o la estas cagando :D).

Ojo con maneras más complejas de hardcodear pero que siguen siendo hardcodeo.

Dónde están esos datos

Bueno, lo primero es saber en dónde se encuentran datos como la "CADENA_HARDCODEADA" anterior. Lo cierto es que esto puede variar un poco. En general estos datos se van a encontrar en la sección .data y .rodata del binario, vamos a escribir ese if en un pequeño programa en C (ojito, que lo anterior no era C):

#include <string.h>

int main(int argc, char *argv[]) {
    if (strcmp(argv[0], "CADENA_HARDCODEADA"));
    return 0;
}

Si destripamos byte a byte la sección .rodata encontraremos la cadena.

$ objdump -j .rodata -s hardcoding

hardcoding:     file format elf32-i386

Contents of section .rodata:
 80484a8 03000000 01000200 43414445 4e415f48  ........CADENA_H
 80484b8 41524443 4f444541 444100             ARDCODEADA

Antes comenté que "en general" esos datos están en la sección .data y/o .rodata, pero como siempre ocurre, eso no tiene por qué ser así siempre. Si el literal que estamos buscando es un número, la mayoría de las veces estará hardcodeado directamente dentro de las instrucciones que tengan que hacer uso del mismo. Veamos un ejemplo sencillito.

#include <stdio.h>

int main(int argc, char *argv[]) {
    if (argc == 1)
        printf("Bla bla bla.\n");
    return 0;
}


En este caso ese 1 no estará en la sección .data ni .rodata sino directamente en las instrucciones que codifican ese if.

$ objdump -d hardcoding | egrep -A 11 "<main>"
080483b4 <main>:
 80483b4:    55                       push   %ebp
 80483b5:    89 e5                    mov    %esp,%ebp
 80483b7:    83 e4 f0                 and    $0xfffffff0,%esp
 80483ba:    83 ec 10                 sub    $0x10,%esp
 80483bd:    83 7d 08 01              cmpl   $0x1,0x8(%ebp)
 80483c1:    75 0c                    jne    80483cf <main+0x1b>
 80483c3:    c7 04 24 a0 84 04 08     movl   $0x80484a0,(%esp)
 80483ca:    e8 21 ff ff ff           call   80482f0 <puts@plt>
 80483cf:    b8 00 00 00 00           mov    $0x0,%eax
 80483d4:    c9                       leave
 80483d5:    c3                       ret

En negrita tenemos el 1 hardcodeado en la instrucción cmpl (compare long).

Existe también otro caso curioso y que a mi me gusta bastante mostrar, strings hardcodeadas directamente en las instruciones jejeje.

#include <stdio.h>

int main(int argc, char *argv[]) {
    char var[] = "Hola\n";

    printf("%s", var);
    return 0;
}

Lo suyo sería pensar que "Hola\n" va a estar en .rodata y que a la hora de ejecutar main() a la variable local var se la hará apuntar a la parte de la sección .rodata donde se encuentra "Hola\n", pero lo que pasa es lo siguiente:

$ objdump -d hardcoding | egrep -A 15 "<main>"
080483c4 <main>:
 80483c4:    55                       push   %ebp
 80483c5:    89 e5                    mov    %esp,%ebp
 80483c7:    83 e4 f0                 and    $0xfffffff0,%esp
 80483ca:    83 ec 20                 sub    $0x20,%esp
 80483cd:    c7 44 24 1a 48 6f 6c     movl   $0x616c6f48,0x1a(%esp)
 80483d4:    61
 80483d5:    66 c7 44 24 1e 0a 00     movw   $0xa,0x1e(%esp)
 80483dc:    b8 c0 84 04 08           mov    $0x80484c0,%eax
 80483e1:    8d 54 24 1a              lea    0x1a(%esp),%edx
 80483e5:    89 54 24 04              mov    %edx,0x4(%esp)
 80483e9:    89 04 24                 mov    %eax,(%esp)
 80483ec:    e8 03 ff ff ff           call   80482f4 <printf@plt>
 80483f1:    b8 00 00 00 00           mov    $0x0,%eax
 80483f6:    c9                       leave 
 80483f7:    c3                       ret

Vemos que la string se hardcodea directamente en instrucciones, 0x616c6f48 corresponde a "Hola" y 0xa a "\n" (a consultar la tabla ASCII). Así que ojito, que no siempre los datos que buscamos están en .data o .rodata, .bss y .stack también existen (como Teruel).

Búsqueda de strings hardcodeadas

Bueno, ya tenemos la teoría cogida, sabemos dónde suelen estar los datos, sabemos que a veces no están en donde esperamos y sabemos mirar las tripas del programa para buscar esos datos, pero esto puede ser bastante coñazo. Para evitarnos tanto lío existe un programa para sacarle las strings a un binario, se llama strings. Para ver un ejemplo tomemos el programa de la "CADENA_HARDCODEADA" que ya usamos anteriormente:

$ strings hardcoding
/lib/ld-linux.so.2
__gmon_start__
libc.so.6
_IO_stdin_used
strcmp
__libc_start_main
GLIBC_2.0
PTRh
[^_]
CADENA_HARDCODEADA

Vemos que no hay misterio ninguno, ahí tenemos la cadena, ¿tanto rollo para algo tan simple?. Bueno, esta muy bien tirar herramientas a lo loco, pero personalmente no es la esencia de lo que hago, a mi me gusta intentar entender hasta el último bit de lo que está pasando y de lo que estoy haciendo (no siempre lo consigo).

Strings básicamente lo que hace es buscar por todo el binario cadenas "printeables". No voy a detenerme aquí a hablar mucho más de strings, pero comentar que existe un fallo de noob que es lanzar strings y si no se encuentra nada es que no hay. Strings busca por defecto cadenas de tamaño mínimo 4 (para evitar bastante ruido), si la cadena hardcodeada fuera de tamaño 3 o menos y lanzasemos strings a lo loco no nos aparecería. No es la primera vez que me encuentro esto en un wargame.

Otra cosa, strings sirve para lo que dice su nombre, buscar strings, no números ni otros tipos de datos, para eso tendremos que reversear el binario y buscarlo entre instrucciones, la sección .data, la .rodata, etc...

Jueguito para practicar

Todo esto está muy bien, pero mola más ponerlo en práctica un poco. Aquí les dejo una serie de pruebillas para juguetear un poco con todo lo que hemos visto ;).

Prueba 1.
Prueba 2.

A divertirse.

No hay comentarios:

Publicar un comentario