Xcode Instruments for iOS: reversing and abuse

The Instruments server

After reading few publications about old iOS jailbreaks prior and including Pangu9, I got interested in lockdown services and the DeveloperImage, especially the Instruments app from Xcode.

After my reverse engineering and I was about to write this, I disappointedly found that someone had already finished the same job.

So I’ve heard about bug collision but now it’s time for research collision? What a waste of time.

The RPC internal of Instruments app is a long story to tell. You can refer to the slides above, and I will only point out what it didn’t mentioned.

The server side of Instruments is an unsandboxed root daemon on iOS: DTServiceHub. The Instruments app communicates with it via lockdown. The RPC packets are serialized NSInvocation dispatched to limited classes.

You can find all available methods on a jailbroken iDevice:

frida -U DTServiceHub

for (var name in ObjC.protocols) {
var protocol = ObjC.protocols[name]
if ('DTXAllowedRPC' in protocol.protocols) {
console.log('@protocol', name)
console.log(' ' + Object.keys(protocol.methods).join('\n '))

And here’s its output on 11.3.1:


They are implemented in multiply channels (services). During initialization, each service’s registerCapabilities: method will be called.

So only the registered classes and their selectors confirms to DTXAllowedRPC protocol can be invoked.

Intersting methods

The services are located in multiple executables, mainly these two module:

  • /Developer/Library/PrivateFrameworks/DVTInstrumentsFoundation.framework/DVTInstrumentsFoundation
  • /Developer/Library/PrivateFrameworks/DVTInstrumentsFoundation.framework/DTServiceHub

The God Mode

The server checks for getenv("DYLD_ROOT_PATH") + "/AppleInternal" ‘s existence to unfold its internal capabilities:

God mode

Looks like we can’t mount this location even jailbroken on iOS 11: https://www.theiphonewiki.com/wiki//AppleInternal

But if someone got a bug that can mess up with the launch_data, he can replace the environment variable to where such child directory is allowed to be touched.

The internal methods have API that can directly invoke posix_spawn:

-[DTUVRenderingService processStartCLIFromMessage:error:]
-[DTPosixSpawnProcessControlService launchSuspendedProcessWithDevicePath:bundleIdentifier:environment:arguments:options:]

Unlimited directory listing and file reading:

-[DTFileBrowserService entriesAtPath:]
-[DTFileBrowserService entriesInPathsArray:]
-[DTFileBrowserService dataFromFileAtPath:]
-[DTFileBrowserService contentsOfDirectoryAtPath:]
-[DTFileBrowserService updateAttributesForItem:]

Swift Playground

In recent DeveloperImage (>iOS 10) introduced a new service DTFileTransferService that allows sending a file to Swift Playgrounds’ document inbox and open it.

If the given file is a zip archive it will try to extract with BOM.framework first, before checking the existence of Playgrounds. So there’s a potential attack surface if you can find bugs in BOM like CVE-2015–7006, which has been exploited in AirDrop to gain remote app installation: http://2015.ruxcon.org.au/assets/2015/slides/ruxcon-2016-dowd.pptx

Yet another directory listing

Directory listing is implemented in DTFileBrowserService and requires AppleInternal devices. But somehow a -[DTDeviceInfoService directoryListingForPath:] seems forgotten:

But so what? Still unable to read even a single file.

Read macOS files from iOS

I have tried to read files from iDevices (with no luck), what about the opposite?

We can notice that each time an app was launched by Instruments, this method will be called: -[DTAssetService startServerForApplicationWithDevicePath:]

This is a emulator for On-Demand Resources:

It starts an http server on a random port, read the configurations from Resources/AssetPackManifestTemplate.plist or Resources/AssetPackManifest.plist, then update theURL key to http://localhost:{PORT}

So what does this HTTP server serve? Hmmmmm…

It maps the whole file system on your macOS to a static HTTP server so iOS apps can pull them through On-Demand Resources, which means when you are using Instruments on your own development, another app can in theory scan TCP ports on localhost, and steal files from your mac on the background. First http://localhost:{PORT}/Library/Preferences/com.apple.loginwindow.plist to get your username, then grab your SSH private key from home directory…

It takes a background app ~10min to find the port

Well since this situation is not so practical and background port scanning takes too much time while NetworkStatistics api or sysctlbyname("net.inet.tcp.pcblist_n",…) unreachable in the sandbox, this problem has limited impact.

Activity Manager

This has been mentioned in the previous Recon talk.

With the following methods you can have an Activity Manager on jailed iOS.

  • -[DTDeviceInfoService runningProcesses]
  • -[DTProcessControlService killPid:]

DTServiceHub has root, it can kill almost every process except itself and launchd.

App Launcher

There’s a method for launch app via SpringBoard, available on non-jailbroken devices:

-[DTSpringBoardProcessControlService launchSuspendedProcessWithDevicePath:bundleIdentifier:environment:arguments:options:]

But normally when we try to launch an app without task-for-pid_allow entitlement (e.g. from App Store) it will fail, because DTServiceHub will try to get the task port from target app, which requires jailbreak. We need to set option DTProcessControlServiceOption_LeaveSuspendedKey to NO to avoid it. Both environment and arguments works so we can mess up the environment variables as well, although not much app cares about them.


Environment matters

CFNetwork Diagnostics

According to this Q&A by Apple, CFNETWORK_DIAGNOSTICS can be set to enable tracing CFNetwork.

Technical Q&A QA1887: CFNetwork Diagnostic Logging

Still works on iOS 12, some of the headers and cookies are printed even for TLS requests:

The CFNetwork diagnostic logs

ImageIO debugging

Both on macOS and iOS, ImageIO.framework respects some environment variables for debugging:

  • IIO_PluginVerbose=1
  • IIO_DebugFileAccess=1
  • IIO_dLogImageIOAPICalls
  • IIO_dLogImageIOSPICalls=1

Well, this looks not so helpful.

SQLite Database dump

Starting from macOS Sierra / iOS 10, the built-in libsqlite.dylib of *OS has enabled the support forSQLITE_ENABLE_SQLLOG.

It’s a funny feature that it will make a copy of every single database it opens, and each query will be recored in plain text log files.

In the incoming article, I’ll write the detail about how to abuse this feature to extract SQLite databases from a connected iPhone without waiting for the full backup.

Hello DTServiceHub

Previously mentioned presentation also released a tool to send and analyze RPC message:

Anyways, my approach is kinda different. Instead of writing my own message serialization, I simply inject the frida scripts to Instruments client and call its functions. I think this won’t be too difficult to port to libimobiledevice.