Reversing 101. “Hooking is the new Hacking”

Photo by Markus Spiske on Unsplash

Si has llegado a este artículo sin haber visto las dos partes anteriores, las puedes leer accediendo a los siguientes enlaces:

Interceptando llamadas a funciones del sistema

El método usado en este caso difiere mucho de los dos anteriores pues no vamos a ver código en ensamblador, aunque sí que hay que conocer algunos aspectos de la programación.

Para resolverlo vamos a programar nosotros mismos un pequeño programa que nos ayude a resolver el reto.

El programa lo que va a hacer es interceptar una llamada a una función del sistema que sea usada por el programa.

Esta técnica, llamada Hooking, se puede usar para muchos propósitos, desde depuración de programas hasta el objetivo que nos trae a este artículo, que es “hackear” el programa para descubrir la flag.

La mayoría de programas, por no decir todos, utilizan llamadas a funciones externas al programa. Sobre todo, y no únicamente, a las que se refieren a entrada / salida del programa. El sistema agrupa dichas funciones en librerías las cuales son cargadas por el sistema operativo.

Así que, el programa tiene que tener definido en alguna parte de este, el nombre de las funciones (símbolos) externos al programa que serán llamadas.

Esquema de llamada a una función del sistema desde un programa

La función que compara cadenas, strcmp()

Ya sabemos que el programa llama a esta función porque lo hemos visto en los dos análisis anteriores. Pero supongamos que no hemos hecho previamente los anteriores análisis, ni hemos usado anteriormente un desensamblador, o un depurador.

En el caso de este binario es fácil saber las llamadas a estas funciones del sistema porque está compilado incluyendo el nombre de los símbolos para depuración.

Esto se puede ver usando el comando file, que nos muestra el tipo de archivo y al final indica el texto “not stripped”. Significa que no se ha eliminado la información de los símbolos de depuración.

Información del tipo de fichero mostrando que tiene símbolos de depuración

Debido a esto, en el interior del ejecutable existen unas tablas de símbolos donde estan los nombres de las funciones del sistema a las que va a llamar el programa.

Podemos comprobar estas funciones usando el programa objdump que nos da información detallada de este ejecutable. La ejecución del comando pasando como argumento el crackme nos indica que usa las funciones puts(), printf() y strcmp(). Esta última es la que nos interesa.

Información de la tabla de símbolos con llamadas a funciones externas

Nuestro objetivo es hacer una función alternativa a strcmp() y decirle al crackme que, en lugar de llamar a la función estandard strcmp() del sistema primero debe de llamar a la nuestra.

Este será nuestro hook.

El programa / función Hook

El esquema anterior cambia a este nuevo donde, entre la llamada a la función del sistema y esa misma función se intercala una nueva función que es la que recibirá los parámetros y posteriormente pasa estos a la función del sistema, esta retorna el control a nuestra función hook, y a su vez esta retorna el control al programa principal, el crackme.

Esquema de llamada a función del sistema con una función Hook

También podríamos evitar hacer la llamada a la función de sistema y que nuestra función resolviera todo lo que se requiere, aunque no es nuestro objetivo.

Vayamos por pasos…

La función strcmp()

El primer paso es entender que hace y que devuelve la función strcmp().

Si miramos en cualquier referencia técnica de funciones de lenguaje C podremos ver que la función recibe dos punteros a dos cadenas de caracteres, las compara y devuelve un valor negativo si el primer carácter de la primera cadena es menos que el primer carácter de la segunda. Devolverá 0 si las cadenas son iguales. Y un valor mayor que 0 si la segunda es mayor que la primera.

Referencia de uso de strcmp() en https://cplusplus.com/reference/cstring/strcmp/

Primera prueba de Hook

Visto esto, vamos a hacer la primera función de hook, con la intención de engañar al crackme.

Dicha función, cada vez que es llamada, mostrará por pantalla el contenido de las cadenas de caracteres a comparar. Esto nos permitirá ver que cadenas está comparando.

Seguidamente, la función finalizará devolviendo 0. Esto resultado informa al programa que la llama de que las dos cadenas de texto son iguales. Por lo tanto, no se hace ninguna llamada a la función strcmp() estándar del sistema.

hookstrcmp0.c

La compilación

El siguiente paso es compilar dicho código para generar una librería que será cargada y ejecutada por el crackme.

Para hacerlo debemos de indicar que genere un archivo objeto compartido para ser usado como una librería, y con carga dinámica, de la siguiente manera:

gcc -shared -fPIC -ldl archivo_fuente.c -o archivo_objeto.so
Resultado de la compilación de un código fuente en lenguaje C como fichero objeto

Esto lo deberemos hacer para cada una de las diferentes versiones que vayamos desarrollando y probando.

Cargando la librería y ejecutando el crackme

Ahora debemos de ejecutar el crackme indicándole que debe usar dicha librería. Para eso usaremos la variable de entorno LD_PRELOAD. Dicha variable nos permite indicar al sistema que debe de cargar una librería concreta previa a la ejecución de un programa.

La librería y el programa los cargamos de la siguiente manera:

$ LD_PRELOAD=$PWD/libreria_a_cargar.so ./programa_a_ejecutar [argumentos]

Esto hará que se ejecute el crackme y en el momento de llamar la función strcmp() llamará a la que hemos creado a tal efecto y esta nos mostrará las dos cadenas de texto a comparar. Pero al devolver el valor 0, la primera comparación la considera correcta y finaliza el programa, con lo que no nos muestra la verdadera flag que estamos buscando.

Resultado de primera prueba de hook donde aún no se revela el flag

Así que podemos modificar dicha función (o hacer una nueva) para que en lugar de 0 devuelva cualquier otro valor, por ejemplo 1, y en ese caso al no ser iguales irá mostrando todas las comparaciones que vaya haciendo.

En este caso, la única diferencia es que la función devuelve 1 en lugar de 0. Indicando que las cadenas son diferentes y, forzando a que se hagan todas las comparaciones en el programa.

hookstrcmp1.c

Lo compilamos de nuevo y lo ejecutamos siguiendo el mismo procedimiento que en el caso anterior.

$ gcc -shared -fPIC -ldl hookstrcmp1.c -o hookstrcmp1.so

$ LD_PRELOAD=$PWD/hookstrcmp1.so ./crackme mipass

El resultado de la ejecución sería el que se observa en siguiente imagen, donde se van mostrando por pantalla todas las comparaciones. La primera es la de la falsa flag, y la segunda es la de la flag correcta, quedando al descubierto, sin necesidad de saber cómo está cifrada y/o como ha sido descifrada de memoria.

Resultado del segundo hook donde se nos revela el flag pero tiene un efecto lateral no deseado

Pero si pasamos la verdadera flag, aunque el objetivo de revelarla ya se cumple, vemos que el programa nos devuelve un mensaje diciendo que no hemos encontrado la flag. Esto es porque nuestra función siempre devuelve el resultado de que las dos cadenas son diferentes.

La manera correcta de usar LD_PRELOAD

Las funciones anteriores tienen un problema. Alteran el funcionamiento normal del programa ya que fuerzan siempre un resultado, verdadero o falso. En este caso podríamos implementar nosotros mismos la comparación de las cadenas, pero ¿para qué vamos a hacer algo que ya está implementado? 🤷‍♂

Así que lo ideal sería que una vez interceptada la llamada a dicha función, esta misma se encargue de llamar a la función correspondiente de la librería estándar del sistema para evitar efectos laterales o resultado inadecuados.

En este caso, nuestra función va a crear un puntero que apuntará a la verdadera función strcmp() del sistema, y para obtener su dirección lo hará a través de la función dlsym().

Referencia de la función dlsym() en https://pubs.opengroup.org/onlinepubs/009695399/functions/dlsym.html

Así que una vez mostrados por pantalla los valores recibidos como parámetros en la función strcmp() donde se nos descubre la flag, pasamos el control a la verdadera función strcmp() en la librería estándar para no afectar al funcionamiento normal del programa.

El código quedaría como el siguiente:

hookstrcmp.c

Y esta sería su ejecución final siguiendo las mismas pautas indicadas anteriormente:

$ gcc -shared -fPIC -ldl hookstrcmp.c -o hookstrcmp.so

$ LD_PRELOAD=$PWD/hookstrcmp.so ./crackme mipass

Y esta vez se podrá comprobar que el programa se comporta correctamente tanto cuando se le pasa la flag incorrecta como la correcta.

Resultado del tercer hook donde el crackme se comporta sin efectos colaterales respondiendo correctamente

El comando ltrace

Los que ya tengan conocimientos avanzados de Linux sabran que esto mismo, o de manera muy parecida, se puede hacer con el comando ltrace.

Con ltrace se pueden interceptar las llamadas realizadas a las librerias compartidas.

Asi que ejecutando ltrace y pasando como argumentos el programa a ejecutar (el crackme) y los argumentos que tenga que recibir nos va a mostrar todas las llamadas externas realizadas.

$ ltrace ./crackme mipass

El resultado nos va a evidenciar lo mismo que ya hemos obtenido con nuestra función hook, con la salvedad de que muestra el resultado de todas las funciones.

Descubriendo el flag usando el comando ltrace

No obstante, a pesar de tener un resultado más rápido y con menos esfuerzo usando dicha función, nunca está de más saber cómo aplicar estas técnicas con nuestras propias funciones.

Una vez resuelto, esta vez no puedo acabar este artículo con la misma frase que los anteriores. En este caso hay que decir:

¡¡¡ Happy Programming !!!

--

--

Gabriel Martí

Ex-Docente CFGM, CFGS Ciberseguridad. Actualmente Consultor en Ciberseguridad. Intereses en robótica, ciberseguridad, reversing. Twitter @gmarti @310hkc41b