Challenge CryptoHorrific
Este reto consiste en extraer un vector de inicialización (IV) y una clave estática (hardcoded) desde un binario de aplicación iOS para descifrar el contenido cifrado mediante AES-128 ECB y la solución consiste en utilizar análisis estático con herramientas como Cutter y la examinación de cadenas de texto para recuperar la información necesaria y obtener la flag.
Para comenzar, se identifica el tipo de archivo del binario principal de la aplicación:
$ cd hackthebox.app
$ file hackthebox
hackthebox: Mach-O 64-bit x86_64 executable, flags:<NOUNDEFS|DYLDLINK|TWOLEVEL|PIE>
Se confirma que se trata de un ejecutable Mach-O de 64 bits para la arquitectura x86_64 de Apple. Dado que no es posible ejecutarlo de forma nativa en otros entornos, se recurre a la herramienta strings para extraer las cadenas de texto presentes en el archivo:
$ strings hackthebox
__PAGEZERO
__TEXT
__text
__TEXT
__stubs
__TEXT
__stub_helper
__TEXT
...
QfTjWnZq4t7w!z%C
challenge
plist
flag
hash
TQ,R
superclass
T#,R
description
...
Dentro de los resultados, se aprecian referencias a términos interesantes como flag y plist. Al tratarse de una aplicación de iOS, es habitual que la configuración y los datos de la misma residan en archivos plist. Utilizando la herramienta plistutil, se inspecciona primero el archivo Info.plist:
$ plistutil -i Info.plist -o info.xml
$ cat info.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
...
<string>ben.hackthebox</string>
...
<array>
<string>iPhoneSimulator</string>
</array>
...
</dict>
</plist>
Esto revela que la aplicación está destinada al emulador de iPhone. Procediendo de igual manera con el archivo challenge.plist, se observan los datos específicos del reto:
$ plistutil -i challenge.plist -o challenge.xml
$ cat challenge.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<array>
<dict>
<key>flag</key>
<string>Tq+CWzQS0wYzs2rJ+GNrPLP6qekDbwze6fIeRRwBK2WXHOhba7WR2OGNUFKoAvyW7njTCMlQzlwIRdJvaP2iYQ==</string>
<key>id</key>
<string>123</string>
<key>title</key>
<string>HackTheBoxIsCool</string>
</dict>
</array>
</plist>
Se identifica una cadena codificada en base64. Al intentar decodificarla de forma directa, se obtiene un resultado ilegible:
$ echo "Tq+CWzQS0wYzs2rJ+GNrPLP6qekDbwze6fIeRRwBK2WXHOhba7WR2OGNUFKoAvyW7njTCMlQzlwIRdJvaP2iYQ==" | base64 -d; echo
N[43jck<o
E+e[kPRxPEoha
La salida contiene caracteres no imprimibles, lo cual indica que los datos están cifrados y se requiere de un análisis profundo del binario. Para ello, se carga el archivo en Cutter, analizando la función SecretManager:
...
var_20h = *___stack_chk_guard;
var_74h = (undefined4)arg3;
instance = (void **)0x0;
var_70h = arg2;
var_68h = arg1;
objc_storeStrong(&instance, CONCAT44(in_register_0000000c, type));
var_88h = (char **)0x0;
objc_storeStrong(&var_88h, CONCAT44(in_R8, arg5));
var_90h = (void **)0x0;
objc_storeStrong(&var_90h, CONCAT44(in_R9, arg6));
memset(&s, 0, 0x11);
objc_msgSend(instance, "getCString:maxLength:encoding:", &s, 0x11, 4);
memset(&var_58h, 0, 0x11);
if (var_88h != (char **)0x0) {
objc_msgSend(var_88h, "getCString:maxLength:encoding:", &var_58h, 0x11, 4);
}
var_98h = (void *)objc_msgSend(var_90h, "length");
size = (int64_t)var_98h + 0x10;
ptr = (void *)malloc(size);
uVar1 = var_74h;
var_b0h = 0;
uVar3 = objc_retainAutorelease(var_90h);
uVar3 = objc_msgSend(uVar3, "bytes");
iVar2 = CCCrypt(uVar1, 0, 3, &s, 0x10, &var_58h, uVar3, var_98h, ptr, size, &var_b0h);
if (iVar2 == 0) {
uVar3 = objc_msgSend(0, "dataWithBytesNoCopy:length:", ptr, var_b0h);
var_60h = objc_retainAutoreleasedReturnValue(uVar3);
} else {
free(ptr);
var_60h = 0;
}
objc_storeStrong(&var_90h, 0);
objc_storeStrong(&var_88h, 0);
objc_storeStrong(&instance, 0);
iVar4 = var_60h;
uVar3 = objc_autoreleaseReturnValue();
if (*___stack_chk_guard == var_20h) {
return uVar3;
}
__stack_chk_fail();
uVar3 = objc_storeStrong(iVar4 + _field int ViewController::l, 0);
return uVar3;
}
El código decompilado invoca a CCCrypt, una API criptográfica de Apple, para realizar el descifrado utilizando el modo AES-128 con padding PKCS7. Posteriormente, al revisar la función viewDidLoad, se puede observar cómo se preparan los parámetros para realizar la llamada a SecretManager:
...
uVar7 = objc_msgSend(uVar6, "pathForResource:ofType:", "", "");
...
uVar9 = objc_msgSend(uVar8, "objectForKey:", "");
...
uVar10 = objc_msgSend(ppcVar2, "SecretManager:key:iv:data:", 1, "", "", uVar4);
...
El de-compilador no logra procesar por completo las estructuras CFString, ocultando los valores de las cadenas. Se examina la vista de desensamblado del binario para localizar las direcciones de estas cadenas constantes:
0x1000010ad mov edx, 1
0x1000010b2 lea rcx, [__CFConstantStringClassReference] ; str.cstr._A_D_G_KaPd
; 0x100003098
0x1000010b9 lea rsi, [__CFConstantStringClassReference] ; str.cstr.QfTjWnZq4t7
; 0x1000030b8
0x1000010c0 mov rdi, qword [data.100003df8] ; 0x100003df8
0x1000010c7 mov r9, qword [var_40h]
0x1000010cb mov qword [var_98h], rdi
0x1000010d2 mov rdi, r9 ; void *instance
0x1000010d5 mov r9, qword [var_98h]
0x1000010dc mov qword [var_a0h], rsi
0x1000010e3 mov rsi, r9 ; char *selector
0x1000010e6 mov r8, qword [var_a0h]
0x1000010ed mov r9, rax
0x1000010f0 mov qword [var_a8h], rax
0x1000010f7 call objc_msgSend ; sym.imp.objc_msgSend ; void *objc_msgSend(void *instance, char *selector)
Las instrucciones revelan fragmentos de la clave y del vector de inicialización: _A_D_G_KaPd y QfTjWnZq4t7. Usando strings en conjunto con grep, se busca en el binario utilizando los segmentos conocidos para obtener las credenciales completas:
$ strings hackthebox| grep "QfTjWnZq4t7"
QfTjWnZq4t7w!z%C
$ strings hackthebox | grep "KaPd"
!A%D*G-KaPdSgVkY
Con las cadenas íntegras, el vector de inicialización (IV) resulta ser QfTjWnZq4t7w!z%C y la clave simétrica corresponde a !A%D*G-KaPdSgVkY. Teniendo ambas piezas, se utiliza CyberChef para revertir el cifrado del archivo .plist. La operación requiere decodificar los datos desde base64 y, debido a que CCCrypt con los parámetros observados configura el algoritmo AES en modo ECB, la clave es suficiente para revelar el texto en claro, recuperando de este modo la flag objetivo:
HTB{FLAG}