Post

Visualizing iOS Code Execution

TL;DR: This write-up covers the tools and methods used to visualize iOS code execution. The techniques for visualizing native and Objective-C execution are demonstrated using TrollInstallerX. The tools included in this write-up are: Frida, Lighthouse, blacktop ipsw, DyldExtractor, Ghidra, Cartographer. Temporary builds of Cartographer and Lighthouse’s frida-drcov.py file are stored here while the pull requests are reviewed.

iOS Coverage Guided RE

Big changes are coming to iOS and the Apple app ecosystem; third party app stores will be made available for all users due to the passing of the European Digital Markets Act (DMA). This introduction of third party app stores will likely cause a rise in iOS malware. This will give malware analysts new samples to reverse engineer - exciting times!

Whether the introduction of third party app stores is a net positive or negative is outside the scope of this write-up - this write-up aims to introduce iOS malware analysts to coverage guided reverse engineering. Coverage guided reverse engineering is a technique that spans dynamic and static analysis, it seeks to use the meta-data collected from dynamic analysis to annotate code blocks within the static view with whether they were executed or not.

The benefit of this technique is that it can speed up reverse engineering and debugging by providing a high-level view of which portions of a binary are actually executing; which helps to avoid wasting time analyzing non-executed portions of a binary.

ios_coverage

This write-up demonstrates how to generate coverage for the privilege escalation exploits packaged in the TrollInstallerX iOS application (which to be clear is not malware - or at least not to anyone but Apple perhaps). A second write-up will demonstrate reverse engineering Private Frameworks.

TrollInstallerX

TrollStore is a fantastic tool for permanently installing .ipa files with arbitrary entitlements (app permissions); however it requires a bootstrap application to install called TrollInstallerX. For lack of advanced iOS malware samples, TrollInstallerX replicates adversarial tactics such as escalating privileges to install a payload (TrollStore), and this makes it a good “sample” to analyze to practice coverage guided reverse engineering.

For this we will use a jailbroken iPhone running iOS 15.5 - though this write-up will work for any iOS version that you can Jailbreak or for apps you are able to side-load (additional write-ups to come on this topic).

Device Setup

Download the TrollInstallerX application here and follow the installation guide here.

Ensure you have frida installed on both the iOS device and the host you’re working from. A good smoke test to confirm everything is working as expected:

  1. Open TrollInstallerX on your device
  2. Run frida-trace -U TrollInstallerX -i 'libsystem_kernel.dylib!vm_alloc*'
    • This will hook all calls to the kernel of the form vm_alloc.*
  3. Click Install TrollStore
    • Note the privilege escalation used - in my case it was PhysPuppet

If everything is working as expected you should see something similar to the following:

It’s worth noting a few things out of this exercise:

  • The priv esc being used for iOS 15.5 is PhysPuppet - this will differ depending on your iOS version
  • We can see many calls to vm_allocate() while the exploit is running

PhysPuppet Coverage Guided Analysis

Here we get to cheat, we can read the write-up on the PhyPuppet exploit here and even the exploit itself is captured in physpuppet.h found here. In a real world setting there won’t be such a convenience of course; though if you’re reverse engineering a suspected privilege escalation - inspecting the calls to libsystem_kernel.dylib is something you’d be interested in.

Reviewing the priv-esc code at physpuppet.h - we can see the exploit occurs with 5 sequential calls to methods within libsystem_kernel.dylib:

  1. mach_memory_object_memory_entry_64(...)
  2. vm_map(...)
  3. vm_deallocate(...)
  4. mach_port_deallocate(...)
  5. vm_allocate(...)

We can then adjust our frida-trace to capture all the calls to these functions:

1
2
3
4
5
6
frida-trace -U TrollInstallerX \
-i 'libsystem_kernel.dylib!mach_memory_object_memory_entry_64' \
-i 'libsystem_kernel.dylib!vm_map' \
-i 'libsystem_kernel.dylib!vm_deallocate' \
-i 'libsystem_kernel.dylib!mach_port_deallocate' \
-i 'libsystem_kernel.dylib!vm_allocate'

And confirm we see the exploit is running in TrollInstallerX as we suspect.

physpuppet-sequence

Lets zoom in on the mach_memory_object_memory_entry_64(...) call in particular - to do that we need to grab the libsystem_kernel.dylib and open up Ghidra.

Collecting and Reviewing Coverage

First lets use the blacktop ipsw tool to download the iPhone Software associated with our device and iOS version. This can be done with the following commands

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ ipsw download ipsw --version 15.5 --device iPhone8,1

$ ipsw extract --dyld iPhone_4.7_15.5_19F77_Restore.ipsw

$ ls -lrt 19F77__iPhone8,1

total 5324704
-rwxr-xr-x  1 dev  staff  546177024 10 Jun 13:47 dyld_shared_cache_arm64
-rwxr-xr-x  1 dev  staff  536625152 10 Jun 13:47 dyld_shared_cache_arm64.1
-rwxr-xr-x  1 dev  staff  541769728 10 Jun 13:47 dyld_shared_cache_arm64.2
-rwxr-xr-x  1 dev  staff  155811840 10 Jun 13:47 dyld_shared_cache_arm64.3
-rwxr-xr-x  1 dev  staff  247545856 10 Jun 13:47 dyld_shared_cache_arm64.4
-rwxr-xr-x  1 dev  staff  210550784 10 Jun 13:47 dyld_shared_cache_arm64.5
-rwxr-xr-x  1 dev  staff  487768064 10 Jun 13:47 dyld_shared_cache_arm64.symbols

Then using the DyldExtractor we will slice out the libsystem_kernel.dylib binary from the shared cache.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ dyldex -l -f libsystem_kernel ./19F77__iPhone8,1/dyld_shared_cache_arm64

Listing Images
--------------
/usr/lib/system/libsystem_kernel.dylib

$ dyldex -e /usr/lib/system/libsystem_kernel.dylib ./19F77__iPhone8,1/dyld_shared_cache_arm64

Extracting /usr/lib/system/libsystem_kernel.dylib
Extractor >> Done :: [/]

$ ls -lrt binaries/usr/lib/system/libsystem_kernel.dylib

-rw-r--r--  1 dev  staff  372917 17 Jun 10:48 binaries/usr/lib/system/libsystem_kernel.dylib

Finally create a new Ghidra project and import this binary.

ghidra_ios_kern

We’ll also want to install the Cartographer Ghidra extension from here - this is a temporary build of Cartographer for this write-up which includes required changes to the main project (captured in the pull request here).

Then we will remap the binary’s image base to start from zero rather than it’s offset within the shared cache, this is required for the coverage to properly apply in the decompiled view. through the Python scripting engine at Window > Python run the following:

1
currentProgram.setImageBase(currentProgram.getImageBase().getNewAddress(0x0), True)

The change to the memory map can be confirmed by going to Window > Memory map and validating that the __TEXT block starts at 0x0.

Next we will use the frida-drcov.py script packaged with Lighthouse - though we will use a slightly modified version available here which has some additional functionality for focusing our coverage collection (without the additional functionality to focus our coverage gather the number of frida hooks injected crashes the iOS process - a pull request here has been created for these changes).

To capture the coverage run the following:

1
2
3
4
5
$ python3 ./frida-drcov.py \ 
    -D <iPhone_device_id> \
    -N libsystem_kernel.dylib \
    -n mach_memory_object_memory_entry_64 \
    TrollInstallerX

Altogether it should look something like the following video:

Tip: It helps to open the frida-cov.log file and add some trailing characters to the module you’re reviewing so you can find it easier in the Cartographer module selection prompt.

Finally we can take the produced frida-cov.log file, open the libsystem_kernel.dylib binary in Ghidra, and load the coverage file via Tools > Code Coverage > Load Coverage Files… . Going to Window > Code Coverage should now show that we have captured the executed blocks within the mach_memory_object_memory_entry_64.

loaded_coverage

Reviewing the function graph we can see which blocks have executed and which have not - hopefully accelerating our reversing process by knowing which sections of code to review and which to ignore.

coverage_graph

Conclusion

This write-up should have hopefully demonstrated how to capture coverage for iOS Code Execution. This method for coverage guided reverse engineering on iOS should hopefully accelerate your work.

This post is licensed under CC BY 4.0 by the author.

Trending Tags