How I (Try To) Reverse Engineer iOS Apps

For a lot of my time as an iOS developer I would see prominent people in the industry sharing details about how the internals of UIKit or other parts of the operating system worked, and it always seemed like magic. The idea that I would be able to do this myself seemed unreachable.

Recently I wanted to investigate an app released by my regional government to verify a hunch about how it worked. Now is as good a time as any to write up the tools that I’ve since learned to use and the steps I take to do this. I do this infrequently enough that having this post is as valuable for me as anyone else.

I’m by no means a reverse engineering or information security expert, and I still feel like I’m just bashing rocks together trying to make fire, but I hope that this serves as a jumping off point to better resources and a nudge for anybody that thinks they can’t do this themselves. You don’t have to be a fancy hacker to do these things, and I think knowing these techniques can make you a better app developer.

Tools

HTTP proxy that can MITM TLS connections

I use Charles Proxy. This will let you see what an app is uploading and downloading from an API. Depending how much of an apps behaviour depends on its API this might get you as far as you need to go.

Jailbroken iOS device

I use my previous carry phone that tops out at iOS 12, which works with Unc0ver. There are options to jailbreak devices running iOS 13 now, though. A lot of reverse engineering will depend on this because it gives you access and control you can’t replicate another way. If you’ve ever accidentally tried and failed to debug your own app when it was built for distribution then you’ve hit one of iOS’s restrictions (the get_task_allow entitlement) that can be worked around on a jailbroken device.

The really cool thing you can do with a jailbroken device is getting a decrypted copy of an App Store app on to your computer for inspection. These are encrypted by the App Store with FairPlay before they’re sent to your device when you install them, and they’re only decrypted when you launch the application. For this you’ll have to install Frida and OpenSSH.

class-dump

I use class-dump or dsdump. These can generate lists of types and their methods for Objective-C or Swift binaries, which end up looking kinda like header files or generated interfaces. This can give you a lot of clues about how the app is implemented and architected. Have a hunch that someone used an open-source project without giving credit?

Disassembler and Decompiler

I use Hopper. This takes a binary file and disassembles it (turns it from 0s and 1s into assembly) and decompiles it (turns it from assembly into pseudo-code.) Because of how Objective-C works, binaries written in it tend to result in pseudo-code that looks realistic enough that it could almost be the original source. Swift doesn’t result in the same trail of breadcrumbs and it’s more difficult for me to read the pseudo-code as a result.

Steps I usually follow

  • Install the app on a jailbroken device
  • Use the HTTP proxy to inspect traffic while I use it. Once I know what domains it’s talking to, set up TLS proxying.
  • Dump the decrypted app to my computer using frida-ios-dump
    • I recently found Fuze, which looks like a polished solution but isn’t released yet.
  • Look in the app bundle. Remember that you’ll get an .ipa file which is actually a .zip.
    • What assets does it include? Anything that they didn’t intend to ship to the App Store?
    • What frameworks are bundled?
    • What entitlements does it have?
  • class-dump the app and framework binaries
  • Load the binaries in your decompiler
    • Search for keywords and URLs related to important functionality
    • Trace function calls in pseudocode view

Things I Don’t Know How To Do Yet

  • Read assembly code at all
  • Read disassembled swift code that well
  • Attach and use a debugger with a distribution app running on a jailbroken device

Some things I’ve used these techniques for

Hopper in particular has been used for some neat things, like:

  • UIStoryboardSegues can be configured to perform with or without animations in the .storyboard file. I was wondering how you might perform a particular segue and decide at runtime, programmatically, if it should animate, such as in response to user interaction and as part of routing a deep link. Its interface only has a perform() method. How does it work internally? I loaded UIKit.framework from the iOS Simulator into Hopper and found this:
UIView.setAnimationsEnabled(self.animated)
self.perform()
UIView.setAnimationsEnabled(previousValue)

We’d initially dismissed this solution as a bit of a hack when we saw it in a Stack Overflow answer to this problem, so this was funny to discover. * Investigating how Xcode Playgrounds worked a few years ago. * Day-job tasks like figuring out why a vendor’s poorly-supported framework that was only distributed as a binary was crashing.