LogoLogo
IDA 9.1
IDA 9.1
  • Welcome to Hex-Rays docs
    • What's new?
  • Getting Started
    • Install IDA
    • Licensing
    • Basic Usage
    • What's next?
  • User Guide
    • User Interface
      • Menu Bar
        • File
          • Load file
          • Script File
          • Script command
          • Produce output files
          • Invoke OS Shell
          • Take database snapshot
          • Save database
          • Save database as...
          • Abort IDA
          • Exit IDA
        • Edit
          • Export data
          • Undo an action
          • Redo an action
          • Clear undo history
          • Disable undo
          • Convert to instruction
          • Convert to data
          • Convert to string literal
          • Convert to array
          • Undefine a byte
          • Give Name to the Location
          • Operand types
            • Offset
            • Number
            • Perform en masse operation
            • Convert operand to character
            • Convert operand to segment
            • Complex Offset Expression
            • Convert operand to symbolic constant (enum)
            • Convert operand to stack variable
            • Change operand sign
            • Bitwise negate operand
            • User-defined operand
            • Set operand type
          • Comments
          • Functions
          • Structs
          • Segments
          • Patch core
          • Other
            • Rename Any Address
          • Plugins
        • Jump
          • Center current line in window
          • Problems List
        • Search
          • REGULAR EXPRESSION SYNTAX SUMMARY
        • View
          • Open subviews
          • Graphs
          • Arrows window
          • Database snapshot manager
          • Highlighting identifiers
          • Browser options
          • Lumina options
          • Assembler level and C level types
          • C++ type details
          • Bookmarks window
          • Calculator
          • View segment registers
          • View Internal Flags
          • Hide
          • Unhide
          • Del hidden range
          • Hide all items
          • Unhide all items
          • Setup hidden items
        • Debugger
          • Debugger window
          • Process Control
            • Start process
            • Process options
            • Pause process
            • Terminate process
            • Step into
            • Step into (backwards)
            • Step over
            • Step over (backwards)
            • Run to cursor
            • Run to cursor (backwards)
            • Run until return
            • Attach to process
            • Detach from process
            • Set current ip
            • Show application screen
          • Breakpoints
          • Watches
          • Tracing
          • Source code view
            • Watch view (source level)
          • Process Memory
            • Take memory snapshot
            • Manual memory regions
            • Refresh memory
          • Thread list
          • Module list
          • Stack trace
          • Exceptions
          • Debugger options
          • Switch debugger
        • Lumina
        • Options
          • Low & High Suspicious Operand Limits
        • Windows
          • Rename a stack variable
          • Miscellanous Options
          • Environment variables
          • Reset Hidden Messages
          • Various dialog help messages
          • Output window
        • List of all menu options
      • Desktops
      • Command line
      • License Manager
      • How To Use List Viewers in IDA
      • Database conversion from idb to i64
    • Disassembler
      • Interactivity
      • Background Analysis
      • Graph view
        • Graphing tutorial
      • Proximity view
      • Navigation
        • Anchor
        • How to Enter a Segment Value
        • How to Enter a Number
        • How to Enter an Identifier
        • How to enter text
        • How to Enter an Address
      • Disassembly Gallery
        • Philips 51XA-G3
        • 6502 and 65C02 Disassembler
        • 6301, 6303, 6800, 6801 and 6803 Disassembler
        • 68040, Amiga
        • 6805 Disassembler
        • 6808 Disassembler
        • 6809 OS9 Flex Disassembler
        • 6809 Disassembler
        • 6811 Disassembler
        • 68HC12 Disassembler
        • 68HC16 Disassembler
        • 68k Amiga Disassembler
        • 68k Mac OS
        • 68k Palm Pilot
        • Unix COFF
        • NEC 78k0 and 78k0s Processor
        • 80196 Processor
        • 8051 Disassembler
        • Analog Devices 218x.
        • Alpha Processor – NT COFF
        • Alpha Processor – Unix ELF
        • Android ARM Executables (.elf)
        • ARC Processor
        • ARM Processor EPOC App
        • ARM Processor EPOC PE File
        • ARM Processor EPOC ROMFile
        • EPOC SIS File Handler
        • ARM Processor iOS (iPhone): Unlock
        • ARM Processor iOS (iPhone): Objective-C metadata
        • ARM Processor iOS (iPhone): Objective-C Instance variables
        • ARM Processor iOS (iPhone): Parameter Identification & Tracking (PIT)
        • ARM Processor iOS (iPhone): Start
        • ARM Processor iOS (iPhone): Switch statements
        • ARM Processor iOS (iPhone): C++ signatures
        • ARM Processor iOS (iPhone): Write
        • ARM Processor: Linux ELF
        • ARM Processor: AOF SDK
        • ARM Processor: Windows CE COFF Format
        • ARM Processor: Windows CE PE Format
        • ATMEL AVR Disassembler
        • C166 Processor
        • C166 Processor with ELF file
        • Rockwell C39
        • Microsoft .NET CLI Disassembler. VisualBasic library
        • CR16
        • Android Dalvik Executables (.dex)
        • Microsoft .NET CLI Disassembler
        • DSP56K
        • Fujitsu FR (.elf)
        • Gameboy
        • H8 300: COFF FILE Format
        • H8 300s: COFF FILE Format
        • H8 500
        • HPPA Risc Processor: HP-UX SOM
        • i51
        • i860
        • Intel i960
        • Intel IA-64 (Itanium)
        • Java Bytecode
        • Angstrem KR 1878
        • Renesas/Hitachi M16C
        • Renesas/Hitachi M32R
        • M740
        • M7700
        • M7900
        • MIPS Processor: Nintendo N64
        • MIPS R5900 Processor : Sony bin
        • MIPS Processor: Sony ELF
        • MIPS Processor: Sony PSX
        • MIPS Processor: Sony PSX
        • MIPS Processor: Unix COFF File Format
        • MIPS Processor: Unix ELF File Format
        • MIPS Processor: Windows CE PE File Format
        • MIPS Processor: Windows CE PE2 File Format
        • Panasonic MN102
        • Atmel OAK DSP
        • 80×86 Architecture: DOS Extender
        • 80×86 Architecture: Watcom Runtime
        • 80×86 Architecture: Geos APP
        • 80×86 Architecture: Geos DRV
        • 80×86 Architecture: Geos LIB
        • 80×86 Architecture: GNU COFF Format
        • 80×86 Architecture: OS/2 Linear Executable Format
        • 80×86 Architecture: Netware NLM
        • 80×86 Architecture: QNX Executable
        • 80×86 Architecture: Watcom Runtime
        • 80×86 Architecture: Windows OMF
        • 80×86 Architecture: Windows Portable Executable Format
        • 80×86 Architecture: Windows Virtual Device Driver
        • 80×86 Architecture: Windows 16 bits DLL
        • X-Box Disassembler
        • PDP 11: SAV File
        • PIC
        • PIC 12xx
        • Power PC AIF ECOFF file Format
        • Power PC Linux ELF
        • Mac OS PEF File
        • Mac OS X File
        • Windows NT PE File
        • Hitachi SH-1 Processor
        • Hitachi SH-3 Processor: Windows CE COFF format
        • Hitachi SH-3 Processor: Windows CE PE format
        • Hitachi SH-4 Processor: ELF File Format
        • Hitachi SH-4 Processor: Windows CE PE File Format
        • Super Nintendo Entertainement System (SNES)
        • SPARC Solaris COFF
        • SPARC Solaris ELF
        • SPARC Sun ELF
        • SPARC Sun ELF SO
        • ST 20C4
        • ST 7
        • ST 9
        • Toshiba TLCS 900
        • TMS 320c2 COFF
        • TMS 320c5
        • TMS 320c54
        • TMS 320c6 COFF File Format
        • TRICORE
        • SunPlus unSP
        • NEC V850
        • Z180 COFF File Format
        • Z380 COFF File Format
        • Z8
        • Z80
      • Supported processors
      • Supported file formats
        • Windmp file loader
      • Bitfields
        • Bit Fields tutorial
      • Structures tutorial
      • Union tutorial
      • Variable length structures tutorial
      • Data types, operands and constructs
      • Packed executables
    • Decompiler
      • Prerequisites
      • Quick primer
      • Exception handler
      • Introduction to Decompilation vs. Disassembly
        • Comparisons of ARM disassembly and decompilation
        • Comparisons of PowerPC disassembly and decompilation
        • Comparisons of MIPS disassembly and decompilation
        • Hex-Rays v7.4 vs. v7.3 Decompiler Comparison Page
        • Hex-Rays v7.3 vs. v7.2 Decompiler Comparison Page
        • Hex-Rays v7.2 vs. v7.1 Decompiler Comparison Page
      • Interactive operation
        • Rename
        • Set type
        • Set number representation
        • Edit indented comment
        • Edit block comment
        • Hide/unhide C statements
        • Split/unsplit expression
        • Force call type
        • Set call type
        • Add/del variadic arguments
        • Del function argument
        • Add/delete function return type
        • Jump to cross reference
        • Jump to cross reference globally
        • Generate HTML file
        • Mark/unmark as decompiled
        • Copy to assembly
        • Show/hide casts
        • Reset pointer type
        • Convert to struct *
        • Create new struct type
        • Split variable
        • Select union field
        • Jump to paired paren
        • Collapse/uncollapse item
        • Map to another variable
      • Batch operation
      • Configuration
      • Third party plugins
      • Floating point support
      • Support for intrinsic functions
      • Overlapped variables
      • gooMBA
      • Failures and troubleshooting
      • FAQ
      • Limitations
      • Tips and tricks
    • Debugger
      • Instant debugger
      • Remote debugging
        • Remote iOS Debugger
        • Android debugger
        • Dalvik debugger
        • Remote GDB Debugger
          • Remote GDB Debugger options
          • Debugging with gdbserver
          • Debugging with VMWare
          • Debugging with OpenOCD
          • Debugging with QEMU
          • External programs and GDB Debugger
          • Debugging code snippets with QEMU
        • PIN debugger
          • Building the PIN tool
          • Connecting a remote PIN tool instance from IDA
          • PIN support for MacOSX
        • Replayer debugger
        • Bochs debugger
          • Bochs Disk Image operation mode
          • Bochs IDB operation mode
          • Bochs PE operation mode
          • Bochs debugger FAQ
      • Local debugging
        • WinDbg Debugger
        • WinDbg: Time Travel Debugging
        • Linux debugger
        • Intel/ARM macOS debugger
      • Debugger tutorials
        • Debugging Dalvik Programs
        • IDA Win32 Local Debugging
        • IDA Linux Local Debugging
        • IDA Linux to Win64 Debugging
        • IDA Win32 to Linux Debugging
        • Debugging Mac OSX Applications with IDA Pro
        • Debugging iOS Applications using CoreDevice (iOS 17 and up)
        • Debugging iOS Applications with IDA Pro
        • Debugging Linux Applications locally
        • Debugging Linux/Windows Applications with PIN Tracer module
        • Debugging Windows Applications with IDA Bochs Plugin
        • Debugging Windows Applications with IDA WinDbg Plugin
        • Using the Bochs debugger plugin in Linux
        • Debugging Windows Kernel with VMWare and IDA WinDbg Plugin
        • Debugging Linux Kernel under VMWare using IDA GDB debugger
        • Windows Debugger Hub
        • Linux Debugger
        • Debugging a Windows executable locally and remotely
        • Debugging the XNU Kernel with IDA Pro
        • Remote debugging with IDA Pro
        • IDA Scriptable Debugger: overview
          • IDA Scriptable Debugger: scriptability
        • Debugging code snippets with QEMU debugger (a la IDA Bochs debugger)
        • Trace Replayer and managing traces
        • Using IDA Pro's tracing features
        • Working with PIN
        • Appcall
    • Creating Signatures
      • FLIRT
        • IDA F.L.I.R.T. Technology: In-Depth
        • Generate FLIRT signature file
        • Supported Compilers
          • Turbo Pascal
          • Delphi
      • Makesig
    • Types
      • Creating Type Libraries
        • IDAClang
        • TILIB
    • Configuration
      • Configuration files
      • Command line switches
      • Keyboard macros
      • UI/Fonts/Themes
      • Shortcuts
      • Customizing IDA
      • CSS-based styling
    • Teams
      • Diffing and Merging Databases with IDA Teams
      • Teams lc command reference manual
      • hv command reference manual
      • Hex-Rays Vault’s visual client user manual
    • Lumina
      • lc command reference manual
    • Plugins
      • Open Plugin Architecture
      • Plugin options
      • Plugins Shipped with IDA
        • Swift plugin
        • Golang plugin
        • Rust plugin
        • picture_search
        • Objective-C Analysis Plugin
        • DYLD Shared Cache Utils
        • Borland RTTI descriptors plugin
        • DWARF plugin
        • Patfind plugin
        • IDA Feeds
          • FLIRT Signature Bundle
      • Plugin Contest
      • How to write your own plugin?
    • Helper Tools
    • idalib
    • Third-Party Licenses
      • Apache License for Ghidra
      • Apache License for LLVM
      • Common Public License Version 1.0
      • APPLE PUBLIC SOURCE LICENSE
      • PCRE2 LICENCE
      • GNU Lesser General Public License v2.1 for libiberty
    • Floating licenses
  • Developer Guide
    • C++ SDK
      • Getting Started
      • Reference
      • Using the Decompiler SDK: Decompiler plugin
      • Examples
      • How to create a plugin?
      • Porting Guide from IDA 8.x to 9.0
    • IDAPython
      • Getting Started
      • Reference
      • Examples
      • How to create a plugin?
      • Porting Guide from IDA 8.x to 9.0
    • IDC
      • Core concepts
        • Expressions
        • Statements
        • Functions
        • Variables
        • Constants
        • Exceptions
        • Classes
        • Predefined symbols
        • loader_input_t class
        • Slices
      • Reference
      • Examples
        • Analyzing encrypted code
  • Admin Guide
    • Lumina server
    • Teams server
    • License server
      • Hex-Rays License Server Migration Guide
      • Hex-Rays License Server on WSL
  • Release Notes
    • IDA 9.1
    • IDA 9.0sp1
    • IDA 9.0
    • IDA 8.5
    • IDA 8.4sp2
    • IDA 8.4sp1
    • IDA 8.4
    • IDA 8.3
    • IDA 8.2sp1
    • IDA 8.2
    • IDA 8.1
    • IDA 8.0sp1
    • IDA 8.0
    • IDA 7.7sp1
    • IDA 7.7
    • IDA 7.6sp1
    • IDA 7.6
    • IDA 7.5sp3
    • IDA 7.5sp2
    • IDA 7.5sp1
    • IDA 7.5
    • IDA 7.4sp1
    • IDA 7.4
    • IDA 7.3
      • IDA 7.3 Undo: IDA can do it
    • IDA 7.2
      • IDA 7.2 The Mac Rundown
    • IDA 7.1
      • IDA 7.1 Debugger API 7.1 Porting Guide
    • IDA 7.0sp1
    • IDA 7.0
      • Internationalization (i18n)
      • Automatic discovery of string literals
      • API 7.0 Porting Guide
      • IDAPython backward compatibility
    • IDA 6.95
    • IDA 6.9
    • IDA 6.8
    • IDA 6.7
    • IDA 6.6
    • IDA 6.5
    • IDA 6.4
    • IDA 6.3
    • IDA 6.2
    • IDA 6.1
    • IDA 6.0
    • IDA 5.7
    • IDA 5.6
    • IDA 5.5
      • 5.5 Gallery
      • 5.5 Comparison
    • IDA 5.4
    • IDA 5.3
    • IDA 5.2
    • IDA 5.1
    • IDA 5.0
    • IDA 4.9SP
    • IDA 4.9
    • IDA 4.8
    • IDA 4.7
    • IDA 4.6
    • IDA 4.x
    • IDA 3.x
    • Cumulative bugfix for IDA
  • Archive
    • IDA’s Windbg plugin
    • IDA’s Bochs debugger plugin
    • IDA’s Bochs debugger plugin 2
    • DosWin32
    • Hex-Rays v1.1 vs. v1.0 Decompiler Comparison Page
    • Hex-Rays v1.2 vs. v1.1 Decompiler Comparison Page
    • Hex-Rays v1.3 vs. v1.2 Decompiler Comparison Page
    • Hex-Rays v1.6 vs. v1.5 Decompiler Comparison Page
    • Hex-Rays v1.7 vs. v1.6 Decompiler Comparison Page
    • Costly Greetings – An Adventure In Hostile Code Analysis
    • An Adventure In Hostile Code Analysis: Description
    • An Adventure In Hostile Code Analysis: Disassembly
    • Improved code flow analysis
    • Program Navigation Bar
    • IDA Home Contest
    • Pimp My IDA: vote results
    • Turning off IDA 6.x compatibility in IDAPython
    • Porting guide for IDA 7.4 turning off IDA 6.x API backwards-compatibility by default
    • Porting guide for IDA 7.4 IDAPython and Python 3
    • IDAPython and Python 3
    • Porting guide for changes in IDAPython-on-Python-3 APIs
    • Debugging iOS Applications With IDA
    • IDA Win32 to Win32 Debugging
    • IDA Win32 to Win64 Debugging
    • Legacy license server: Floating Licenses
      • Installing on Linux
      • Installing on Windows
      • Installing on OS X
    • Decompiler Installation
    • Enumerated types tutorial
  • Bug Bounty
Powered by GitBook
LogoLogo

Need Help?

  • FAQs
  • Support

Community

  • Forum
  • Plugins

Resources

  • Blog
  • Download center

© 2025 Copyright Hex-Rays

On this page
  • Overview
  • Getting Started
  • Preparing a Debugging Environment
  • Source Level Debugging
  • Debugging DYLD
  • Debugging the DYLD Shared Cache
  • Initial Analysis
  • Debugger Configuration
  • Further Analysis
  • Debugging System Applications
  • Patching the debugserver
  • IDA Configuration
  • Conclusion
  • Troubleshooting
  • Notes

Was this helpful?

Export as PDF
  1. User Guide
  2. Debugger
  3. Debugger tutorials

Debugging iOS Applications with IDA Pro

Last updated 2 months ago

Was this helpful?

Copyright 2020 Hex-Rays SA

Overview

This tutorial discusses optimal strategies for debugging native iOS applications with IDA Pro.

IDA Pro supports remote debugging on any iOS version since iOS 9 (including iPadOS). Debugging is generally device agnostic so it shouldn't matter which hardware you're using as long as it's running iOS. The debugger itself can be used on any desktop platform that IDA supports (Mac/Windows/Linux), although using the debugger on Mac makes more features available.

Note that IDA supports debugging on both jailbroken and non-jailbroken devices. Each environment provides its own unique challenges and advantages, and we will discuss both in detail in this writeup.

Getting Started

The quickest way to get started with iOS debugging is to use Xcode to install a sample app on your device, then switch to IDA to debug it.

In this example we'll be using an iPhone SE 2 with iOS 13.4 (non-jailbroken) while using IDA 7.5 SP1 on OSX 10.15 Catalina. Start by launching Xcode and use menu File>New>Project... to create a new project from one of the iOS templates, any of them will work:

After selecting a template, set the following project options:

Note the bundle identifier primer.idatest, it will be important later. For the Team option choose the team associated with your iOS Developer account, and click OK. Before building be sure to set the target device in the top left of the Xcode window:

Now launch the build in Xcode. If it succeeds then Xcode will install the app on your device automatically.

Preparing a Debugging Environment

Now that we have a test app installed on our device, let's prepare to debug it. First we must ensure that the iOS debugserver is installed on the device. Since our device is not jailbroken, this is not such a trivial task. By default iOS restricts all remote access to the device, and such operations are managed by special MacOS Frameworks.

$ ios_deploy listen
Device connected:
- name:    iPhone SE 2
- model:   iPhone SE 2
- ios ver: 13.4
- build:   17E8255
- arch:    arm64e
- id:      XXXXXXXX-XXXXXXXXXXXXXXXX

Use the mount phase to install DeveloperDiskImage.dmg, which contains the debugserver:

$ export DEVELOPER=/Applications/Xcode.app/Contents/Developer
$ export DEVTOOLS=$DEVELOPER/Platforms/iPhoneOS.platform/DeviceSupport
$ ios_deploy mount -d $DEVTOOLS/13.4/DeveloperDiskImage.dmg

The device itself is now ready for debugging. Now let's switch to IDA and start configuring the debugger. Load the idatest binary in IDA, Xcode likely put it somewhere in its DerivedData directory:

$ alias ida64="/Applications/IDA\ Pro\ 7.5\ sp1/ida64.app/Contents/MacOS/ida64"
$ export XCDATA=~/Library/Developer/Xcode/DerivedData
$ ida64 $XCDATA/idatest/Build/Products/Debug-iphoneos/idatest.app/idatest

Then go to menu Debugger>Select debugger... and select Remote iOS Debugger:

When debugging a binary remotely, IDA must know the full path to the executable on the target device. This is another task that iOS makes surprisingly difficult. Details of the filesystem are not advertised, so we must use ios_deploy to retrieve the executable path. Use the path phase with the app's bundle ID:

$ ios_deploy path -b primer.idatest
/private/var/containers/Bundle/Application/<UUID>/idatest.app/idatest

NOTE: the path contains a hex string representing the application's 16-byte UUID. This id is regenerated every time you reinstall the app, so you must update the path in IDA whenever the app is updated on the device.

Now go to Debugger>Debugger options>Set specific options... and ensure the following fields are set:

Make special note of the Symbol path option. This directory contains symbol files extracted from your device. Both IDA and Xcode use these files to load symbol tables for system libraries during debugging (instead of reading the tables in process memory), which will dramatically speed up debugging.

Xcode likely already created this directory when it first connected to your device, but if not you can always use ios_deploy to create it yourself:

$ ios_deploy symbols
Downloading /usr/lib/dyld
Downloading 0.69 MB of 0.69 MB
Downloading /System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64e
Downloading 1648.38 MB of 1648.38 MB
Extracting symbol file: 1866/1866
/Users/troy/Library/Developer/Xcode/iOS DeviceSupport/13.4 (17E8255)/Symbols: done

Also ensure that the Launch debugserver automatically option is checked. This is required for non-jailbroken devices since we have no way to launch the server manually. This option instructs IDA to establish a connection to the debugserver itself via the MacOS Frameworks, which will happen automatically at debugging start.

Lastly, Xcode might have launched the test application after installing it. Use the proclist phase to retreive the app's pid and terminate it with the kill phase:

$ ios_deploy proclist -n idatest
32250
$ ios_deploy kill -p 32250

Finally we are ready to launch the debugger. Go to main in IDA's disassembly view, use F2 to set a breakpoint, then F9 to launch the process, and wait for the process to hit our breakpoint:

You are free to single step, inspect registers, and read/write memory just like any other IDA debugger.

Source Level Debugging

You can also use IDA to debug the source code of your iOS application. Let's rebuild the idatest application with the DWARF with dSYM File build setting:

Since the app is reinstalled, the executable path will change. We'll need to update the remote path in IDA:

$ ios_deploy path -b primer.idatest

Be sure to enable Debugger>Use source-level debugging, then launch the process. At runtime IDA will be able to load the DWARF source information:

Note that the debugserver does not provide DWARF information to IDA - instead IDA looks for dSYM bundles in the vicinity of the idb on your local filesystem. Thus if you want IDA to load DWARF info for a given module, both the module binary and its matching dSYM must be in the same directory as the idb, or in the idb's parent directory.

For example, in the case of the idatest build:

$ tree
.
├── idatest.app
│   ├── idatest
│   └── idatest.i64
└── idatest.app.dSYM
    └── Contents
        └── Resources
            └── DWARF
                └── idatest

IDA was able to find the idatest binary next to idatest.i64, as well as the dSYM bundle next to the parent app directory.

If IDA can't find DWARF info on your filesystem for whatever reason, try launching IDA with the command-line option -z440010, which will enable much more verbose logging related to source-level debugging:

Looking for Mach-O file "idatest.app/idatest.dSYM/Contents/Resources/DWARF/idatest"
File "idatest.app/idatest.dSYM/Contents/Resources/DWARF/idatest" exists? -> No.
Looking for Mach-O file "idatest.app.dSYM/Contents/Resources/DWARF/idatest"
File "idatest.app.dSYM/Contents/Resources/DWARF/idatest" exists? -> Yes.
Looking for cpu=16777228:0, uuid=7a09f307-7503-3c0d-a182-ab552c1bf182.
Candidate: cpu=16777228:0, uuid=7a09f307-7503-3c0d-a182-ab552c1bf182.
Found, with architecture #0
DWARF: Found DWARF file "idatest.app.dSYM/Contents/Resources/DWARF/idatest"

Debugging DYLD

IDA can also be used to debug binaries that are not user applications. For example, dyld.

The ability to debug dyld is a nice advantage because it allows us to observe critical changes in the latest versions of iOS (especially regarding the shared cache) before a jailbreak is even available. We document this functionality here in the hopes it will be useful to others as well.

~/Library/Developer/Xcode/iOS DeviceSupport/13.4 (17E8255)/Symbols/usr/lib/dyld

The target application will be a trivial helloworld program:

#include <stdio.h>

int main(void)
{
  puts("hello, world!\n");
  return 0;
}

Compile and install this app on your device, then set the following fields in Debugger>Process options...

Under Debugger>Debugger options, enable Suspend on debugging start. This will instruct IDA to suspend the process at dyld's entry point, before it has begun binding symbols. Now launch the process with F9 - immediately the process will be suspended at __dyld_start:

Double-click on the helloworld module to bring up its symbol list and go to the _main function:

Note that function sub_1009CBF98 is the stub for puts:

The iOS debugger supports watchpoints. Now would be a good time to use one:

ida_dbg.add_bpt(0x1009CC000, 8, BPT_WRITE)

Resume the process and wait for dyld to trigger our watchpoint:

The instruction STR X21 [X19] triggered the watchpoint, and note the value in X21 (BB457A81BA95ADD8) which is the authenticated pointer to puts. Where did this value come from? We can see that X21 was previously set with MOV X21, X0 after a call to this function:

dyld3::MachOLoaded::ChainedFixupPointerOnDisk::Arm64e::signPointer

It seems like we're on the right track. Also note that IDA was able to extract a nice stack trace despite dyld's heavy use of PAC instructions to authenticate return addresses on the stack:

Address    Module  Function
100CA5E14  dyld    ____ZNK5dyld311MachOLoaded21fixupAllChainedFixups_block_invoke
100CA5EEC  dyld    dyld3::MachOLoaded::walkChain
100CA5BF0  dyld    dyld3::MachOLoaded::forEachFixupInAllChains
100CA5B50  dyld    dyld3::MachOLoaded::fixupAllChainedFixups
100CA2210  dyld    ____ZN5dyld36Loader18applyFixupsToImage_block_invoke.68
100CB0218  dyld    dyld3::MachOAnalyzer::withChainStarts
100CA2004  dyld    ____ZN5dyld36Loader18applyFixupsToImage_block_invoke_3
100CB3314  dyld    dyld3::closure::Image::forEachFixup
100CA15EC  dyld    dyld3::Loader::applyFixupsToImage
100CA0A00  dyld    dyld3::Loader::mapAndFixupAllImages
100C88784  dyld    dyld::launchWithClosure
100C86BE0  dyld    dyld::_main
100C81228  dyld    dyldbootstrap::start
100C81034  dyld    __dyld_start

This leads us to the following logic in the dyld-733.6 source:

// authenticated bind
newValue = (void*)(bindTargets[fixupLoc->arm64e.bind.ordinal]);
if (newValue != 0)
	newValue = (void*)fixupLoc->arm64e.signPointer(fixupLoc, newValue);

Here, fixupLoc (off_109CC00) and newValue (address of puts) are passed as the loc and target arguments for Arm64e::signPointer:

uint64_t discriminator = authBind.diversity;
if ( authBind.addrDiv )
	discriminator = __builtin_ptrauth_blend_discriminator(loc, discriminator);
switch ( authBind.key ) {
  case 0: // IA
    return __builtin_ptrauth_sign_unauthenticated(target, 0, discriminator);
  case 1: // IB
    return __builtin_ptrauth_sign_unauthenticated(target, 1, discriminator);
  case 2: // DA
    return __builtin_ptrauth_sign_unauthenticated(target, 2, discriminator);
  case 3: // DB
    return __builtin_ptrauth_sign_unauthenticated(target, 3, discriminator);
}

Thus, the pointer to puts is signed using its destination address in helloworld:__auth_got as salt for the signing operation. This is quite clever because the salt value is subject to ASLR and therefore cannot be guessed, but at this point the executable has already been loaded into memory – so it won’t change by the time the pointer is verified in the stub.

To see this in action, use F4 to run to the BRAA instruction in the stub and note the values of the operands:

The branch will use the operands to verify that the target address has not been modified after it was originally calculated by dyld. Since we haven't done anything malicious, one more single step should take us right to puts:

Just for fun, let's rewind the process back to the start of the stub:

IDC>PC = 0x1009CBF98

Then overwrite the authenticated pointer to puts with a raw pointer to printf:

ida_bytes.put_qword(0x1009CC000, ida_name.get_name_ea(BADADDR, "_printf"))

Now when we step through the stub, the BRAA instruction should detect that the authenticated pointer has been modified, and it will purposefully crash the application by setting PC to an invalid address:

Any attempt to resume execution will inevitably fail:

It seems we now have an understanding of secure symbol bindings in dyld. Fascinating!

Debugging the DYLD Shared Cache

This section discusses how to optimally debug system libraries in a dyld_shared_cache.

NOTE: full support for dyld_shared_cache debugging requires IDA 7.5 SP1

Debugging iOS system libraries is a challenge because the code is only available in the dyld cache. IDA allows you to load a library directly from the cache, but this has its own complications. A single module typically requires loading several other modules before the analysis becomes useful. Fortunately IDA is aware of these annoyances and allows you to debug such code with minimal effort.

To start, consider the following sample application that uses the CryptoTokenKit framework:

#import <CryptoTokenKit/CryptoTokenKit.h>

int main(void)
{
  TKTokenWatcher *watcher = [[TKTokenWatcher alloc] init];
  NSArray *tokens = [watcher tokenIDs];
  for ( int i = 0; i < [tokens count]; i++ )
    printf("%s\n", [[tokens objectAtIndex:i] UTF8String]);
  return 0;
}

Assume this program has been compiled and installed on the device as ctk.app.

Instead of debugging the test application, let's try debugging the CryptoTokenKit framework itself - focusing specifically on the -[TKTokenWatcher init] method.

Initial Analysis

When opening the cache in IDA, choose the load option Apple DYLD cache for arm64e (single module) and select the CryptoTokenKit module:

Wait for IDA to finish the initial analysis of CryptoTokenKit. Immediately we might notice that the analysis suffers because of references to unloaded code. Most notably many Objective-C methods are missing a prototype, which is unusual:

However this is expected. Modern dyld caches store all Objective-C class names and method selectors inside the libobjc module. Objective-C analysis is practically useless without these strings, so we must load the libobjc module to access them. Since a vast majority of modules depend on libobjc in such a way, it is a good idea to automate this in a script.

For a quick fix, save the following idapython code as init.py:

# improve functions with branches to unloaded code
idaapi.cvar.inf.af &= ~AF_ANORET

def dscu_load_module(module):
    node = idaapi.netnode()
    node.create("$ dscu")
    node.supset(2, module)
    load_and_run_plugin("dscu", 1)

# load libobjc, then analyze objc types
dscu_load_module("/usr/lib/libobjc.A.dylib")
load_and_run_plugin("objc", 1) 

Then reopen the cache with:

$ ida64 -Sinit.py -Oobjc:+l dyld_shared_cache_arm64e

This will tell IDA to load libobjc immediately after the database is created, then perform the Objective-C analysis once all critical info is in the database. This should make the initial analysis acceptable in most cases. In the case of CryptoTokenKit, we see that the Objective-C prototypes are now correct:

Now let's go to the -[TKTokenWatcher init] method invoked by the ctk application:

If we right-click on the unmapped address 0x1B271C01C, IDA provides two options in the context menu:

In this case the better option is Load ProVideo:__auth_stubs, which loads only the stubs from the module and properly resolves the names:

This is a common pattern in the latest arm64e dyldcaches, and it is quite convenient for us. Loading a handful of __auth_stubs sections is enough to resolve most of the calls in CryptoTokenKit, which gives us some nice analysis for -[TKTokenWatcher init] and its helper method:

Debugger Configuration

Now that the static analysis is on par with a typical iOS binary, let's combine it with dynamic analysis. We can debug this database by setting the following options in Debugger>Process options:

Here we set the Input file field to the full path of the CryptoTokenKit module. This allows IDA to easily detect the dyldcache slide at runtime. When CryptoTokenKit is loaded into the process, IDA will compare its runtime load address to the imagebase in the current idb, then rebase the database accordingly.

By default the imagebase in the idb corresponds to the first module that was loaded:

IDC>msg("%a", get_imagebase())
CryptoTokenKit:HEADER:00000001B8181000

Thus, it is easiest to set Input file to the module corresponding to the default imagebase.

Note however that we could also use this configuration:

Provided that we update the imagebase in the idb to the base of the libobjc module:

ida_nalt.set_imagebase(ida_segment.get_segm_by_name("libobjc.A:HEADER").start_ea)

This will result in the same dyld slide and should work just as well, because the the imagebase and the Input file field both correspond to the same module. This is something to keep in mind when debugging dyldcache idbs that contain multiple libraries.

Now let's try launching the debugger. Set a breakpoint at -[TKTokenWatcher initWithClient:], use F9 to launch the process, then wait for our breakpoint to be hit:

IDA was able to map our database (including CryptoTokenKit, libobjc, and the satellite __auth_stubs sections) into process memory. We can single step, resume, inspect registers, and perform any other operation that is typical of an IDA debugging session.

Further Analysis

Note that after terminating the debugging session you can continue to load new modules from the cache. If a dyld slide has been applied to the database, new modules will be correctly loaded into the rebased address space. This did not work in previous versions of IDA.

For example, after a debugging session we might notice some more unresolved calls:

IDA is aware that the address space has shifted, and it will load the new code at the correct address:

You are free to load new modules and relaunch debugging sessions indefinitely.

Debugging System Applications

The previous examples used custom applications to demonstrate IDA's debugging capabilities. In this case IDA can utilize the debugserver included in Apple's iOS developer tools, but there are situations in which this server is not sufficient for our needs.

The debugserver will refuse to debug any application that we didn't build ourselves. To demonstrate this, try launching IDA with an empty database and use Debugger>Attach>Remote iOS Debugger to attach to one of the system daemons:

You will likely get this error message:

It is possible to install a custom version of the debugserver that can debug system processes, but this requires a jailbroken device. We document the necessary steps and IDA configuration here. The device used in this example is an iPhone 8 with iOS 13.2.2, jailbroken with checkra1n 0.10.1.

Patching the debugserver

First we must obtain a copy of the debugserver binary from the DeveloperDiskImage.dmg:

$ export DEVELOPER=/Applications/Xcode.app/Contents/Developer
$ export DEVTOOLS=$DEVELOPER/Platforms/iPhoneOS.platform/DeviceSupport
$ hdiutil mount $DEVTOOLS/13.2/DeveloperDiskImage.dmg
$ cp /Volumes/DeveloperDiskImage/usr/bin/debugserver .

Now save the following xml as entitlements.plist:

<?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>task_for_pid-allow</key> <true/>
	<key>get-task-allow</key> <true/>
	<key>platform-application</key> <true/>
	<key>com.apple.springboard.debugapplications</key> <true/>
	<key>run-unsigned-code</key> <true/>
	<key>com.apple.system-task-ports</key> <true/>
</dict> 
</plist>
$ ldid -Sentitlements.plist debugserver

This will grant the debugserver permission to debug any application, including system apps. Now we can copy the server to the device and run it:

$ scp debugserver root@iphone-8:/usr/bin/
$ ssh root@iphone-8
iPhone-8:~ root# /usr/bin/debugserver 192.168.1.7:1234
debugserver-@(#)PROGRAM:LLDB  PROJECT:lldb-900.3.98 for arm64.
Listening to port 1234 for a connection from 192.168.1.7...

Note that we specified 192.168.1.7 which is the IP of the host machine used in this example. Be sure to replace this with the IP of your host so that the server will accept incoming connections from IDA.

IDA Configuration

To enable debugging with the patched debugserver, set the following options in dbg_ios.cfg:

// don't launch the debugserver. we did it manually
AUTOLAUNCH = NO
// your device's UUID. this is used when fetching the remote process list
DEVICE_ID = "";
// debugging symbols extracted by Xcode
SYMBOL_PATH = "~/Library/Developer/Xcode/iOS DeviceSupport/13.2.2 (17B102)/Symbols";

We're now ready to open a binary in IDA and debug it. Copy the itunesstored binary from your device, it is typically found here:

/System/Library/PrivateFrameworks/iTunesStore.framework/Support/itunesstored

After loading the binary use Debugger>Select debugger and choose Remote iOS Debugger, then under Debugger>Process options set the following fields:

Since we set AUTOLAUNCH = NO, IDA now provides the Hostname and Port fields so we can specify how to connect to our patched debugserver instance.

Now use Debugger>Attach to process and choose itunesstored from the process list. Since we have modified the debugserver it should agree to debug the target process, allowing IDA to create a typically robust debugging environment:

Note that although we're not using the debugserver from DeveloperDiskImage.dmg, IDA still depends on other developer tools to query the process list. We discuss how to install the DeveloperDiskImage in the Getting Started section above, but for a quick workaround you can always just specify the PID manually:

Now that we've successfully attached to a system process, let's do something interesting with it. Consider the method -[PurchaseOperation initWithPurchase:]. This logic seems to be invoked when a transaction is performed in the AppStore. Set a breakpoint at this method, then open the AppStore on your device and try downloading an app (it can be any app, even a free one).

Immediately our breakpoint is hit, and we can start unwinding the logic that brought us here:

Stepping through this function, we see many Objective-C method call sites:

Instead of using F7 to step into the _objc_msgSend function, we can use shortcut Shift-O to take us directly to the Objective-C method that is being invoked:

It seems that we're well on our way to reverse-engineering transactions in the AppStore. The remaining work is left as an exercise for the reader :)

Conclusion

Hopefully by now we've shown that IDA's iOS Debugger is quite versatile. It can play by Apple's rules when debugging on a non-jailbroken device, and it can also be configured to use an enhanced debugserver when a jailbreak is available.

Also keep in mind that all previous examples in this writeup should work equally well with the patched debugserver. We encourage you to go back and try them.

\

Troubleshooting

$ ida64 -z10000 -L/tmp/ida.log

Often times these packets contain messages or error codes that provide clues to the issue.

For more enhanced troubleshooting, you can also enable logging on the server side. Go to Debugger>Debugger options>Set specific options and set the Syslog flags field:

This will instruct the debugserver to log details about the debugging session to the iOS system log (all valid flags are documented under the SYSLOG_FLAGS option in dbg_ios.cfg).

Start collecting the iOS system log with:

$ ios_deploy syslog -f /tmp/sys.log

Then launch the debugger. Now both the client (/tmp/ida.log) and the server (/tmp/sys.log) will log important events in the debugger session, which will often times reveal the issue.`

Notes

Fortunately Hex-Rays provides a solution. Download the utility from our download center. This is a command-line support utility that can perform critical tasks on iOS devices without requiring a jailbreak. Try running it with the listen phase. If ios_deploy can detect your device it will print a message:

Use this path for the fields in Debugger>Process options...

In this example we'll be using IDA to discover how dyld uses to perform secure symbol bindings. Start by loading the dyld binary in IDA. It is usually found here:

The stub reads a value from off_109CC000, then performs a . We can assume that at some point, dyld will fill off_109CC000 with an authenticated pointer to puts. Let's use IDA to quickly track down this logic in dyld.

First we'll need access to the dyldcache that contains the CryptoTokenKit framework. The best way to obtain the cache is to extract it from the package for your device/iOS version. This ensures that you are working with the original untouched cache that was installed on your device.

Then use to codesign the server:

\

We discuss the Shift-O action in detail in our , but it is worth demonstrating that this action works just as well in arm64/iOS environments.

IDA uses the to communicate with the iOS debugserver. Thus, the best way to diagnose possible issues is to log the packets transmitted between IDA and the server. You can do this by running IDA with the -z10000 command-line option:

This tutorial replaces the old iOS debugging tutorial, which is available .

ios_deploy
ARMv8.3 Pointer Authentication
branch with pointer authentication
ipsw
ldid
mac debugger primer
Remote GDB Protocol
here