Blog post 20 July 2021 by Jim Aspers, Security Specialist at Secura
iOS Apps on ARM Macs: Pentesting Opportunities
When inspecting iOS applications for security vulnerabilities, penetration testers usually rely on coming jailbroken devices. For applications with jailbreak detection, this requirement can lead to delays in execution, as bypassing jailbreak detection may be a complicated task.
Although methods exist that allow injecting security assessment tooling into the target application on a non-jailbroken device (see e.g. frida-agent), this is generally time-consuming and limited in possibilities. The availability of an extensive and comfortable platform for performing security testing, that will not be flagged by applications as being jailbroken, would be highly beneficial.
MacOS can play the role of such platform. The operating system contains many powerful native features that are useful for reverse engineering, such as dtrace and its derivatives (e.g. dtruss, opensnoop). Next to that, Wi-Fi and Bluetooth traffic can trivially be intercepted locally on the system (e.g. using Wireshark or a proxy such as Burp Suite), instead of having to set up an external man-in-the-middle setup.
Shortly after the arrival of the ARM-powered Mac computers, the possibility of running iOS applications on the macOS operating system was announced by Apple. In order to explore the possibilities offered by this feature for pentesting, while dealing with Apple’s restrictions, we looked for a way to run arbitrary iOS applications on macOS machines.
Although installing iOS apps is possible, Apple still maintains firm control over what can be installed and what can be not, aiming to provide the same piracy protection/DRM that is offered by the iOS platform. To this end, Apple encourages their users to install iOS applications through the macOS App Store. On top of that, when installing from an .ipa package manually, only signed install bundles can be installed. After the community discovered the possibility of ‘sideloading’ applications that were bought using the same Apple ID that the macOS machine was registered with, Apple added another restriction which meant that only applications approved for running on macOS will be installable (see for instance this report).
Although being far from ‘clean’, we hope the demonstrated method can be used by application pentesters to their advantage.
Decrypting Target Application
iOS application binaries are encrypted using Apple’s FairPlay DRM, rendering them useless for anyone except for the holder of the app buyer’s Apple ID. In addition to being an effective anti-piracy mechanism, this encryption implies the following:
- The application cannot be transferred to a device that is not coupled to the same Apple ID;
- The application cannot be analysed statically in a disassembler for reverse engineering;
- The application cannot be modified on-disk for patching.
For this reason, security testing and especially reverse engineering are generally carried out on an unencrypted iOS application.
The process of obtaining an unencrypted version of all application binaries, including libraries and frameworks, can be most conveniently carried out using frida-ios-dump (see AloneMonkey/frida-ios-dump). This Frida-based tool makes use of the fact that in order to execute an encrypted binary, an iOS device needs to decrypt the binary in memory to an executable format. The tool finds and dumps the decrypted binary images from a jailbroken device’s memory right after launching the target application, and creates a .ipa installer archive for the app containing unencrypted binaries.
For the purpose of demonstration, the ‘Intelligent Hub’
application developed by VMware was downloaded from the iOS App Store on
a jailbroken device. This application is not available for
installation through the macOS App Store, and contains many libraries
and frameworks. As such, it makes a perfect target for our experiment.
For the remainder of this article, this app will be used as the subject
we would like to run on a macOS system, and will be referred to as the
To dump an .ipa archive for the target application containing
decrypted binaries, the following command is executed while the
jailbroken iOS device is connected through USB:
Now, if we attempt to install the resulting archive directly through macOS’ iOS Application Installer.app (found in /System/Library/CoreServices/Applications/), we run into an error:
Looking at logs in Console.app, we see that the archive signature is not accepted
This is expected behaviour, as the IPA has not been resigned for containing the now decrypted binaries. We could resign the IPA, but this process can become cumbersome rather quickly, especially when multiple frameworks are included or when the target application requires exotic entitlements. Tools such as AltSign can be used for this purpose, but as we would like to be able to install arbitrary (modified) application bundles at any time for security testing, we proceed to look for a method to get around the signature validation on the installed application.
Learning By Example
Inspecting output in Console.app when installing iOS apps officially marked compatible with macOS from the App Store, we find that the installation process does more than just putting the installer bundle’s files into place. As an example for studying the installation process, we used the application ‘Aangifte 2020’ developed by de Belastingdienst. This application was available as an iOS app both through the iOS and macOS App Stores. Throughout this article, we will refer to this application as the ‘donor application’.
The first observation is that a special ‘wrapper’ folder structure in /Applications is created:
The folder maa.iOS.app was found to contain a typical iOS application bundle folder structure.
Further observations show that the installer does more than just creating this wrapper for the actual iOS application. Looking at system logs and file activity, numerous OS components are involved in a this seeminly complex process, involving records coupled to bundle details in a system database.
Instead of further reverse engineering the installation process, the possibility of using the valid application wrapper to host our target application is explored.
Playing the Parasite
Going back to the decrypted application archive for the VMware Intelligent Hub application, the contents of the archive can be extracted trivially:
We can overwrite the donor application bundle’s contents with the extracted contents of the target application’s bundle:
Now, this will not work out-of-the-box. All macOS and iOS binaries (including dynamically linked libraries and frameworks) are required to be digitally signed. For creating a valid signature, only Apple-issued certificates can be used. Modifying a binary thus involves re-signing the modified code. These signatures are embedded into the binary itself. For libraries, frameworks and other important resources inside an application bundle (e.g. Info.plist), the signatures are stored in a dedicated file (_CodeSignature/CodeResources) additionally. This way, the OS can guarantee the integrity of the executed code. For details, see Apple's developer documentation on this.
For this reason, simply overwriting our valid wrapper’s application bundle contents with those of Hub.app will not work: the code signatures for binaries inside the extracted bundle are invalid. As we copied over modified application bundle contents to the donor application's wrapper, we will need to add a code signature to the relevant files. However, we do not want to generate the required provisioning profile including all of the required entitlements for each application under test manually; this would involve significant manual labour in Apple’s Developer Portal. Additionally, this would require editing the binaries’ entitlements to match with our codesigning identity. Indeed, if we would take this approach, we might as well just resign the decrypted installer archive in its entirety. To get around all of this, here we use what is called 'ad-hoc signing'. From man codesign(1):
When using ad-hoc signing instead of regular signing with a proper code signing identity, the binaries' code signatures only contain a hash of the respective signed binary. This hash will be verified against the OS' static trust cache by the kernel before execution. The trust cache contains hashes for known Apple system binaries, and cannot be modified at runtime without exploiting a serious security vulnerability in a default macOS configuration.
Keeping this in mind, we start by ad-hoc signing our modified wrapped bundle:
As expected from our knowledge about code signing, if we apply the ad-hoc code signature and try to run the application, the loader crashes the application at load time. Just before the crash, we observe the following log entry originating from the kernel:
We see the involved component is AppleMobileFileIntegrity, commonly known as AMFI. AMFI runs as part of the kernel (KEXT), and handles integrity checking of binaries. The encountered message shows return code 0xe00002f0.
Looking up the observed IOreturn return subcode in the XNU kernel source reveals that the origin of this error can be explained:
The kernel returned a kIOReturnNotFound as a result when asked to lookup our binary's hash in the trust store. This makes sense, as we know that the trust cache is a precompiled, hardcoded data structure that only contains hashes for binaries trusted by Apple. Adding our hashes to the trust store is beyond the scope of this article, and we will use a trivial bypass instead.
For testing purposes, unlike iOS, macOS allows the user to (partially) disable AMFI. If we disable AMFI, the absence of our ad-hoc signed binary's hash in the trust cache will no longer be a blocking issue. Disabling AMFI can be done by passing a particularly interesting boot argument to the macOS kernel. The argument can be added from a live system as follows, to be effective after reboot:
Note that in order to be able to alter boot arguments, System Integrity Protection (SIP) needs to be disabled on recent versions of macOS.
After adding the boot arguments and rebooting the machine, the ad-hoc signed application bundle is no longer blocked, and can be launched as a regular application through Finder.app.
NOTE: Disabling AMFI, and most importantly SIP, disables the core of macOS’ operating system security model. SIP should NEVER be disabled on a production system without careful consideration of the security risks involved!
It is important to note that for this method to work, the donor application we use to serve as the valid wrapper for our target app should not be launched ever before its contents are being overwritten. This is related to the fact that upon first launch, macOS seemingly stores a reference of the iOS application’s bundle identifier in relation to the wrapper bundle itself. This means that if we want to move another target app inside the wrapper, we either need to keep the same bundle identifier, or simply just uninstall and reinstall the donor application from the App Store. Modifying the wrapped iOS application bundle’s contents is possible without restrictions, as long as the bundle identifier is left unchanged and the entire bundle is (ad-hoc) re-signed after modification.
Let’s Get To Work!
As a result from our little hack, we can now successfully run our target application, the VMware Intelligent Hub, on our macOS system:
Especially interesting for a pentester, inspecting the application’s data stores becomes comfortable:
We can modify the application itself with minimal restrictions (keeping in mind the required re-signing). This allows for applying patches in no-time. An example for changing the application display name:
As a result, the application’s display name (and thus the window title) changed after restarting the application:
As the iOS application runs in the native macOS environment, it is treated almost identically to regular macOS applications. This means that network traffic can be monitored and intercepted using common tools such as Wireshark, Burp Suite or Charles. Additionally, macOS native tracing and debugging tooling can be used, such as lldb and dtrace-like tools:
Attaching a full Xcode debugging environment becomes trivial as well, providing a significantly enhanced experience over using plain jailbreak tools:
Did we succeed? Well, partly. We can now run arbitrary iOS applications on our macOS systems. However, returning to our problem statement posed in the introduction, several applications that employ jailbreak detection still flag our system as being jailbroken. More research would be required in order to know whether this approach is suitable to solve that problem.
Moreover, we have not managed to actually install any arbitrary iOS application. We merely ‘parasite’ off of a donor application. Actually reverse engineering the way macOS handles spawning iOS apps internally and applying this for reaching our goal will be covered in a follow-up post (see https://github.com/srepsa/launchr for a sneak peek). Stay tuned!