CI2C: Curso de Ingeniería Inversa para Chainikis. Parte II
INGENIERIA_INVERSA
CI2C: Curso de Ingeniería Inversa para Chainikis. Parte II
2017-03-02
Por
Wh1t3 D3M0n

Aha!. No habéis tenido suficiente con la parte I. Algunos valientes habéis vuelto a por más. No hay problema tenemos de sobra para los bravos de corazón que desean continuar el camino hacia la iluminación ingenieril.
En esta entrega vamos a explorar otra forma de crackear el programa que utilizamos en la parte I. Para los que no los despistados, el programa era este:

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

char *the_pass = "MyVoiceIsMyPassport";

int
main (void)
{
  char   user_pass[1024];

  printf ("Introduce la contraseña: ");
  fgets (user_pass, 1024, stdin);
  if (!strncmp (user_pass, the_pass, strlen(the_pass)))
    {
      printf ("Bien Hecho!\n");
    }
  else
    printf ("Sigue Intentándolo\n");
}

A estas alturas ya sabéis como encontrar la clave usando strings, como modificar la clave por una que nosotros queramos (este era el ejercicio propuesto, espero que hayáis hecho los deberes) y como crackear el programa para saltarnos la comprobación de la clave.

En esta ocasión vamos a ver una técnica alternativa para crackear la clave en un caso sencillo como este. La técnica se conoce como inyección de librerías dinámicas... El nombrecito mola o qué?

Funciones externas

Vamos a echar un ojo al desensamblado de nuestro programa víctima. En concreto a la parte en la que comprobamos la clave utilizando la función strncmp.

   0x00000000004006fa <+118>:	lea    -0x1(%rax),%rdx
   0x00000000004006fe <+122>:	mov    0x20093b(%rip),%rcx        # 0x601040 <the_pass>
   0x0000000000400705 <+129>:	lea    -0x410(%rbp),%rax
   0x000000000040070c <+136>:	mov    %rcx,%rsi
   0x000000000040070f <+139>:	mov    %rax,%rdi
   0x0000000000400712 <+142>:	callq  0x400540 <strncmp@plt>
   0x0000000000400717 <+147>:	test   %eax,%eax
   0x0000000000400719 <+149>:	jne    0x400727 <main+163>

Os resulta familiar verdad?. Bien, fijémonos en la llamada a la función.

callq  0x400540 <strncmp@plt>

Como podéis ver, gdb identifica la función como strncmp@plt. Sin entrar en muchos detalles por ahora, esto significa que la función strncmp está definida en una librería dinámica externa. En concreto, esta función forma parte de la librería C estándar, conocida como libc.so.

Veámoslo:

$ ldd c1
	linux-vdso.so.1 =>  (0x00007ffefeffd000)
	libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f3d5be0d000)
	/lib64/ld-linux-x86-64.so.2 (0x00007f3d5c1fd000)

Si, la segunda línea es la librería estándar. Vamos a olvidarnos por el momento de las otras dos que no nos interesan para el tema que nos ocupa.

Para poder ejecutar el programa, el sistema tiene que cargar estas librerías dinámicas, de forma que podamos llamar a funciones como fgets, printf o strncmp. La librería se va a cargar en una determinada posición de memoria que, en general, va a ser diferente para cada programa. Dependiendo del tamaño del programa, que otras librerías tenemos que cargar y otros factores, nuestra función strncmp se puede encontrar en cualquier lugar de la memoria. Bueno, no en cualquier, pero en principio no podemos saberlo a priori.

Para solucionar este problema el sistema utiliza unos cuantos elementos. Por una parte tenemos el Enlazador Dinámico (Dynamic linker). Este es, de hecho, la última librería que nos muestra el comando ldd. El Enlazador dinámico es el encargado de cargar la librería y hacer que los programas puedan encontrar las funciones que ofrece, independientemente de en que parte de la memoria acabe.

Una de las cosas que hace el Enlazador Dinámico, es lo que se llama relocalización (relocation). Sin entrar en muchos detalles por el momento, la relocalización es el proceso por el que las direcciones de las funciones de la librería se ajustan dependiendo de la posición en memoria en la que la librería acabe.

Finalmente, el sistema ofrece una optimización. En lugar de relocalizar todas las funciones (todos los símbolos en general... cualquier cosa del programa que tenga nombre y sea accesible desde fuera) al cargar el programa, lo que lleva tiempo y haría que el programa tardase mucho en cargar, espera a que el programa llame a la función para llevar a cabo este proceso. Esto se conoce como Enlazado Holgazan (Lazy Binding)... solo se resuelven las funciones que se utilizan y solo cuando se utilizan.

Para poder llevar a cabo este proceso, el programa no puede llamar directamente a las funciones... básicamente no tiene ni idea de en que posición de memoria están. Así que el compilador general una tabla de punteros que apuntan a la función que localiza el símbolo asociado. Una vez que se conoce en que dirección de memoria está la función a la que queremos acceder, esa tabla se modifica para que apunte a la función real, de tal forma que la próxima vez que llamemos a la función saltaremos directamente a su código y no a la rutina de relocalización.

PLT. La Tabla de Enlazado de Procedimientos... Flipa

Bueno, puede que no os hayáis enterado del rollo que acabamos de soltar. No os preocupéis volveremos sobre este tema más adelante. Por el momento lo que tenéis que saber es que, las funciones externas, las que ofrecen librerías dinámicas no se llaman directamente.

El programa tiene que acceder a la denominada PLT Tabla de Enlazado de procedimientos (Procedure Linkage Table). El enlazador dinámico... o un ente externo muy listo, si lo preferís, se va a encargar de que en esa tabla aparezcan las direcciones reales de las funciones que queremos ejecutar.

En nuestro caso, strncmp es una función externa, y nuestro programa víctima ejecuta esta función con un salto (callq) a la entrada PLT asociada a strncmp. Si echáis un ojo al resto de la función main, veréis instrucciones similares para acceder a fgets o puts.

Que tiene de interesante todo este rollo. Bien, la respuesta es: la pre-carga de librerías.

Linux nos permite proporcionar una librería alternativa que se cargará antes que nada en la memoria. El efecto que esto tiene es que, al final, el puntero en la tabla PLT va a apuntar al código de esa librería que hemos pre-cargado, en lugar de a la librería estándar. En otras palabras, disponemos de una herramienta con la que modificar el comportamiento de cualquier función de una forma muy sencilla.

Crackeando el programa y 2

Con esta nueva herramienta, lo que vamos a hacer es una versión de strncmp que, por una parte nos muestre los parámetros que el programa está pasando a la función, y que por otra parte devuelva siempre el valor 0. La función strncmp original devolverá el valor 0 sólo cuando las dos cadenas que recibe como parámetro sean iguales. Haciendo que la función devuelva siempre 0 estaremos haciendo creer al programa principal que ambas cadenas son iguales, aunque no lo sean.

Manos a la obra. Nuestra función strncmp sería algo como esto:

#include <stdio.h>

int
strncmp (char *s, char *d, int n)
{
  pritnf ("[Ci2C] Cadena 1: %s\n", s);
  pritnf ("[Ci2C] Cadena 2: %s\n", d);

  return 0;
}

Grabamos este código en un fichero llamado c1-so.c. Ahora tendremos que generar una librería dinámica a partir de este fichero:

$ gcc -shared -fPIC -o libci2c.so.1.0.0 c1-so.c
c1-so.c:4:1: warning: conflicting types for built-in function ‘strncmp’ [enabled by default]

Veamos que estamos haciendo. El flag -shared le dice al compilador que queremos generar un objeto dinámico. El flag -fPIC le dice al compilador que tiene que generar código independiente de la posición (Position Independent Code). Esto nos permite cargar la librería dinámica en cualquier posición de memoria dejar al enlazador dinámica hacer su magia. El resto ya deberíais saber que significa.

Bueno, bueno, es hora de probar nuestra librería:

$  LD_PRELOAD=./libci2c.so.1.0.0 ./c1
Introduce la contraseña: aa
[Ci2C] Cadena 1: aa

[Ci2C] Cadena 2: MyVoiceIsMyPassport
Bien Hecho!

Genial!... No solo hemos crackeado el programa (da igual que contraseña introduzcamos), también estamos mostrando en la pantalla la contraseña real. Dos pájaros de un tiro!

Mú Fácil

Hasta aquí las cosas han sido, mú, mú fáciles, pero espero que hayáis pillado la idea general. Crackear un programa nunca es tan fácil, pero gracias a este sencillo ejemplo, hemos introducido unas cuantas herramientas que vamos a usar extensivamente en las próximas entregas.

No os preocupéis si no habéis entendido completamente todo el proceso del enlazado dinámico. Volveremos sobre él varias veces muy pronto y al final, sin daros cuenta lo habréis pillado.

No os perdáis la siguiente entrega... diversión asegurada!

SOBRE Wh1t3 D3M0n
Me conocen como Wh1th3 D3M0n y soy un hacker. Me oculto entre las sombras de la red, paseándome entre máquinas y datos como un espectro… invisible a los ojos de los usuarios…

Que va es coña!

Twitter: @ChainkMaster | Blog: https://thehackerkid.tumblr.com/