Do it Live! Dynamic iOS Forensic Testing


Testing and forensics go hand in hand. You cannot be sure about a certain artifact on what it contains or what certain pieces mean without testing (and not just once, but over and over and on multiple devices and operating systems!) I probably do this more than most forensic investigators but it is something I obsess about. If I’m not absolutely sure, I test – always. Even then I will caveat it to very specific instances of my testing platform. I hope this article will help most investigators and researches up and running with dynamic iOS testing.

Requirements & Prerequisites:

  • Jailbroken iOS Test Device (the newer iOS the better, generally!)

    • iPad, iPhone, iPod Touch – doesn’t matter.

  • Familiarity with your jailbreak, every jailbreak is different and may provide different sets of utilities.

  • Patience – Testing is neither quick nor easy and can be downright infuriating sometimes.

Connecting to Your iOS Device

Most newer jailbreaks will come with some sort of SSH server on them, determine how to connect to it via SSH. All examples in this article will be performed on MacOS (big surprise) however it shouldn’t be impossible to do this on other platforms. I’ve written a couple of articles on accessing jailbroken devices:

To connect to your iOS device, you can use normal access via Wi-Fi, but this may lead to stability issues when copying off large amounts of data or general connection issues. You may want to access the device off the network instead. I like to use a utility from the libimobiledevice suite of tools called iproxy. This allows me to access it via a USB lightning cable and no network. This uses usbmuxd to create a TCP tunnel over USB. More info/similar tools here if you need it.

Run iproxy in a separate terminal window or set it up as a Launch Agent.

Usage: iproxy 4242 22 [UDID]

  • Local Port 4242 – You can use whatever port you like, I like 4242 (It’s the answer to everything.)

  • Device Port 22 – Default SSH port for most jailbreaks, except for Meridian that likes to be fancy and run on 2222 (customize as required).

  • Device ID (UDID) is optional but useful if you are connected to multiple devices at a time.

Nothing will be displayed apart from “waiting for connection” until you connect a device. Once you do, you’ll see the devices UDID and port number in the iproxy output.

In another terminal window, SSH into it using the local port you setup with iproxy. You can use localhost or, whatever your personal preference is. As for the username you have two choices – root or mobile, root is obviously root and mobile is a limited user account. I always choose root but worth knowing about both. Also FWIW, make sure you change the default iOS password of these accounts from ‘alpine’ with passwd if it’s connecting to any network at all.

Executing Non-Native Binaries

Depending on which jailbreak you are using, certain utilities may not be made available to you. With any luck the basics will be provided by the jailbreak using Jonathan Levin’s binpack. If you want to upload your own, you’ll have to sign them and provide entitlements to run them on the device.

One tool that I really like for file monitoring (discussed later) is fsmon by NowSecure. I’ve attempted to build this from source for iOS but once uploaded I get an error that I don’t know how to fix (granted I didn’t research it much). Instead I pulled the binary from the DEB package provided here

I can unarchive the DEB archive using The Unarchiver (My favorite unarchive on macOS!). I then unarchive the data.tar.gz using The Unarchiver or native utilities to get the ‘/usr/bin/fsmon’ binary.

The fsmon binary is Fat or Universal binary meaning it can have multiple architectures stuffed into a single file. This particular binary has arm_v7 (32-bit) and arm64 (64-bit) Mach-O binaries.

We will need to thin this binary to a single architecture to get to run on our device. I chose arm64 for this since I have a 64-bit device (iPhone 7). You can thin/sign/entitle on the Mac too using jtool but just in the event you are not working from one (and why not!? I’m sitting here judging you right now.) I’ll upload the binary to the device and run the same commands.

Using scp I can upload this binary. For my particular jailbreak I needed to create a symlink (ln -s) to the scp binary provided by Jonathan’s binpack before I could use it.

Using scp with the CAPITAL P argument (why the port flags are not consistent between SCP and SSH is beyond me) and our iproxy port of 4242 to copy fsmon to root’s home directory, /var/root.

Now if we try to run it from root’s home directory, it will fail (“Operation no permitted”) since binaries on newer iOS’s can only run from certain directories. That’s not the only problem, it also needs to be signed and entitled.

First let’s deal with only one binary, in order to sign and entitle we need to extract the 64-bit binary from Fat binary. We can do with a couple tools. If you are on macOS and have lipo (get it? fat binary…lipo…thin…sorry, this makes me giggle every time.) I will output to a file named fsmon64 so I know it is the 64-bit binary and upload it to the device.

lipo fsmon -thin arm64 -output fsmon64

Since I’ve uploaded mine to the device already, I don’t have lipo on the device I will use Jonathan’s jtool instead.

jtool -arch arm64 -e arch fsmon

This will extract the 64-bit binary to a file named fsmon.arch_arm64 into the current directory. You will have to change the permissions to execute it (chmod 700), but we still need to deal with signature and entitlements. As shown below it has an adhoc signature and no entitlements.

To get the signature and entitlements from any binary you can run the following jtool command, example below is from the dd binary. Notice it has a Platform Binary signature and the “platform-applications” entitlement.

jtool --sig --ent <binary>

If I tried to execute the fsmon binary in /var/root, I’ll still get the “Operation not permitted” error. If I move it to a directory, I should be able to execute from I’ll get a different error, “Killed: 9”, next step is fixing the signature and entitlements.

Extract the entitlements from another (working) binary on the device using jtool and save this to a file named ent.xml for use later.

jtool --ent > ~/ent.xml

Using jtool again, lets sign fsmon.arch_arm64 as a platform application and provide the binary the entitlements we just extracted. Verify it worked with (--sig/--ent) and execute it. Yay, working binary! (Feel free to rename as necessary, again I use fsmon64 or just fsmon.) On newer versions of jtool, ‘-platform’ is no longer required.

jtool --sign platform --inplace --ent ~/ent.xml /jb/bin/fsmon.arch_arm64

Reviewing Directories and Files

Quick iOS partition review - take a look at the /etc/fstab file. There are two primary partitions (and a baseband one if you’re into that kinda thing.) The first mounted on / is the system partition where the operating system files are contained. It is theoretically read only as noted by the ‘ro’, however recall that we just put the fsmon binary in /jb/bin. When it comes to jailbreaks that ‘ro’ is more of a reminder of what it is on stock devices. The data partition mounted on /private/var is where all the user data is, this is the primary partition that you’ll be using for your forensic analysis. All native and 3rd party application plists, databases, and other files are located there.

In order to find data associated with a particular application I can use the find command and the bundle ID associated with an app.

find /private/var -ipath *net.whatsapp.Whatsapp*

In the example below, I started looking for WhatsApp data. This is a good initial triage of the applications data, this doesn’t get me all the files but it helps direct me to the related directories. You’ll notice that GUID in the file path, this will be different for all applications across all devices and will change.

Going to the following directories and perusing the data will help me determine what type of data a certain application stores and how it stores it.

  • iCloud Artifacts:

    • /private/var/mobile/Library/Caches/CloudKit/

  • /private/var/mobile/Library/Application Support/CloudDocs/session/containers/

  • The combination of the ‘Shared App Group’ and ‘Data Application’ directories will generally hold most of the user data associated with an application.

    • /private/var/mobile/Containers/Shared/AppGroup/133904F3-EAA0-48E9-905C-90BB93A7DDA2/

    • /private/var/mobile/Containers/Data/Application/97AD4FDE-2089-455A-8B21-06E4E2225626/

  • It is worth mentioning that if you are looking for the Applications bundle the Bundle ID will not get you there, instead look for the name of the App.

    • /private/var/containers/Bundle/Application/6B6D4621-845A-4EE7-AECF-D68CC00E5C4E/

Finding the right directory/directories for the app in question can be time consuming. One of my favorite tools that solves this issue is cda. Using the same method above, I’ll upload this binary to my device.

As shown below, all you need to provide cda is a search term. I provided it the term ‘whatsapp’ and it provided me the same directories (more even) as above. If you’re not quite sure what you are looking for yet, provide it a single period ‘.’ and get a listing by Bundle ID. It is worth noting that this will only provide app data, if you are doing research on native iOS data you won’t get it using this – time to dig in and find it the hard way.

Looking into one of the WhatsApp directories, I start to get a feeling for what data an application is storing. You will normally find SQLite databases, plist files, media, log files and other files related to an application. ~90% of what you’ll be looking at are SQLite databases and plist files, so I’ll focus on those for this part of the article.

Starting with SQLite databases, we can use sqlite3 (provided by the binpack) on the device to triage the database. My process is to look at the database names first and see if anything is obvious. If I’m looking at a chat app, I’ll look for keywords like chat, message, etc. I’ll focus on WhatsApp’s ChatStorage.sqlite database for this example – seems like a reasonable choice for chat messages. Using sqlite3, I’ll dump the table listing and peruse it for anything interesting. Seems to me that ZWAMESSAGE would contain the chat messages!

Using a SQL SELECT statement I can dump the contents of this table; however this is where sqlite3 may not provide the best analytical view. In this case I might use scp to copy it off the device and use a GUI based SQL viewer to create queries and join multiple tables. (FWIW, I like DB Browser for SQLite). Need a SQLite reference guide, check out the one Heather Mahalik and I created for our classes (FOR585 – Advanced Smartphone Forensics, FOR518 – Mac Forensic Analysis and Incident Response).

I will still use sqlite3 for per-message testing to answer different questions.

  • What does xyz flag in this column mean? Are 0 and 1 the only values?

  • If I send a piece of media, what does it look like in the database? Is it stored in a different table?

  • Message direction or contact information which column stores that?

I will run the same query over and over testing different flags in different columns as I populate data manually on the device. Does this take time? Sure does! But it’s the only way to be sure. Applications are known to change database schemas and values as their application updates.

sqlite3 ProTip: Ctrl+D to quit out of the sqlite3 shell

Next let’s dive into those pesky plist files. For third party applications I will usually start in the applications Preferences directory. The Preferences directory will usually contain a configuration plist file containing some useful bits like usernames, servers, usage timestamps, keys, and passwords. Yes, plaintext, non-obfuscated passwords! 🤷🏻‍♀️

The example below is one for Whatsapp (specifically the one located in the Shared App Group directory. There may be multiple for each application.) I’m using jlutil (Jonathan’s interpretation of plutil, the native macOS utility, to view this plist.)

Another option is plconvert which will convert it to a text file representation and show a less-than-helpful representation on standard out. It will also output to an XML file, here named tmpfile by myself. I consider this slightly less “clean” as it will leave these converted files everywhere on the system, but it’s all personal preference. I will say the plconvert is more useful from a timestamp interpretation and data BLOB stance.

File Monitoring

Say you’re testing a application that has the ability to take photos using the iPhone camera. Where are those photos stored? Or how about if you toggle one of the switches in the Settings application, where is that stored? In a database or a plist file?

An easy way to figure this out is to use a file system monitor, it should at least point you in the right direction. As shown above I prefer to use the fsmon utility from NowSecure, however Jonathan’s binpack includes fs_usage which provides the same data. I prefer the layout of fsmon’s output.

In the example below, I took a picture of my sidekick Elwood with the Camera app on the iPhone while running fsmon. (I’d like to say he helped with this article, but he just slept all day next to me. Cats don’t make the best research assistants.) In the fsmon output you can see that when I took a picture, the “Camera” process saves the picture in a few temp files before it finally saves it in the DCIM directory as an HEIC file (the newer iOS image format) and creates a thumbnail image for the Camera app, PreviewWellImage.tiff.

I hope this helps everyone getting started doing their own iOS forensic research!

(In the rare event anyone wants to see my sidekick Elwood…here is the pic I took. Super Lazy.)