10 septiembre 2014

Solucionando crackmes precargando librerías

Una de las técnicas más utilizadas en sistemas UNIX para analizar o modificar el comportamiento de una aplicación es la de precargar una libreria para hacer que el linker priorize nuestros funciones en vez de las disponibles en librerias externas.

De hecho, en iOS, todo el sistema de MobileSubstrate y la app Flex, se basan en este concepto para extender o modificar las funcionalidades de las aplicaciones de forma bastante cómoda.

La técnica consiste en definir una variable de entorno (LD_PRELOAD o DYLD_INSERT_LIBRARIES en OSX/iOS). La cual es parseada por el linker dinámico del sistema (/lib/ld.so o dyld) y cargará nuestra libreria permitiendonos varias opciones:

  • inspeccionar los parametros y sus contenidos
  • cambiar el valor de retorno de una función
  • extender la funcionalidad
  • reemplazar la implementación
  • volcar los backtraces (desde donde se llama)
  • ...


Para ejemplificar toda esta teoría he escrito un pequeño crackme:

        I2luY2x1ZGUgPHN0ZGlvLmg+CiNpbmNsdWRlIDxzdHJpbmcuaD4KCgppbnQgbWFpbihpbnQgYXJnYywgY2hhciAqKmFyZ3YpIHsKCWNoYXIgKnBhc3MgPSAoY2hhciAqKWFyZ3ZbMF07CglpZiAoYXJnYzwyKSB7CgkJcHJpbnRmICgiR2ltbWUgYW4gYXJnXG4iKTsKCQlyZXR1cm4gMTsKCX0KCWlmICghc3RyY21wICgiYSIsICJiIikpIHsKCQlwcmludGYgKCJBcmUgeW91IHRyaWNraW5nIG1lP1xuIik7CgkJcmV0dXJuIDE7Cgl9CglpZiAoIXN0cmNtcCAoYXJndlsxXSwgcGFzcykpIHsKCQlwcmludGYgKCJZb3Ugd2luIVxuIik7CgkJcmV0dXJuIDA7Cgl9CglwcmludGYgKCJXcm9uZ1xuIik7CglyZXR1cm4gMTsKfQo=

El desensamblado del crackme en OSX se vé tal que así:




La inyección de librerías sólo es posible en ejecutables no estáticos y que no tengan el bit SUID activado. Esto podemos comprobarlo facilmente con ls y rabin2.

$ isSuid() { ls -l $1 |awk '{print $1}' | grep -q s && echo YES || echo NO; }
$ isStatic() { rabin2 -I $1 | grep ^static | grep -q true && echo YES || echo NO; }     
$ isSuid ./a.out
 NO
$ isStatic ./a.out
NO

Estamos de suerte! el crackme cumple las condiciones para seguir con el post ;)

En este punto debemos saber que símbolos y librerías importa. Para ello utilizaremos rabin2, un programa que forma parte del framework de Radare2 y que nos permite inspeccionar y extraer información de binarios, principalmente ejecutables, librerías, etc.

        $ rabin2 -qi a.out
        printf
        strcmp
        dyld_stub_binder
        $ rabin2 -ql a.out
        /usr/lib/libSystem.B.dylib

Os recomiendo usar siempre la versión de git de radare2, y pasaros por el IRC (freenode canal #radare) si tenéis cualquier duda o idea :)

        $ git clone https://github.com/radare/radare2
        $ cd radare2
        $ sys/install.sh

Bien, parece que este ejecutable está usando printf y strcmp. Es bastante probable, que strcmp se esté usando para comprobar la contraseña suministrada por el usuario. Así que vamos a crear una librería que nos muestre con que parámetros se está ejecutando.

        $ cat mylib.c
        int strcmp(char *a, char *b) { return 0; }
        $ gcc -fPIC -shared mylib.c -o mylib.dylib
        $ rarun2 program=./a.out preload=mylib.dylib arg1=something
        Are you tricking me?

Si cambiamos el valor de retorno el crackme lo detectará. Así que debemos crear algún tipo de filtro para que sólo devuelva correcto desde la llamada que comprueba la contraseña, y no la previa que sirve para verificar que no hemos hookeado 'strcmp'.

En este ejemplo, he optado por reescribir strcmp para no complicar más el ejemplo y evitar explicar el lazy binding y el rtld-next para redirigir la ejecución a la implementación nativa.

Para esto podemos comprobar el caller utilizando esta macro:


        

El número mágico 0x100000ee7 es la dirección de la siguiente instrucción que encontramos después del 'call strcmp' que queremos interceptar, que es la dirección que encontraremos en el backtrace de la pila.



Compilamos la librería y ejecutamos el crackme con rarun2 para que éste nos defina las variables de entorno y todo lo necesario para inyectar la librería al programa, independientemente de estar en OpenBSD, OSX o Linux.

        $ gcc -shared -fPIC mylib.c -o mylib.dylib
        $ rarun2 program=./a.out preload=mylib.dylib arg1=something
        You Win!

Cabe destacar que precargar una librería nos permite introducirnos en la tabla de resoluciones de llamadas a librerías externas. Así que no nos será posible hookear funciones del mismo binario... o sí?

En el caso de tener la posibilidad de modificar el binario podemos parchear las llamadas a la función que queremos interceptar. O sencillamente, si queremos modificar por completo esa función podemos parchear los primeros bytes de esta para redirigir la ejecución con un jmp a la plt.

De esta forma cada vez que el programa llame a esa función interna el flujo de ejecución se redirigirá hacia un símbolo importado de alguna librería externa.

Pongamos un ejemplo para ver la explicación un poco más clara:


        

Compilamos y desensamblamos el programa

        $ gcc test.c
        $ r2 -Aqc 'pD $SS @ $S' a.out



Como observamos, el simbolo sym._test está en la dirección 0x100000e90. Si queremos reimplementar esa función por una externa debemos parchear la primera instrucción para que realize un salto a algún import, que estos a su vez están asociados a un símbolo local del binario, el cual es utilizado por el linker para saltar a la función cargada de una librería con ese nombre.

      $ rabin2 -i a.out
        [Imports]
        ordinal=000 plt=0x100000f38 bind=NONE type=FUNC name=printf
        ordinal=001 plt=0x100000f3e bind=NONE type=FUNC name=strcmp
        ordinal=002 plt=0x00000000 bind=NONE type=FUNC name=dyld_stub_binder
        3 imports
        $ rabin2 -s a.out | grep imp.
        vaddr=0x100000f38 paddr=0x00000f38 ord=003 fwd=NONE sz=0 bind=LOCAL type=FUNC name=imp.printf
        vaddr=0x100000f3e paddr=0x00000f3e ord=004 fwd=NONE sz=0 bind=LOCAL type=FUNC name=imp.strcmp

Una vez obtenida la dirección de los imports podemos proceder a parchear el ejecutable:

        $ r2 -qc 'wa jmp 0x100000f38 @ 0x100000e90' -w a.out

Si desensamblamos ahora la función sym._test veremos como realiza un salto a sym.imp.printf:



Así pues, si ejecutamos el programa ahora veremos como nos muestra que la contraseña que esperaba era el argv[0], es decir, el nombre del ejecutable.

        $ ./a.out
        ./a.out

Volvemos a ejecutar el crackme original:

        $ ./a.out.orig ./a.out.orig
        LE WIN!

Para este simple ejemplo no nos sería necesario inspeccionar mucho más allá de insertar un salto, ya que en este caso solo nos interesaba conocer el primer argumento de strcmp. Pero para otro tipos de análisis de binarios puede sernos de gran utilidad el poder redirigir la ejecución de ciertas funciones internas para reimplementarlas desde fuera.

Este tipo de técnica és utilizada en multitud de casos reales de ingenieria inversa, para analizar comunicaciones de malware, entender como se funciona un flasher, etc.

Si estás interesado en conocer más detalles sobre esta y otras técnicas, os recomiendo que echéis un vistazo al curso de ingeniería inversa y exploiting que vamos a impartir próximamente con Yago, NighterMan, Ricardo, Newlog y un servidor: pancake.


Nos leemos!

Artículo por cortesía de Sergi Álvarez
--pancake

2 comments :

Jokin Astobiza dijo...

Increible paso a paso detallando todo el proceso. MUCHAS GRACIAS!
No lo he probado, y aunque me considere "apañado" en muchos temas informáticos, soy solo un iniciado en este mundo(quién puede considerarse experto en la seguridad?), pero me he sentido como los de Matrix leyendo las pantallas con código verde. Se entiende claramente!
Pancake, muchas gracias por este paper, muy bueno

Jokin Astobiza dijo...

Creo que es porque el programa original hace una comprobación de seguridad strcmp-ando(comparando) "a" con "b", que da diferente de 0 si no modificamos nada.
Pero si modificamos strcmp para que siempre devuelva 0("correcto"), se da cuenta de que le estamos haciendo trampas.
Extracto:
if (!strcmp ("a", "b")) {
printf ("Are you tricking me?\n");
return 1;
}