A Guide to Repacking iOS Applications
By on 23 July, 2018
Introduction
Jailbreaking iOS getting harder with every new version released, repacking and resigning iOS applications to be sideloaded on non-jailbroken iOS device has been a subject that has generated significant interest from security researchers in recent years. Sideloading applications is restricted on non-jailbroken devices due to several codesigning enforcements implemented in the iOS kernel. This is done in order to prevent malicious actors from distributing and running untrusted code on unsuspecting user's devices. These codesigning enforcements in conjunction with Apple's AppStore application review process has done a great deal in preventing malicious applications being distributed to iOS users.
Whilst these measures make it harder for attackers to target AppStore users, they also make it harder for security researchers to independently evaluate the security of iOS applications. There are several reasons why security researchers would want to sideload applications. The two most common reasons are:
- To avoid the need to bypass several binary protections that application developers implement to prevent reverse engineering.
- The lack of a public jailbreak on certain versions of iOS.
This guide aims to educate security researches on the various challenges on repacking and resigning iOS applications and to provide advice on how to overcome them.
Existing Work
There have been some notable research and blog posts that address the problem of repacking and resigning applications to be deployed on non-jailbroken devices. However, the referenced research either lacked sufficient detail or only demonstrate trivial examples of repacking and resigning iOS applications.
These limitations lead to the creation of this guide, which addresses the repackaging and resigning of applications with varying build configurations on non-jailbroken devices. In particular, the repackaging and resigning of the following build configurations will be addressed:
- Applications downloaded from the AppStore
- Applications that contain Frameworks and/or dylibs
- Applications that use App Extensions and Advanced App Capabilities
- Applications that bundle WatchKit applications
Methodology
Repacking iOS applications can be broadly broken down into six steps:
- Decrypting MachO binaries bundled within the application IPA
- Patching the application with custom code/libraries
- Generating a Provisioning Profile for the target iOS device that the repacked application is to be deployed
- Updating application metadata to match the Provisioning Profile
- Resigning the application's MachO binaries
- Archiving the bundle and sideloading onto the target iOS device
This methodology will be used throughout this guide to demonstrate the repacking and resigning of example iOS applications.
Getting Started
Building a workspace
In order to get started a researcher would need to setup a workspace with appropriate tooling. A jailbroken iOS device to decrypt applications from the AppStore and a non-jailbroken iOS device to sideload the repackaged application.
For the purpose of this guide a iPod Touch 6th gen running iOS 10 that was jailbroken using Saigon beta2 and a non-jailbroken iPhone 6s running iOS 11. It should be noted that both the jailbroken and non-jailbroken device are 64bit devices.
MacOS High Sierra
MacOS is required to perform a number of operations required to repack an iOS application. This includes tools like Xcode, otool and codesign. At the time of writing, the author was not aware of FOSS alternatives that would reliably achieve the same operations required to repack iOS applications. However, should FOSS tools be available in the future, this guide will be updated to use them.
Xcode 9+
This is used to generate Provisioning Profiles with the right entitlements that are required as part of the repacking process.
optool
This is an open source tool that allows patching of MachO binaries. For the purpose of this guide, optool is used to add load commands to the MachO binary.
FridaGadget
In order to demonstrate patching an application with custom code, this guide makes uses Frida server bundled as a shared library commonly referred to as FridaGadget. When the patched application is spawned, it would load the dylib that launches Frida server. One can then connect to this server and being instrumenting the application.
idevice* utilities
To install repacked applications onto the target device, one of the several options available is idevice* utilities. This guide uses ideviceinstaller, ideviceimagemounter and idevicedebug to install and run repacked applications. The utilities ideviceimagemounter and idevicedebug are only required to spawn an application using debugserver.
Repacking Applications
1. Repacking App Store Binaries
Application | Simple Notepad |
URL | https://itunes.apple.com/us/app/simple-notepad-best-notebook-text-editor-pad-to-write/id1064117835?mt=8 |
Version | 1.1 |
IPA SHA1 | 0e7f8f53618372c6e3a667ead6f37d7afc5ab057 |
Downloading iOS application bundles (IPAs) from the AppStore can be accomplished using iTunes 12.6. It should be noted that newer versions of iTunes do not support downloading applications from the AppStore. One can download iTunes 12.6 from the following link. For the first repacking demonstration, Simple Notepad, a trivial note taking iOS application is used to get the reader familiar with six steps to repacking applications briefly described in Methodology section.
Unpacking the Simple Notepad IPA using unzip or a similar utility reveals a single MachO executable binary named "Plain Notes". The following snippet shows the layout of MachO binaries within the Simple Notepad application bundle:
Payload/
Plain Notes.app/
Plain Notes
1.1 Decrypting MachO Binaries
There are several automated solutions created to decrypt AppStore binaries such as Clutch, dumpdecrypted or dump-ios. However, some of these automated solutions struggle to cope with applications that implement binary protections such as debugger detection and/or hooking detection. Which is why it is sometimes necessary to decrypt binaries manually. An excellent guide on decrypting AppStore binaries can be found at the following link.
The first step to decrypting the Simple Notepad is to setup debugserver to intercept and attach to the application when it is launched. The following code snippet demonstrates setting up debugserver to attach to the Simple Notepad application:
amarekano-ipod:~/amarekano root# ./debugserver *:6666 -waitfor "Plain Notes"
debugserver-@(#)PROGRAM:debugserver PROJECT:debugserver-360.0.26.1
for arm64.
Waiting to attach to process Plain Notes...
Listening to port 6666 for a connection from *...
Once debugserver has been setup and has intercepted the launch of Simple Notepad, connect to the debugserver instance using lldb. To locate the decrypted image in memory one would need to know the size of the image and the image offset. Offsets can be gathered from inspecting the load commands of the application binary using otool:
Amars-Mac:Plain Notes.app amarekano$ otool -l Plain\ Notes | grep -A4 LC_ENCRYPTION_INFO_64
cmd LC_ENCRYPTION_INFO_64
cmdsize 24
cryptoff 16384
cryptsize 1146880
cryptid 1
Due to system wide ASLR enforced on iOS, it is necessary to locate the base address of the image in memory and then compute the offset from that address. This can be done once lldb is connected to debugserver and is attached to the process:
(lldb) image list "Plain Notes"
[ 0] BA5E5051-D100-3B60-B5C8-181CAC0BB3EE 0x000000010004c000 /var/containers/Bundle/Application/2FD88AFF-6841-44D2-878D-8BA1698F3343/Plain Notes.app/Plain Notes (0x000000010004c000)
The value 0x000000010004c000 is the base address of the binary image in memory. Using this value, we can now dump the decrypted image. The lldb command to dump the binary is shown below:
(lldb) memory read --force --outfile ./decrypted.bin --binary --count 1146880 0x000000010004c000+16384
1146880 bytes written to './decrypted.bin',
The count parameter is the cryptsize value and the offset to the decrypted section is the base address + cryptoffset. Once we've dumped the decrypted memory region, we then need to splice this into the original AppStore binary. iOS applications downloaded using iTunes, typically contain multi arch FAT binaries. In order to patch the binary correctly, one would first have to locate the offset for the arm64 architecture using otool:
Amars-Mac:Plain Notes.app amarekano$ otool -fh Plain\ Notes | grep -A5 architecture\ 1
architecture 1
cputype 16777228
cpusubtype 0
capabilities 0x0
offset 1441792
size 1517824
Using the offset of the encrypted section from the encryption information and the offset of the arm64 arch within the FAT binary, use dd to splice the decrypted image into the original binary. The seek value shown below is the sum of the two offset values i.e cryptoff + offset in the FAT binary:
Amars-Mac:Plain Notes.app amarekano$ dd seek=1458176 bs=1 conv=notrunc if=./decrypted.bin of=Plain\ Notes
1146880+0 records in
1146880+0 records out
1146880 bytes transferred in 4.978344 secs (230374 bytes/sec)
Once patched, set the cryptid value to 0, this can be done by loading the patched binary into MachO-View, selecting the right architecture and Load command and updating the value as shown in the following screenshot:
Once updated, save the changes made to the binary image and copy over the patched binary into the unpacked application IPA.
1.2 Patching the Application
Copy the FridaGadget.dylib and FridaGadget.config to the unzipped IPA, within the Payload/Plain Notes.app/ directory. The unpacked bundle with the decrypted application binary and FridaGadget should look as follows:
Payload/
Plain Notes/
Plain Notes
FridaGadget.dylib
FridaGadget.config
The contents of the FridaGadget.config file are listed below:
Amars-Mac:Plain Notes.app amarekano$ cat FridaGadget.config
{
"interaction": {
"type": "listen",
"address": "0.0.0.0",
"port": 8080,
"on_load": "wait"
}
The values of address and port can be configured to any interface and port accessible on the target device. This will be the interface and port the Frida server will be listening on when spawned via the repacked application.
Add a load command to the application binary using optool. This load command instructs the application binary to load the FridaGadget.dylib into the application's process space.
Amars-Mac:sandbox amarekano$ optool install -c load -p "@executable_path/FridaGadget.dylib" -t Payload/Plain\ Notes.app/Plain\ Notes
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/Plain Notes.app/Plain Notes...
1.3 Generating a Provisioning Profile
Having patched the application binary to load the custom dylib i.e. FridaGadget. Begin the process of signing the application binaries to be sideloaded on our target non-jailbroken device. The first step in that process is to generate a provisioning profile for the target device. This is achieved by created an empty Xcode project and that targets the non-jailbroken device.
Before building this project, one would typically require to add an AppleID to Xcode which would then generate and manage signing certificates for the application. The AppleID could be one associated with a free developer account or one that is part of the Apple Developer Program. Setting up an an AppleID on Xcode can be done via the menu; Preferences > Accounts.
The signing cert used is shown in the screenshot below:
Note: If when building the empty project, you happen to deploy it to the device, then make sure you delete it before sideloading the repackaged application.
The next step is to extract the provisioning profile from this empty project and reuse it to repack our target application. This provisioning profile is located under:
~/Library/Developer/Xcode/DerivedData/repackdemo-<a random string>/Build/Products/Debug-iphoneos/repackdemo.app/embedded.mobileprovision
Copy this file to the Payload/Plain Notes.app directory of the unpacked application as shown in the directory layout below:
Payload/
Plain Notes/
Plain Notes
FridaGadget.dylib
FridaGadget.config
embedded.mobileprovision
Extract the entitlements from the generated provisioning profile using the following commands:
Amars-Mac:repackdemo.app amarekano$ security cms -D -i embedded.mobileprovision > profile.plist
Amars-Mac:repackdemo.app amarekano$ /usr/libexec/PlistBuddy -x -c 'Print :Entitlements' profile.plist > entitlements.plist
Amars-Mac:repackdemo.app amarekano$ 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>6M9TEGX89M.com.example.amarekano.repackdemo</string>
<key>com.apple.developer.team-identifier</key>
<string>6M9TEGX89M</string>
<key>get-task-allow</key>
<true/>
<key>keychain-access-groups</key>
<array>
<string>6M9TEGX89M.*</string>
</array>
</dict>
</plist>
The entitlements.plist file contains the entitlements required by the application binary to work on a non-jailbroken iOS environment. These entitlements.plist will be used at a later stage in the resigning process to sign the application binary.
1.4 Updating Application Metadata
The next step is to update the Bundle Identifier of the Simple Notepad app to the bundle identifier of the Provisioning Profile generated in the previous section. In this example the bundle identifier within the Provisioning Profile is "com.example.amarekano.repackdemo". Update the Info.plist file within the Payload/Plain Notes.app directory of the unpacked application with the following command:
Amars-Mac:sandbox amarekano$ /usr/libexec/PlistBuddy -c "Set :CFBundleIdentifier com.example.amarekano.repackdemo" Payload/Plain\ Notes.app/Info.plist
1.5 Resigning MachO Binaries
To generate a list of valid codesigning identities on a MacOS system, run the following command:
Amars-Mac:~ amarekano$ security find-identity -p codesigning -v
1) 49808436B649808449808436B6651498084336B6 "iPhone Developer: m******"
2) F4F6830FB32AAF4F6830E2C5F4F68309F4FF6830 "Mac Developer: m*******"
3) 41A1537676F3F41A153767FCC41A153767CC3767 "iPhone Developer: A******"
4) 1E309C6B45C0E309C69E10E309C670E309C69C60 "iPhone Developer: a*********"
4 valid identities found
Use the signing identity that was used to generate the Provisioning Profile to resign the various MachO binaries. Begin by signing the FridaGadget.dylib:
Amars-Mac:sandbox amarekano$ codesign --force --sign "iPhone Developer: m***************" Payload/Plain\ Notes.app/FridaGadget.dylib
Payload/Plain Notes.app/FridaGadget.dylib: replacing existing signature
Once the dylib has been signed then sign the application binary with the entitlements generated in Section 1.4.
Amars-Mac:sandbox amarekano$ codesign --force --sign "iPhone Developer: m*****************" --entitlements entitlements.plist Payload/Plain\ Notes.app/Plain\ Notes
Payload/Plain Notes.app/Plain Notes: replacing existing signature
1.6 Archive and Install
Once the binaries have been resigned, repack the Payload directory to an IPA using the zip utility
Amars-Mac:sandbox amarekano$ zip -qr Simple_Notes_resigned.ipa Payload/
The repacked IPA is then installed onto the target device using ideviceinstaller, which is part of the idevice utilities suite:
Amars-Mac:sandbox amarekano$ ideviceinstaller -i Simple_Notes_resigned.ipa
WARNING: could not locate iTunesMetadata.plist in archive!
Copying 'Simple_Notes_resigned.ipa' to device... DONE.
Installing 'com.example.amarekano.repackdemo'
Install: CreatingStagingDirectory (5%)
Install: ExtractingPackage (15%)
Install: InspectingPackage (20%)
Install: TakingInstallLock (20%)
Install: PreflightingApplication (30%)
Install: InstallingEmbeddedProfile (30%)
Install: VerifyingApplication (40%)
Install: CreatingContainer (50%)
Install: InstallingApplication (60%)
Install: PostflightingApplication (70%)
Install: SandboxingApplication (80%)
Install: GeneratingApplicationMap (90%)
Complete
1.7 Running the Repacked Application
The DeveloperDiskImage for the version of iOS on the target device needs to be mounted to launch applications using debugserver. The Developer Disk images for various versions of iOS are located under the following directory on MacOS:
/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport
The two files of interest are the Disk image itself and its corresponding signature.
- DeveloperDiskImage.dmg
- DeveloperDiskImage.dmg.signature
Using ideviceimagemounter, mount the disk images onto the target device as shown below:
Amars-Mac:sandbox amarekano$ ideviceimagemounter DeveloperDiskImage.dmg DeveloperDiskImage.dmg.signature
Uploading DeveloperDiskImage.dmg
done.
Mounting...
Done.
Status: Complete
Once successfully mounted, launch the application in debug mode using idevicedebug:
Amars-Mac:sandbox amarekano$ idevicedebug -d run com.example.amarekano.repackdemo
The reason the application needs to run in debug mode is so that the main application thread is suspended at launch, giving Frida server time to spin up and listen on a pre-configured port.
Once running in debug mode, connect to the Frida server and resume the application thread using the following commands:
Amars-Mac:sandbox amarekano$ frida -H 192.168.1.196:8080 -n Gadget
____
/ _ | Frida 10.7.7 - A world-class dynamic instrumentation toolkit
| (_| |
> _ | Commands:
/_/ |_| help -> Displays the help system
. . . . object? -> Display information about 'object'
. . . . exit/quit -> Exit
. . . .
. . . . More info at http://www.frida.re/docs/home/
[Remote::Gadget]-> %resume
[Remote::Gadget]-> String(ObjC.classes.NSBundle.mainBundle().objectForInfoDictionaryKey_('CFBundleName'))
"Plain Notes"
[Remote::Gadget]-> String(ObjC.classes.NSBundle.mainBundle().objectForInfoDictionaryKey_('CFBundleIdentifier'))
"com.example.amarekano.repackdemo"
[Remote::Gadget]->
Here the target iOS device has an IP address of 192.168.1.196 and Frida server is listening on port 8080. Once connected to the Frida server, one can begin instrumenting the application using Frida.
2. Repacking Applications that use Frameworks
Application | Adobe Acrobat |
URL | https://itunes.apple.com/app/adobe-reader/id469337564?mt=8 |
Version | 18.03.31 |
IPA SHA1 | 1195a40f3f140b3c0ed57ae88cfbc017790ddba6 |
When repacking applications that use frameworks, one needs to bear in mind that it requires decrypting and patching the framework binaries that are also included in the application IPA. To demonstrate this, the Adobe Acrobat's iOS application is used as an example.
Unzipping the Acrobat IPA, reveals the following distribution of MachO binaries:
Payload/
Adobe Acrobat.app/
Frameworks/
AdobeCreativeSDKCore.framework/
AdobeCreativeSDKCore
AdobeCreativeSDKUtility.framework/
AdobeCreativeSDKUtility
AdobeCreativeSDKGoogleLogin.framework/
AdobeCreativeSDKGoogleLogin
Adobe Acrobat
The Frameworks used by the application are located under the Frameworks/ directory shown above. Some applications will also bundle dylibs which would also be located under the Frameworks/ directory. This particular version of Adobe Acrobat application did not include any dylibs.
2.1 Decrypt MachO Binaries
Decrypting applications that contain frameworks requires decrypting the main application binary as well the individual frameworks before they can be repacked. This could be done manually by attaching a debugger and dumping the decrypted image from memory. Alternatively, automated solutions such as Clutch2 can be used to generate decrypted binaries. The following snippet shows the encryption info of the application binary and demonstrates the dumping of the decrypted application binary from memory using lldb:
Amars-Mac:Adobe Acrobat.app amarekano$ otool -l Adobe\ Acrobat | grep -A4 LC_ENCRYPTION_INFO_64
cmd LC_ENCRYPTION_INFO_64
cmdsize 24
cryptoff 16384
cryptsize 16465920
cryptid 1
....
(lldb) image list "Adobe Acrobat"
[ 0] 397432D5-9186-37B8-9BA6-181F633D9C1F 0x000000010009c000 /var/containers/Bundle/Application/15E6A273-A549-4317-99D3-34B8A6623B5E/Adobe Acrobat.app/Adobe Acrobat (0x000000010009c000)
(lldb) memory read --force --outfile ./decbins/acrobat.bin --binary --count 16465920 0x000000010009c000+16384
16465920 bytes written to './decbins/acrobat.bin'
Similarly, one would have to decrypt each of the three frameworks bundled with the Adobe Acrobat application. The following snippet shows the encryption info of the AdobeCreativeSDKCore framework binary and demonstrates the dumping of the decrypted framework binary from memory using lldb.
Amars-Mac:AdobeCreativeSDKCore.framework amarekano$ otool -l AdobeCreativeSDKCore | grep -A4 LC_ENCRYPTION_INFO_64
cmd LC_ENCRYPTION_INFO_64
cmdsize 24
cryptoff 16384
cryptsize 770048
cryptid 1
....
(lldb) image list AdobeCreativeSDKCore
[ 0] 3FA3C800-9B6A-3117-A193-36C775B81A43 0x00000001015ac000 /private/var/containers/Bundle/Application/15E6A273-A549-4317-99D3-34B8A6623B5E/Adobe Acrobat.app/Frameworks/AdobeCreativeSDKCore.framework/AdobeCreativeSDKCore (0x00000001015ac000)
(lldb) memory read --force --outfile ./decbins/AdobeCreativeSDKCore.bin --binary --count 770048 0x00000001015ac000+16384
770048 bytes written to './decbins/AdobeCreativeSDKCore.bin'
Once the application binary and framework binaries have been decrypted, splice the decrypted binaries into the original binaries and then use MachO View to set the cryptid flag to 0 for each binary.
2.2 Patching the Application
To patch the application, copy over the FridaGadget.dylib and FridaGadget.config file to the unpacked application bundle under the Payload/Adobe Acrobat.app/ directory. Once copied, use optool to add a load command to the application binary as shown below:
Amars-Mac:sandbox amarekano$ optool install -c load -p "@executable_path/FridaGadget.dylib" -t Payload/Adobe\ Acrobat.app/Adobe\ Acrobat
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/Adobe Acrobat.app/Adobe Acrobat...
2.2 Patching the Application
To patch the application, copy over the FridaGadget.dylib and FridaGadget.config file to the unpacked application bundle under the Payload/Adobe Acrobat.app/ directory. Once copied, use optool to add a load command to the application binary as shown below:
Amars-Mac:sandbox amarekano$ optool install -c load -p "@executable_path/FridaGadget.dylib" -t Payload/Adobe\ Acrobat.app/Adobe\ Acrobat
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/Adobe Acrobat.app/Adobe Acrobat...
2.3 Updating Application Metadata
Update the bundle identifier within the application's Info.plist to match that of the generated provisioning profile. Generating Provisioning Profiles is discussed in Section 1.3
Amars-Mac:sandbox amarekano$ /usr/libexec/PlistBuddy -c "Set :CFBundleIdentifier com.example.amarekano.repackdemo" Payload/Adobe\ Acrobat.app/Info.plist
Copy the provisioning profile for the target device to the unpacked application bundle under the Payload/Adobe Acrobat.app/ directory.
2.4 Resigning MachO Binaries
Begin by removing the existing the code signatures by deleting the _CodeSignature directories within the extracted "Adobe Acrobat.app" and under the individual framework directories. The folder layout is shown as follows:
Payload/
Adobe Acrobat.app/
_CodeSignature
Frameworks/
AdobeCreativeSDKCore.framework/
_CodeSignature
AdobeCreativeSDKUtility.framework/
_CodeSignature
AdobeCreativeSDKGoogleLogin.framework/
_CodeSignature
Once these have been deleted, sign the binaries. To do so, start by first signing the FridaGadget.dylib:
Amars-Mac:sandbox amarekano$ codesign --force --sign "iPhone Developer: m***************" Payload/Adobe\ Acrobat.app/FridaGadget.dylib
Payload/Adobe Acrobat.app/FridaGadget.dylib: replacing existing signature
Followed by the Framework binaries:
Amars-Mac:sandbox amarekano$ codesign --force --sign "iPhone Developer: m***************" Payload/Adobe\ Acrobat.app/Frameworks/AdobeCreativeSDKCore.framework/AdobeCreativeSDKCore
Payload/Adobe Acrobat.app/Frameworks/AdobeCreativeSDKCore.framework/AdobeCreativeSDKCore: replacing existing signature
Amars-Mac:sandbox amarekano$ codesign --force --sign "iPhone Developer: m***************" Payload/Adobe\ Acrobat.app/Frameworks/AdobeCreativeSDKGoogleLogin.framework/AdobeCreativeSDKGoogleLogin
Payload/Adobe Acrobat.app/Frameworks/AdobeCreativeSDKGoogleLogin.framework/AdobeCreativeSDKGoogleLogin: replacing existing signature
Amars-Mac:sandbox amarekano$ codesign --force --sign "iPhone Developer: m***************" Payload/Adobe\ Acrobat.app/Frameworks/AdobeCreativeSDKUtility.framework/AdobeCreativeSDKUtility
Payload/Adobe Acrobat.app/Frameworks/AdobeCreativeSDKUtility.framework/AdobeCreativeSDKUtility: replacing existing signature
And finally the application binary with the right entitlements.
Amars-Mac:sandbox amarekano$ codesign --force --sign "iPhone Developer: m***************" --entitlements entitlements.plist Payload/Adobe\ Acrobat.app/Adobe\ Acrobat
Adobe Acrobat: replacing existing signature
Generating entitlements from a provisioning profile is discussed in Section 1.3.
2.5 Archive and Install
Once all the MachO binaries have been resigned, simply archive it back to an IPA and then sideload it on the target device:
Amars-Mac:sandbox amarekano$ zip -qr Adobe_resigned.ipa Payload/
To install the application onto the target device, we use ideviceinstaller as shown below:
Amars-Mac:sandbox amarekano$ ideviceinstaller -i Adobe_resigned.ipa
WARNING: could not locate iTunesMetadata.plist in archive!
Copying 'Adobe_resigned.ipa' to device... DONE.
Installing 'com.example.amarekano.repackdemo'
Install: CreatingStagingDirectory (5%)
...<truncated for brevity>...
Install: GeneratingApplicationMap (90%)
Install: Complete
2.6 Running the Repacked Application
Launch the application via debug mode using idevicedebug
Amars-Mac:sandbox amarekano$ idevicedebug -d run com.example.amarekano.repackdemo
Once the application is running in debug mode, connect to the running Frida server
Amars-Mac:sandbox amarekano$ frida -H 192.168.1.116:8080 -n Gadget
____
/ _ | Frida 10.7.7 - A world-class dynamic instrumentation toolkit
| (_| |
> _ | Commands:
/_/ |_| help -> Displays the help system
. . . . object? -> Display information about 'object'
. . . . exit/quit -> Exit
. . . .
. . . . More info at http://www.frida.re/docs/home/
[Remote::Gadget]-> %resume
[Remote::Gadget]-> String(ObjC.classes.NSBundle.mainBundle().objectForInfoDictionaryKey_('CFBundleName'))
"Adobe Acrobat"
[Remote::Gadget]-> String(ObjC.classes.NSBundle.mainBundle().objectForInfoDictionaryKey_('CFBundleIdentifier'))
"com.example.amarekano.repackdemo"
[Remote::Gadget]->
3. Repacking Applications that use App Extensions
Application | |
URL | https://itunes.apple.com/gb/app/linkedin/id288429040?mt=8 |
Version | 2018.04.05 |
IPA SHA1 | 275ca4c75a424002d11a876fc0176a04b6f74f19 |
Some iOS applications utilise App Extensions for Inter Process Communication (IPC) offered on iOS. An example of which is the Share Extension that allows sharing content across applications. These extensions are typically bundled with the IPA as separate executables. The following snippet shows the layout of various MachO binaries within the LinkedIn application bundle:
Payload/
LinkedIn.app/
Frameworks/
lmdb.framework/
lmdb
...
libswiftAVFoundation.dylib
...
Plugins/
IntentsExtension.appex/
IntentsExtension
IntentsUIExtension.appex/
IntentsUIExtension
...
LinkedIn
App Extensions are located under the Plugins/ directory as show above. Each App Extension has the bundle extension .appex.
3.1 Decrypting MachO Binaries
When dealing with applications that bundle App Extensions, in addition to decrypting the application binary and frameworks, one would also have to decrypt binary images of App Extensions. In order to decrypt App Extensions, first setup debugserver to intercept and attach to a launched App Extension. In the snippet below, debugserver is setup to at attach to IntentsExtension:
amarekano-ipod:~/amarekano root# ./debugserver *:6666 -waitfor IntentsExtension &
Once debugserver is setup, launch the App Extension. This can be performed manually as shown below:
amarekano-ipod:~/amarekano root# /var/containers/Bundle/Application/AC8C5212-67D0-41AB-A01A-EEAF985AB824/LinkedIn.app/PlugIns/IntentsExtension.appex/IntentsExtension
Once debugserver attaches, connect using lldb and dump the decrypted image from memory.
(lldb) image list IntentsExtension
[ 0] 2F48A100-110F-33F9-A376-B0475C46037A 0x00000001000f0000 /var/containers/Bundle/Application/AC8C5212-67D0-41AB-A01A-EEAF985AB824/LinkedIn.app/PlugIns/IntentsExtension.appex/IntentsExtension (0x00000001000f0000)
(lldb) memory read --force --outfile ./decbins/intentsextension.bin --binary --count 114688 0x00000001000f0000+16384
114688 bytes written to './decbins/intentsextension.bin'
(lldb) exit
Once decrypted, splice the dumped binary into the original App Extension binary and set the cryptid flag to 0 in the same way one would do when decrypting other MachO binaries.
3.2 Patching the Application
Patching the application binary to load the FridaGadget follows the same process as demonstrated in previous examples.
Amars-Mac:sandbox amarekano$ optool install -c load -p "@executable_path/FridaGadget.dylib" -t Payload/LinkedIn.app/LinkedIn
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/LinkedIn.app/LinkedIn...
3.3 Generating a Provisioning Profile
Generally, applications that use App Extensions almost always require Advanced App Capabilities. These capabilities typically allow application developers to utilise several Apple technologies such as Siri, Apple Pay, iCloud etc. The LinkedIn application for example uses the iCloud and Siri capabilities. This was revealed by examining the application entitlements:
Amars-Mac:sandbox amarekano$ codesign -d --entitlements :- "Payload/LinkedIn.app/"
Executable=/Users/amarekano/Desktop/sandbox/Payload/LinkedIn.app/LinkedIn
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
... <truncated for brevity> ...
<key>application-identifier</key>
<string>8AXPVS6C36.com.linkedin.LinkedIn</string>
...<truncated for brevity>...
<key>com.apple.developer.icloud-services</key>
<array>
<string>CloudDocuments</string>
</array>
<key>com.apple.developer.siri</key>
<true/>
... <truncated for brevity>,,,
</dict>
Advanced App Capabilities are only available to iOS developers using a paid developer account. Therefore, a suitable provisioning profile would have to be created, using a paid developer account, in order to use these app capabilities in the repacked application. This can be achieved by creating a dummy Xcode project and specify a paid developer signing cert as shown in the screenshot below:
Once a project has been created, capabilities required by the application can be enabled under the Capabilities tab. The following screenshot shows the required capabilities enabled in a dummy Xcode project.
Building this dummy Xcode project would generate a valid Provisioning profile with the right entitlements needed to repack the LinkedIn application. Extracting these entitlements from the generated provisioning profile is shown below:
Amars-Mac:sandbox amarekano$ security cms -D -i embedded.mobileprovision > profile.plist
Amars-Mac:sandbox amarekano$ /usr/libexec/PlistBuddy -x -c 'Print :Entitlements' profile.plist > entitlements.plist
Amars-Mac:sandbox amarekano$ 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>MS4K289Y4F.com.example.amarekano.advrepackdemo</string>
...<truncated for brevity>...
<key>com.apple.developer.icloud-services</key>
<string>*</string>
<key>com.apple.developer.siri</key>
<true/>
<key>com.apple.developer.team-identifier</key>
<string>MS4K289Y4F</string>
... <truncated for brevity>...
<key>get-task-allow</key>
<true/>
<key>keychain-access-groups</key>
<array>
<string>MS4K289Y4F.*</string>
</array>
</dict>
</plist>
3.4 Updating Application Metadata
On successful generation of the Provisioning profile, add the profile to the application bundle and update the various Info.plist files for the application and the application extensions as shown below:
Amars-Mac:sandbox amarekano$ /usr/libexec/PlistBuddy -c "Set :CFBundleIdentifier com.example.amarekano.advrepackdemo" Payload/LinkedIn.app/Info.plist
Amars-Mac:sandbox amarekano$ /usr/libexec/PlistBuddy -c "Set :CFBundleIdentifier com.example.amarekano.advrepackdemo.IntentsExtension" Payload/LinkedIn.app/PlugIns/IntentsExtension.appex/Info.plist
Amars-Mac:sandbox amarekano$ /usr/libexec/PlistBuddy -c "Set :CFBundleIdentifier com.example.amarekano.advrepackdemo.IntentsUIExtension" Payload/LinkedIn.app/PlugIns/IntentsUIExtension.appex/Info.plist
Amars-Mac:sandbox amarekano$ /usr/libexec/PlistBuddy -c "Set :CFBundleIdentifier com.example.amarekano.advrepackdemo.MessagingNotificationContentExtension" Payload/LinkedIn.app/PlugIns/MessagingNotificationContentExtension.appex/Info.plist
Amars-Mac:sandbox amarekano$ /usr/libexec/PlistBuddy -c "Set :CFBundleIdentifier com.example.amarekano.advrepackdemo.NewsModuleExtension" Payload/LinkedIn.app/PlugIns/NewsModuleExtension.appex/Info.plist
Amars-Mac:sandbox amarekano$ /usr/libexec/PlistBuddy -c "Set :CFBundleIdentifier com.example.amarekano.advrepackdemo.ShareExtension" Payload/LinkedIn.app/PlugIns/ShareExtension.appex/Info.plist
Amars-Mac:sandbox amarekano$ /usr/libexec/PlistBuddy -c "Set :CFBundleIdentifier com.example.amarekano.advrepackdemo.WVMPTodayExtension" Payload/LinkedIn.app/PlugIns/WVMPTodayExtension.appex/Info.plist
Amars-Mac:sandbox amarekano$
Do note that generating a Provisioning Profile with entitlements to use the Advanced App Capabilities, requires a unique bundle identifier. In this instance the bundle identifier used was "com.example.amarekano.advrepackdemo".
3.5 Resigning MachO Binaries
When resigning application binaries, the order in which binaries are resigned is important when repacking applications with multiple executable binaries. Start by resigning the App Extensions as shown below:
Amars-Mac:sandbox amarekano$ codesign --force --sign "iPhone Developer: m***************" Payload/LinkedIn.app/PlugIns/IntentsExtension.appex/IntentsExtension
Payload/LinkedIn.app/PlugIns/IntentsExtension.appex/IntentsExtension: replacing existing signature
Amars-Mac:sandbox amarekano$ codesign --force --sign "iPhone Developer: m***************" Payload/LinkedIn.app/PlugIns/IntentsUIExtension.appex/IntentsUIExtension
Payload/LinkedIn.app/PlugIns/IntentsUIExtension.appex/IntentsUIExtension: replacing existing signature
...
Followed by resigining the Frameworks and dylibs:
Amars-Mac:sandbox amarekano$ codesign --force --sign "iPhone Developer: m***************" Payload/LinkedIn.app/Frameworks/lmdb.framework/lmdb
Payload/LinkedIn.app/Frameworks/lmdb.framework/lmdb: replacing existing signature
...
Amars-Mac:sandbox amarekano$ codesign --force --sign "iPhone Developer: m***************" Payload/LinkedIn.app/Frameworks/libswiftAVFoundation.dylib
Payload/LinkedIn.app/Frameworks/libswiftAVFoundation.dylib: replacing existing signature
...
Amars-Mac:sandbox amarekano$ codesign --force --sign "iPhone Developer: m***************" Payload/LinkedIn.app/FridaGadget.dylib
Payload/LinkedIn.app/FridaGadget.dylib: replacing existing signature
Finally resign the LinkedIn application binary with the entitlements generated in Section 3.3
Amars-Mac:sandbox amarekano$ codesign --force --sign "iPhone Developer: m***************" --entitlements entitlements.plist Payload/LinkedIn.app/LinkedIn
Payload/LinkedIn.app/LinkedIn: replacing existing signature
3.6 Archive and Install
Once the application has been resigned, archive the Payload directory into an IPA and install the repacked IPA onto the target device.
Amars-Mac:sandbox amarekano$ zip -qr LinkedIn_resigned.ipa Payload/
Amars-Mac:sandbox amarekano$ ideviceinstaller -i LinkedIn_resigned.ipa
WARNING: could not locate iTunesMetadata.plist in archive!
Copying 'LinkedIn_resigned.ipa' to device... DONE.
Installing 'com.example.amarekano.advrepackdemo'
Install: CreatingStagingDirectory (5%)
...<truncated for brevity>...
Install: GeneratingApplicationMap (90%)
Install: Complete
3.7 Running the Repacked Application
Once the repacked application has been successfully installed on the target device, launch it using idevicedebug and then connect to the spawned Frida server.
Amars-Mac:sandbox amarekano$ idevicedebug -d run com.example.amarekano.advrepackdemo &
Amars-Mac:sandbox amarekano$ frida -H 192.168.1.91:8080 -n Gadget
____
/ _ | Frida 10.7.7 - A world-class dynamic instrumentation toolkit
| (_| |
> _ | Commands:
/_/ |_| help -> Displays the help system
. . . . object? -> Display information about 'object'
. . . . exit/quit -> Exit
. . . .
. . . . More info at http://www.frida.re/docs/home/
[Remote::Gadget]-> %resume
[Remote::Gadget]-> String(ObjC.classes.NSBundle.mainBundle().objectForInfoDictio
naryKey_('CFBundleName'))
"LinkedIn"
[Remote::Gadget]-> String(ObjC.classes.NSBundle.mainBundle().objectForInfoDictio
naryKey_('CFBundleIdentifier'))
"com.example.amarekano.advrepackdemo"
[Remote::Gadget]->
4. Repacking Applications that Bundle Companion WatchOS Apps
Application | Tube Map - London Underground |
URL | https://itunes.apple.com/gb/app/tube-map-london-underground/id320969612?mt=8 |
Version | 5.6.12 |
IPA SHA1 | 727f80c3f096dc25da99b9f950a1a8279af1b36c |
Very often, application developers will include a companion WatchOS application along with their mobile applications. To demonstrate the repacking of iOS application that bundle WatchOS applications, this guide uses the Tube Map application. The layout of the MachO binaries within the IPA is shown below:
Payload/
TubeMap.app/
Frameworks/
AFNetworking.framework/
AFNetworking
.... <truncated for brevity> ...
Watch/
TubeMap WatchKit App.app/
Frameworks/
libswiftCore.dylib
... <truncated for brevity> ...
Plugins/
TubeMap WatchKit Extension.appex/
TubeMap WatchKit Extension
TubeMap WatchKit App
TubeMap
4.1 Decrypting MachO binaries
Repacking applications that bundle WatchOS applications presents a unique challenge when it comes to decrypting binaries. All executable binaries within the example applications up until this point in the guide were compiled for a single processor architecture. It was therefore possible to launch these encrypted binaries in a debugger and then dump their decrypted sections. WatchOS binaries are compiled for armv7k architecture. Unfortunately, at the time of writing the author of this guide didn't have a jailbroken iWatch at hand to decrypt the WatchOS binaries. A workaround to to this challenge is discussed below.
If the WatchOS application isn't going to be assessed, one could simply delete the Watch/ folder and repack the application just like the previous examples have demonstrated. However, this approach could cause stability issues when running the application, particularly when assessing functionality that involves the WatchOS application.
Assuming a jailbroken iWatch is available, one would have to setup debugserver on the jailbroken watch and launch the WatchOS binaries within the debugger. Once applications are running within the debugger, the process to dump decrypted sections of the binaries is the same as regular iOS application binaries that have been demonstrated in the previous examples.
4.2 Patching the Application
Patching the decrypted application is once again a matter of adding the FridaGadget dylib to the unpacked IPA and then using optool to insert a load command into the application binary to load the Frida dylib.
4.3 Updating Application Metadata
Updating the TubeMap application's metadata involves updating the Info.plist files of the application and the Info.plist files of the application extensions. A suitable provisioning profile needs to be added to the unpacked IPA that targets the specific device.
4.4 Resigning MachO Binaries
First resign all bundled frameworks and dylibs and then the application binary.
Amars-Mac:Frameworks amarekano$ codesign --force --sign "iPhone Developer: m*******" AFNetworking.framework/AFNetworking
AFNetworking.framework/AFNetworking: replacing existing signature
...
Amars-Mac:Frameworks amarekano$ codesign --force --sign "iPhone Developer: m*******" libswiftCore.dylib
libswiftCore.dylib: replacing existing signature
...
Amars-Mac:TubeMap.app amarekano$ codesign --force --sign "iPhone Developer: m*******" --entitlements ../../entitlements.plist TubeMap
4.5 Archive and Install
Once the application has been resigned, archive the Payload directory into an IPA and install the repacked IPA onto the target device.
Amars-Mac:sandbox amarekano$ zip -qr TubeMap_resigned.ipa Payload/
Amars-Mac:sandbox amarekano$ ideviceinstaller -i TubeMap_resigned.ipa
WARNING: could not locate iTunesMetadata.plist in archive!
Copying 'TubeMap_resigned.ipa' to device... DONE.
Installing 'com.example.amarekano.repackdemo'
Install: CreatingStagingDirectory (5%)
...<tuncated for brevity>...
Install: GeneratingApplicationMap (90%)
Install: Complete
4.6 Running the Repacked Application
Once the repacked application has been successfully installed on the target device, launch it using idevicedebug and then connect to the spawned Frida server.
Amars-Mac:sandbox amarekano$ frida -H 192.168.1.67:8080 -n Gadget
____
/ _ | Frida 10.7.7 - A world-class dynamic instrumentation toolkit
| (_| |
> _ | Commands:
/_/ |_| help -> Displays the help system
. . . . object? -> Display information about 'object'
. . . . exit/quit -> Exit
. . . .
. . . . More info at http://www.frida.re/docs/home/
[Remote::Gadget]-> %resume
[Remote::Gadget]-> String(ObjC.classes.NSBundle.mainBundle().objectForInfoDictio
naryKey_('CFBundleName'))
"Tube Map"
[Remote::Gadget]-> String(ObjC.classes.NSBundle.mainBundle().objectForInfoDictio
naryKey_('CFBundleIdentifier'))
"com.example.amarekano.repackdemo"
[Remote::Gadget]->
Closing Thoughts
Repacking application is not always straight forward and occasionally researchers will encounter problems when running repacked applications. This could be due to several reasons. Some of these include the applications implementing repack detection by inspecting the bundle id at runtime, detecting debuggers etc. To debug issues as they occur, one should scan the device syslog and dmesg for errors/warnings when running the repacked application. These two sources provide valuable information on running processes.
Finally, the information provided in this guide is to aid security researchers in evaluating iOS applications for security vulnerabilities.