# Debugging iOS Applications using CoreDevice (iOS 17 and up)

This guide shows a walkthrough of how one can use IDA Pro to debug an app installed using Xcode on a device running iOS 17 or iOS 18. The pre-debugging setup is specific to non-jailbroken devices running iOS 18 with a macOS host but the steps in IDA can be reused for most iOS/iPadOS targets (jailbroken, Corellium) with a different platform as the host. The iOS debugger is available with all platforms IDA supports though due to Apple's iOS tools being available on macs only, the workflow is easier on macOS.

Tested on macOS 15.0 (24A335) using Xcode 16.0 (16A242) with an iPhone 11 running iOS 18.0 (22A3354).

## 1. Installing an app on a non-jailbroken device

Since it's the simplest way to install an app onto an iOS device, we'll be using Xcode to quickly build and install a sample app onto our target device.

Create an iOS Game app by choosing the appropriate template:

![Xcode template selection dialog with iOS Game selected](https://1800237466-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FmbcivpLb9jyc0Sv4VOMC%2Fuploads%2Fgit-blob-03b3fc27c1e5815334123efab104a70e9de19d03%2Fgame_template.png?alt=media)

Enter a **Product name**, make note of the resulting **Bundle Identifier**. For the scope of this guide, the rest of the project options aren't relevant.

![Xcode project options dialog with "spritegame" as the Product Name and the Bundle Identifier "com.acme.spritegame" highlighted in red](https://1800237466-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FmbcivpLb9jyc0Sv4VOMC%2Fuploads%2Fgit-blob-9c8bb89cfaf8860aa35de75931dfdf42f59eaad2%2Fproject_options.png?alt=media)

In the main Xcode window, ensure that the appropriate target device (Run Destinations) is selected.

![Xcode target device selection with an iPhone "red\_iphone11" selected](https://1800237466-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FmbcivpLb9jyc0Sv4VOMC%2Fuploads%2Fgit-blob-01db4d42e06a7977dbccbe2de2d7605ff9176e18%2Fdevice_selection.png?alt=media)

If the device hasn’t been paired, a warning may appear requesting you to accept the “Trust” prompt on the device to pair with the host. If the device hasn’t been used for development recently, Xcode will install a Developer Disk Image to enable development services. It will also copy and extract the shared cache symbols to the host (in `~/Library/Developer/Xcode/iOS DeviceSupport/<device_and_os_version>/`).

If you try to Run (clicking on the “play” icon or using CMD-R) the app, an error may appear prompting you to select a development team. Xcode requires that a developer certificate is selected for the signing of the application.

![Xcode error: "Signing for "spritegame" requires a development team. Select a development team in the Signing & Capabilities editor."](https://1800237466-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FmbcivpLb9jyc0Sv4VOMC%2Fuploads%2Fgit-blob-311464f5833568feecd53ffc3f1933af7db6e7ca%2Fsigning_error.png?alt=media)

The developer team can be selected via the **Signing & Capabilities** editor.

![Xcode Signing & Capabilities editor with an arrow pointing at the selected team](https://1800237466-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FmbcivpLb9jyc0Sv4VOMC%2Fuploads%2Fgit-blob-1c20d8f0e4989eafee2760ae59981111e47273d1%2Fsigning_and_capabilities.png?alt=media)

Now that a developer certificate was selected for the app, it must also be trusted on the device. If you attempt to **Run** the app, another warning should appear prompting you to trust the certificate on the device. (Please follow those instructions to trust that certificate)

![Xcode error: "The request to open "com.acme.spritegame" failed." "Verify that the Developer App certificate for your account is trusted on your device. Open Settings on the device and navigate to General -> VPN & Device Management, then select your Developer App certificate to trust it."](https://1800237466-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FmbcivpLb9jyc0Sv4VOMC%2Fuploads%2Fgit-blob-dbc02dd4af7896af3dac08d1202f8d257283805e%2Fcertificate_error.png?alt=media)

Finally, **Run** the app and ensure that it starts properly on the target device.

![Xcode bar showing that spritegame is running on red\_iphone11](https://1800237466-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FmbcivpLb9jyc0Sv4VOMC%2Fuploads%2Fgit-blob-4efd1852faef382c9ffccf92f317a08d4145ec7b%2Fspritegame_runs_in_xcode.png?alt=media)

## 2. Setting up the debug environment, launching the app

Debugging an application on an iPhone is enabled by the `debugserver` that will attach to the application's process. The `debugserver` communicates with clients using (an extended version of) the GDB protocol. IDA comes with an iOS debugger that can "talk" that same protocol. The techniques necessary to prepare the debugging environment on an iPhone have evolved over time (and will likely keep evolving) but fundamentally the goal is always to establish a connection with a `debugserver`.

The device communication stack was revamped with iOS 17. Devices expose a series of remote services (through `remoted` which is visible via Bonjour). One of those services is a `debugproxy` which is... a proxy to the `debugserver`. `debugproxy` is a secure service, it is not available to any client. To gain access to secure services, a trusted tunnel must be setup (requires the device to be paired with the host), between the host and the device (the service to set up the tunnel is itself available via `remoted`).

One of the primary frameworks used for these communications is CoreDevice, with it Apple also provides `devicectl` which is a very convenient utility that can be used to automate certain tasks to control devices. We will use `devicectl` to perform certain tasks such as launching an application on the device. Unfortunately `devicectl` doesn't provide a direct interface to setup a trusted tunnel and retrieve ports of services exposed through it. It is however possible to reuse some commands of `devicectl` to create a tunnel and keep it open. In addition, the ports of services are written to system logs after the tunnel is set up so we can recover them with a few tricks.

To make commands provided below immediately usable, we'll define two helper environment variables. The device name is the same as the one that was used for the target device in Xcode, it can also be found using `xcrun devicectl list devices`. The bundle identifier is the identifier of the application we’d like to debug

```bash
export DEVICE_NAME="red_iphone11"
export BUNDLE_ID="com.acme.spritegame"
```

Trigger the creation of a trusted tunnel

To request the creation of the trusted tunnel, the `devicectl device notification observe` command was selected because it can keep the tunnel open for an arbitrary amount of time (controlled using the timeout). Here the name for the Darwin notification ('foobar') to observe is one that will presumably never be posted. The timeout (3000\[s]) was chosen to be long enough for a debugging session.

Retrieve the details of the `debugproxy` service (provided through the trusted tunnel) from the system logs. The command below will filter for the specific log messages we’re looking for (ipv6 address of device through tunnel and port of `debugproxy` service). In short `log show` (see `log(1)`) will show messages from the system logs; `--last 5m` will limit the search to the past 5 minutes (the tunnel should have been created when the previous command was started so 5 minutes ought to be enough); The messages we're looking for are "Info" messages so `--info` is necessary; `--predicate` is used for message filters.

```bash
log show --last 5m --info --predicate '((eventMessage CONTAINS "Adding remote service") && (eventMessage CONTAINS "debugproxy")) || (eventMessage CONTAINS "Tunnel established - interface")'  --style compact
```

> NOTE: if no log messages match the filter, it is recommended to force the recreation of the tunnel
>
> * kill the `remotepairingd` daemon: `sudo pkill -9 remotepairingd`
> * retry previous two steps
>
> Some applications can keep the tunnel open, examples: Xcode, Console and Safari. It is preferable to close them before creating the tunnel. If multiple sets of messages match the filter (with different connection details), only the *last* one should be considered (previous connections would likely be stale).

```bash
2024-09-23 10:04:31.396 Df remotepairingd[1067:8211ab] [com.apple.dt.remotepairing:remotepairingd] device-138 (00008030-000D3988119A802E): Tunnel established - interface: utun5, local fd57:8329:afda::2-> remote fd57:8329:afda::1
2024-09-23 10:04:31.422 I  remoted[342:81eff3] [com.apple.RemoteServiceDiscovery:remoted] coredevice-15> Adding remote service "com.apple.internal.dt.remote.debugproxy": {
	Properties => {
		Features => [<capacity = 1>
			0: com.apple.coredevice.feature.debugserverproxy
		]
		ServiceVersion => 1
		UsesRemoteXPC => true
	}
	Entitlement => com.apple.private.CoreDevice.canDebugApplicationsOnDevice
	Port => 49350
}
```

The relevant pieces of information in the messages are:

`remote fd57:8329:afda::1` in the first message and `Port => 49350` in the second message.

> TIP: There are some really good third party tools out there such as DoronZ's [pymobiledevice3](https://github.com/doronz88/pymobiledevice3) that reimplement the necessary machinery to create a trusted tunnel and make services available.

Launch the app (`--start-stopped` will make it wait at the process entry point)

```bash
xcrun devicectl device process launch -d $DEVICE_NAME --start-stopped $BUNDLE_ID
```

Retrieve the PID of the process.

```bash
xcrun devicectl device info processes -d $DEVICE_NAME | grep $(awk '{print A[split($1, A, "\.")]}' <<< $BUNDLE_ID)
```

```bash
820   /private/var/containers/Bundle/Application/4629EEFD-0AD5-4B3C-B773-FD7D643BC376/spritegame.app/spritegame
```

We have now created a trusted tunnel, found the connection details necessary for the `debugproxy`, launched an app and fetched its PID.

## 3. Debugging in IDA

Open the application executable (typically located in `~/Library/Developer/Xcode/DerivedData/<project_id>/Build/Products/Debug-iphoneos/<appname>.app/<appname>`, the path to the build folder can also be retrieved via Xcode **Product>Copy Build Folder Path**) in IDA.

Open the **Debugger>Process options..** dialog.

![IDA with menu bar open on Debugger>Process options...](https://1800237466-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FmbcivpLb9jyc0Sv4VOMC%2Fuploads%2Fgit-blob-220e3168deabbfaa08bc9012a2ef647f8d8d2fc9%2Fprocess_options.png?alt=media)

Fill-in the **Hostname** (address) and **Port** of the `debugproxy`. Please use the ones retrieved from the system logs earlier.

The **Application** and **Parameters** fields can safely be ignored since we’ll be attaching to a process. For this example the input file is the main executable for the application, as such the **Input file** field doesn't need to be modified.

![IDA debug application setup dialog with Hostname and Port fields filled-in](https://1800237466-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FmbcivpLb9jyc0Sv4VOMC%2Fuploads%2Fgit-blob-39334424af75c401fd5dd344879c630fe99762de%2Fdebug_setup.png?alt=media)

Open the **Debugger specific options**.

IDA can speed up the loading of shared cache symbols if they have been copied and extracted to the host machine, it is highly recommended to provide the **Symbol path** (normally `~/Library/Developer/Xcode/iOS DeviceSupport/<device_and_os_version>/Symbols`). **Launch debugserver automatically** should be disabled as it is used to communicate with devices using the MobileDevice framework (no longer a viable option as of iOS 17). Accept this dialog.

![IDA iOS configuration dialog with Symbol path field filled-in](https://1800237466-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FmbcivpLb9jyc0Sv4VOMC%2Fuploads%2Fgit-blob-a1641d5a44ac565c554a141be2c138d8c6183085%2Fios_config.png?alt=media)

Optionally, open the **Debugger options** and enable **Suspend on debugging start**. The **Debugger setup** dialog can then be closed as well as the **Debug application setup** dialog.

![IDA Debug options dialog with "Suspend on debugging start" checkbox checked.](https://1800237466-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FmbcivpLb9jyc0Sv4VOMC%2Fuploads%2Fgit-blob-90a531a412173148dcaafc7e5b115b24eec59866%2Fdebug_options.png?alt=media)

Now that the necessary connection details have been given to IDA, we can attach to the target process.

Open **Debugger>Attach to process...** We will provide the PID manually so accept this dialog.

![IDA Choose process dialog with a single entry "\<enter a PID to attach>"](https://1800237466-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FmbcivpLb9jyc0Sv4VOMC%2Fuploads%2Fgit-blob-2c322e75257fff5c07a15140ad62e07694eee1ea%2Fchoose_process.png?alt=media)

Enter the PID of the target process retrieved earlier using `devicectl` and accept this dialog.

![IDA dialog asking for a PID input](https://1800237466-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FmbcivpLb9jyc0Sv4VOMC%2Fuploads%2Fgit-blob-5e4b9e685f4d3f986d2a17bd61859c69420c06a3%2Fprovide_pid.png?alt=media)

Profit! The Debugging session should start.

![IDA debugging session successfully started, IDA View showing PC on first instruction of \_\_dyld\_start](https://1800237466-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FmbcivpLb9jyc0Sv4VOMC%2Fuploads%2Fgit-blob-1ff93b281681fa8fe8b2b229acb1d5b3421a3215%2Fida_debugging_start.png?alt=media)
