The free version of IDA unfortunately does not support the ARM processor type.
armv7(which is 32-bit) and
arm64. This design of a fat binary allows an application to be deployed on all devices. To analyze the application with class-dump, we must create a so-called thin binary, which contains one architecture only:
strings <path_to_binary>) or radare2's rabin2 (
rabin2 -zz <path_to_binary>). When using the CLI-based ones you can take advantage of other tools such as grep (e.g. in conjunction with regular expressions) to further filter and analyze the results.
CC_SHA256function, it indicates that the application will be performing some kind of hashing operation using the SHA256 algorithm. Further information on how to analyze iOS's cryptographic APIs is discussed in the section "iOS Cryptographic APIs".
connect. Using the iOS Bluetooth documentation you can determine the critical functions and start analysis around those function imports.
Techniques discussed in this section are generic and applicable irrespective of the tools used for analysis.
For static analysis in this section, we will be using Ghidra 9.0.4. Ghidra 9.1_beta auto-analysis has a bug and does not show the Objective-C classes.
buttonClickfunction of the
ViewControllerclass. We will look into the
buttonClickfunction later in this section. When further checking the other strings in the application, only a few of them look a likely candidate for a hidden flag. You can try them and verify as well.
buttonClickfunction identified in the above step, or start analyzing the application from the various entry points. In real world situation, most times you will be taking the first path, but from a learning perspective, in this section we will take the latter path.
[AppDelegate application:didFinishLaunchingWithOptions:]is called when the application is started for the first time.
[AppDelegate applicationDidBecomeActive:]is called when the application is moving from inactive to active state.
AppDelegateclass, we can conclude that there is no relevant code present. The lack of any code in the above functions raises the question - from where is the application's initialization code being called?
ViewControllerclass in the Symbol Tree view. In this class, function
viewDidLoadfunction looks interesting. If you check the documentation of
viewDidLoad, you can see that it can also be used to perform additional initialization on views.
setHiddenflag set to 1 in lines 27-29. You can keep a note of these observations and continue exploring the other functions in this class. For brevity, exploring the other parts of the function is left as an exercise for the readers.
buttonClickfunction is an obvious target. As earlier mentioned, this function also contains the string we see in the pop-ups. At line 29 a decision is being made, which is based on the result of
isEqualString(output saved in
uVar1at line 23). The input for the comparison is coming from the text input field (from the user) and the value of the
label. Therefore, we can assume that the hidden flag is stored in that label.
viewDidLoadfunction, and understand what is happening in the native function identified. Analysis of the native function is discussed in "Reviewing Disassembled Native Code".
isEqualStringscan help us in quickly understanding the semantics of the code. In case of C/C++ native code, if all the binaries are stripped, there can be very few or no symbols present to assist us into analyzing it.
viewDidLoadfunction in the previous section. The function is located at offset 0x1000080d4. The return value of this function used in the
setTextfunction call for the label. This text is used to compare against the user input. Thus, we can be sure that this function will be returning a string or equivalent.
blis used to call the function at 0x100008158.
strbstores a byte to the address in register). We can see the same pattern for other function calls, the returned value is stored in X19 register and each time the offset is one more than the previous function call. This behavior can be associated with populating each index of a string array at a time. Each return value is been written to an index of this string array. There are 11 such calls, and from the current evidence we can make an intelligent guess that length of the hidden flag is 11. Towards the end of the disassembly, the function returns with the address to this string array.
FridaGadget.dyliblibrary. A detailed explanation of the repackaging and resigning process can be found in the next chapter "Manual Repackaging". We won't cover Objection in detail in this guide, as you can find exhaustive documentation on the official wiki pages.
FridaGadget.dylibduring startup so we can instrument the app with Frida.
Please note that the following steps apply to macOS only, as Xcode is only available for macOS.
com.example.myapp. Note that you can use a single App ID to re-sign multiple apps. Make sure you create a development profile and not a distribution profile so that you can debug the app.
AwesomeRepackaging.mobileprovision-replace this with your own filename in the shell commands below.
embedded.mobileprovisionfrom the app container, which is in the Xcode subdirectory of your home directory:
~/Library/Developer/Xcode/DerivedData/<ProjectName>/Build/Products/Debug-iphoneos/<ProjectName>.app/. The NCC blog post "iOS instrumentation without jailbreak" explains this process in great detail.
get-task-allowkey is also important: when set to
true, other processes, such as the debugging server, are allowed to attach to the app (consequently, this would be set to
falsein a distribution profile).
lsofis a powerful command, and provides a plethora of information about a running process. It can provide a list of all open files, including a stream, a network file or a regular file. When invoking the
lsofcommand without any option it will list all open files belonging to all active processes on the system, while when invoking with the flags
-c <process name>or
-p <pid>, it returns the list of open files for the specified process. The man page shows various other options in detail.
lsoffor an iOS application running with PID 2828, list various open files as shown below.
ptracesystem call to be as powerful as you're used to but, for some reason, Apple decided to leave it incomplete. iOS debuggers such as LLDB use it for attaching, stepping or continuing the process but they cannot use it to read or write memory (all
PT_WRITE*requests are missing). Instead, they have to obtain a so-called Mach task port (by calling
task_for_pidwith the target process ID) and then use the Mach IPC interface API functions to perform actions such as suspending the target process and reading/writing register states (
thread_set_state) and virtual memory (
For more information you can refer to the LLVM project in GitHub which contains the source code for LLDB as well as Chapter 5 and 13 from "Mac OS X and iOS Internals: To the Apple's Core" [#levin] and Chapter 4 "Tracing and Debugging" from "The Mac Hacker's Handbook" [#miller].
task_for_pid-allowentitlement must be added to the debugserver executable so that the debugger process can call
task_for_pidto obtain the target Mach task port as seen before. An easy way to do this is to add the entitlement to the debugserver binary shipped with Xcode.
/usr/bin/directory on the mounted volume. Copy it to a temporary directory, then create a file called
entitlements.plistwith the following content:
image listgives a list of main executable and all dependent libraries.
iOS is a modern operating system with multiple techniques implemented to mitigate code execution attacks, one such technique being Address Space Randomization Layout (ASLR). On every new execution of an application, a random ASLR shift offset is generated, and various process' data structures are shifted by this offset.
hiddenflag set. In the disassembly, the text value of this label is stored in register
X21, stored via
X0, at offset 0x100004520. This is our breakpoint offset.
image list -o -f. The output is shown in the screenshot below.
In the above output, you may also notice that many of the paths listed as images do not point to the file system on the iOS device. Instead, they point to a certain location on the host computer on which LLDB is running. These images are system libraries for which debug symbols are available on the host computer to aid in application development and debugging (as part of the Xcode iOS SDK). Therefore, you may set breakpoints to these libraries directly by using function names.
X0contains the hidden string, thus let's explore it. In LLDB you can print Objective-C objects using the
po(print object) command.
frida-trace, a function tracing tool.
frida-traceaccepts Objective-C methods via the
-mflag. You can pass it wildcards as well-given
-[NSURL *], for example,
frida-tracewill automatically install hooks on all
NSURLclass selectors. We'll use this to get a rough idea about which library functions Safari calls when the user opens a URL.
frida-traceconsole. Note that the
initWithURL:method is called to initialize a new URL request object.
frida-traceCLI as well. For example, you can trace calls to the
openfunction by running the following command:
ftraceavailable to trace syscalls or function calls of an iOS app. Only
DTraceexists, which is a very powerful and versatile tracing tool, but it's only available for MacOS and not for iOS.
__textsection (which contains the instructions) we also need to load the
lipo -thin arm64 <app_binary> -output uncrackable.arm64(ARMv7 can be used as well).
__datasection from the binary.
__datasection from the Mach-O binary we will use LIEF, which provides a convenient abstraction to manipulate multiple executable file formats. Before loading these sections to memory, we need to determine their base addresses, e.g. by using Ghidra, Radare2 or IDA Pro.
__textand 0x10000d3e8 for
__datasection to load them at in the memory.
While allocating memory for Unicorn, the memory addresses should be 4k page aligned and also the allocated size should be a multiple of 1024.
You may notice that there is an additional memory allocation at address 0x0, this is a simple hack around
stack_chk_guardcheck. Without this, there will be a invalid memory read error and binary cannot be executed. With this hack, the program will access the value at 0x0 and use it for the
The Mach-O backend in Angr is not well-supported, but it works perfectly fine for our case.
0x1000080d4was identified as the final target which contains the secret string.
lipo -thin arm64 <app_binary> -output uncrackable.arm64(ARMv7 can be used as well).
Projectby loading the above binary.
callableobject by passing the address of the function to be executed. From the Angr documentation: "A Callable is a representation of a function in the binary that can be interacted with like a native python function.".
callableobject to the concrete execution engine, which in this case is
FridaGadget.dylib. Download it first:
get-task-allowentitlement enabled. This entitlement allows other processes (like a debugger) to attach to the app. Xcode is not adding the
get-task-allowentitlement in a distribution provisioning profile; it is only whitelisted and added in a development provisioning profile.
get-task-allowentitlement. How to re-sign an application is discussed in the next section.
FridaGadget.dylib) with the certificate listed in the profile.
Info.plistmatches the one specified in the profile because the codesign tool will read the Bundle ID from
Info.plistduring signing; the wrong value will lead to an invalid signature.
security find-identity -v.
entitlements.plistis the file you created for your empty iOS project.
ipainstaller -lto list the applications installed on the device. Get the name of the target application from the output list.
ipainstaller -i [APP_NAME]to display information about the target application, including the installation and data folder locations.
Payload/[APP].app/main.jsbundleto a temporary file.
JStilleryto beautify and de-obfuscate the contents of the temporary file.
ObjCcommand can be used to access information within the running app. Within the
ObjCcommand the function
enumerateLoadedClasseslists the loaded classes for a given application.
ObjC.classes.<classname>.$ownMethodsthe methods declared in each class can be listed.
Processcommand. Within the
Processcommand the function
enumerateModuleslists the libraries loaded into the process memory.
Processcommand exposes multiple functions which can be explored as per needs. Some useful functions are
initWithURL:method and prints the URL passed to the method. The full script is below. Make sure you read the code and inline comments to understand what's going on.
\dm.. You'll find an example in the following section "In-Memory Search".
\ilto list them all:
0x0000000100b7c000and the Realm Framework at
\/?) to learn about the search command and get a list of options. The following shows only a subset of them:
\e~search. For example,
\e search.quiet=true;will print only the results and hide search progress:
sto there and retrieve the current memory region with
This time we run the
\dm.command for all
@@hits matching the glob
frida-gadget.soand re-signed. A detailed explanation of this process is in the section "Dynamic Analysis on Non-Jailbroken Devices. To use these tools on a jailbroken phone, simply have frida-server installed and running.
memory dump all.