Rethinking & Repackaging iOS Apps: Part 1

View from below looking up at high rise purple buildings

Share

In October 2014, Jonathan Zdziarksi (“JZ”) wrote a blog post about a little-known feature of the iOS app ecosystem: it’s possible to patch App Store apps and redeploy them on to non-jailbroken devices. (You should probably read his post before reading this one.)

This is the first installment of a two-part series in which we will build on JZ’s work to present a more flexible, powerful means of modifying App Store apps on jailed iOS devices. To play along, you will need an Apple iOS account.

iOS Tools on Jailbroken Devices

We’re used to using our favorite tools like CydiaSubstrate and Theos that allow us to write runtime patches in Objective-C and apply them to apps on jailbroken devices. Another stalwart tool is Cycript, which allows us to explore and modify running applications on iOS.

Typically, iOS penetration testers will use jailbroken devices to install and run these tools against App Store apps. However, jailbroken devices aren’t actually necessary. This series will show you how to do the same work on jailed devices, and will provide full source code for a patched version of Theos that does the heavy lifting for you.

Part 1 – Dynamic Libraries

When reading JZ’s blog post, you’ll see that to patch an iOS app, it was necessary to crank up a disassembler, reverse-engineer the binary, and patch it in ARM assembly language before re-signing and re-installing the app. While effective, that technique is slow, cumbersome, and complex.

We wanted something more user-friendly, something where we could write Objective-C code and automatically load it into our App Store apps without disassembling anything. We wanted Theos for jailed devices — but let’s learn to walk before we run.

Without going into too much detail, every iOS app is a Mach-O binary, which contains a header containing “load commands.” These commands tell the dynamic linker (the software responsible for loading an app and the shared libraries which the app depends upon) what shared/dynamic libraries must be loaded before the app can run.

You can see the libraries required by an app by running the otool command:

carl@europa ~> otool -L FooApp

FooApp:

/usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 235.1.0)

/usr/lib/libiconv.2.dylib (compatibility version 7.0.0, current version 7.0.0)

/System/Library/Frameworks/CFNetwork.framework/CFNetwork (compatibility version 1.0.0, current version 711.0.6)

/usr/lib/libz.1.dylib (compatibility version 1.0.0, current version 1.2.5)

/System/Library/Frameworks/UIKit.framework/UIKit (compatibility version 1.0.0, current version 3318.0.0)

/System/Library/Frameworks/Security.framework/Security (compatibility version 1.0.0, current version 0.0.0)

/System/Library/Frameworks/QuartzCore.framework/QuartzCore (compatibility version 1.2.0, current version 1.10.0)

/System/Library/Frameworks/OpenGLES.framework/OpenGLES (compatibility version 1.0.0, current version 1.0.0)

/System/Library/Frameworks/Foundation.framework/Foundation (compatibility version 300.0.0, current version 1140.11.0)

/System/Library/Frameworks/CoreVideo.framework/CoreVideo (compatibility version 1.2.0, current version 1.8.0)

/System/Library/Frameworks/CoreMedia.framework/CoreMedia (compatibility version 1.0.0, current version 1.0.0)

/System/Library/Frameworks/CoreGraphics.framework/CoreGraphics (compatibility version 64.0.0, current version 600.0.0)

/System/Library/Frameworks/AVFoundation.framework/AVFoundation (compatibility version 1.0.0, current version 2.0.0)

/System/Library/Frameworks/AudioToolbox.framework/AudioToolbox (compatibility version 1.0.0, current version 492.0.0)

/usr/lib/libobjc.A.dylib (compatibility version 1.0.0, current version 228.0.0)

/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1213.0.0)

/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation (compatibility version 150.0.0, current version 1140.1.0)

You can see that the app loads the CFNetwork framework; here’s the corresponding load command shown in the Hopper disassembler:

CFNetwork framework- Hopper disassembler

Given this knowledge, all we need to do is:

  1. Create a custom shared library (.dylib)
  2. Patch the target app’s Mach-O header to insert a new load command for our .dylib
  3. Sign the app
  4. Redeploy the app to a jailed device

Creating a Custom Shared Library (.dylib)

This is easy. Take the following Objective-C code and save it as test.m:

Creating a Custom Shared Library - Test

Then compile it as an ARM .dylib:

clang -arch armv7 -isysroot $(xcodebuild -sdk iphoneos -version Path) -shared test.m -framework Foundation -o test.dylib

Voila! You now have test.dylib that will automatically call injected_function() whenever it is loaded by an iOS app.

Inserting a New Load Command into the Target App

Fortunately, we don’t have to modify the target app’s Mach-O load commands by hand. There’s a tool for that: optool.

Assuming you’ve already decrypted and copied the target app off a jailbroken device (you did read JZ’s blog post, right?), you can run optool to insert the new load command:

optool install -c load -p "@executable_path/test.dylib" -t /path/to/your/Application.app/binary

And it runs:

Found thin header...

Inserting a LC_LOAD_DYLIB command for architecture: arm

Successfully inserted a LC_LOAD_DYLIB command for arm

Writing executable to /path/to/your/Application.app/binary...

That’s it! The load command has been inserted into the app. Note the use of the magic value “@executable_path,” which tells the dynamic linker to look for the .dylib in the same directory as the app binary. You can now add your new .dylib to the app’s main directory:

cp test.dylib /path/to/your/Application.app/

Next, re-sign everything with your Apple developer certificate:

codesign -fs "iPhone Developer" /path/to/your/Application.app/test.dylib

codesign -fs "iPhone Developer" /path/to/your/Application.app

Finally, re-ZIP the .app folder, save the ZIP as an IPA, and publish it to your device using Xcode (see JZ’s blog for full instructions). The next time you run the app, it will automatically load test.dylib, which will run injected_function() from test.dylib. Text.dylib writes “Testing!!” to the system log before continuing to normally run the app.

In Part 2…

Now that we have discussed the essentials of adding dynamic libraries to App Store apps, we can refine the technique to make our favorite tools – CydiaSubstrate, Theos, and Cycript – work with jailed devices. We’ll cover:

  • Patching CydiaSubstrate to load and work on jailed devices
  • Patching Theos to generate .dylibs compatible with jailed devices
  • Adding functionality to enable function hooking on jailed devices
  • Embedding Cycript into apps on jailed devices
    • MSFunctionHook() doesn’t work on jailed devices because the kernel prohibits memory pages with PROT_EXEC|PROT_WRITE flags
    • We work around this by abusing Mach-O lazy binding to replace function pointers

Stay tuned!

NOTE: The source code for the final product is available here; you can also see the second installment of this series for further details.

Please note that it’s a work-in-progress. We welcome any bug reports and patches.

Subscribe to Bishop Fox's Security Blog

Be first to learn about latest tools, advisories, and findings.


Carl Livitt

About the author, Carl Livitt

Bishop Fox Alumnus

Carl Livitt is a Bishop Fox alumnus. He was a Principal Researcher at Bishop Fox with decades of experience in mobile and application security, hardware and embedded devices, reverse engineering, and global-scale penetration testing.

Carl is credited with the discovery of many vulnerabilities within both commercial and open-source software. He was brought in as a third-party expert to lead the team that confirmed several security issues with St. Jude Medical implantable devices. His work eventually led to an official communication from the FDA.

Carl has served as a contributing author to Hacking Exposed Web Applications 3rd Edition as well as a technical advisor for Network Security Assessment 1st Edition. He has been interviewed on NPR and quoted in publications including USA Today and eWeek. Carl co-authored the iOS reverse engineering framework iSpy, which was featured at Black Hat USA's Tools Arsenal.

More by Carl

This site uses cookies to provide you with a great user experience. By continuing to use our website, you consent to the use of cookies. To find out more about the cookies we use, please see our Privacy Policy.