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_SHA256
function, 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".CBCentralManager
or 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.
_objc_msgSend
or _objc_release
are specially meaningful for the Objective-C runtime.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.
buttonClick
function of the ViewController
class. We will look into the buttonClick
function 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.buttonClick
function 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.AppDelegate
class, 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?ViewController
class in the Symbol Tree view. In this class, function viewDidLoad
function looks interesting. If you check the documentation of viewDidLoad
, you can see that it can also be used to perform additional initialization on views.setHidden
flag 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.buttonClick
function 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 uVar1
at 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.viewDidLoad
function, and understand what is happening in the native function identified. Analysis of the native function is discussed in "Reviewing Disassembled Native Code".setText
, isEqualStrings
can 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.viewDidLoad
function in the previous section. The function is located at offset 0x1000080d4. The return value of this function used in the setText
function 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.bl
is used to call the function at 0x100008158.strb
stores 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.dylib
library. 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.dylib
during 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.mobileprovision
from 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-allow
key 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 false
in a distribution profile).lsof
is 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 lsof
command 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.lsof
for an iOS application running with PID 2828, list various open files as shown below.list_frameworks
command in objection to list all the application's bundles that represent Frameworks.ptrace
system 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_READ_*
and PT_WRITE*
requests are missing). Instead, they have to obtain a so-called Mach task port (by calling task_for_pid
with 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_get_state
/thread_set_state
) and virtual memory (mach_vm_read
/mach_vm_write
).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-allow
entitlement must be added to the debugserver executable so that the debugger process can call task_for_pid
to 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.plist
with the following content:image list
gives 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.
hidden
flag set. In the disassembly, the text value of this label is stored in register X21
, stored via mov
from X0
, at offset 0x100004520. This is our breakpoint offset.image list -o -f
. The output is shown in the screenshot below.b 0x100074520
.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.
X0
contains 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-trace
accepts Objective-C methods via the -m
flag. You can pass it wildcards as well-given -[NSURL *]
, for example, frida-trace
will automatically install hooks on all NSURL
class selectors. We'll use this to get a rough idea about which library functions Safari calls when the user opens a URL.frida-trace
as follows:frida-trace
console. Note that the initWithURL:
method is called to initialize a new URL request object.frida-trace
CLI as well. For example, you can trace calls to the open
function by running the following command:strace
or ftrace
available to trace syscalls or function calls of an iOS app. Only DTrace
exists, which is a very powerful and versatile tracing tool, but it's only available for MacOS and not for iOS.__data
section.__text
section (which contains the instructions) we also need to load the __data
section.lipo -thin arm64 <app_binary> -output uncrackable.arm64
(ARMv7 can be used as well).__text
and __data
section from the binary.__text
and __data
section.__text
and __data
section 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.__text
and 0x10000d3e8 for __data
section 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 aroundstack_chk_guard
check. 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 thestack_chk_guard
check.
The Mach-O backend in Angr is not well-supported, but it works perfectly fine for our case.
0x1000080d4
was identified as the final target which contains the secret string.lipo -thin arm64 <app_binary> -output uncrackable.arm64
(ARMv7 can be used as well).Project
by loading the above binary.callable
object 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.".callable
object to the concrete execution engine, which in this case is claripy.backends.concrete
.FridaGadget.dylib
. Download it first:FridaGadget.dylib
into the app directory and use optool to add a load command to the "UnCrackable Level 1" binary.get-task-allow
entitlement enabled. This entitlement allows other processes (like a debugger) to attach to the app. Xcode is not adding the get-task-allow
entitlement in a distribution provisioning profile; it is only whitelisted and added in a development provisioning profile.get-task-allow
entitlement. How to re-sign an application is discussed in the next section.FridaGadget.dylib
) with the certificate listed in the profile.Info.plist
matches the one specified in the profile because the codesign tool will read the Bundle ID from Info.plist
during signing; the wrong value will lead to an invalid signature.security find-identity -v
.entitlements.plist
is the file you created for your empty iOS project.Payload/[APP].app/main.jsbundle
. This file contains the JavaScript code. Most of the time, the JavaScript code in this file is minified. With the tool JStillery, a human-readable version of the file can be retried, which will allow code analysis. The CLI version of JStillery and the local server are preferable to the online version because the latter discloses the source code to a third party./private/var/containers/Bundle/Application/[GUID]/[APP].app
from iOS 10 onward, so the main JavaScript application file can be modified at this location.ipainstaller -l
to 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.Application:
.Payload/[APP].app/main.jsbundle
to a temporary file.JStillery
to beautify and de-obfuscate the contents of the temporary file.Payload/[APP].app/main.jsbundle
file.ObjC
command can be used to access information within the running app. Within the ObjC
command the function enumerateLoadedClasses
lists the loaded classes for a given application.ObjC.classes.<classname>.$ownMethods
the methods declared in each class can be listed.Process
command. Within the Process
command the function enumerateModules
lists the libraries loaded into the process memory.Process
command exposes multiple functions which can be explored as per needs. Some useful functions are findModuleByAddress
, findModuleByName
and enumerateRanges
besides others.initWithURL:
method is called to initialize a new URL request object. We can look up the declaration of this method on the Apple Developer Website: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
:\dm.
. You'll find an example in the following section "In-Memory Search".\il
to list them all: