viernes, 29 de marzo de 2019

Reversing(XIV): Solución Reto PoliCTF "Crack me if you can"

En este post la solución a un reto de 'reversing' de un archivo APK (Android Application Package), es decir, de una aplicación para el sistema operativo Android.

Este reto tiene el título "Crack me if you can" y mi valoración sobre su dificultad es:  ☆☆☆.

Su enunciado dice lo siguiente:

John bets nobody can find the passphrase to login!

Y nos dan un archivo cifrado (crack-me-if-you-can_d4e396383e3f64ec7698efaf42f7f32b.tar.gz.gpg) y la clave de cifrado (GPG Key: viphHowrirOmbugTudIbavMeuhacyet').

Solución: Al descifrar el archivo que nos dan obtengo el archivo APK (crack-me-if-you-can.apk) y lo ejecuto en un emulador de Android.

La aplicación me pide que introduzca una contraseña, introduzco una cualquiera ("passphrase"), pulso "Sign in" y se muestra un mensaje de error:
Para el análisis de la APK utilizo jadx, un decompilador de archivos APK.

Al examinar las clases veo:
Es decir, se realizan diversos reemplazos a la siguiente cadena:

[[c%l][c{g}[%{%Mc%spdgj=]T%aat%=O%bRu%sc]c%ti[o%n=Wcs%=No[t=T][hct%=buga[d=As%=W]e=T%ho[u%[%g]h%t[%}%

Y si el resultado de los reemplazos realizados es igual a la contraseña introducida se muestra el mensaje de éxito: "Good to go! =)".

Para obtener la contraseña correcta creo un script en Python para realizar esos mismos reemplazos a dicha cadena:

flag = '[[c%l][c{g}[%{%Mc%spdgj=]T%aat%=O%bRu%sc]c%ti[o%n=Wcs%=No[t=T][hct%=buga[d=As%=W]e=T%ho[u%[%g]h%t[%}%'

flag = flag.replace('spdgj', 'yb%e')
flag = flag.replace('aat', 'his')
flag = flag.replace('buga', 'Goo')
flag = flag.replace('=', '_')
flag = flag.replace('\\}', '', 1)
flag = flag.replace('\\{', '', 1)
flag = flag.replace('R', 'f', 1)
flag = flag.replace('c', 'f', 1)
flag = flag.replace(']', '')
flag = flag.replace('[', '')
flag = flag.replace('%', '')
flag = flag.replace('c', 'a')
flag = flag.replace('aa', 'ca')

print 'Flag:', flag

Ejecuto este script:
Por tanto, la solución a este reto es:
flag{Maybe_This_Obfuscation_Was_Not_That_Good_As_We_Thought}

Para comprobar si lo he hecho bien, ejecuto la APK en el emulador de Android, introduzco la contraseña y pulso "Sign in":
Y, tal y como se puede ver en la figura anterior, se muestra el mensaje de éxito.

jueves, 21 de marzo de 2019

Reversing(XIII): Solución Reto RingZer0 Team "RingZer0 Authenticator"

En este post la solución a otro de los retos de 'reversing' de la plataforma RingZer0 Team.

Este reto tiene el título "RingZer0 Authenticator" y mi valoración sobre su dificultad es:  ☆☆.

Su enunciado dice lo siguiente:

To improve the security of our site, contestants now need to authenticate using our RingZer0 Authenticator.

Y nos dan un archivo ejecutable (77b36c523c341967b8880240a5bee0a3.exe).

Solución: Ejecuto este fichero se me pide que introduzca un nombre de usuario y un código de autenticación:
Introduzco dos cadenas cualesquiera, por ejemplo: "Username" y "AuthCode", y se muestra un mensaje de error:
Como primera opción en este tipo de retos suelo utilizar OllyDbg. Abro en esta herramienta el archivo ejecutable y, también como suelo hacer habitualmente, empiezo el análisis buscando todas las cadenas de texto o 'strings' utilizadas en el programa (en la parte superior izquierda de la ventana principal: clic botón derecho, 'Search for' > 'All referenced strings'):
Y obtengo lo siguiente:
Como se observa en la figura anterior, entre las cadenas de texto está el mensaje de error que me ha aparecido al ejecutar el programa: "Wrong Authentication code"y también el mensaje que supuestamente aparecerá si introduzco los valores correctos"Authentication succesful!". Si hago doble clic sobre este último voy a la parte del código donde se encuentra esta cadena de texto:
Más arriba veo que, después de que se capturan los valores introducidos en el cuadro de texto ('textbox'), en 0x0040163D hay una llamada a una función. Si esa función devuelve 0 en el contenido del registro EAX la instrucción de salto condicional (JZ) que se se encuentra más abajo realizará un salto a 0x0040167C y se mostrará el mensaje de error.

Pongo un punto de interrupción en la instrucción CALL que llama a dicha función (clic botón derecho sobre ella, 'Breakpoint' > 'Toggle') para que cuando ejecute el programa en modo depuración ('debug mode') éste se detenga en esa instrucción:
Tras ejecutar el programa en modo depuración, 'Run debugged application (F9)'introduzco otra vez "Username" y "AuthCode", pulso 'Validate' el programa se detiene en el punto de interrupción que acabo de colocar.

Veo que el registro EBX contiene la dirección a partir de la que se ha incluido la cadena que he introducido como nombre de usuario ("Username") y que ESI contiene la dirección a partir de la que se almacena la cadena con el código de autenticación ("AuthCode"):
Ejecuto la CALL'Step into (F7)', entrando en el código de la función a la que llama para ejecutar instrucción a instrucción:
En 0x004014B8 veo una instrucción REPE CMPS, que comparará carácter a carácter las dos cadenas a las que apunten los registros ESI y EDI, respectivamente.

Como se ve, en la cuarta línea anterior a esa instrucción se moverá a EDI la dirección a partir de la cual se almacena la cadena "RingZer0", en la siguiente se inicializará a 9 el valor del registro ECX (el número de caracteres de la cadena "RingZer0" más uno) y en la siguiente se moverá al registro ESI la dirección a partir de la cual se almacena la cadena con el nombre de usuario que he introducido ("Username").

Por tanto, lo que se comparará carácter a carácter en la instrucción REPE CMPS son las dos cadenas indicadas en el párrafo anterior. Después de la comparación de cada par de caracteres el valor del registro ECX se decrementará en 1 y los valores contenidos en ESI y EDI se incrementarán en 1 para apuntar a los dos siguientes caracteres de sendas cadenas a comparar.

Las comparaciones de pares de caracteres finalizarán cuando un par de los caracteres comparados sean diferentes entre sí o cuando ECX sea igual a cero.

La instrucción JNE, que se encuentra en la siguiente línea de la instrucción REPE CMPS, producirá un salto a la instrucción en la dirección indicada si ambas cadenas son diferentes.

Para ver todo lo anterior pongo un punto de interrupción en la instrucción en cuestión:
Como se observa en las figuras anteriores, lo que se compara es el primer carácter de "RingZer0" ("R") con el primero del nombre de usuario que he introducido ("U") y ECX tiene el valor 9, y tras esta primera iteración, como "R" y "U" son diferentes, finalizan las comparaciones y se cede el control a la siguiente instrucción (JNE) que realizará un salto a una instrucción XOR que pone a cero el valor de EAX, y un poco más adelante finalizará la función.
Por tanto, en este caso y como ya he dicho antes, como tras la ejecución de esta función EAX valdrá 0 el programa mostrará el mensaje de error.

Tras este pequeño análisis de la primera validación que realiza el programa es evidente que para superar este reto el nombre de usuario que se introduzca debe ser "RingZer0", pero como veo más adelante sólo eso no es suficiente. Vuelvo a ejecutar el programa en modo depuración e introduzco "RingZer0" como nombre de usuario y "AuthCode" como código de autenticación:
Como se observa en las figuras anteriores, ahora lo que se compara es el primer carácter de "RingZer0" ("R") con el primero del nombre de usuario que he introducido ("R") y ECX tiene el valor 9, y tras esta primera iteración:
Las comparaciones de pares de caracteres continúan y, como se observa en las figuras anteriores, ECX se ha decrementado en 1 (tiene el valor 8), ESI y EDI se han incrementado en 1 (tienen valores 0x0061F6AB y 0x00405025, respectivamente) y en la segunda iteración lo que se compara es el siguiente carácter de "RingZer0" ("i") con el siguiente del nombre de usuario que he introducido ("i"), y así sucesivamente hasta la última comparación de pares de caracteres:
Una vez finalizada la ejecución de la instrucción REPE CMPS, como ambas cadenas son iguales, la siguiente instrucción (JNE) no producirá el salto y el programa continuará con la siguiente validación:
En 0x004014C5 veo una instrucción REPNE SCAS, que buscará el contenido de AL (que será cero, ya que AX se pone a ese valor antes de ejecutarse esa instrucción) en cada uno de los caracteres de la cadena a la que apunte el registro EDI (el código de autenticación introducido, ya que en la instrucción anterior se moverá a este registro la dirección a a partir de la que se almacena dicho código).

Como se ve en la segunda línea anterior a esa instrucción, el valor del registro ECX se inicializará a -1 y después de la búsqueda en cada carácter este valor se decrementará en 1, y el valor contenido en EDI se incrementará en 1 para a puntar al siguiente carácter de la cadena ("AuthCode") en el que buscar.

Para verlo exactamente pongo un punto de interrupción en la instrucción en cuestión:
Como se observa en las figuras anteriores, se busca 0 en el primer carácter de "AuthCode" ("A", código ASCII en hexadecimal 41) y ECX tiene el valor -1 en decimal (se representa como 0xFFFFFFFF), y tras esta primera iteración:
La búsqueda continúa y, como se observa en las figuras anteriores, ECX se ha decrementado en 1 (tiene el valor -2 en decimal, que se representa como 0xFFFFFFFE), EDI se ha incrementado en 1 (tiene valor 0x0061F6BC) y en la segunda iteración lo que se busca es 0 en el siguiente carácter de "AuthCode" ("u", código ASCII en hexadecimal 75). Y así sucesivamente hasta la última búsqueda:
Una vez finalizada la ejecución de la instrucción REPNE SCAS, el registro ECX tiene valor -10 en decimal (se representa como 0xFFFFFFF6):
Posteriormente se pondrá a cero el registro EAX y se comparará ECX (-10 en decimal, que se representa como 0xFFFFFFF6) con el valor -17 en decimal (se representa como 0xFFFFFFEF), y, como ambos valores son distintos, la siguiente instrucción (JE) no producirá el salto y finalizará la función:
Por tanto, en este caso y como ya he dicho antes, como tras la ejecución de esta función EAX valdrá 0 el programa mostrará el mensaje de error.

Tras este pequeño análisis de la segunda validación que realiza el programa deduzco que para superar este reto, además de que hay que introducir "RingZer0" como nombre de usuario, el código de autenticación tiene que tener una longitud de 15 caracteres, pero como veo más adelante tampoco estas dos cosas son sólo suficientes para ello. Reinicio la ejecución en modo depuración e introduzco "RingZer0" como nombre de usuario y "AuthCode1234567" como código de autenticación:
Ahora, como se observa en las figuras anteriores, una vez finalizada la ejecución de la instrucción REPNE SCAS, el registro ECX tiene valor -17 en decimal (se representa como 0xFFFFFFEF), que se compara con ese mismo valor, y, como evidentemente ambos son iguales, la siguiente instrucción (JE) producirá el salto y el programa continuará con la siguiente validación.

En esta tercera validación lo que se comprobará es que los 15 caracteres del código de autenticación introducido sean dígitos (del 0 al 9). Para ello, se restará 30 en hexadecimal (código ASCII de cero) al código ASCII en hexadecimal de cada carácter del código de autenticación introducido y si el resultado es mayor que 9 significa que ese carácter no es un dígito.

En nuestro caso he incluido caracteres alfabéticos en el código de autenticación que he introducido ("AuthCode1234567"), por lo que está validación no se superará, finalizará la función y el programa mostrará el mensaje de error, pero para verlo exactamente pongo un punto de interrupción en la instrucción que comprueba cada uno de los caracteres del código de autenticación para asegurarse que todos ellos son dígitos:
Como se observa en las figuras anteriores, el resultado de restar 30 en hexadecimal (código ASCII de cero, en decimal 48) al código ASCII del primer carácter del código de autenticación introducido ("A", cuyo código ASCII es 41 en hexadecimal, 65 en decimal) es 11 en hexadecimal (17 en decimal), y como éste es mayor que 9 en hexadecimal (9 en decimal) la siguiente instrucción (JA) producirá un salto a una instrucción XOR que pondrá a cero el valor de EAX, y un poco más adelante finalizará la función:
Por tanto, en este caso y como ya he dicho antes, como tras la ejecución de esta función EAX valdrá 0 el programa mostrará el mensaje de error.

Tras este pequeño análisis de la tercera validación que realiza el programa es evidente que para superar este reto, además de que hay que introducir "RingZer0" como nombre de usuario y el código de autenticación tiene que tener una longitud de 15 caracteres, los caracteres del código de autenticación tienen que ser todos ellos dígitos (del 0 al 9), pero como veo más adelante tampoco estas tres cosas son por sí solas suficientes para ello. Vuelvo a ejecutar el programa en modo depuración e introduzco "RingZer0" como nombre de usuario y "123456789012345" como código de autenticación:
Ahora, como se satisfacen las tres primeras validaciones, el programa continuará con la siguiente validación. Pongo un punto de interrupción en la instrucción CMP que hay justo antes de la instrucción de salto condicional JE, para ver que dos valores se compararán:
Se compara el valor 98 en hexadecimal (152 en decimal) con 0F en hexadecimal (15 en decimal) y la siguiente instrucción (JE), como ambos valores son distintos, no producirá el salto, se pondrá EAX a cero, se finalizará la función y el programa mostrará el mensaje de error.

Para evitar esto modifico el contenido de la dirección a la que apunta EAX y pongo como valor 98 en hexadecimal (152 en decimal) en lugar de 0F en hexadecimal (15 en decimal):
Como se observa en las figuras anteriores, ahora el salto se producirá. Pongo un punto de interrupción en cada una de las cuatro instrucciones de comparación que se ven a continuación, controlando los saltos condicionales tras su ejecución, para ver que valores se compararán:
- En la comparación en 0x401517 se compara el valor 97 en hexadecimal (151 en decimal) con 61 en hexadecimal (97 en decimal).
- En la comparación en 0x40151F se compara el valor 78 en hexadecimal (120 en decimal) con  en hexadecimal ( en decimal).
- En la comparación en 0x401527 se compara el valor 0F en hexadecimal (15 en decimal) con 49 en hexadecimal (73 en decimal).
- En la comparación en 0x40152F se compara el valor 15 en hexadecimal (21 en decimal) con 9B en hexadecimal (155 en decimal).

Si los valores comparados en cada una de estas cinco instrucciones CMP son iguales se pondrá EAX a uno, se finalizará la función y el programa mostrará el mensaje de éxito:
Por tanto, para conocer el código de autenticación correcto necesito saber cómo se obtienen los valores que se compararán en las cinco instrucciones CMP anteriores.

Para ello, vuelvo a ejecutar el programa en modo depuración (introduzco "RingZer0" como nombre de usuario y "123456789012345" como código de autenticación) y pongo un punto de interrupción en la instrucción en 0x004014FA que llama a una función:
Ejecuto una a una en modo depuración las instrucciones de esta función y veo que se toman de 3 en 3 los dígitos del código de autenticación que he introducido, y con cada grupo de 3 dígitos (d1, d2, d3) se realiza lo siguiente:

(1) d1 * 186 en decimal (BA en hexadecimal).
(2) 48 en decimal (30 en hexadecimal) - d2.
(3) d3 * 13 en decimal (D en hexadecimal).

El resultado para cada grupo de 3 dígitos es el Byte menos significativo de: (1) + (2) + (3).

Una vez finalizada la ejecución de esta función el resultado obtenido para cada uno de los 5 grupos de 3 dígitos se comparará con un cierto valor (en la ejecución en modo depuración de las 5 instrucciones de comparación he visto que estos valores, en decimal, eran: 152, 151, 120, 15 y 21, respectivamente) con objeto de mostrar el mensaje de error si alguno de ellos es distinto o el de éxito en el caso de que todos ellos coincidan.

Para ver si estoy en lo cierto, calculo el código de autenticación que me debería permitir pasar esta última validación. Para ello creo un pequeño script en python:

x=[152,151,120,15,21]
print ''
for i in range(len(x)):
    grupo=[]
    for d1 in range(0, 10):
        for d2 in range(0, 10):
            for d3 in range(0, 10):
                y=(d1*186 + 48 - d2 + d3*13) & 0xff
                if y==x[i]:
                   grupo.append(str(d1)+str(d2)+str(d3))

    print '[+]',i+1,grupo

Ejecuto este scritpt y obtengo:
Lógicamente, existen varios números de autenticación que satisfacen esta cuarta y última validación. En la figura anterior se muestran los elementos válidos de tres dígitos correspondientes a cada uno de los 5 grupos en los que se divide el código de autenticación, es decir, cualquier combinación de un elemento del grupo 1 con uno del 2, más otro del 3, más otro del 4 y más otro del 5 formará un código de autenticación válido.

Para comprobar si esto es así escojo, por ejemplo, el elemento "375" del grupo 1, "621" del grupo 2, "724" del grupo 3, "967" del grupo 4 y "852" del grupo 5. Concateno todos ellos y obtengo como número de autenticación: "375621724967852", y ejecuto el programa, introduzco "RingZer0" como nombre de usuario y como código de autenticación el indicado anteriormente, pulso "Validate" y se muestra el mensaje de éxito:
Para superar este reto en la plataforma introduzco ese mismo código de autenticación y pulso 'Authenticate', con lo que se mostrará la flag:
Y, finalmente, envío dicha flag: