π Quick Start with Frida to Reverse-Engineer Any iOS Application
How to start with reverse-engineering of iOS application using the Frida toolkit. Tracing network communication and filesystem requests of a third-party iOS application. Tips and tricks.
Quick Start
If you don't know what is Frida, skip the "Quick Start" and move forward to the next chapter: "What Frida Is".
Basically, you have to install frida-server
on a jailbroken iPhone and frida-tools
on a personal computer using instructions from the official site: https://www.frida.re/docs/installation/. Then follow these 3 steps:
1. Find the running application on the iOS device
$ frida-ps -U | grep -i targetapp
3688 TargetApp
You can actively use grep -i
, where -i
stands for case insensitive mode.
2. Start tracing the iOS process
$ frida-trace -i CCCrypt -U TargetApp
CCCrypt: Auto-generated handler: __handlers__/libSystem.B.dylib/CCCrypt.js
$ frida-trace -m "-[NSFileManager *System*]" -U 3688 # pid
fileSystemRepresentationWithPath: Auto-generated handler: __handlers__/__NSFileManager_getFileSystemRep_6756a454.js
-i
is to hook C function-m
is to hook Objective-C function-U
stands for "USB" (find device attached by USB)- You can use either pid or application name retrieved from the
frida-ps -U
output
3. Modify auto-generated JS scripts
# "code" stands for Visual Studio Code
$ code __handlers__\libSystem.B.dylib\CCCrypt.js
frida-trace
generates JavaScript templates which you should modify further. For example, __handlers__\libSystem.B.dylib\CCCrypt.js
has been generated. So you can open it and modify.
βοΈ What Frida Is
Frida is a dynamic code instrumentation toolkit. Frida allows you to write tweaks in JavaScript:
- to catch function invocation
- to print and modify incoming arguments
- to print and modify returns
- to inject your own code before and after function invocation
β What Frida Is Not
- Frida is not a debugger.
So you cannot modify executable code in runtime, pause process execution, change opcodes, etc. - Frida is not a disassembler.
Frida doesn't stand for a static analysis, it is about dynamic analysis.
How Frida is Implemented
- Frida injects Googleβs V8 engine into a targeted process.
- JS runs inside the targeted process (thanks to injected engine).
- You write JS tweaks which are injected into the targeted process.
Basic Workflow on iOS with Jailbreak
If one process needs to get attached to another in order to "trace one" or to get "injected into" - jailbreak is implied. Because it is all about superuser privileges.
- Take your iPhone. π±
- Take a 3rd party application. π
- Jailbreak your iPhone. π¨ (This is legal).
- Install Frida (server) to the jailbroken iPhone. π²
- Install Frida (client) to your computer. π»
- Trace a third party application with Frida command-line tools. π¨βπ»
6.1.frida-ps
6.2.frida-trace
- Trace functions, print data, tweak the execution on-the-fly. π±πΆ
There is also an option to use a dynamic linker feature like LD_PRELOAD or DYLD_INSERT_LIBRARIES to inject Fridaβs Gadget into your application. It needs superuser privileges (jailbreak).
Workflow on iOS Without a Jailbreak
Without Jailbreak you need to embed Fridaβs Gadget into your application. (See how to inject Frida Gadget in Android application).
Case 1. Your own application
Add a frida-gadget
shared library into your project. Then load the library. Frida server is bringed up in the address space of the process, and jailbreak is not needed in such case.
Case 2. A third party application
Patch the application binary or one of its libraries, e.g. by using a tool like insert_dylib.
- Step 1. Use insert_dylib.
- Step 2. Purchase a developer account.
- Step 3. Re-sign the application.
- Step 4. Install the application.
Want to Trace? What to Trace?
If you don't know where to start from, always start from a threat model. (See my other article about threat modeling of mobile application).
You can imagine application as a black box that interacts with the outer world via inputs and outputs. Ordinary mobile application has following simplified threat model. Β
Particularly, filesystem and network communication are pretty interesting interaction points which can give you a lot of useful information. These point are the
Trace Everything
$ frida-discover -U TargetApp
frida-discover
could be useful to get at least some idea where to start from. A small part of frida-discover
output:
libcommonCrypto.dylib
Calls Function
15 sub_bec0
15 sub_bee4
8 sub_96f8
8 sub_befc
3 CCDigest
Caveats
- Application UI is decently lugging while being traced, because
frida-discover
collects statistics about a huge amount of API. - Don't rely on
frida-discover
too much: often important calls are missed in the report, e.g.CCCrypt
in the example above. The report is a good thing to get some ideas where to start from, kind of "libcorecrypto.dylib is in the report therefore cryptography is used", but then you need to carefully research a particular scope.
Tracing Filesystem
- Print each file that have been open in runtime.
$ frida-trace -m "-[NSFileManager fileExistsAtPath:]" -U TargetApp
-[NSFileManager fileExistsAtPath:]: Loaded handler at ".\__handlers__\__NSFileManager_fileExistsAtPath__.js"
- Modify auto-generated tweak.
# Visual Studio Code
$ code __handlers__/__NSFileManager_fileExistsAtPath__.js
- Print the
path
.
args[2]
stands for the 1st argument NSString *path
of the fileExistsAtPath
. To print NSString
object as a string the JavaScript object args[2]
must be wrapped into Objective-C object: ObjC.Object(args[2])
.
- Re-run
frida-trace
again. You're going to see the following output:
11230 ms -[NSFileManager fileExistsAtPath:/var/.../somefile isDirectory:0x16b0f296f]
/* TID 0x7b03 */
11807 ms -[NSFileManager fileExistsAtPath:/var/.../SC_Info]
- What should you do next:
- Install
sshd
(SSH Daemon) on the iPhone. - Copy file to your computer via ssh:
$ scp root@192.168.77.102:/path/to/printed/file ./
Password: alpine
Tracing the Network (HTTP/ S)
NSURLSession
is widely used to communicate with a server (e.g. REST API) in iOS applications.
- Let's trace it.
$ frida-trace -m "-[NSURLSession dataTaskWithRequest*]" -U <pid/name>
It will generate 2 handlers: for a function with completionHandler
and without.
-[NSURLSession dataTaskWithRequest:]: Loaded handler at "__handlers__/__NSURLSession_dataTaskWithRequest__.js"
-[NSURLSession dataTaskWithRequest:completionHandler:]: Loaded handler at "__handlers__/__NSURLSession_dataTaskWithReque_bbc2edc2.js"
- Modify auto-generated tweaks.
- Re-run
frida-trace
again. Then you will see the following output:
/* TID 0xa933 */
7952 ms -[NSURLSession dataTaskWithRequest:0x2836ea5c0 completionHandler:0x16b17ebd8]
7952 ms --- URL:
7952 ms https://targetapp.com/v1/some/api
7952 ms --- headers:
7952 ms {
"Content-Type" = "application/octet-stream";
}
7952 ms --- body:
7952 ms <793d8f2f c103e547 ....>
7952 ms --- method:
7952 ms POST
Tracing Encryption
If you have any suspicion about encryption used inside the application to hide the contents (files, network protocol), try to catch CCCrypt
function, or anything else from CommonCrypto.h.
$ frida-trace -i CCCrypt -U TargetApp
Modify auto-generated tweak.
Decrypted/encrypted data appears in the onLeave
function.
You can see the trick here: arguments are not accessible in the onLeave
handler, and the way to have arguments there is to pass them via this
pointer.
this.args = args;
Other Tweaks
There is a decent amount of JS tweaks for Frida: https://github.com/iddoeldor/frida-snippets. You can take a look to get more insights what you can in your investigations.
Tips and Tricks
- In the most cases you don't know exactly function used in the traced application. I would suggest using wildcard instead of fully qualified prototype to catch several API functions.
Use wildcard
instead offrida-trace \ -m "-[NSURLSession dataTaskWithRequest*]" \ -U TargetApp
frida-trace \ -m "-[NSURLSession dataTaskWithRequest:]" \ -m "-[NSURLSession dataTaskWithRequest:completionHandler:]" \ -U TargetApp
- Don't forget that JavaScript tweak receives JavaScript objects as input. Therefore in order to access methods and fields of Obective-C object represented by JavaScript object the Objective-C wrapper must be used:
// Wrap var request = new ObjC.Object(args[2]); // Access request.toString(); request.URL().toString();
onLeave
callback doesn't have an access to the arguments ofonEnter
callback. Often a traced function returns data via the output parameters, as in a case ofCCCrypt
. In order to print a result when the function quits, you can pass arguments fromonEnter
toonLeave
viathis
pointer:onEnter: function (log, args, state) { this.args=args; }, onLeave: function (log, retval, state) { log(this.args[2].toInt32()); }