Patching and Re-Signing iOS Apps

Running modifed iOS binaries on non-jailbroken devices can sometimes be a desirable proposition - especially if you just bricked your last jailbroken iPhone and were forced to update to a non-jailbroken version of iOS (this never happened to me or anyone I know). For example, you can use this technique to instrument the app for dynamic analysis. Or perhaps you need to do some GPS spoofing to catch region-locked Pokemon in Africa, but are worried about being caught in the act. Either way, you can use the following process to re-sign a modified app and run it on your own non-jailbroken device. Note that this technique works only on if the binary isn't FairPlay-encrypted (i.e. obtained from the app store).

Thanks to Apple's confusing provisioning and code signing system, re-singing an app is more challenging than one would expect. iOS will refuse to run an app unless you get the provisioning profile and code signature header absolutely right. This requires you to learn about a whole lot of concepts - certificates, BundleIDs, application IDs, team identifiers, and how they are tied together using Apple's build tools. Suffice it to say, getting the OS to run a particular binary that hasn't been built using the default way (XCode) can be an daunting process.

The toolset we're going to use consists of optool, Apple's build tools and some shell commands. This method is inspired by the resign script from Vincent Tan's Swizzler project. An alternative way of repackaging using different tools was described by NCC group.

To reproduce the steps listed below, download UnCrackable iOS App Level 1 from the OWASP Mobile Testing Guide repo. Our goal is to make the UnCrackable app load FridaGadget.dylib during startup so we can instrument it using Frida.

Getting a Developer Provisioning Profile and Certificate

The provisioning profile is a plist file signed by Apple that whitelists your code signing certificate on one or multiple devices. In other words, this is Apple explicitly allowing your app to run in certain contexts, such as debugging on selected devices. The provisioning profile also lists the entitlements granted to your app. The code signing certificate contains the private key you'll use to do the actual signing.

Depending on whether you're registered as an iOS developer, you can use one of the following two ways to obtain a certificate and provisioning profile.

With an iOS developer account:

If you have developed and deployed apps iOS using Xcode before, you'll already own a code signing certificate. Use the security tool to list your existing signing identities:

$ security find-identity -p codesigning -v
1) 61FA3547E0AF42A11E233F6A2B255E6B6AF262CE "iPhone Distribution: Vantage Point Security Pte. Ltd."
2) 8004380F331DCA22CC1B47FB1A805890AE41C938 "iPhone Developer: Bernhard Müller (RV852WND79)"

 

Registered developers can obtain provisioning profiles from the Apple Developer Portal. This involves first creating a new App ID, and then issuing a provisioning profile allowing that App ID to run on your device(s). For repackaging purposes, it doesn't really matter what App ID you choose - you can even re-use an existing one. The important thing is to have a matching provisioning profile. Make sure you create a development provisioning profile and not a distribution profile, as you'll want to be able to attach a debugger to the app.

In the shell commands listed below I'm using my own signing identity which is associated with my company's development team. I created the app-id "sg.vp.repackaged", as well as a provisioning profile aptly named "AwesomeRepackaging", which yielded a file named "AwesomeRepackaging.mobileprovision" - exchange this with your own id/filename as needed.

With a regular iTunes account:

Mercifully, Apple will issue a free development provisioning profile to you even if you're not a paying developer. You can obtain the profile with Xcode using your regular Apple account - simply build an empty iOS project and extract embedded.mobileprovision from the app container. The NCC blog entry explains this process in great detail.

Once you have obtained the provisioning profile, you can check its contents with the security tool. Besides the allowed certificates and devices, you'll find the entitlements granted to the app in the profile. You'll need those later for code signing, so extract them to a separate plist file as shown below. It is also worth having a look at the contents of the file to check if everything looks as expected.

$ security cms -D -i AwesomeRepackaging.mobileprovision > profile.plist
$ /usr/libexec/PlistBuddy -x -c 'Print :Entitlements' profile.plist > entitlements.plist
$ cat entitlements.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>application-identifier</key>
<string>LRUD9L355Y.sg.vantagepoint.repackage</string>
<key>com.apple.developer.team-identifier</key>
<string>LRUD9L355Y</string>
<key>get-task-allow</key>
<true/>
<key>keychain-access-groups</key>
<array>
<string>LRUD9L355Y.*</string>
</array>
</dict>
</plist>

 

Note the application identitifier (App ID), which is a combination of the Team ID (LRUD9L355Y) and Bundle ID (sg.vantagepoint.repackage). This provisioning profile is only valid for the one app with this particular app id. The "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).

Other Preparations

To make our app load an additional library at startup we need some way of inserting an additional load command into the Mach-O header of the main executable. We'll be using optool to automate this process:

$ git clone https://github.com/alexzielenski/optool.git
$ cd optool/
$ git submodule update --init --recursive

 

We'll also use ios-deploy, a tool that enables deploying and debugging of iOS apps without using Xcode (if you don't have node.js yet, you can install it with homebrew).

$ npm install -g ios-deploy

 

To follow the examples below, you also need FridaGadget.dylib:

$ curl -O https://build.frida.re/frida/ios/lib/FridaGadget.dylib

 

Besides the tools listed above, we'll be using standard tools that come with OS X and Xcode (make sure you have the Xcode command line developer tools installed).

Patching, Repackaging and Re-Signing

Time to get serious! As you surely now, IPA files are actually ZIP archives, so use any zip tool to unpack the archive. Then, copy FridaGadget.dylib into the app directory, and add a load command to the "UnCrackable Level 1" binary using optool.

$ unzip UnCrackable_Level1.ipa
$ cp FridaGadget.dylib Payload/UnCrackable\ Level\ 1.app/
$ optool install -c load -p "@executable_path/FridaGadget.dylib" -t Payload/UnCrackable\ Level\ 1.app/UnCrackable\ Level\ 1
Found FAT Header
Found thin header...
Found thin header...
Inserting a LC_LOAD_DYLIB command for architecture: arm
Successfully inserted a LC_LOAD_DYLIB command for arm
Inserting a LC_LOAD_DYLIB command for architecture: arm64
Successfully inserted a LC_LOAD_DYLIB command for arm64
Writing executable to Payload/UnCrackable Level 1.app/UnCrackable Level 1...

 

Such blatant tampering of course invalidates the the code signature of the main executable, so this won't run on a non-jailbroken device. You'll need to replace the provisioning profile and sign both the main executable and FridaGadget.dylib with the certificate listed in the profile.

First, we add our own provisioning profile to the package:

$ cp AwesomeRepackaging.mobileprovision Payload/UnCrackable\ Level\ 1.app/embedded.mobileprovision\

 

Next, we need to make sure that the BundleID in Info.plist matches the one specified in the profile. The reason for this is that codesign will read the Bundle ID from Info.plist during signing - a wrong value will result in an invalid signature.

$ /usr/libexec/PlistBuddy -c "Set :CFBundleIdentifier sg.vantagepoint.repackage" Payload/UnCrackable\ Level\ 1.app/Info.plist

 

Finally, we use the codesign tool to re-sign both binaries:

$ rm -rf Payload/F/_CodeSignature
$ /usr/bin/codesign --force --sign 8004380F331DCA22CC1B47FB1A805890AE41C938 Payload/UnCrackable\ Level\ 1.app/FridaGadget.dylib
Payload/UnCrackable Level 1.app/FridaGadget.dylib: replacing existing signature
$ /usr/bin/codesign --force --sign 8004380F331DCA22CC1B47FB1A805890AE41C938 --entitlements entitlements.plist Payload/UnCrackable\ Level\ 1.app/UnCrackable\ Level\ 1
Payload/UnCrackable Level 1.app/UnCrackable Level 1: replacing existing signature

 

Installing and Running the App

Now you should be all set for running the modified app. Deploy and run the app on the device as follows.

$ ios-deploy --debug --bundle Payload/UnCrackable\ Level\ 1.app/

 

If everything went well, the app should launch on the device in debugging mode with lldb attached. Frida should now be able to attach to the app as well. You can verify this with the frida-ps command:

$ frida-ps -U
PID Name
--- ------
499 Gadget

 

You can now use Frida to instrument the app as usual. Perhaps it is not as "uncrackable" as its name implies?

Troubleshooting

If something goes wrong (which it usually does), mismatches between the provisioning profile and code signing header are the most likely suspect. In that case it is helpful to read the official documentation and gaining an understanding of how the whole system works. I also found Apple's entitlement troubleshooting page to be a useful resource.

About this Article

This article is part of the Mobile Reversing Shaken, not Stirred series. Click the blue label on the top of this page to list orther articles in this series.

About the OWASP Mobile Security Testing Guide

I wrote this howto for the OWASP Mobile Security Testing Guide (MSTG), a manual for testing the security of mobile apps. The MSTG is an open source effort and we welcome contributions and feedback. To discuss and contribute, join the OWASP Mobile Security Project Slack Channel. You can sign up here:

http://owasp.herokuapp.com/

About the Author

Bernhard Mueller is a full-stack hacker, security researcher, and winner of BlackHat's Pwnie Award.

Follow him on Twitter: @muellerberndt