TL;DR: iOS UnCrackable Level 1 crackme application can be solved without using an iOS device using Angr’s dynamic execution engine.

Time To Read: 5 min

OWASP MSTG UnCrackable Level 1 Crackme App is an iOS crackme application. The goal of the crackme is to find a secret string hidden somewhere inside the application. There are multiple ways to solve the crackme using Frida, or a debugger, or even by manually reversing the native code. In this post I will solve the challenge using Angr’s dynamic execution engine. With the current approach, by using static analysis assisted with dynamic execution, the crackme can be solved without needing an iOS device.

To perform static analysis, multiple tools are available - Ghidra, R2 or IDA Pro. The application code is not obfuscated and can be easily followed in any of these tools. I am using Ghidra in this post.

The application gives a message “Verification Failed” when a wrong input is provided to it.

On searching the binary for this string, it is found in buttonClick function. The message is displayed after a comparison operation using isEqualToString . The comparison is being performed between the input string and the value of a label marked as hidden.

To find the secret string, we need to find the value of this label. Further, on analysing the function viewDidLoad , the value of the label is being set using the return value of the function at offset 0x1000080d4 .

Shifting our attention to function at 0x1000080d4 , there are multiple sub-function calls, and return values from each of these sub-functions is stored at an index of an array at address 0x10000dbf0 . This array is the hidden string we are looking for! Each of the above sub-functions are not too complicated, but nevertheless require some manual effort to reverse them and obtain the eventual hidden string.

The function at 0x1000080d4 or any of its sub-functions are self contained, i.e, there are no library calls or system calls; we can easily run this code in any emulator like Unicorn or its likes. Angr is a reverse engineering framework with multiple features and one of them being to dynamically execute code. For using dynamic execution feature of Angr, we need to identify and pass the points in the program from where we want to emulate the code and the addresses where the eventual secret string generated. In this case, the function at 0x1000080d4 is the function we want to emulate and the return value is the information we are interested in. The return value is a pointer to the hidden string.

The eventual Angr script looks as following:

import angr import claripy def solve (): # Load the binary by creating angr project. project = angr . Project ( 'uncrackable.arm64' ) # Pass the address of the function to the callable func = project . factory . callable ( 0x1000080d4 ) # Get the return value of the function ptr_secret_string = claripy . backends . concrete . convert ( func ()) . value print ( "Address of the pointer to the secret string: " + hex ( ptr_secret_string )) # Extract the value from the pointer to the secret string secret_string = func . result_state . mem [ ptr_secret_string ] . string . concrete print ( f "Secret String: {secret_string}" ) solve ()

Angr Project is the basic building block, and must needed to access any functionality of Angr. Using callable API we inform the Angr engine to execute the code from the offset 0x1000080d4 , and the return value post concrete execution (dynamic execution is also called concrete execution) is captured in ptr_secret_string . The value stored in the pointer is accessed using result_state .

This solution is also contributed to the OWASP MSTG and with some more details on reverse engineering part.