Write up Missile Launch – Reversing con un toque estegano

Write up realizado por Javi Tallón (jtsec.es). Enlace del reto

Descripción del reto:
El equipo reverser PKTeam del país Fwhibbit ha podido obtener un binario del país enemigo Vulpensa. Según la información obtenida el binario oculta la flag con una pequeña ofuscación provocando que nadie obtenga acceso sin autorización a los códigos de lanzamientos de misiles del ejercito de Vulpensa. 
Ayuda al equipo reverser a poder dar con la flag que será vital para la vida del los fwhibbitenses y evitar un conflicto!

Cuando Naivenom prometió a través del grupo de Telegram de la Comunidad Fwhibbit una camiseta de los Conejos al primero que resolviera este reto no dudé ni un momento, solté en lo que estaba trabajando y me puse manos a la obra: ¡necesitaba esa camiseta!

Llevando ya más de una década trabajando en ciberseguridad, es una verdadera alegría descubrir como este grupo de gente se esfuerza por hacer más accesible a todo el mundo un conocimiento que antaño era tratado bajo un halo de secretismo y mística, como si fuesen artes oscuras y gracias a iniciativas como este blog el nivel de los profesionales en ciberseguridad en España no deja de subir. ¡Al final ganamos todos!

¡Pero basta de palabrería y pongámonos manos a la obra! El reto consistía en un reversing de un binario en ARMv7, el procesador de la Raspberry Pi 2. Yo tengo un par de ellas en casa, haciendo funciones de Media Center con Kodi, y la verdad me daba bastante pereza desarmar el tinglado para ponerme a hacer el reto. 

Afortunadamente ya había emulado con anterioridad binarios de ARM en mi máquina y era bastante sencillo montar un entorno con qemu.

Examinando el fichero con file era posible ver que efectivamente se trataba de un binario compilado para ARM, que poseía símbolos y que lamentablemente estaba compilado dinámicamente, así que es posible que nos diera algún problemilla con las librerías:

page2image1798688

Así que, sin más dilación, instalamos qemu con soporte para ARM:

page2image1797344

Así como el paquete qemu-user que nos proporciona emulación de manera transparente, permitiendo la ejecución de binarios no nativos como el que tenemos que analizar.

page3image1782112

Ahora sí ya podíamos intentar ejecutar el binario emulado en nuestro sistema:

page3image1799808

Lamentablemente como habíamos predicho, al estar el binario enlazado dinámicamente, necesita la presencia de ciertas librerías en el sistema, y puesto que nuestra máquina corre bajo una arquitectura x86, estas no están disponibles.

Para solucionar este inconveniente utilizaremos la herramienta apt-file que nos dirá si el fichero que nos falta se encuentra disponible en algún paquete de nuestra distribución de Linux. Cruzamos los dedos y…:

page3image1785696

Bingo! Instalamos el paquete para tener la librería que nos falta que como podéis ver es el enlazador dinámico de Linux pero para la arquitectura ARM:

page4image1782560

Normalmente un binario compilado dinámicamente no solo necesita el enlazador dinámico, sino que utilizará otras muchas librerías, o como mínimo, si se trata de un binario sencillo, la libc. Podemos comprobar que librerías están enlazadas usando objdump:

page4image1774496

Donde vemos que la única necesaria es libc.so.6, que ya es proporcionada por el paquete que recién acabamos de instalar.

De esta manera, únicamente tendremos que decirle a qemu que se ejecute usando como path la ruta donde se encuentran los binarios de las bibliotecas de ARM que acabamos de instalar:

page4image1781888

Yuju! ¡Ya no tendré que desmontar mi Media Center!

El binario parece que pide una flag por pantalla, cuando introducimos un valor erroneo, nos contesta con un “Nope:(.” mientras que supuestamente, si metemos el valor bueno, nos devolverá la flag que da solución al reto.

Además, qemu nos permite realizar debugging con gdb siempre que tengamos instalado el paquete gdb-multiarch. Para ello lanzamos el ejecutable de la siguiente manera, especificando el puerto de escucha:

page5image1793088

Mientras que en otra consola ejecutamos gdb-multiarch (apt-get install gdb-multiarch) y realizamos los siguientes pasos:

  • Especificamos la arquitectura (ser arch arm)
  • Especificamos la ruta donde están las bibliotecas dinámicas (set sysroot /usr/arm-linux-gnueabihf/)
  • Cargamos el fichero (file rev3_fwr)
  • Nos conectamos a qemu en el puerto que está escuchando (target remote localhost:1234).
page5image1800032

Podemos probar que todo va correctamente poniendo un breakpoint en la dirección de main:

page5image1794880

Otra opción muy interesante que nos puede proporcionar información es el uso del strace integrado en qemu, al que podemos acceder pasando a qemu-arm el parámetro -strace.

Para la parte de análisis estático me hubiera encantado poder usar IDA Pro, el entorno con el que me encuentro más cómodo, sin embargo, la versión free no soporta la arquitectura ARM, así que, puesto que se debía tratar de un binario sencillo, decidí darle una oportunidad a Hopper, cuya versión demo si que soporta tanto x86 como ARMv7, aunque tiene la peculiaridad de que se cierra a los 30 minutos de uso, así que tendremos que ser rápidos 😀

Descargando Hopper de su web y ejecutándolo, pasamos a abrir el fichero que queremos analizar. La pestaña Procs nos permite acceder fácilmente a la lista de procedimientos que el desensamblador ha encontrado en el binario, así que vamos a ver qué encontramos en el main del programa:

page6image1789952

Hopper por defecto muestra una vista del código desensamblado, pero pulsando espacio nos mostrará un diagrama de bloques básicos como hace el viejo IDA.

ARM se caracteriza por ser una arquitectura de tipo RISC (Reduced Instruction Set Computer) en contraposición a X86, que es de tipo CISC (Complex Instruction Set Computer), y por ello podemos observar fácilmente que el número de diferentes instrucciones es mucho menor en ARM que en x86.  Es por esto que los binarios de ARM suelen ocupar más que los mismos compilados para x86.

En ARM básicamente encontraremos funciones para cargar/guardar valores de memoria a registro (LDR/STR), y las ya conocidas funciones de acceso a la pila (PUSH/POP), aritmética (ADD/SUB/MUL/…) o lógicas y de salto (B/BL). El cheatsheet que acompaña al reto es muy útil y me lo guardo para la colección!

Otra característica de los procesadores ARM es la presencia de dos modos de operación, el normal de 32 bits, o el llamado thumb-mode de 16 bits. Es posible saltar de un modo a otro con los mnemónicos BX o BLX y podemos fácilmente saber si estamos en thumb mode o no porque en thumb mode las direcciones de memoria son impares. Afortunadamente parece que todo el código que nos afecta está en modo normal (instrucciones de memoria pares :D).

Podemos también observar que el número de registros es mucho mayor en la arquitectura ARM que en x86, contando con 16 registros (r0-r15), siendo r15 el que se utiliza como contador de programa mientras que r14 es usado para almacenar la dirección desde la que se ejecutó el último BL y poder continuar la ejecución fácilmente tras una llamada a función.  R11 es usado como frame pointer y aparece nombrado como fp en Hopper.

Según la convención de llamada usada en ARM 32 bits los argumentos de las funciones van por orden en los primeros registros de propósito general r0 a r3 (caller saved) mientras que los registros r4 a r11 (callee saved) se usan para alojar las variables locales y el valor de retorno se devuelve en los registros r0 a r3.

Sabiendo esto y dando un vistazo de alto nivel al primer bloque básico es posible observar cómo:

page8image1793536
  • 0x104c0 llama a la función puts para mostrar la cadena “M i s s i l e  l a u n c h  c o d e   by @n4ivenom fwr{} Rev”
  • 0x104c4 se carga en R14 (LR) la dirección 0x106d8 y se van inicializando una serie de variables locales que apuntan a direcciones a partir 0x106d8, o dicho de otra manera, se inicializan de manera estática.
  • 0x104ec idem pero inicializando a partir de 0x106ec. Probablemente se estén creando dos vectores de bytes estáticos en #0x20 y #0x34 respectivamente. 
  • 0x10518 llama a la función printf para pintar “>>>”
  • 0x10524 llama a gets para obtener la entrada del usuario con la flag
  • 0x10528 carga en una variable local (#-0x8) el valor apuntado por el vector de #0x34
  • 0x10530 salta de manera incondicional a 0x10550

Además, es posible ver un par de bucles que seguramente sean los responsables de que la solución no aparezca como una cadena de texto…

El siguiente bloque básico interesante es el que comienza en 0x10560 y en él se llama a la función strcmp utilizando la entrada del usuario y el valor correcto de la flag calculado en el anterior bloque básico.

page8image1793760

strcmp devuelve en r0 el resultado y el compilador lo ha movido a r3 antes de compararlo con cero, de manera que si la respuesta es incorrecta se saltará a 0x105cc donde se mostrará el mensaje “Nope:(.” y si es correcta se continuará la ejecución. 

Una aproximación rápida para resolver el reto sería modificar el resultado del strcmp para que nos devolviera la flag poniendo un breakpoint en la dirección dónde se compara el resultado devuelto por la comparación (break *main+204) y redirigiendo el flujo a donde se pinta el flag (set $pc=0x10580), sin embargo, el resultado no es el esperado:

Parece que nos va a tocar profundizar en el análisis estático…

En ARM es muy frecuente encontrar indirecciones cortas como las que hemos visto ya que normalmente las constantes se almacenan al final de cada función donde se usan en unas estructuras llamadas pool de constantes o de literales.

En concreto, como hemos visto, los datos sobre los que se realizan las operaciones son cargados desde 0x106d8 y desde 0x106ec, y el desensamblador los detecta como enteros de 4 bytes (dd). Puesto que probablemente se trate uno de una serie de enteros sino de una cadena de bytes vamos pidiéndole a Hopper que los ponga como bytes (db). 

Por suerte los shortcuts son los mismos que en IDA Pro y podemos conseguirlo pulsando sobre la letra ‘d’.

De esta manera nos queda el siguiente array de datos:

0x106d8 = C6 D7 D2 DB A9 CC C9 CB C5 BF C3 CF C4 C5 AD AC DD 00 00 00 –>#0x20
0x106ec = B4 C8 C9 D3 A9 D3 B4 C8 C5 BF B0 C1 D3 D3 B7 A4 B4 00 00 00 –> #0x34

A partir de ahí podemos observar un bucle que itera utilizando como contador la variable local #-0x8 que recordemos apuntaba al vector en #0x34 hasta que encuentre un 0 en la memoria apuntada, y va incrementando de uno en uno, mientras que realiza una transformación de los valores incrementando cada byte en 0x60 y quitándole el resto (extendiendo a cero):

page9image1787488

Este trozo en pseudocódigo podría escribirse así:

R3 = [#0x34]
[#0x08] = R3

R3 = [#0x08]
R3 = Byte([R3])

Si es 0 para, si no:

R3 = [#0x08]
R2 = R3+1
[#0x08] = R2
R2 = Byte([R3])
R2 = R2+0x60
R2 = ZeroExtendByte(R2)
[R3] = R2

Luego se realiza el strcmp y en el caso de que la solución sea correcta, se realiza un bucle equivalente al que hemos visto antes pero usando como puntero la variable en #0x0c y con el contenido almacenado en #0x20, que recordemos era inicializado a partir de lo que vemos en 0x106d8 y que finalmente dará como resultado que se muestre la flag que resuelve el reto:

page10image1792640

A partir de aquí, lo más sencillo sin llegar a ejecutar el código sería programar un sencillo script en Ruby que reprodujera el comportamiento que hemos visto, es decir, sumar 0x60 a cada byte y le haga %0x100 para simular la extensión a zero:

file=’reto.txt’
File.readlines(file).each do |line|
new_value = (line.to_i(16) + 0x60 ) % 0x100
puts «#{line.strip} (#{line.to_i(16).chr}) + 0x60 = #{new_value.to_s(16)} (#{new_value.chr})»
end

¡Ponemos en reto.txt cada uno de los valores que queremos convertir y ejecutamos!

page11image1791968

WTF!?

Para nuestra sorpresa la salida no es la esperada. ¡No parece un ASCII muy legible!

Sin embargo, algunos carácteres me son familiares… ¡Es la misma salida que daba cuando modificamos el flujo haciendo análisis dinámico!

Por otra parte, teóricamente, la flag de solución debería tener el formato fwh{….}, así que las cuatro primeros caracteres deben corresponder a “fwh{“, que en ASCII es 0x66,0x77, 0x68, 0x7b. Casualmente 0x40 bytes más que la solución que hemos encontrado ¡y 0x60 bytes menos que el byte cifrado! 

Modificamos el script para restar 0x60 en lugar de sumar y… ¡voilá!

page12image1788608

¡Estas cadenas tienen mucho más sentido! La cadena de entrada sería: ThisIsThe_PassWDT y la flag que resuelve el reto: fwr{Ilike_codeML}. ¡Parece que a un conejo le ha patinado un signo!

Si queremos que el programa nos pase la flag de manera directa tendremos que llamarlo de la siguiente manera:

perl -e ‘print «\x14\x28\x29\x33\x09\x33\x14\x28\x25\x1f\x10\x21\x33\x33\x17\x -L /usr/arm-linux-gnueabihf/ rev3_fwr

page12image1795552

¡Reto resuelto!

PS: Hopper tiene otra opción que no había visto y que podría hacer más sencillo el análisis estático. Si pulsamos Window > Show Pseudo Code of Procedure nos muestra pseudocódigo de la función, y como podemos ver, va sumando 0x60, que no restando ;D

page13image1797120

Hablando con Naivenom y según la descripción del reto la idea era que hubiese que modificar el binario para poder desvelar la flag oculta realizando esteganografía, así que para resolver adecuadamente el reto habría que parchear las sumas paras convertirlas en restas debido al método de ofuscación básico de la string y así su bypassear su ocultación.

En concreto tendremos que modificar las dos instrucciones “add r2, r2, #0x60” que hay en las direcciones 0x10538 y 0x1059c, cuyos offset en el fichero son 0x538 y 0x59c respectivamente.

En Hopper podemos ver los opcodes correspondientes a esta instrucción en el panel de la derecha:

page13image1736864

De manera que la instrucción se encodea como “60 20 82 E2”, y podemos cambiarla fácilmente pulsando Alt+A en cada una y escribiendo la nueva instrucción “sub r2, r2, #0x60”, cuyo nuevo opcode es “60 20 42 E2” según podemos ver en el panel de la derecha.

Para terminar vamos a File > Produce New Executable y…

page14image1750528

¡Lamentablemente la versión free no nos permite parchear ficheros! Así que nos tocará hacerlo a mano con nuestro editor hexadecimal favorito, yo he usado hexedit (‘/’ para buscar, Ctrl+X para salir y guardar):

page14image1749856

Y ejecutar pasándole la clave sin encodear:

page14image1755008

Un comentario en «Write up Missile Launch – Reversing con un toque estegano»

  1. Buenas! Lo primero muy buena entrada, enhorabuena por la camiseta 😉
    Estaba siguiendo los pasos pero no consigo conectar gdb al puerto en el que (se supone) se está ejecutando el binario. Estoy usando Ubuntu 16.04. En gdb-multiarch me da «localhost:1234: Connection timed out.» mientras que en el terminal que estoy ejecutando no me aparece nada, y cuando hago Ctrl+C da violación de segmento y un error de ubuntu que dice «qemu arm crashed with sigsegv in accept()» así que entiendo que es algún problema con el socket. He instalado todo como mostrabas y revisasdo los comandos quince veces :S
    Gracias de antemano!

Los comentarios están cerrados.