Rethinking & Repackaging iOS Apps: Part 2

In the first part of our series, we looked at how to modify an iOS application binary by inserting load commands to inject custom dynamic libraries. In Part 2, we take this a step further by introducing a toolchain designed to make some of our favorite iOS application hacking tools available on non-jailbroken devices.

To facilitate this, we forked the fantastic Theos project by DHowett. For the uninitiated, Theos is basically a build environment that allows you to (among other things) easily write, build, and deploy Cydia Substrate tweaks for apps on jailbroken devices. Theos takes care of all the cross-compiling, linking, etc., leaving you free to write the code you want without any fuss.

Until now, Theos only worked on jailbroken devices because it requires additional frameworks and libraries to be installed on your iOS device, as well as requiring a modification to the operating system to inject dynamic libraries into applications at runtime. We have circumvented this by including all of the required frameworks and libraries; our approach also involves modifying application binaries instead of the operating system. Full details of how this was done will be released later, but until then, let’s concentrate on how to use this thing.

We’ll run through an example of how to patch an app by modifying the Trulia house-hunting app to spoof our location, so that our device always appears to be located inside the White House in Washington, D.C.

Getting Started

First, check out the code from GitHub. I installed it into ~/Projects/theos-jailed on my laptop. From there, create a new tweak just as you normally would with Theos:

carl@europa ~/Projects> ~/Projects/theos-jailed/bin/
NIC 2.0 - New Instance Creator
[1.] iphone/application
[2.] iphone/library
[3.] iphone/preference_bundle
[4.] iphone/tool
[5.] iphone/tweak
Choose a Template (required): 5
Project Name (required): Trulia
Package Name [com.yourcompany.trulia]: com.bishopfox.trulia
Author/Maintainer Name [Carl]:
[iphone/tweak] MobileSubstrate Bundle filter []:
[iphone/tweak] List of applications to terminate upon installation (space-separated, '-' for none) [SpringBoard]:
Instantiating iphone/tweak in trulia/...

Upon completion, Theos-jailed creates a Trulia directory inside the current directory, which in this case is ~/Projects/trulia.

Extracting the App

To extract the Trulia app from the iPhone it’s installed on, we use the iFunBox application. Right click on Trulia, then choose “Backup to .ipa package."


We save the .ipa file in ~/Projects/trulia and rename it trulia.ipa.
carl@europa ~/Projects> cd trulia/
carl@europa ~/P/trulia> ls -l
total 68720
drwxr-xr-x 4 carl staff 136 Mar 25 22:37 Cycript.framework
-rw-r--r-- 1 carl staff 253 Mar 25 22:37 Makefile
drwxr-xr-x 4 carl staff 136 Mar 25 22:37 PatchApp
-rw-r--r-- 1 carl staff 57 Mar 25 22:37 Trulia.plist
-rw-r--r-- 1 carl staff 1703 Mar 25 22:37 Tweak.xm
-rw-r--r-- 1 carl staff 35148819 Mar 25 22:39 [Trulia][].ipa
-rw-r--r-- 1 carl staff 203 Mar 25 22:37 control
drwxr-xr-x 6 carl staff 204 Mar 25 22:37 fishhook
-rwxr-xr-x 1 carl staff 10718 Mar 25 22:37
lrwxr-xr-x 1 carl staff 33 Mar 25 22:37 theos -> /Users/carl/Projects/theos-jailed
carl@europa ~/P/trulia> mv [Trulia\]\[\].ipa trulia.ipa

Uninstalling the App

Due to namespace collisions, you can’t reinstall a patched app over the original app. You have to uninstall the original first. You can either do this on your device or do it with iFunBox, as shown below:


Decrypting the App

Apps are encrypted on Apple devices. To patch an app, it is necessary to decrypt it; however, this step can – at present – only be done on a jailbroken device. Thus, we copy the Trulia .ipa file to a jailbroken iPad Mini; again, we use iFunBox to do this:

Hit “Install App” and choose the .ipa file you previously saved. In a few seconds, the app should be installed on the jailbroken device. Ensure that you have installed the Clutch tool, which we will use for decrypting the Trulia app. Now, SSH into your device and run clutch Trulia to decrypt Trulia and save a new .ipa file containing the decrypted app ( Note: We’re using port redirection to connect to the ipad:22):
carl@europa ~/P/trulia> ssh root@localhost -p 33333
Kraken:~ root# clutch Trulia

DEBUG | Cracker.m:360 | Saved cracked app info!
elapsed time: 6.96s

Applications cracked:


Total success: 1 Total failed: 0

Let’s rename it to something more user-friendly:

Kraken:~ root# cd /User/Documents/Cracked/
Kraken:/User/Documents/Cracked root# mv Trulia-v416-Foo-(Clutch-1.4.7).ipa trulia-cracked.ipa

Finally, back on our laptop, we copy the decrypted .ipa from the jailbroken iPad into our ~/Projects/trulia/ folder:

carl@europa ~/P/trulia> scp -P 33333 "root@localhost:/User/Documents/Cracked/trulia-cracked.ipa" ./trulia.ipa

Success! We now have a fully decrypted, hackable version of the Trulia app in trulia.ipa.

Writing a Tweak

Remember: Our intention is to spoof our location to the Trulia app. To do this, we have to hook/swizzle some of the Apple CLLocationManager framework methods, specifically the class that implements the CLLocationManagerDelegate protocol. The process of doing this is exactly the same for Theos-jailed as it is for a regular Theos tweak – you can use %hook, %end, %ctor, etc. as normal.

We open up ~/Projects/trulia/Tweak.xm and insert this code:

1.	#import <dlfcn.h>
2. #import "Cycript.framework/headers/cycript.h"
3. #import <CoreLocation/CoreLocation.h>
4. #include "fishhook/fishhook.h"
5. #include <objc/message.h>
7. #define CYCRIPT_PORT 31337
8. #define SPOOFED_ALTITUDE 0.00
9. #define SPOOFED_LATITUDE 38.89876
10. #define SPOOFED_LONGITUDE -77.036679 // The Whitehouse
12. %ctor {
13. // Start Cycript
14. CYListenServer(31337);
15. NSLog(@"Injected into Trulia :)");
16. }
18. void spoof_updateLocations(id cls, SEL selector, CLLocationManager *locationManager, NSArray *locations) {
19. static CLLocationCoordinate2D spoofedCoords;
21. spoofedCoords.latitude=SPOOFED_LATITUDE;
22. spoofedCoords.longitude=SPOOFED_LONGITUDE;
24. CLLocation *realLocation = [locations lastObject];
25. CLLocation *spoofedLocation = [[CLLocation alloc] initWithCoordinate:spoofedCoords
27. horizontalAccuracy:[realLocation horizontalAccuracy]
28. verticalAccuracy:[realLocation verticalAccuracy]
29. course:[realLocation course]
30. speed:[realLocation speed]
31. timestamp:[realLocation timestamp]];
32. if([cls respondsToSelector:@selector(locationManager:oldDidUpdateLocations:)]) {
33. [cls performSelector:@selector(locationManager:oldDidUpdateLocations:) withObject:locationManager withObject:@[spoofedLocation]];
34. }
35. }
37. %hook CLLocationManager
38. -(void)setDelegate:(id)delegate {
39. IMP old_updateLocationsMethod = class_replaceMethod(object_getClass(delegate), @selector(locationManager:didUpdateLocations:), (IMP)spoof_updateLocations, "@:@@");
40. class_addMethod(object_getClass(delegate), @selector(locationManager:oldDidUpdateLocations:), old_updateLocationsMethod, "@:@@");
41. %orig;
42. }
43. %end

The code replaces –[CLLocationManager setDelegate:] with a new version that ensures all callbacks to the delegate method locationManager:didUpdateLocations: are intercepted, modified with new coordinates, and then passed to the original delegate method.

To build the tweak, simply run “make:"

carl@europa ~/P/trulia> make
/Users/carl/Projects/trulia/theos/makefiles/targets/Darwin/ Deploying to iOS 3.0 while building for 6.0 will generate armv7-only binaries.
Making all for tweak Trulia...
Preprocessing Tweak.xm...
Compiling Tweak.xm...
Compiling fishhook/fishhook.c...
Linking tweak Trulia...
Stripping Trulia...
Signing Trulia...

Upon completion, we will have a new shared library called Trulia.dylib that we will eventually inject into the Trulia app.

But first, we must prepare our provisioning profile.

Preparing a Provisioning Profile – The App ID

As any iOS developer knows, installing an app onto an iOS device requires a matching provisioning profile that contains a lot of information pertaining to developer certificates, app entitlements, etc. It can get pretty hairy, but we’ve tried to make this part of the process as easy as possible.

First, you need to know the correct entitlements for the Trulia app. Entitlements are essentially permissions given to the app by you, the developer. You can grant entitlements for things like Apple Pay, push notifications, etc. Our version of Theos-patched comes with a tool that will tell you the entitlements required to repackage any given app. To run it, just do this:

carl@europa ~/P/trulia> ./ info trulia.ipa
[+] Unpacking the .ipa file (/Users/carl/Projects/trulia/trulia.ipa)...

= Summary =
Do all of the things mentioned under "What to do now", above.
Make sure that the provisioning profile contains the correct entitlements (see above).
Once you've installed the provisioning profile on your device, run the following command:
./ patch trulia.ipa /path/to/your/file.mobileprovision

Bundle ID: com.trulia.Trulia-patched
Required Entitlements:

In this case, there are no special entitlements required for Trulia to operate. You’ll also see a “Bundle ID." This is important and you’ll need it later. For now though, sign into the Apple Developer Member Center and select “Certificates, Identifiers & Profiles”:

From here, select “Identifiers:"

You need to add a new App ID. When prompted for the “Bundle ID,” enter the value provided by the “ info …” command. In this case, it’s “com.trulia.Trulia-patched:"


You’ll be prompted to enter “App Services." These are actually the entitlements mentioned earlier. In the case of Trulia, we don’t need any additional entitlements, so it’s safe to leave all of these options unchecked:


Follow the wizard to the end. You’ll get there eventually; you can see I called this App ID “Trulia Patched:"



Preparing a Provisioning Profile: The Profile

Once you’ve generated an App ID, you can generate a matching provisioning profile. Select the menu option to do so, then add a new profile:

You’re going to use an “iOS App Development” profile:

You’ll be asked to choose a corresponding “App ID." Choose the one you just created:

You’ll be asked to select the certificates used for signing. Choose your cert – I only have one:


You’ll also be asked to select the devices to which you’d like to install the provisioning profile. I chose all of mine – seven in total. Finally, you’ll be asked to give the profile a name. I called mine “Trulia Patched Profile:"


Great success! It worked:


Hit “Download” and copy the .mobileprovision file into your project folder, which in our case is ~/Projects/trulia/:

Now, we’re ready to install it on a device.

Installing the Provisioning Profile

You can use Xcode to install a provisioning profile onto your non-jailbroken device. Go to Window/Devices and right click on your device:

You’ll be shown a list of profiles already installed on the device. Click the + button to add your newly generated profile:

Choose the Trulia .mobileprovision file and it will be installed for you:


Patching the IPA File

Go ahead and inject the Trulia.dylib file (and accompanying libraries) into the Trulia app. The tool automates the entire process. Simply run:

carl@europa ~/P/trulia> ./ patch trulia.ipa Trulia_Patched_Profile.mobileprovision
[+] Unpacking the .ipa file (/Users/carl/Projects/trulia/trulia.ipa)...
[+] Copying .dylib dependences into ".patchapp.cache/Payload/"
[+] Codesigning .dylib dependencies with certificate "iPhone Developer: CARL LIVITT (XXXXXXXXXX)"
[+] Patching ".patchapp.cache/Payload/" to load "Trulia.dylib"
[+] Generating entitlements.xml for distribution ID XXXXXXXXXX
[+] Codesigning Plugins and Frameworks with certificate "iPhone Developer: CARL LIVITT (XXXXXXXXXX)"
ls: .patchapp.cache/Payload/*/com.*: No such file or directory
ls: .patchapp.cache/Payload/*: No such file or directory
ls: .patchapp.cache/Payload/*: No such file or directory
[+] Codesigning the patched .app bundle with certificate "iPhone Developer: CARL LIVITT (XXXXXXXXXX)" replacing existing signature
[+] Repacking the .ipa
[+] Wrote "trulia-patched.ipa"
[+] Great success!

That’s it! The tool inserts the relevant load command, adds your Cydia Substrate tweak, performs code signing using your developer certificate, and rebuilds the .ipa file as trulia-patched.ipa.

Installing the Patched App

The last step is to use Xcode to install the patched app back onto the iOS device. For this, we use the device manager again:

When prompted, choose the newly generated trulia-patched.ipa file. Xcode will install it onto the device. You should see an entry like this in the Xcode console when the installation is finished:
Mar 25 23:23:49 iPhwn SpringBoard[43] <Warning>: Installed apps did change.
Added: {(
Removed: {(
Modified: {(

The devices window will also show that Trulia is installed:


You should now be able to launch your app. If it crashes and you see this in the Xcode console, it means you forgot to decrypt the app using Clutch:

kernel[0] &lt;Notice&gt;: AppleFairplayTextCrypterSession::fairplayOpen() failed, error -42022

Just go back to the beginning and use Clutch to decrypt the app using a jailbroken device.

Testing the Spoofed Location

Everything should happen like magic. If the patch was successful, this is where Trulia will think it is:


Taking It Further: Cycript

We promised Cycript, and it’s enabled by default in Theos-jailed. Any time you run your app, a Cycript server is automatically instantiated on port 31337/TCP of the device. To connect to it, you will need the Cycript tools installed on your MacBook (download them here). After that:

carl@europa ~/P/trulia> ~/bin/cycript -r
cy# UIApp
#"<UIApplication: 0x146833e0>"

Here you see us passing the -r option to Cycript, which specifies the IP address of the target iPhone and port 31337. From here, Cycript can be used as normal. For example, let’s enumerate the view controllers on screen:

cy# ?expand
expand == true
cy# UIApp.keyWindow.recursiveDescription

<UILabel: 0x1856ba30; frame = (28.75 6.74365; 142.5 24.5127); text = 'Washington, DC'; autoresize = W; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x1856bc00>>

The output has been truncated for readability, but above we can see the UILabel object corresponding to the following label on the app’s UI:


We can easily change it:

cy# label = new Instance(0x1856ba30)
#"<UILabel: 0x1856ba30; frame = (28.75 6.74365; 142.5 24.5127); text = 'Washington, DC'; autoresize = W; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x1856bc00>>"
cy# [label setText:@"Bishop Fox!"]

The change is immediately reflected in the UI:

All of your normal Cycript tricks should work just fine. Have fun!

That’s All (For Now) Folks!

There’s a lot going on under the hood to make this happen. We also implemented the Fishhook library to dynamically rebind function symbols within iOS apps. This gives us functionality equivalent to MSHookFunction. More documentation will follow on that soon.