OWASP Mobile Application Security Testing Guide: MASTG TECH 0084

---
title: Debugging
platform: ios
---

Coming from a Linux background, you'd expect the `ptrace` system call to be as powerful as you're used to, but, for some reason, Apple decided to leave it incomplete. iOS debuggers such as LLDB use it to attach, step, or continue the process, but they cannot use it to read or write memory (all `PT_READ_*` and `PT_WRITE*` requests are missing). Instead, they have to get a so-called Mach task port (by calling `task_for_pid` with the target process ID) and then use the Mach IPC interface API functions to perform actions such as suspending the target process and reading/writing register states (`thread_get_state`/`thread_set_state`) and virtual memory (`mach_vm_read`/`mach_vm_write`).

> For more information you can refer to the LLVM project in GitHub which contains the [source code for LLDB](https://github.com/llvm/llvm-project/tree/main/lldb "LLDB") as well as Chapter 5 and 13 from "Mac OS X and iOS Internals: To the Apple's Core" [#levin] and Chapter 4 "Tracing and Debugging" from "The Mac Hacker's Handbook" [#miller].

## Debugging with LLDB

The default `debugserver` executable that Xcode installs can't be used to attach to arbitrary processes (it is usually used only for debugging self-developed apps deployed with Xcode). To enable debugging of third-party apps, the `task_for_pid-allow` entitlement must be added to the debugserver executable so that the debugger process can call `task_for_pid` to obtain the target Mach task port, as described earlier. An easy way to do this is to add the entitlement to the [debugserver binary shipped with Xcode](https://web.archive.org/web/[PII_REDACTED]4236/https://iphonedevwiki.net/index.php/Debugserver "Debug Server on the iPhone Dev Wiki").

To obtain the executable, mount the following DMG image:

```bash
/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport/<target-iOS-version>/DeveloperDiskImage.dmg
```

You'll find the debugserver executable in the `/usr/bin/` directory on the mounted volume. Copy it to a temporary directory, then create a file called `entitlements.plist` with the following content:

```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/ PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>com.apple.springboard.debugapplications</key>
    <true/>
    <key>run-unsigned-code</key>
    <true/>
    <key>get-task-allow</key>
    <true/>
    <key>task_for_pid-allow</key>
    <true/>
</dict>
</plist>
```

Apply the entitlement with @MASTG-TOOL-0114:

```bash
codesign -s - --entitlements entitlements.plist -f debugserver
```

Copy the modified binary to any directory on the test device. The following examples use usbmuxd to forward a local port through USB.

```bash
iproxy 2222 22
scp -P 2222 debugserver root@localhost:/tmp/
```

Note: On iOS 12 and later, follow this procedure to sign the debugserver binary obtained from the Xcode image.

1) Copy the debugserver binary to the device via scp, for example, in the /tmp folder.

2) Connect to the device via SSH and create the file, named entitlements.xml, with the following content:

   ```xml
   <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN http://www.apple.com/DTDs/PropertyList-1.0.dtd">
   <plist version="1.0">
   <dict>
      <key>platform-application</key>
      <true/>
      <key>com.apple.private.security.no-container</key>
      <true/>
      <key>com.apple.private.skip-library-validation</key>
      <true/>
      <key>com.apple.backboardd.debugapplications</key>
      <true/>
      <key>com.apple.backboardd.launchapplications</key>
      <true/>
      <key>com.apple.diagnosticd.diagnostic</key>
      <true/>
      <key>com.apple.frontboard.debugapplications</key>
      <true/>
      <key>com.apple.frontboard.launchapplications</key>
      <true/>
      <key>com.apple.security.network.client</key>
      <true/>
      <key>com.apple.security.network.server</key>
      <true/>
      <key>com.apple.springboard.debugapplications</key>
      <true/>
      <key>com.apple.system-task-ports</key>
      <true/>
      <key>get-task-allow</key>
      <true/>
      <key>run-unsigned-code</key>
      <true/>
      <key>task_for_pid-allow</key>
      <true/>
   </dict>
   </plist>
   ```

3) Type the following command to sign the debugserver binary using @MASTG-TOOL-0111:

   ```bash
   ldid -Sentitlements.xml debugserver
   ```

4) Verify that the debugserver binary can be executed via the following command:

```bash
./debugserver
```

You can now attach debugserver to any process running on the device.

```bash
VP-iPhone-18:/tmp root# ./debugserver *:1234 -a 2670
debugserver-@(#)PROGRAM:debugserver  PROJECT:debugserver-320.2.89
for armv7.
Attaching to process 2670...
```

With the following command, you can launch an application via debugserver running on the target device:

```bash
debugserver -x backboard *:1234 /Applications/MobileSMS.app/MobileSMS
```

Attach to an already running application:

```bash
debugserver *:1234 -a "MobileSMS"
```

You may connect now to the iOS device from your host computer:

```bash
(lldb) process connect connect://<ip-of-ios-device>:1234
```

Typing `image list` lists the main executable and all dependent libraries.

## Debugging Release Apps

In the previous section, we learned about how to set up a debugging environment on an iOS device using LLDB. In this section, we will use this information and learn how to debug a third-party release application. We will continue using @MASTG-APP-0025 and debug it.

In contrast to a debug build, code compiled for a release build is optimized for maximum performance and minimal binary size. As a general best practice, most debug symbols are stripped in release builds, which adds complexity when reverse-engineering and debugging binaries.

Because the debug symbols are missing, symbol names are absent from the backtraces, and setting breakpoints using function names is not possible. Fortunately, debuggers also support setting breakpoints directly on memory addresses. In the remainder of this section, we will learn how to do so and ultimately solve the crackme challenge.

Some groundwork is needed before setting a breakpoint using memory addresses. It requires determining two offsets:

1. Breakpoint offset: The _address offset_ of the code where we want to set a breakpoint. This address is obtained by static code analysis using a disassembler such as Ghidra.
2. ASLR shift offset: The _ASLR shift offset_ for the current process. Since the ASLR offset is randomly generated on each application instance, it must be obtained for each debugging session. This is determined using the debugger itself.

> iOS is a modern operating system that uses multiple techniques to mitigate code-execution attacks, including Address Space Layout Randomization (ASLR). On each application execution, a random ASLR shift offset is generated, which shifts various process data stru