Challenge CryptoHorrific
This challenge consists of extracting a hardcoded initialization vector (IV) and a static AES key from an iOS application binary to decrypt an AES-128 ECB encrypted flag and the solution consists of using static analysis with tools like Cutter and examining text strings to recover the necessary information.
To begin the analysis, the file type of the main application binary is identified:
$ cd hackthebox.app
$ file hackthebox
hackthebox: Mach-O 64-bit x86_64 executable, flags:<NOUNDEFS|DYLDLINK|TWOLEVEL|PIE>
It is confirmed to be a Mach-O 64-bit executable for Apple's x86_64 architecture. Since the binary cannot be executed natively in standard non-Apple environments, the strings command is utilized to extract textual references embedded in the file:
$ strings hackthebox
__PAGEZERO
__TEXT
__text
__TEXT
__stubs
__TEXT
__stub_helper
__TEXT
...
QfTjWnZq4t7w!z%C
challenge
plist
flag
hash
TQ,R
superclass
T#,R
description
...
Among the extracted data, interesting keywords such as flag and plist appear. Because iOS applications often store configurations and static data in Property List (plist) files, they are the next target for inspection. Using the plistutil tool, the Info.plist file is converted into a readable format:
$ 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>
The output reveals that the application is built for an iPhone emulator. Proceeding with the same approach for challenge.plist, the specific data corresponding to the challenge is uncovered:
$ 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>
A base64 encoded string is stored under the flag key. When an initial attempt to decode it directly is made, the result yields unreadable text:
$ echo "Tq+CWzQS0wYzs2rJ+GNrPLP6qekDbwze6fIeRRwBK2WXHOhba7WR2OGNUFKoAvyW7njTCMlQzlwIRdJvaP2iYQ==" | base64 -d; echo
N[43jck<o
E+e[kPRxPEoha
The unprintable characters in the output indicate that the data is encrypted, necessitating a deeper reverse-engineering process of the binary. The binary is then loaded into Cutter, where the SecretManager method is thoroughly analyzed:
...
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;
}
The decompiled function invokes CCCrypt, a well-known Apple cryptographic API. The parameters point to an AES-128 decryption algorithm using ECB mode and PKCS7 padding. Inspecting the viewDidLoad method provides insight into how the arguments are passed to the SecretManager:
...
uVar7 = objc_msgSend(uVar6, "pathForResource:ofType:", "", "");
...
uVar9 = objc_msgSend(uVar8, "objectForKey:", "");
...
uVar10 = objc_msgSend(ppcVar2, "SecretManager:key:iv:data:", 1, "", "", uVar4);
...
The decompiler struggles to completely resolve the CFString structures, leaving the string values hidden in the pseudocode. By analyzing the binary's disassembly view, the exact addresses of these constant strings can be pinpointed:
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)
The assembly instructions expose partial fragments of the encryption key and initialization vector: _A_D_G_KaPd and QfTjWnZq4t7. Using strings piped to grep, the binary is searched for these partial segments to retrieve the full hardcoded values:
$ strings hackthebox| grep "QfTjWnZq4t7"
QfTjWnZq4t7w!z%C
$ strings hackthebox | grep "KaPd"
!A%D*G-KaPdSgVkY
The search successfully yields the initialization vector (IV) QfTjWnZq4t7w!z%C and the symmetric key !A%D*G-KaPdSgVkY. With these cryptographic components at hand, CyberChef is employed to decrypt the string from the plist file. The process involves a base64 decoding step followed by AES decryption. Since the algorithm runs in ECB mode, only the key is strictly required to successfully decipher the data and retrieve the final flag:
HTB{FLAG}