You may either start a local debugging session on a new process or start a local debugging session and attach it to an existing process. Both options are accessible through the command line.
idat -rlinux MY_PROGRAM
will start the program, create a temporary database that allows the user to work with the target at once.
istart_local_process.gif
The command
idat -rlinux+
will offer you a choice of running processes to connect to.
and we can proceed with our local Linux debugging session.
select_local_process
attached_to_local_process
IDA Linux to Win64 Debugging
One of the fanciest options offered by the IDA 4.8 debugger is the debugging of Windows executables from a Linux machine. The 64 bits remote debugging server is started on the Windows64 machine.
and IDA for Linux is started with the following command line
the command line switch specifies the debugger type (windows in this case), the machine name/IP (192.168.1.56) and the last + specifies that a list of running processes will be requested from the target machine. IDA will then display that list and you'll be able to connect to processes on the Windows64 machine.
and here is the 64 bit program, ready to be debugged under Linux.
Linux Debugger
Since version 4.7, IDA offers a console Linux debugger and a console Linux disassembler (since version 5.1 IDA also offers a Mac OS X debugger and disassembler). The Linux version of IDA brings the power of combined disassembly and debugging to the Linux world.
The Linux version of IDA:
is able to disassemble any file supported by the Windows version.
supports all the features of the Windows console version, including interactivity, scripting and plugins.
Debugging Windows Applications with IDA Bochs Plugin
Check the tutorial about debuggind Windows apps with IDA Bochs:
can connect to Windows machines running our debugging server and debug Windows executables.
remote debugging server that allows you to debug Linux programs from another Linux machine, or even a Windows one.
Below: the Linux Debugger working locally.
Below: the Windows Debugger about to debug a remote Linux binary.
Below: the Windows Debugger in a remote debugging session.
A typical use of the remote linux debugger would be the safe analysis of an hostile Windows binary: the Linux debugger, for example, brings unprecedented flexibility and security to the virus analyst. A typical use of the remote Windows debugger would be Linux debugging in a comfortable, well known GUI.
The IDA debugger, disassembler and remote debuggers are not sold separately but are included in the normal IDA distribution.
Debugging Linux Kernel under VMWare using IDA GDB debugger
Current versions of VMWare Workstation include a GDB stub for remote debugging of the virtual machines running inside it. In version 5.4, IDA includes a debugger module which supports the remote GDB protocol. This document describes how to use it with VMWare. As an example, we'll debug a Linux kernel.
Debugging a Linux kernel
Let's assume that you already have a VM with Linux installed. Before starting the debugging, we will copy symbols for the kernel for easier navigation later. Copy either /proc/kallsyms or /boot/Sytem.map* file from the VM to host.
Now edit the VM's .vmx file to enable GDB debugger stub:
Add these lines to the file:
debugStub.listen.guest32 = "TRUE"
debugStub.hideBreakpoints= "TRUE"
monitor.debugOnStartGuest32 = "TRUE"
Save the file.
In VMWare, click "Power on this virtual machine" or click the green Play button on the toolbar.
A black screen is displayed since VMWare is waiting for a debugger to connect.
Start IDA.
If you get the welcome dialog, choose "Go".
Choose Debugger | Attach | Remote GDB debugger.
Enter "localhost" for hostname and 8832 for the port number.
Choose <attach to the process started on target> and click OK.
We land in the BIOS, but since we're not interested in debugging it, we can skip directly to the kernel. Inspect the kallsyms or System.map file you downloaded from the guest and search for the start_kernel symbol:
Copy the address, and navigate to it in IDA (Jump | Jump to addres... or just "g").
Press F2 or choose "Add breakpoint" from the context menu.
Check "Hardware breakpoint" and select "Execute" in "Modes". Click OK.
Press F9. You will see loading messages and then the execution will stop at the entrypoint.
Adding symbols
Symbols are very useful during debugging, and we can use the kallsyms or System.map file to add them to IDA. Go to File | Python command... and paste the following short script (don't forget to edit the file path):
ksyms = open(r"") # path to the kallsyms/map filefor line in ksyms:if line[9]=='A': continue # skip absolute symbolsaddr = int(line[:8], 16)name = line[11:-1]if name[-1]==']': continue # skip module symbolsidaapi.set_debug_name(addr, name)MakeNameEx(addr, name, SN_NOWARN)Message("%08X: %s\n"%(addr, name))
Click OK and wait a bit until it finishes. After that you should see the symbols in the disassembly and name list:
Happy debugging!
Copyright 2009 Hex-Rays SA
IDA Linux Local Debugging
Debugging Linux Applications with IDA Pro, locally
Last updated on July 29, 2020 — v0.1
You may already know that IDA lets you debug an application from an already existing IDB, by selecting the debugger using the drop-down debugger list.
However, it is also possible to start IDA in a way that it will initially create an empty IDB, and then either:
start a new process under its control
attach to an existing process
Launch IDA with a fresh new process
To do so, you will have to launch IDA from the command line, like so:
IDA will then launch the /bin/ls program, and break at its entrypoint
Attaching IDA to an existing process
For this example, we’ll launch, from a shell, a /usr/bin/yes process, and attach to.
Now, we’ll launch IDA so it offers a selection of processes to (and use quick filtering (Ctrl+F) to quickly find our process):
IDA will then attach to the selected process, and leave it suspended at the place it was when it was attached to:
IDA Scriptable Debugger: overview
Debugging facilities in IDA 4.9 and 4.9 SP
Since we enhanced the usability of IDA remote debugging options, many possibilities are now open. Explore the graph below to discover some of the possible connections.
instant debugging, no need to wait for the analysis to be complete to start a debug session.
easy connection to both local and remote processes.
ida -rlinux /bin/ls
ida -rlinux+
support for 64 bits systems and new connection possibilities.
WindowCE remote debugging
click on the links below to explore some of the remote debugging features.
This guide illustrates how to configure the Bochs debugger plugin under Linux/MacOS. Downloading and compiling Bochs Please download the Bochs source code tarball and extract it.
Run the 'configure' script (it is possible to pass other switches) and make sure that the switches marked in bold are present:
Note: under MacOS Lion 10.7.3 use the following switches:
For a complete installation guide please check: http://bochs.sourceforge.net/doc/docbook/user/compiling.html. Now run "make" and "make install". Then type "whereis bochs" to get something like:
Configuring IDA and the Bochs debugger plugin
Opening a database and selecting the Bochs debugger
After installing Bochs, run IDA Pro and open a Windows PE file and select 'Debugger -> switch debugger' and select "Local Bochs Debugger": If a PE file was loaded, then the Bochs debugger plugin will operate in "PE mode":
In case the other two modes (IDB or Disk Image mode) are used then there is no need to specify any additional configurations options, otherwise please continue reading this guide. Before launching the debugger with F9, the Bochs debugger plugin needs to know where to find the MS Windows DLLs and which environment variables to use. Attempting to run the debugger without configuring it may result in errors like this:
Here is a basic list of DLLs that are needed by most programs: • advapi32.dll • comctl32.dll • comdlg32.dll • gdi32.dll • kernel32.dll • msvcrt.dll • mswsock.dll • ntdll.dll • ntoskrnl.exe • shell32.dll • shlwapi.dll • urlmon.dll • user32.dll • wininet.dll • ws2_32.dll • wsock32.dll Let us create a directory in $HOME/bochs_windir/ and place those DLLs there. Specifying the Windows DLL path and environment variables using the startup file The startup file is a script file found in idadir\plugins\bochs directory. If IDC was the currently active language then startup.idc is used, otherwise startup.ext (where ext is the extension used by the currently selected extlang). In this tutorial we will be working with IDC, so we will edit the startup.idc file. (Please note that changes to this file will affect all databases. For local changes (database specific configuration) take a copy of the startup script file and place it in the same directory as the database then modify it). It is possible to specify a path map for a complete directory, for example:
This line means that /home/lallous/bochs_windir/* will be visible to the debugged program as c:\windows\system32* (for example /home/lallous/bochs_windir/ntdll.dll will be visible as c:\windows\system32\ntdll.dll)
If all DLLs referenced by the program are in the bochs_windir directory, then running the process again should work: (Bochs has already started and IDA switched to debugging mode.) There are two things that should be configured. Press “.” to switch to the output window (or use the Debugger / Modules list window to inspect the modules list):
Now, after we run the program again we should get a more correct module list:
It is equally important to specify some environment variables. We will use the env keyword to define all the environment variables:
Specifying the Windows DLL path and environment variables using environment variables An alternative way of configuring the DLLs path and environment variables is to use the IDABXPATHMAP and the IDABXENVMAP environment variables. To specify the path map, export the following environment variable:
(Please note that the forward slash (/) will be replaced with a backslash automatically by the plugin) Similarly, specify the environment variables with the IDABXENVMAP environment variable:
(Please note that we used the ++ to separate between multiple variables)
In case you require to do specific changes (per database) to the startup file then please take a copy of it and place it in the same directory as the database. Refer to the help IDA Pro help file for more information.
Debugging a Windows executable locally and remotely
Debugging a Windows executable locally and remotely
Last updated on September 01, 2020 - v0.2
This short tutorial introduces the main functionality of the IDA Debugger on Windows. IDA supports debugging of various binaries on various platforms, locally and remotely, but in this tutorial we will focus on debugging regular applications running on Windows.
Let’s see how the debugger can be used to locally debug a simple buggy C console program compiled under Windows.
Debugger tutorials
Here you can find a comprehensive set of step-by-step tutorials categorized by different debugging types and platforms.
1. The path to bochsys.dll is still not properly mapped. In our case, we need to add the following line to the startup file:
```
/// map /Users/elias/idasrc/current/bin/idaq.app/Contents/MacOS/plugins/bochs/bochsys.dll=c:\windows\system32\bochsys.dll
```
(As opposed to the path keyword that maps complete directories, the map keyword to map individual files).
To hide the presence of bochsys.dll, simply map it to another name:
```
/// map /Users/elias/idasrc/current/bin/idaq.app/Contents/MacOS/plugins/bochs/bochsys.dll=c:\windows\system32\kvm.dll
```
6. The executable's path: we also need to add a map for the executable itself or a path entry for the whole folder:
```
/// path /Users/elias/idasrc/current/bin/=c:\malware
```
This program computes averages of a set of values (1, 2, 3, 4 and 5). Those values are stored in two arrays: one containing 8 bit values, the other containing 32-bit values.
Running this program gives us the following results:
Obviously, the computed average on the integer array is wrong. Let us use the IDA debugger to understand the origin of this error.
Loading the file
The debugger is completely integrated into IDA: to debug, we usually load the executable into IDA and create a database. We can disassemble the file interactively, and all the information which he will have added to the disassembly will be available during debugging. If the disassembled file is recognized as debuggable, the Debugger menu automatically appears in main window:
Since IDA has many debugger backends, we have to select the desired backend. We will use Local Windows debugger in our tutorial:
Instruction breakpoints
Once we located our int_average() function in the disassembly (it is at 0x40104A), we can add a breakpoint at its entry point, by selecting the Add breakpoint command in the popup menu, or by pressing the F2 key:
Program execution
Now we can start the execution. We can launch the debugger by pressing the F9 key or by clicking the Start button in the debugger toolbar. IDA displays a big warning message before really starting the debugger:
Indeed, running untrusted binaries on your computer may compromise it, so you should never run them. Since in our tutorial we are playing with a toy sample, it is okay, we can accept him. However, please consider using remote debugging for untrusted binaries.
Once we accept it, the program runs until it reaches our breakpoint:
Address evaluation
By analyzing the disassembled code, we can now locate the loop which computes the sum of the values, and stores the result in EAX. The [edx+ecx*4] operand clearly shows us that EDX points to the array and ECX is used as an index in it. Thus, this operand will successively point to each integer from the integers array:
Step by step and jump targets
Let us advance step by step in the loop, by clicking on the adequate button in the debugger toolbar or by pressing the F8 key. If necessary, IDA draws a green arrow to show us the target of a jump instruction:
The bug uncovered
Now, let’s have a look at value of [esp+count]. The ECX register (our index in the array) is compared to this register at each iteration: so, we can conclude that it is used as a counter in the loop. But, we also observe that it contains a rather strange number of elements: 14h (= 20). Remember that our original array contains only 5 elements! It seems we just found the source of our problem…
Hardware breakpoints
To be sure, let us add a hardware breakpoint, just behind the last value of our integers array (in fact, on the first value of the chars array). If we reach this breakpoint during the loop, it will indeed prove that we read integers outside our array. For that jump to EDX, which points to the array, by clicking on a small arrow in the CPU register view:
IDA displays a sequence of bytes, so we need to create an array. Do the following:
press Alt-D, D to create a doubleword
press * and specify the size of 5 elements
Let us add a hardware breakpoint with a size of 4 bytes (the size of an integer) in Read/Write mode immediately after our array. Please note that the cursor is located after the array we created:
As foreseen, if we continue the execution, the hardware breakpoint detects a read access to the first byte of the chars array.
Please note that EIP points to the instruction following the one which caused the hardware breakpoint! It is in fact rather logical: to cause the hardware breakpoint, the preceding instruction has been fully executed, so EIP now points to the next one.
Caller’s mistake
By looking at the disassembly, we see that the value stored in [esp+count] comes from the count argument of our int_average() function. Let us try to understand why the caller gives us such a strange argument: if we go the call of int_average(), we easily locate the push 14h instruction, passing an erroneous count value to our function.
Now, by looking closer at the C source code, we understand our error: we used the sizeof() operator, which returns the number of bytes in the array, rather than returning the number of items in it! As, for the chars array, the number of bytes was equal to the number of items, we didn’t notice the error…
Debugging a remote process
Our debuggers support debugging processes running on a remote computer. We just need to set up a remote debugging session and then we can debug the same way as in local debugging. Let us consider the following three simple steps.
Starting the remote server
Regardless of the platform where IDA itself runs (be it Windows, Mac, or Linux), we need to launch a remote debugger server on the computer where the remotely debugged application will run.
For Windows, we have two different debugger servers:
for 32-bit programs, use win32_remote.exe
for 64-bit programs, use win64_remote64.exe
So, we copy the relevant debugger server to the remote computer and launch it:
If the debugger server is accessible by others, it is a good idea to use a password for the connection (the -P command line option).
Once this is done, we can return to the local computer, where we will run IDA, and configure it.
Configuring IDA to connect to the remote server
We have to select the Remote Windows debugger:
and specify the correct values in the Debugger > Process options dialog:
Please note that the Application, Input file, and Directory must be correct on the remote computer. We may eventully specify command line arguments for the application in the Parameters field.
If you have specified a password when launching the remote debugger server, you must specify it in the Password field.
Starting a debug session
Once we have configured IDA, the rest is the same as with local debugging: press F9 to start a debugging session.
Attaching to a running process
In some cases we cannot launch the debugged process ourselves. Instead, we need to attach to an existing process. This is possible and very easy to do: just select Debugger > Attach to process from the menu and select the desired process.
Other features
IDA debugger gives you access to the entire process memory, allowing you to use all powerful features: you can create structure variables in memory, draw graphs, create breakpoints in DLLs, define and decompile functions, etc. It is even possible to single step in the pseudocode window, if you have the decompiler installed!
The way the debugger reacts to exceptions is fully configurable by the user. The user can select various Actions to be performed when the breakpoint is hit. An IDC or Python can be executed upon hitting a breakpoint:
We invite you to play with the debugger and discover its many unique and powerful features!
Remote debugging is the process of debugging code running on one networked computer from another networked computer:
The computer running the IDA Pro interface will be called the "debugger client".
The computer running the application to debug will be called the "debugger server".
Remote debugging will be particularly useful in the following cases:
To debug virus/trojans/malwares : in this way, the debugger client will be as isolated as possible from the compromised computer.
To debug applications encountering a problem on one computer which is not duplicated on other computers.
To debug distributed applications.
To always debug from your main workstation, so you won't have to duplicate IDA configuration, documentation and various debugging related resources everywhere.
In the future, to debug applications on more operating systems and architectures.
This small tutorial will present how to setup and use remote debugging in practice.
The remote IDA debugger server
In order to allow the IDA client to communicate with the debugger server over the network, we must first start a small server which will handle all low-level execution and debugger operations.
Debugger servers
The IDA distribution ships with the following debugger servers:
For Windows: win32_remote32 (x86), win64_remote.exe (x64)
For Linux: linux_server32 (x86), linux_server (x64), armlinux_server32 (ARM), armlinux_server (ARM64)
For Android: android_x86_server, android_x64_server, android_server32 (ARM),
With these, we can:
Locally debug applications and shared libraries from the IDA graphical and text versions.
Remotely debug applications and shared libraries from the IDA graphical and text versions.
So let's first copy the small x64 Windows debugger server file to our debugger server.
This server accepts various command line arguments:
Let's start it by specifying a password, to avoid unauthorized connections:
Note that the remote debugger server can only handle one debugger session at a time. If you need to debug several applications simultaneously on the same host, launch several servers on different network ports by using the -p switch.
Setting up the debugger client.
First, we copy the executable we want to debug from the debugger server (Windows or Linux) to the debugger client (Windows or Linux). We can then load this file into IDA, as usual. To setup remote debugging, we select the 'Process options...' menu item in the Debugger menu:
Specify the Application and Directory paths. Note that these file paths should be valid on the remote debugger server. Also do not forget to enter the host name or IP address of the debugger server: remote debugging will only be enabled if these settings are specified ! You also might have to open the TCP port in the remote machine firewall. Finally, we enter the password we chose for the remote IDA debugger server.
Starting remote debugging.
Both debugger server and debugger client are now ready to start a remote debugging session. In fact, you can now use all debugger related commands as you would with the local Windows PE debugger or local Linux debugger! For example,we can run the process until RIP reaches the application entry point, by jumping to this entry point then pressing the F4 key:
If we now directly terminate the process (by pressing CTRL-F2) and look at win64_remote's output (on the debugger server), we indeed properly observe it accepted then closed our network connection:
Attaching to a running process.
Another interesting possibility is to attach to an already running process on the remote computer. If you click on the 'Attach to process...' command from the Debugger menu, IDA will display a listing of all remote running processes, you can then filter to choose the one you want to attach to (notepad.exe in this case):
Double clicking on a process from the list will automatically suspend the process and attach to it, allowing you to debug it without starting it manually.
Detaching from the debugged process.
Finally, if the debugger server is running Windows XP, Windows Server 2003 or Linux, you can also detach from a process you were currently debugging, simply by using the 'Detach from process' command in the Debugger menu:
On Windows, please note that IDA can also attach to Windows services running either locally or remotely. In particular, the 'Detach from process' command will be especially useful if you previously attached to a Windows service: it will allow you to stop the debugger without terminating a critical Windows service on the debugger server!
IDA Win32 Local Debugging
The IDA Debugger allows you to either start a new process (Run) or attach itself to an existing process (Attach)
instant_debugger_menu
Let's select "Local Windows debugger". What we get is a list of currently running processes to which we can attach with a simple click.
connect_to_local
and here is the result once we are attached to the program.
attached_to_local
IDA Win32 to Linux Debugging
Connecting a Windows Debuging session to a Linux machine is essentially similar to a Windows to Windows connection. The Linux remote debugger server is started on the command line.
then connect to the Linux Machine by selecting the attach to remote Linux command. We can then select the process we want to debug and connect to it with a simple click.
we are now connected to the remote process running on the Linux machine.
#include <stdio.h>
char char_average(char array[], int count)
{
int i;
char average;
average = 0;
for (i = 0; i < count; i++)
average += array[i];
average /= count;
return average;
}
int int_average(int array[], int count)
{
int i, average;
average = 0;
for (i = 0; i < count; i++)
average += array[i];
average /= count;
return average;
}
void main(void)
{
char chars[] = { 1, 2, 3, 4, 5 };
int integers[] = { 1, 2, 3, 4, 5 };
printf("chars[] - average = %d\n",
char_average(chars, sizeof(chars)));
printf("integers[] - average = %d\n",
int_average(integers, sizeof(integers)));
}
>sample.exe
chars[] - average = 3
integers[] - average = -65498543
For Mac: mac_server32 (x86), mac_server (x64), mac_server_arm (ARM64), mac_server_arme (ARM64e)
C:\> win64_remote -?
IDA Windows 64-bit remote debug server(MT) v9.0.30. Hex-Rays (c) 2004-2024
Usage: win64_remote [options]
-p ... (--port-number ...) Port number
-i ... (--ip-address ...) IP address to bind to (default to any)
-c ... (--certchain-file ...) TLS certificate chain file
-k ... (--privkey-file ...) TLS private key file
-v (--verbose) Verbose mode
-t (--no-tls) Use plain, unencrypted TCP connections
-P ... (--password ...) Password
-k (--on-broken-connection-keep-session) Keep debugger session alive when connection breaks
-K (--on-stop-kill-process) Kill debuggee when closing session
C:\>win64_remote -Pmypassword
IDA Windows 64-bit remote debug server(MT) v9.0.30. Hex-Rays (c) 2004-2024
2024-11-04 03:20:51 Listening on 0.0.0.0:23946 (my ip 172.20.156.1)...
C:\\> .\win64_remote.exe -Pmypassword
IDA Windows 64-bit remote debug server(MT) v9.0.30. Hex-Rays (c) 2004-2024
2024-11-04 03:20:51 Listening on 0.0.0.0:23946 (my ip 172.20.156.1)...
2024-11-04 03:29:26 [1] Accepting connection from 172.20.144.1...
2024-11-04 03:37:56 [1] Closing connection from 172.20.144.1...
Debugging Windows Kernel with VMWare and IDA WinDbg Plugin
Debugging the Windows Kernel with VMWare and IDA WinDbg Plugin
We will now demonstrate how to debug the kernel through a virtual machine.
In this example we will be using VMware Workstation 15 Player and Windows 7.
Run the VM and use the bcedit command to configure the boot menu as stated in the article.
Edit the VM hardware settings and add a new serial port with option use named pipe:
Restart the VM to debug. At the boot prompt, select the menu item containing [debugger enabled] from the boot menu.
Configuring Windbg debugger plugin
The connection string com:port=\\.\pipe\com_2,baud=115200,pipe,reconnect for Windbg plugin should refer to the named pipe we set up in the previous steps.
Starting the debugger step by step
Start IDA Pro with an empty database:
Select the Windbg debugger using "Debugger > Select debugger":
Then configure it to use “Kernel mode debugging” debugging in the “Debugger specific options” dialog:
After the debugger is properly configured, edit the process options and set the connection string:
Finally, start debugging using "Debugger > Attach to process":
IDA Pro may display a wait box "Refreshing module list" for some time. Then it will display something like this:
Starting the debugger using a command line option
The simplest way to start WinDbg Plugin is to run IDA Pro with the following option:
{MODE=1} means "Kernel mode"
+0 means the "<Kernel>" process
Debugging
In kernel mode IDA Pro will display one entry in the threads window for each processor.
For example a two processor configuration yields:
This screenshot shows how we are debugging the kernel and changing the disassembly listing (renaming stack variables, or using structure offsets):
At the end you can detach from the kernel and resume it or detach from the kernel and keep it suspended.
To detach and resume, simply select the “Debugger > Detach from process”, however to detach and keep the kernel suspended select “Debugger > Terminate Process”.
Debugging the kernel through kdsrv.exe
In some cases, when debugging a 64bit kernel using a 1394 cable then 64bit drivers are needed, thus dbgeng (32bits) will not work. To workaround this problem we need to run the kernel debugger server from the x64 debugging tools folder and connect to it:
Go to “Debugging Tools (x64)” installation
Run kdsrv.exe (change the port number/transport appropriately):
kdsrv -t tcp:port=6000
Debugging Linux/Windows Applications with PIN Tracer module
Introduction
The PIN tracer is a remote debugger plugin used to record execution traces. It allows to record traces on Linux and Windows (x86 and x86_64) from any of the supported IDA platforms (Windows, Linux and MacOSX). Support for MacOSX targets is not yet available.
IDA Scriptable Debugger: scriptability
Since 2003 IDA offers a debugger that complements the static analysis nicely. In many cases, one just can't beat dynamic analysis. The IDA Debugger now supports 32-bit and 64-bit MS Windows executablesMS Windows, Linux, Mac OS X both locally and remotely. However, because the debugger API requires the mastery of our SDK and uses an event based model, it has proved quite difficult to use for some of our users.
because the API uses an event based model makes it hard to program a linear sequence of actions in a natural way. The user is forced to install an event handler and to implement a finite state machine that implements the core logic of his plugin. While this may, in many ways, be a more powerful approach, this is probably too complex for more mundane tasks.
because the API is only available at the plugin level, the simplest debugger actions requires writing a plugin which is a much bigger investment of time and efforts than writing a small IDC script.
Debugging code snippets with QEMU debugger (a la IDA Bochs debugger)
Introduction
IDA Pro 5.6 has a new feature: automatic running of the QEMU emulator. It can be used to debug small code snippets directly from the database. In this tutorial we will show how to dynamically run code that can be difficult to analyze statically.
Windows Debugger Hub
Since version 4.3, IDA offers a PE Windows debugger in addition to its Windows disassembler. The Windows debugger in IDA combines the power of static disassembly to dynamic debugging to allow its users to debug unknown binaries at a level close to source code. A Linux version of the debugger is also available, there is some more information about it here
The Windows Debugger in IDA:
is able to debug any file supported by the Windows DBG interface, including true 64 bits files.
can benefit from all the features of the Windows Disassembler, including interactivity, scripting and plugins.
Now run ida64 and specify the following connection string (change the transport value appropriately):
IDA 5.2 will address both issues. The old event based model will remain available, but a simpler linear model will become available thanks to the function get_debugger_event(). This function pauses the execution of the plugin (or the script) until a new debugger event happens. The user can specify if she is interested only in the events that suspend the process or in all events. A timeout can also be confifured, after which the execution will continue if no event arose.
The new function allows us to drop the event based model (except in the cases when it is superior to linear logic) and write IDC scripts to control the debugger. For example, to launch the debugger, run to a specific location, print some data and single step twice, the following lines will suffice:
In IDA 5.1 this would have required a event handler and a small finite state automata, for a total more than 200 lines of code. Please note that, in the above example, the error handling code is omitted for clarity. In real life, you might want to check for unexpected conditions like an exception happening after StepInto().
To illustrate how easier it is to write scripts with the new approach, we rewrote the core functionality of the UUNP unpacker plugin. The original program requires about 600 lines of code and has a rather complex logic. The new script only requires 100 lines of code (almost half of them being comments and empty lines). More importantly, the script is easy to understand and modify for your needs.
This is a reimplementation of the uunp universal unpacker in IDC. It illustrates the use of the new debugger functions in IDA v5.2
AppBpt(some_address);
StartDebugger("","",""); // start debugger with default params
GetDebuggerEvent(WFNE_SUSP, -1); // ... and wait for bpt
Message ("Stopped at %a, event code is %x\n", GetEventEA(), GetEventId());
StepInto(); // request a single step
GetDebuggerEvent(WFNE_SUSP, -1); // ... and wait for app to execute
StepInto(); // request a single step
GetDebuggerEvent(WFNE_SUSP, -1); // ... and wait for app to execute
#include <idc.idc>
//--------------------------------------------------------------------------
static main()
{
auto ea, bptea, tea1, tea2, code, minea, maxea;
auto r_esp, r_eip, caller, funcname;
// Calculate the target IP range. It is the first segment.
// As soon as the EIP register points to this range, we assume that
// the unpacker has finished its work.
tea1 = FirstSeg();
tea2 = SegEnd(tea1);
// Calculate the current module boundaries. Any calls to GetProcAddress
// outside of these boundaries will be ignored.
minea = MinEA();
maxea = MaxEA();
// Launch the debugger and run until the entry point
if ( !RunTo(BeginEA()) )
return Failed(-1);
// Wait for the process to stop at the entry point
code = GetDebuggerEvent(WFNE_SUSP, -1);
if ( code <= 0 )
return Failed(code);
// Set a breakpoint at GetProcAddress
bptea = LocByName("kernel32_GetProcAddress");
if ( bptea == BADADDR )
return Warning("Could not locate GetProcAddress");
AddBpt(bptea);
while ( 1 )
{
// resume the execution and wait until the unpacker calls GetProcAddress
code = GetDebuggerEvent(WFNE_SUSP|WFNE_CONT, -1);
if ( code <= 0 )
return Failed(code);
// check the caller, it must be from our module
r_esp = GetRegValue("ESP");
caller = Dword(r_esp);
if ( caller < minea || caller >= maxea )
continue;
// if the function name passed to GetProcAddress is not in the
// ignore-list, then switch to the trace mode
funcname = GetString(Dword(r_esp+8), -1, ASCSTR_C);
// ignore some api calls because they might be used by the unpacker
if ( funcname == "VirtualAlloc" )
continue;
if ( funcname == "VirtualFree" )
continue;
// A call to GetProcAddress() probably means that the program has been
// unpacked in the memory and now is setting up its import table
break;
}
// trace the program in the single step mode until we jump to
// the area with the original entry point.
DelBpt(bptea);
EnableTracing(TRACE_STEP, 1);
for ( code = GetDebuggerEvent(WFNE_ANY|WFNE_CONT, -1); // resume
code > 0;
code = GetDebuggerEvent(WFNE_ANY, -1) )
{
r_eip = GetEventEa();
if ( r_eip >= tea1 && r_eip < tea2 )
break;
}
if ( code <= 0 )
return Failed(code);
// as soon as the current ip belongs OEP area, suspend the execution and
// inform the user
PauseProcess();
code = GetDebuggerEvent(WFNE_SUSP, -1);
if ( code <= 0 )
return Failed(code);
EnableTracing(TRACE_STEP, 0);
// Clean up the disassembly so it looks nicer
MakeUnknown(tea1, tea2-tea1, DOUNK_EXPAND|DOUNK_DELNAMES);
MakeCode(r_eip);
AutoMark2(tea1, tea2, AU_USED);
AutoMark2(tea1, tea2, AU_FINAL);
TakeMemorySnapshot(1);
MakeName(r_eip, "real_start");
Warning("Successfully traced to the completion of the unpacker code\n"
"Please rebuild the import table using renimp.idc\n"
"before stopping the debugger");
}
//--------------------------------------------------------------------------
// Print an failure message
static Failed(code)
{
Warning("Failed to unpack the file, sorry (code %d)", code);
return 0;
}
PIN support for MacOSX
Recording traces on MacOSX target is not supported yet.
However, it’s possible to record traces from a Linux or Windows target using the MacOSX version of IDA.
Building the PIN tool
Before using the PIN tracer the PIN tool module (distributed only in source code form) must be built as the Intel PIN license disallows redistributing PIN tools in binary form.
First of all download PIN from http://www.pintool.org , and unpack it on your hard drive.
the PIN tools are a little sensitive to spaces in paths. Therefore, we recommend unpacking in a no-space path. E.g., "C:\pin", but not "C:\Program Files (x86)\.
The building process of the PIN tool is different for Windows and Linux.
Building on Windows
Install Visual Studio. It is possible to build the PIN tool with the Express version of Visual Studio for C++.
pintool 6.9 and higher should be built with PIN version 3.0 and higher, for earlier versions of pintool you should use PIN build 65163.
Unpack the .zip file into /path/to/pin/source/tools/
Open /path/to/pin/source/tools/idapin/IDADBG.sln in Visual Studio, select the correct build configuration (either Win32 or x64) and build the solution.
Alternatively you can use GNU make:
Install GNU make as a part of cygwin or MinGW package
Unpack the .zip file into /path/to/pin/source/tools/
Prepare Visual Studio environment (e.g. %VCINSTALLDIR%\Auxiliary\Build\vcvars32.bat for 32-bit pintool or %VCINSTALLDIR%\Auxiliary\Build\vcvars64.bat for 64-bit one)
Pintool 6.9 and higher are compatible with versions 6.5-6.8 of IDA so currently you can use them.
Start process
Once the PIN tool module is built we can use it in IDA. Open a binary in IDA and wait for the initial analysis to finish. When it’s done select the PIN tracer module from the debuggers drop down list or via Debugger > Select debugger:
pin debugger select
After selecting the PIN tracer module select the menu Debugger > Debugger options > Set specific options. The following new dialog will be displayed:
pin options
In this dialog at least the following options are mandatory:
PIN executable: This is the full path to the PIN binary (including the “pin.exe” or “pin” file name). In some versions “pin.sh” may exist – in this case you should use it.
Directory with idadbg: This is the directory where the idadbg.so or idadbg.dll PIN tool resides. Please note that only the directory must be specified.
Fill the form with the correct paths and press OK in this dialog and enable option Autolaunch PIN for localhost.
We can interact with the PIN tracer like with any other debugger module: add breakpoints and step into or step over functions by pressing F7 or F8 alternatively.
Now we put a breakpoint in the very first instruction of function main
pin bpt in main
and launch the debugger by pressing the F9 key or by clicking the Start button in the debugger toolbar.
pin starting debug session
Make several steps by pressing F8. We can see all the instructions that were executed changed their color:
pin debug several steps
Now let the application run and finish by pressing F9 again. After a while the process will terminate and IDA will display a dialog telling us that is reading the recorded trace. Once IDA reads the trace the debugger will stop and the instructions executed will be highlighted (like with the built-in tracing engine) as in the following picture:
pin ended debug session
We can see in the graph view mode the complete path the application took in some specific function by switching to the graph view, pressing space bar and then pressing “w” to zoom out:
pin execution flow
Attach to an existing process
Instead of launching a new process we could attach to a running process and debug it. For that we could have selected the "Debugger > Attach to process…" menu item. IDA will display a list of active processes.
pin attaching to process
We just select the process we want to attach to. IDA will then attach to the selected process, and leave it suspended at the place it was when it was attached to:
pin attached to process
Remote debugging
In case of remote debugging you can run IDA and PIN backend on different platforms.
Starting the remote PIN backend
The first thing to do, is to start the PIN debugging backend on the target machine. Command line depends of bitness of the target application.
For example, a 64-bit application ls would be started for debugging by the following comand:
whereas a 32-bit one hello32 as follows:
there is a more complicated way to start an application regardless bitness:
Also you can attach to already running programs:
For example:
pin attaching remote64
Connecting IDA to the backend
The next step is to select PIN tracer module in IDA via Debugger > Select debugger and switch IDA to remote PIN backend. For this you should disable option Autolaunch PIN for localhost in the PIN options dialod (Debugger > Debugger options > Set specific options):
pin options remote
and then tell IDA about the backend endpoint, through the menu action Debugger > Process options…
pin setting process options
Once IDA knows what host to contact (and on what port), debugging an application remotely behaves exactly the same way as if you were debugging it locally.
Target
As an example we will use shellcode from the article "Alphanumeric RISC ARM Shellcode" in Phrack 66. It is self-modifying and because of alphanumeric limitation can be quite hard to undestand. So we will use the debugging feature to decode it.
The sample code is at the bottom of the article but here it is repeated:
Copy this text to a new text file, remove all line breaks (i.e. make it a single long line) and save. Then load it into IDA.
Loading binary files into IDA
IDA displays the following dialog when it doesn't recognize the file format (as in this case):
Since we know that the code is for ARM processor, choose ARM in the "Processor type" dropdown and click Set. Then click OK. The following dialog appears:
When you analyze a real firmware dumped from address 0, these settings are good. However, since our shellcode is not address-dependent, we can choose any address. For example, enter 0x10000 in "ROM start address" and "Loading address" fields.
IDA doesn't know anything about this file so it didn't create any code. Press C to start disassembly.
Configuring QEMU
Before starting debug session, we need to set up automatic running of QEMU.
Download a recent version of QEMU with ARM support (e.g. from http://homepage3.nifty.com/takeda-toshiya/qemu/index.html). If qemu-system-arm.exe is in a subdirectory, move it next to qemu.exe and all DLLs.
Note: if you're running Windows 7 or Vista, it's recommended to use QEMU 0.11 or 0.10.50 ("Snapshot" on Takeda Toshiya's page), as the older versions listen for GDB connections only over IPv6 and IDA can't connect to it.
Edit cfg/gdb_arch.cfg and change "set QEMUPATH" line to point to the directory where you unpacked QEMU. Change "set QEMUFLAGS" if you're using an older version.
In IDA, go to Debug-Debugger options..., Set specific options.
Enable "Run a program before starting debugging".
Click "Choose a configuration". Choose Versatile or Integrator board. The command line and Initial SP fields will be filled in.
Memory map will be filled from the config file too. You can edit it by clicking the "Memory map" button, or from the Debugger-Manual memory regions menu item. See below for more details
Now on every start of debugging session QEMU will be started automatically.
Executing the code
By default, initial execution point is the entry point of the database. If you want to execute some other part of it, there are two ways:
Select the code range that you want to execute, or
Rename starting point ENTRY and ending point EXIT (convention similar to Bochs debugger)
In our case we do want to start at the entry point so we don't need to do anything. If you press F9 now, IDA will write the database contents to an ELF file (database.elfimg) and start QEMU, passing the ELF file name as the "kernel" parameter. QEMU will load it, and stop at the initial point.
Now you can step through the code and inspect what it does. Most of the instructions "just work", however, there is a syscall at 0x0010118:
ROM:00010118 SVCMI 0x414141
Since the QEMU configuration we use is "bare metal", without any operating system, this syscall won't be handled. So we need to skip it.
Navigate to 010118 and press F4 (Run to cursor). Notice that the code was changed (patched by preceding instructions):
Right-click next line (0001011C) and choose Set IP.
Press F7 three times. Once you're on BXPL R6 line, IDA will detect the mode switch and add a change point to Thumb code:
Go to 01012C and press U (Undefine).
Press Alt-G (Change Segment Register Value) and set value of T to 1. The erroneous CODE32 will disappear.
Go back to 00010128 and press C (Make code). Nice Thumb code will appear:
In Thumb code, there is another syscall at 00010152. If you trace or run until it, you can see that R7 becomes 0xB (sys_execve) and R0 points to 00010156.
Hint: if the code you're investigating has many syscalls and you don't want to handle them one by one, put a breakpoint at the address 0000000C (ARM's vector for syscalls). Return address will be in LR.
Saving results to database
If you want to keep the modified code or data for later analysis, you'll need to copy it to the database. For that:
Edit segment attributes (Alt-S) and make sure that segments with the data you need have the "Loader segment" attribute set.
Choose Debugger-Take memory snapshot and answer "Loader segments".
Now you can stop the debugging and inspect the new data.
Note: this will update your database with the new data and discard the old. Repeated execution probably will not be correct.
Happy debugging!
Please send any comments or questions to [email protected]
can connect to our Linux remote debugging server and allows you to debug Linux executables from a familiar Windows environment.
Below: the Windows Debugger working locally.
Below: the Windows Debugger about to debug a remote Linux binary.
A typical use of the remote Windows debugger would be the analysis of an hostile Linux binary or an hostile Windows binary on a safe and clean machine. The IDA Windows debugger brings unprecedented flexibility and security to the virus analyst. Another typical use of the remote Windows debugger would be Linux debugging in a comfortable, well known GUI. Yet another possibility offered by our Windows debugger is 64 bit development. 64 bit development is still in its infancy and the IDA 64 bit debugger server allows you to debug 64 bit applications from within a stable Windows 32 environment.
Here are a few links to the IDA Windows Debugger on our site:
Starting with version 6.6, IDA Pro can debug Android applications written for the Dalvik Virtual Machine. This includes source level debugging too. This tutorial explains how to set up and start a Dalvik debugging session.
Installing Android Studio
First of all we have to install the Android SDK from the official site .
Environment Variables
IDA needs to know where the adb utility resides, and tries various methods to locate it automatically. Usually IDA finds the path to adb, but if it fails then we can define the ANDROID_SDK_HOME or the ANDROID_HOME environment variable to point to the directory where the Android SDK is installed to.
Android Device
Start the Android Emulator or connect to the Android device.
Information about preparing a physical device for development can be found at .
Check that the device can be correctly detected by adb:
Installing the App
IDA assumes that the debugged application is already installed on the Android emulator/device.
Please download:
and
from our site. We will use this application in the tutorial.
We will use adb to install the application:
Loading Application into IDA
IDA can handle both .apk app bundles, or just the contained .dex files storing the app’s bytecode. If we specify an .apk file, IDA can either extract one of the contained .dex files by loading it with the ZIP load option, or load all classes*.dex files when using the APK loader.
Dalvik Debugger Options
The main configuration of the dalvik debugger happens resides in "Debugger > Debugger Options > Set specific options":
Connection Settings
ADB executable
As mentioned above IDA tries to locate the adb utility. If IDA failed to find it then we can set the path to adb here.
Connection string
Specifies the argument to the adb connect command. It is either empty (to let adb figure out a meaningful target) or a <host>[:<port>] combination to connect to a remote device somewhere on the network.
Emulator/device serial number
Serial number of an emulator or a device. Passed to adb``'s -s option. This option is useful if there are multiple potential target devices running. For the official Android emulator, it is typically emulator-5554.
Start Application
Fill from AndroidManifest.xml
Press button and point IDA to either the APK or the AndroidManifest.xml file of the mobile app. IDA then automatically fetches the package name and application start activity, as well as the debuggable flag from the specified file.
Package Name
Package name containing the activity to be launched by the debugger.
Activity
Start activity to be launched by the debugger.
Alternative Start Command
Usually IDA builds the start command from the package and activity name and launches the APK from the command line as follows:
If that does not match your desired debugging setup, you can enter an alternative start command here. Note that you have to provide package and activity as part of the startup command.
APK Debuggable
The value of the debuggable flag, as extracted from the AndroidManifest.xml or the APK. APKs that do not have the debuggable flag set (most do not) cannot be started on unpatched phones. Hence, while this value is false, IDA will display a (silencable) warning when starting a debugging session. To produce a debuggable APK that has the flag set to true, please revert to third-party tooling.
Detect Local Variable Types
This controls the behavior of IDA’s type guessing engine. "Always" and "Never" are pretty self-explanatory: The options force-enable or force-disable type guessing. "Auto" means that type guessing is disabled for Android APIs < 28 and enabled on APIs >= 28. If you work with very old (i.e. API 23 and lower) Android devices and experience crashes during debugging, set this option to "Never". Note that when type guessing is disabled, IDA automatically assumes int for unknown variable types, which causes warnings on API 30 and above.
Local Variables with Type Guessing Deactivated
Local Variables with Type Guessing Activated
Other Options
Show object ID
If active, IDA shows the object ID assigned by the Java VM for composite (non-trivial) types in the local variables window.
Preset BPTs
If active, IDA sets breakpoints at the beginning of all (non-synthetic, non-empty) methods of the start activity class specified in the Activity field above.
Path to Sources
To use source-level debugging we have to set paths to the application source files. We can do it using the "Options > Sources path" menu item.
Our Dalvik debugger presumes that the application sources reside in the current (".") directory. If this is not the case, we can map current directory (".") to the directory where the source files are located.
Let us place the source files DisplayMessageActivity.java and MainActivity.java in the same directory as the MyFirstApp.apk package. This way we do not need any mapping.
Setting Breakpoints
Before launching the application it is reasonable to set a few breakpoints. We can rely on the decision made by IDA (see above the presetBPTs option) or set breakpoints ourselves. A good candidate is the onCreate method of the application’s main activity.
We can use the activity name and the method name onCreate to set a breakpoint:
Naturally, we can set any other breakpoints any time. For example, we can do it later, when we suspend the application.
Starting the Debugger
At last we can start the debugger. Check that the Dalvik debugger backend is selected. Usually it should be done automatically by IDA:
If the debugger backend is correct, we are ready to start a debugger session. There are two ways to do it:
Launch a new copy of the application (Start process)
Attach to a running process (Attach to process)
Launching the App
To start a new copy of the application just press <F9> or use the "Debugger > Start process" menu item. The Dalvik debugger will launch the application, wait until application is ready and open a debugger session to it.
We may wait for the execution to reach a breakpoint or press the “Cancel” button to suspend the application.
In our case let us wait until execution reach of onCreate method breakpoint.
Attaching to a Running App
Instead of launching a new process we could attach to a running process and debug it. For that we could have selected the "Debugger > Attach to process…" menu item. IDA will display a list of active processes.
We just select the process we want to attach to.
Particularities of Dalvik Debugger
All traditional debug actions like Step into, Step over, Run until return and others can be used. If the application sources are accessible then IDA will automatically switch to the source-level debugging.
Below is the list of special things about our Dalvik debugger:
In Dalvik there is no stack and there is no SP register. The only available register is IP.
The method frame registers and slots (v0, v1, …) are represented as local variables in IDA. We can see them in the "Debugger > Debugger Windows > Locals" window (see below)
Locals Window
IDA considers the method frame registers, slots, and variables (v0, v1, …) as local variables. To see their values we have to open the "Locals" window from the "Debugger > Debugger windows > Locals" menu item.
At the moment the debugger stopped the execution at the breakpoint which we set on onCreate method.
Perform “Step over” action (the hot key is <F8>) two times and open the "Locals" window, we will see something like the following:
If information about the frame is available (the symbol table is intact) or type guessing is enabled then IDA shows the method arguments, the method local variables with names and other non-named variables. Otherwise some variable values will not be displayed because IDA does not know their types.
Variables without type information are marked with "Bad type" in the "Locals" window. To see the variable value in this case please use the "Watch view" window and query them with an explicit type (see below).
Watch View Window
To open the "Watch view" window select the "Debugger > Debugger windows > Watch view" menu item. In this window we can add any variable to watch its value.
note that we have to specify type of variable if it is not known. Use C-style casts:
(Object*)v0
(String)v6
(char*)v17
We do not need to specify the real type of an object variable, the “(Object*)” cast is enough. IDA can derive the real object type itself.
Attention! On Android API versions 23 and below an incorrect type may cause the Dalvik VM to crash. There is not much we can do about it. Our recommendation is to never cast an integer variable to an object type, the Dalvik VM usually crashes if we do that. But the integer cast “(int)” is safe in practice.
Keeping the above in the mind, do not leave the cast entries in the "Watch view" window for a long time. Delete them before any executing instruction that may change the type of the watched variable.
Overall we recommend to debug on a device that runs at least Android API 24.
Troubleshooting
Check the path to adb in the "Debugger specific options"
Check the package and activity names
Check that the emulator is working and was registered as an adb device. Try to restart the adb daemon.
The stack trace is available from "Debugger > Debugger windows > Stack trace" (the hot key is <Ctrl-Alt-S>).
When the application is running, it may execute some system code. If we break the execution by clicking on the “Cancel” button, quite often we may find ourselves outside of the application, in the system code. The value of the IP register is 0xFFFFFFFF in this case, and stack trace shows only system calls and a lot of 0xFFFFFFFF. It means that IDA could not locate the current execution position inside the application. We recommend to set more breakpoints inside the application, resume the execution and interact with application by clicking on its windows, selecting menu items, etc. The same thing can occur when we step out the application.
Use “Run until return” command to return to the source-level debugging if you occasionally step into a method and the value of the IP register becomes 0xFFFFFFFF.
(int)v7
Check that the application was successfully installed on the emulator/device
Check the output window of IDA for any errors or warnings
Turn on more debug print in IDA with the -z50000 command line switch.
Android APIs 24 and 25 are known to return wrong instruction sizes during single stepping. Try migrating to a different Android API if you have trouble with single steps.
IDA exposes a subset of the JDWP specification as IDC commands. (Usually the name from the specification prefixed with JDWP_).
Android APIs 23 and below crash if type guessing is enabled. Remedy this by setting the Detect Local Variable Types option to Never or migrate to a newer Android API.
$ adb devices
List of devices attached
emulator-5554 device
$ adb -s emulator-5554 install MyFirstApp.apk
am start -D -n '<package>/<activity>' -a android.intent.action.MAIN -c android.intent.category.LAUNCHER
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:
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.
In the main Xcode window, ensure that the appropriate target device (Run Destinations) is selected.
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.
The developer team can be selected via the Signing & Capabilities editor.
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)
Finally, Run the app and ensure that it starts properly on the target device.
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
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.
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
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 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)
Retrieve the PID of the process.
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.
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.
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.
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.
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.
Enter the PID of the target process retrieved earlier using devicectl and accept this dialog.
Profit! The Debugging session should start.
Debugging Windows Applications with IDA WinDbg Plugin
Quick overview:
The Windbg debugger plugin is an IDA Pro debugger plugin that uses Microsoft's debugging engine (dbgeng) that is used by Windbg, Cdb or Kd.
To get started, you need to install the latest Debugging Tools from Microsoft website:
or from the Windows SDK / DDK package.
Please make sure you should install the x86 version of the debugging tools which is used by both IDA Pro and IDA Pro 64. The x64 version will NOT work.
After installing the debugging tools, make sure you select « Debugger / Switch Debugger » and select the WinDbg debugger.
last
one should be considered (previous connections would likely be stale).
Xcode template selection dialog with iOS Game selected
Xcode project options dialog with "spritegame" as the Product Name and the Bundle Identifier "com.acme.spritegame" highlighted in red
Xcode target device selection with an iPhone "red_iphone11" selected
Xcode error: "Signing for "spritegame" requires a development team. Select a development team in the Signing & Capabilities editor."
Xcode Signing & Capabilities editor with an arrow pointing at the selected team
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."
Xcode bar showing that spritegame is running on red_iphone11
IDA with menu bar open on Debugger>Process options...
IDA debug application setup dialog with Hostname and Port fields filled-in
IDA iOS configuration dialog with Symbol path field filled-in
IDA Debug options dialog with "Suspend on debugging start" checkbox checked.
IDA Choose process dialog with a single entry "<enter a PID to attach>"
IDA dialog asking for a PID input
IDA debugging session successfully started, IDA View showing PC on first instruction of __dyld_start
Also make sure you specify the correct settings in the “Debugger specific options” dialog:
User mode: Select this mode for user mode application debugging (default mode)
Kernel mode: Select this mode to attach to a live kernel.
Non Invasive debugging: Select this mode to attach to a process non-invasively
Output flags: These flags tell the debugging engine which kind of output messages to display and which to omit
Kernel mode debugging with reconnect and initial break: Select this option when debugging a kernel and when the connection string contains 'reconnect'. This option will assure that the debugger breaks as soon as possible after a reconnect.
To make these settings permanent, please edit the IDA\cfg\dbg_windbg.cfg file.
**
To specify the debugging tools folde**r you may add to the PATH environment variable the location of Windbg.exe or edit %IDA%\cfg\ida.cfg and change the value of the DBGTOOLS key.
After the debugger is properly configured, edit the process options and leave the connection string value empty because we intend to debug a local user-mode application.
Now hit F9 to start debugging:
The Windbg plugin is very similar to IDA Pro's Win32 debugger plugin, nonetheless by using the former, one can benefit from the command line facilities and the extensions that ship with the debugging tools.
For example, one can type “!chain” to see the registered Windbg extensions:
“!gle” is another command to get the last error value of a given Win32 API call.
Another benefit of using the Windbg debugger plugin is the use of symbolic information.
Normally, if the debugging symbols path is not set, then the module window will only show the exported names. For example kernel32.dll displays 1359 names:
Let us configure a symbol source by adding this environment variable before running IDA:
set _NT_SYMBOL_PATH=srv*C:\Temp\pdb*http://msdl.microsoft.com/download/symbols
It is also possible to set the symbol path directly while debugging:
and then typing “.reload /f” to reload the symbols.
Now we try again and notice that more symbol names are retrieved from kernel32.dll:
Now we have 5818 symbols instead!
It is also possible to use the “x” command to quickly search for symbols:
(Looking for any symbol in any module that contains the word “continue”)
Debugging a remote process
We have seen how to debug a local user mode program, now let us see how to debug a remote process.
First let us assume that “pcA” is the target machine (where we will run the debugger server and the debugged program) and “pcB” is the machine where IDA Pro and the debugging tools are installed.
To start a remote process:
On “pcA”, type:
dbgsrv -t tcp:port=5000
(change the port number as needed)
On “pcB”, setup IDA Pro and Windbg debugger plugin:
“Application/Input file”: these should contain a path to the debuggee residing in “pcA”
Connection string: tcp:port=5000,server=pcA
Now run the program and debug it remotely.
To attach to a remote process, use the same steps to setup “pcA” and use the same connection string when attaching to the process.
More about connection strings and different protocols (other than TCP/IP) can be found in “debugger.chm” in the debugging tools folder.
Debugging the kernel with VMWare
We will now demonstrate how to debug the kernel through a virtual machine.
In this example we will be using VMWare 6.5 and Windows XP SP3.
Configuring the virtual machine:
Run the VM and then edit “c:\boot.ini” file and add one more entry (see in bold):
[operating systems]
multi(0)disk(0)rdisk(0)partition(1)\WINDOWS="Microsoft Windows XP Professional" /noexecute=optin /fastdetect multi(0)disk(0)rdisk(0)partition(1)\WINDOWS="Local debug" /noexecute=optin /fastdetect /debug /debugport=com1 /baudrate=115200
Actually the last line is just a copy of the first line but we added the “/debug” switch and some configuration values.
Now shutdown the virtual machine and edit its hardware settings and add a new serial port with option “use named pipes”:
Press “Finish” and start the VM. At the boot prompt, select “Local debug” from the boot menu:
Configuring Windbg debugger plugin:
Now run IDA Pro and select Debugger / Attach / Windbg
Then configure it to use “Kernel mode” debugging and use the following connection string:
com:port=\.\pipe\com_1,baud=115200,pipe
It is possible to use the 'reconnect' keyword in the connection string:
com:port=\.\pipe\com_1,baud=115200,pipe,reconnect
Also make sure the appropriate option is selected from the debugger specific options.
Please note that the connection string (in step 1) refers to the named pipe we set up in the previous steps.
Finally, press OK to attach and start debugging.
When IDA attaches successfully, it will display something like this:
If you do not see named labels then try checking your debugging symbols settings.
Note: In kernel mode IDA Pro will display one entry in the threads window for each processor.
For example a two processor configuration yields:
VMWare configuration
Threads in IDA
This screenshot shows how we are debugging the kernel and changing the disassembly listing (renaming stack variables, or using structure offsets):
At the end you can detach from the kernel and resume it or detach from the kernel and keep it suspended.
To detach and resume, simply select the “Debugger / Detach”, however to detach and keep the kernel suspended select “Debugger / Terminate Process”.
Debugging the kernel through kdsrv.exe
In some cases, when debugging a 64bit kernel using a 1394 cable then 64bit drivers are needed, thus dbgeng (32bits) will not work. To workaround this problem we need to run the kernel debugger server from the x64 debugging tools folder and connect to it:
Go to “Debugging Tools (x64)” installation
Run kdsrv.exe (change the port number/transport appropriately):
kdsrv -t tcp:port=6000
Now run ida64 and specify the following connection string (change the transport value appropriately):
Using Trace Replayer Debugger and Managing Traces in IDA
Copyright 2014 Hex-Rays SA
Introduction
Quick Overview
The trace replayer is an IDA pseudo debugger plugin that appeared first in IDA 6.3. This plugin can replay execution traces recorded with any debugger backend in IDA, such as local Win32 or Linux debuggers, WinDbg, remote GDB debugger, etc...
Following this tutorial
This tutorial was created using the Linux version of IDA and a Linux binary as target. However, it can be followed on any supported platform (MS Windows, Mac OS X and Linux) by setting up remote debugging. Please refer to the for more information regarding .
Supplied files
Among with the tutorial the following files are also provided at
Replaying and managing traces
Recording traces
Before using the trace replayer plugin we will need to record an execution trace of a program. We will use the following toy vulnerable program as an example:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int foo(char *arg, int size)
{
char *buf;
if ( strlen(arg) > size )
{
printf("Too big!\n");
return 1;
}
buf = malloc(size);
strcpy(buf, arg);
printf("Buffer is %s\n", buf);
free(buf);
return 0;
}
int main(int argc, char **argv)
{
if ( argc != 3 )
{
printf("Invalid number of arguments!\n");
return 2;
}
return foo(argv[1], atoi(argv[2]));
}
Please compile this sample program (in this example, we used GCC compiler for Linux) or use the supplied ELF binary, open the binary in IDA and wait until the initial analysis completes. When done, select a suitable debugger from the drop down list (“Local Linux debugger”, or “Remote Linux debugger” if you're following this tutorial from another platform):
We have two ways of telling IDA to record a trace:
Break on process entry point and manually enable tracing at this point.
Or put a trace breakpoint at the very first instruction of the program.
In the case we prefer the first approach we will need to click on the menu “Debugger → Debugger Options” and then mark the check box “Stop on process entry point” as shown bellow:
After checking this option press OK and run the program pressing F9. When the entry point is reached, we can select from the menu “Debugger → Tracing” one of the following three options:
Instruction tracing: All instructions executed will be recorded.
Function tracing: Only function calls and returns will be recorded.
Basic block tracing: Similar to instruction tracing but, instead of single stepping instruction by instruction, IDA will set temporary breakpoints in the end of every known basic block, as well as on function calls.
For this example we will select “Instruction tracing”. Check this option and let the program continue by pressing F9. The program will resume execution and finish quickly. Now, we have a recorded trace! To see it, select “Debugger → Tracing → Trace Window”. A new tab will open with a content similar to the following:
As previously stated, there are two ways to record traces: enabling it manually, or using an “Enable tracing” breakpoint. To set such a breakpoint we will go to the program's entry point (Ctrl+E) and put a breakpoint (F2) in the very first instruction. Then right click on the new breakpoint and select “Edit breakpoint”. In the dialog check the option “Enable tracing” and then select the desired “Tracing type” (for this example, we'll use “Instructions”):
Remove the “Stop on process entry point” option we set in the prior example and press F9 to run the program.
This way is more convenient than the first because the tracing is turned on automatically and does not need manual intervention.
Working with traces
Now we have a new recorded trace, no matter which method we used. What can we do with it? First, we can check which instructions were executed, as they are highlighted in the disassembly, like in the screenshot bellow:
(the highlight color can be changed in “Debugger → Tracing → Tracing Options”)
Highlighting makes it clear which instructions have been executed.
We can also check what functions have been executed (instead of instructions) by opening the “Trace Window” via “Debugger → Tracing → Trace Window”, right clicking on the list and then selecting “Show trace call graph”:
Now let's inspect the register values in order to understand why the check at 0x0848566 doesn't pass. Please select “Debugger → Switch debugger” and in the dialog box click on the “Trace replayer” radio button:
Click OK and press F4 in the first instruction of the “main” function.
The trace replayer will suspend execution at the “main” function and display the register values that were recorded when the program was executed:
We can single step by pressing F7, as usual. Let us keep pressing F7 until the “jz” instruction is reached:
The comparison “cmp [ebp+arg_0], 3” was not successful (ZF=0) so the check does not pass. We need to give to the program two arguments to pass this check and record a new trace.
Loading an overlay and viewing differences in flow
Before doing another run, let's save the first trace to a file. Select “Debugger → Tracing → Trace Window”, right click in the middle of the newly opened tab, and select “Save trace” from the popup menu:
Then save the file:
You will also be offered a chance to give the trace a description:
Now let's record a new trace but this time we will pass two command line arguments to the program. Select “Debugger → Process Options” and set “AAAA 4” as the arguments:
Close the dialog, revert to the “Local Linux debugger”, and press F9. A new trace will be recorded. If we check the “main” function we will see that different instructions have been executed this time:
Let's check which instructions are different between the first and the second run.
First, we will need to load the previous trace as “overlay”:
Select the trace we saved:
Note that we have now other options in the 'Overlay' submenu, now that there is an overlay present:
Now go back to the disassembly view and check how the disassembly code is highlighted in three different colors:
The code highlighted in yellow is the code executed in the current trace (the one listed in the “Trace Window”). The pink code was executed only in the overlay trace. And the code in purple is the code common to both traces. We can immediately see that there is some new code that have been executed, like the calls to atoi and foo.
Let's go to the “foo” function and see what happened here:
The code in yellow tells us that the check for the size at 0x800484FC passed and the calls to malloc, strcpy and printf were executed. Let's save this trace for later analysis and comparison with the future runs. As before, go to the trace window, right click on the list and select “Save trace”. Set the trace's description to 'Correct execution'.
It's time to record another trace with different arguments to see what happens. For this new execution, we will longer command line arguments (eight “A” characters instead of four). Let's change the arguments in “Debugger → Process Options”, switch back to the “Local Linux debugger”, and run it. We have a new trace. Let's compare it against the previously recorded one. As we did before, go to the “Trace Window”, right click on the list, select “Overlay”, then “Load overlay”, and select the trace with description “Correct execution”.
As we see, the code that alerts us the about a too big string was executed (it's highlighted in yellow). Let's save this recorded trace with the “String too big” description. Now we will record one more trace but this time we will use the number “-1” as the second command line argument.
Change the arguments in “Debugger → Process Options” as shown bellow:
Then switch back again, to the “Local Linux debugger” (or to “Remote Linux debugger” if needed) and run it by pressing F9. The process will crash somewhere in the call to strcpy:
Stop the debugger and save this trace (let's call it “Crash”). Then diff this trace against the “Correct execution” trace.
We will see the following in the disassembly view:
As we see, pretty much the same code as in the previous run was executed until the call to strcpy. It's time to replay this last execution and see what happened.
Diffing traces
When both a “main trace” and an “overlay trace” are present, the context menu item “Overlay → Subtract overlay” becomes available.
This allows one to subtract the list of events (e.g., instructions) that are present in the overlay, from the main trace:
Will give the following results:
As you can see, many events that were present in both the overlay & the main trace have been removed. Only those that were only present in the main trace remain.
Reverting the diff
The diffing operation is reversible:
This will restore the main trace as it were, before the contents of the overlay were removed from it.
Replaying traces
We know that the program is crashing somewhere in the call to strcpy but we don't know why the check at 0x080484FC passes since -1 is smaller than the size of the string (8 bytes). Let's put a breakpoint at the call to strlen at 0x080484F0, switch to the “Trace replayer” debugger, and "run" the program by pressing F9. Please note that we do not really run the program, we are merely replaying a previously recorded trace.
The debugger will stop at the strlen call:
In the trace replayer we can use all usual debugging commands like “run to cursor” (F4), “single step” (F7), or “step over” (F8). Let's press F8 to step over the strlen call and check the result:
It returns 8 as expected. Now move to the address 0x080484FC and press F4 or right click on this address, select “Set IP”, and press F7 (we need to inform the replayer plugin that we changed the current execution instruction in order to refresh all the register values). The difference between “Run to” (F4) and “Set IP” is that “Run to” will replay all events happened until that point but “Set IP” will directly move to the nearest trace event happened at this address (if it's in the recorded trace, of course).
Regardless of how we moved to this point IDA will display the following:
As we see, the jump was taken because CF was set to 1 in the previous instruction (“cmp edx, eax”). Let's step back to this instruction to see what values were compared. Select “Debugger → Step back” from the menu:
The flags are reset to 0 and we can see that EAX (0xFFFFFFFF) and EDX (8) are compared. Press F7 to step one instruction again and you will notice CF changes to 1. The instruction JBE performs an unsigned comparison between 8 and 0xFFFFFFFF and, as 8 <= 0xFFFFFFFF, the check passes. We just discovered the cause of the bug.
Let's continue analyzing it a bit more. Scroll down until the call to malloc at 0x08048517, right click, choose “Set IP”, and press F7 (or simply press F4). As we see, the argument given to malloc is 0xFFFFFFFF (4 GB).
Press F8 to step over the function call:
Obviously, malloc can not allocate so much memory and returns NULL. However, the program does not check for this possibility and tries to copy the contents of the given buffer to the address 0, resulting in a crash.
Summary
In this tutorial we showed you the basics of trace management and the trace replayer module in IDA. We hope you enjoy this new feature. Happy debugging!
IDA 7.3 introduces the Remote XNU Debugger. It is designed to communicate with the GDB stub included with popular virtualization tools, namely VMWare Fusion (for OSX) and Corellium (for iOS). The debugger allows you to observe the Darwin kernel as it is running, while at the same time utilising the full power of IDA's analysis capabilities. It works equally well on Mac, Windows, and Linux.
This writeup is intended to quickly get you familiar with debugger, as well as offer some hints to make the experience as smooth as possible.
Debugging OSX with VMWare
To get started with debugging OSX, we will perform a simple experiment. This is the same experiment outlined in , but we will be performing the equivalent in IDA - which we hope you'll find is much simpler.
Begin with the following setup:
create an OSX virtual machine with . in this example the virtual machine is OSX 10.13.6, but the experiment should work with any recent OSX version.
open Terminal in the VM and enable some basic XNU debugging options:
shut down the VM and add the following line to the :
Launch IDA, and when prompted with the window IDA: Quick start, choose Go to start with an empty database. Then go to menu Debugger>Attach>Remote XNU Debugger and set the following options:
Click OK, then select <attach to the process started on target>, and wait for IDA to attach. This step might take a few seconds (later we'll discuss how to speed things up). Once attached, the target is usually suspended in machine_idle:
IDA should have printed the message FFFFFF8000200000: process kernel has started, meaning it successfully detected the kernel image in memory. Now let's find the version string. Conveniently, the string appears in the kernel's symbol table, so we can simply use shortcut G and enter the name _version to jump right to it:
Use IDAPython to overwrite the bytes at this address:
Resume the process and allow the VM to run freely. Go back to Terminal in the VM and run the same command as before:
The output should look almost the same, except Darwin has been replaced with IDAPRO. So, we have modified kernel memory without breaking anything! You can continue to explore memory, set breakpoints, pause and resume the OS as you desire.
Using the KDK
If you have installed a from Apple, you can set KDK_PATH in dbg_xnu.cfg to enable DWARF debugging:
Even if there is no KDK available for your OSX version, you can still utilise the KDK_PATH option in IDA to speed up debugging. For example, in the experiment above we could have done the following:
make your own KDK directory:
copy the kernelcache from your VM:
decompress the kernelcache:
set KDK_PATH in dbg_xnu.cfg:
Now whenever IDA needs to extract information from the kernel or kexts, it will parse the kernelcache file on disk instead of parsing the images in memory. This should be noticeably faster.
Debugging a Development Kernel
Our next goal is to use the KDK to create a rich database that can be used to debug XNU in greater detail. In this example we will debug the development kernel included in the Apple KDK. Let's open this file in IDA:
Wait for IDA to load the DWARF info and complete the autoanalysis. This may take a few minutes, but we only need to do it once.
While we wait, we can prepare the virtual machine to use the development kernel instead of the release kernel that is shipped with OSX (Note: System Integrity Protection must now be disabled in the VM). Open Terminal in the VM and run the following commands:
copy the development kernel from the KDK:
reconstruct the kernelcache:
reboot:
after rebooting, check that the development kernel was properly installed:
The VM is now ready for debugging.
\
IDA Configuration
Return to IDA and use Debugger>Select debugger to select Remote XNU Debugger. Then open Debugger>Process options and set the following fields:
Now go to Debugger>Debugger options>Set specific options and make sure the KDK path field is set:
You can ignore the other options for now, and press OK.
\
Assembly-Level Debugging + DWARF
IDA supports source-level debugging for the XNU Kernel. However for demonstration purposes we will focus on assembly-level debugging, while taking advantage of source-level DWARF information like local variables. This is a bit more stable, and is still quite useful.
Before attaching the debugger, open Options>Source paths... and un-check the checkbox:
Then click Apply. This will prevent IDA from complaining when it can't find a source file.
Finally, select Debugger>Attach to process>attach to the process started on target. After attaching, jump to function dofileread, and use F2 to set a breakpoint. Resume the debugger and and wait for the breakpoint to be hit (typically it will be hit right away, if not try simply running a terminal command in the guest). Once XNU hits our breakpoint, open Debugger>Debugger windows>Locals:
We can now perform detailed instruction-level debugging with the assistance of DWARF. You can continue to single step, set breakpoints, and inspect or modify local variables just like any other IDA debugger.
\
KEXT Debugging
IDA also supports debugging kext binaries. To demonstrate this, we will debug IONetworkingFamily, a submodule of IOKit that is typically shipped with the KDK. Begin by opening the binary in IDA:
Select Remote XNU Debugger from the debugger menu. Then in Debugger>Process options, set:
Note that we provide the bundle ID of the kext (com.apple.iokit.IONetworkingFamily) as the Input file field. This allows the debugger to easily identify the target kext at runtime.
Also note that loading all kexts in kernel memory can be a slow operation, which is why it is disabled by default. Open Debugger>Debugger options>Set specific options and ensure the KDK path field is set, then set the KEXT Debugging option to KDK only:
This tells the debugger to only load kexts that are present in the KDK. Since the KDK binaries are on the local filesystem, IDA can parse the kexts in a negligible amount of time - which is ideal since we're really only interested in IONetworkingFamily.
Now power on your VM and allow it to boot up. Once it is running idle, attach the debugger. Immediately IDA should detect the kernel and all relevant kexts in memory, including IONetworkingFamily:
Double-click to bring up the debug names for this module, and search for IONetworkInterface::if_ioctl:
Now set a breakpoint at this function and resume the OS. Typically the breakpoint will be hit right away, but if it isn't try performing an action that requires a network interface (for instance, performing a google search). Once execution breaks in the kext we can use the database to debug it in detail:
\
Debugging a Prelinked Kernelcache
For simplicity, all of the examples up until now have dealt with a subset of the kernel, but it is also possible to load a complete prelinked kernelcache in IDA and debug it. Naturally, we have some suggestions for this.
Extending the KDK
If you're interested in debugging the entire prelinked kernel, the biggest concern is speed. IDA must create a detailed and accurate depiction of kernel memory, which could contain hundreds of kext modules. If we're not careful, this can be slow.
Fortunately there is an easy solution. Try the following:
create a writable copy of Apple's KDK:
copy the kernelcache from your VM to the new KDK:
decompress the kernelcache:
Now IDA can use both the KDK and the kernelcache to extract debugging information for almost any kext at runtime. This should be fast.
Loading the Kernelcache
When loading a kernelcache, IDA now offers more load options:
In this example we want to load everything, so choose the kernel + all kexts option and wait for IDA to load all the subfiles and finish the autoanalysis. This will take a while but there's no way around it, it's a lot of code.
IMPORTANT NOTE: Try to avoid saving the IDA database file in the KDK directory. It is important to keep irrelevant files out of the KDK since they might slow down IDA's KDK parsing algorithm.
Now we might want to improve the static analysis by loading DWARF info from the KDK. In IDA 7.3 the dwarf plugin supports batch-loading all DWARF info from a KDK into a kernelcache database. Currently this feature must be invoked manually, so we have provided to make it easier.
Copy kdk_utils.py to the plugins directory of your IDA installation. This plugin will create a new menu Edit>Other>KDK utils, with two new menu actions:
Load KDK: This action will automatically detect all matching DWARF files in a given KDK, then apply the DWARF info to the subfiles in the database (including the kernel itself).
Load DWARF for a prelinked KEXT: This action is useful if you have DWARF info for a prelinked kext that is not included in Apple's KDK. For a given DWARF file, the action will find a matching kext in the database and apply the DWARF info to this subfile.
Try opening Edit>Other>KDK utils>Load KDK and provide the KDK path:
Wait for IDA to scan the KDK for matching DWARF files and load them. This operation can also take a while, but it's worth it for all the extra structures, prototypes, and names that are added to the database. In the end we have a very detailed database that we are ready to use for debugging.
Now open Debugger>Process options and set the following options:
Then open Debugger>Debugger options>Set specific options and set the following fields:
Note that we set the KEXT Debugging option to all. This tells the debugger to detect every kext that has been loaded into memory and add it to the Modules list, including any non-prelinked kexts (there are likely only a handful of them, so it doesn't hurt).
Finally, power on the VM and attach to it with Debugger>Attach to process>attach to the process started on target. IDA should be able to quickly generate modules for the kernel and all loaded kexts:
You are now free to explore the entire running kernel! Try performing any of the previous demos in this writeup. They should work about the same, but now they are all possible with one single database.
Kernel ASLR + Rebasing
It is worth noting that rebasing has been heavily improved in IDA 7.3. Even large databases like the one we just created can now be rebased in just a few seconds. Previous IDA versions would take quite a bit longer. Thus, IDA should be able to quickly handle kernel ASLR, even when working with prelinked kernelcaches.
Debugging the OSX Kernel Entry Point
In this example we demonstrate how to gain control of the OS as early as possible. This task requires very specific steps, and we document them here. Before we begin, we must make an important note about a limitation in VMWare's GDB stub.
Physical Memory
Currently VMWare's 64-bit GDB stub does not allow us to debug the kernel entry point in physical memory. According to VMWare's support team, the correct approach is to use the 32-bit stub to debug the first few instructions of the kernel, then switch to a separate debugger connected to the 64-bit stub once the kernel switches to 64-bit addressing.
Since IDA's XNU debugger does not support 32-bit debugging, this approach is not really feasible (and it's not very practical anyway).
Workaround
Rather than add support for the 32-bit stub just to handle a few instructions, the official approach in IDA is to break at the first function executed in virtual memory (i386_init). This allows us to gain control of the OS while it is still in the early stages of initialization, which should be enough for most use cases.
Here's how you can do it:
Disable ALSR for the kernel. Open Terminal in the VM and run the following command:
Then power off the VM.
Add this line to the .vmx file:
This ensures that hardware breakpoints are enabled in the GDB stub. For most versions of VMWare, TRUE is the default value, but it's better to be safe.
UEFI Debugging
It is possible to debug the EFI firmware of a VMWare Fusion guest. This gives us the unique opportunity to debug the OSX bootloader. Here's how it can be easily done in IDA:
First copy the bootloader executable from your VM:
Now shut down the VM and add this line to the .vmx file:
Load the boot.efi binary in IDA, open Debugger>Debugger options, check Suspend on library load/unload, and set Event condition to:
This will suspend the OS just before the bootloader entry point is invoked. Note: For some older versions of OSX, the bootloader will be named "boot.sys". You can check the name under the .debug section of the executable.
Now select Remote XNU Debugger from the Debugger menu, and set the following fields in Debugger>Process options:
\
We're now ready to start debugging the bootloader. Power on the VM (note that the VM is unresponsive since it is suspended), and attach to it with Debugger>Attach to process. After attaching IDA will try to detect the EFI_BOOT_SERVICES table. You should see the debugger print something like this to the console:
Now resume the process. You should see many UEFI drivers being loaded, until eventually boot.efi is loaded and IDA suspends the process:
At this point, the bootloader entry point function is about to be invoked. Jump to _ModuleEntryPoint in boot.efi and press F4. We can now step through boot.efi:
\
GetMemoryMap
To facilitate UEFI debugging, IDA provides an IDC helper function: xnu_get_efi_memory_map. This function will invoke EFI_BOOT_SERVICES.GetMemoryMap in the guest OS and return an array of EFI_MEMORY_DESCRIPTOR objects:
This function can be invoked at any point during firmware debugging.
UEFI Debugging + DWARF
If you build your own EFI apps or drivers on OSX, you can use IDA to debug the source code.
In this example we will debug a sample EFI application. On OSX the convention is to build EFI apps in the Mach-O format, then convert the file to PE .efi with the mtoc utility. In this example, assume we have an EFI build on our OSX virtual machine that contains the following files in the ~/TestApp directory:
TestApp.efi - the EFI application that will be run
TestApp.dll - the original Mach-O binary
TestApp.dll.dSYM - DWARF info for the app
Here's how we can debug this application in IDA:
On your host machine, create a directory that will mirror the directory on the VM:
Copy the efi, macho, dSYM, and c files from your VM:
Open the TestApp.efi binary in IDA, and wait for IDA to analyze it.
Note that you can improve the disassembly by loading the DWARF file from TestApp.dll.dSYM. You can do this with Edit>Plugins>Load DWARF file, or you can load it programatically from IDAPython:
Debugging iOS with Corellium
IDA can also debug the iOS kernel, provided you have access to a virtual iOS device from .
To get started with debugging iOS, we will perform a simple experiment to patch kernel memory. The device used in this example is a virtual iPhone XS with iOS 12.1.4, but it should work with any model or iOS version that Corellium supports. Begin by powering on your device and allow it to boot up. In the Corellium UI, look for the line labeled SSH under Advanced options:
Ensure you can connect to the device by running this command over ssh:
We will use IDA to patch this version string.
Now launch IDA, and when prompted with the window IDA: Quick start, choose Go to start with an empty database and open Debugger>Attach>Remote XNU Debugger. In the Corellium UI, find the hostname:port used by the kernel GDB stub. It should be specified in the line labeled kernel gdb:
And set the Hostname and Port fields in IDA's application setup window:
Now click on Debug options>Set specific options, and for the Configuration dropdown menu, be sure to select Corellium-ARM64:
You can ignore the other config options for now, and click OK.
Click OK again, and wait for IDA to establish a connection to Corellium's GDB stub (this may take a few seconds). Then select <attach to the process started on target> and wait for IDA to attach. This might take several seconds (we will address this later), but for now simply wait for IDA to perform the initial setup.
If IDA could detect the kernel, it should appear in the Modules list:
and the kernel version will be printed to the console:
Navigate to this address and use IDAPython to overwrite the string:
Resume the OS, and try running the same command as before:
If we could successfully write to kernel memory, IDAPRO should appear in the output.
Creating a KDK for iOS
Typically a Kernel Development Kit is not available for iOS devices, but we can still utilise the KDK_PATH option in IDA to achieve faster debugging. In the example above, the initial attach can be slow because IDA must parse the kernel image in memory (which can be especially slow if the kernel has a symbol table).
Here's how you can speed things up:
create the KDK directory:
copy the kernelcache from the virtual device:
uncompress the kernelcache with :
Now whenever the debugger must extract information from the kernel, it will parse the local file on disk. This should be noticeably faster, especially if the device is hosted by Corellium's web service.
\
Debugging the iOS Kernel Entry Point
Corellium allows us to debug the first few instructions of kernel initialization. This can be very useful if we want to gain control of the OS as early as possible. In the Corellium UI, power on your device with the Start device paused option:
Now start IDA with an empty database and attach to the suspended VM:
From the XNU source, this is likely the _start symbol in osfmk/arm64/start.s, which simply branches to start_first_cpu. After stepping over this branch:
Press shortcut P to analyze start_first_cpu. This is where the kernel performs its initial setup (note that the value in X0 is a pointer to the boot_args structure). This function is interesting because it is responsible for switching the kernel to 64-bit virtual addressing. Typically the switch happens when this function sets X30 to a virtual address, then performs a RET:
Use F4 to run to this RET instruction. In this example X30 will now point to virtual address 0xFFFFFFF007B84474. After single stepping once more, we end up in arm_init in virtual memory:
After this single step, IDA detected that execution reached the kernel's virtual address space and automatically initialized the debugging environment. In this case a message will be printed to the console:
This signifies that IDA successfully detected the kernel base and created a new module in the Modules list. If the kernel has a symbol table, debug names will be available. Also note that PC now points inside the segment __TEXT_EXEC:__text instead of MEMORY, because the debugger parsed the kernel's load commands to generate proper debug segments.
Now that we know the address of arm_init, we can streamline this task:
power on the device with Start device paused
attach to the paused VM
set a hardware breakpoint at arm_init:
This gives us a quick way to break at the first instruction executed in virtual memory. You can continue debugging iOS as normal.
Known Issues and Limitations
Here is a list of known shortcomings in the XNU Debugger. Eventually we will address all of them, but it is unlikely we will resolve all of them by the next release. If any of the following topics are important to you, please let us know by sending an email to [email protected]. Issues with vocal support from our users are automatically prioritised.
iBoot debugging
Debugging the iOS firmware/bootloader is not yet supported. An On-Premise Corellium box is required for this functionality, so we will only implement it if there is significant demand.
32-bit XNU
The XNU Debugger does not support debugging 32-bit XNU. Since pure 32-bit OSes are quite outdated it is unlikely we will support them unless there is exceptional demand.
KDP
The XNU Debugger relies on the Remote GDB Protocol, and currently Apple's Kernel Debugging Protocol (KDP) is not supported. It is possible to add KDP support to IDA in the future.
Debugging Mac OSX Applications with IDA Pro
Debugging Mac OSX Applications with IDA Pro
Last updated on March 6, 2021 — v2.0
Overview
power on the virtual machine, open Terminal, and run this command:
Let's use IDA to modify this version string.
Also add this line to the .vmx file:
This will tell VMWare to suspend the OS before it boots.
Power on the VM. It will remain suspended until we attach the debugger.
Load a kernel binary in IDA, and set the following XNU debugger options:
Note that we un-checked the Debug UEFI option. This option is explained in detail in the UEFI Debugging section, but for now just ensure it is disabled. This will prevent IDA from doing any unnecessary work.
Attach the debugger. The VM will be suspended in the firmware before the boot sequence has begun:
Now jump to the function _i386_init and set a hardware breakpoint at this location:
We must use a hardware breakpoint because the kernel has not been loaded and the address is not yet valid. This is why steps 1 and 2 were important. It ensures the stub can set a breakpoint at a deterministic address, without trying to write to memory.
Resume the OS, and wait for our breakpoint to be hit:
IDA should detect that execution has reached the kernel and load the kernel module on-the-fly. You can now continue to debug the kernel normally.
TestApp.c - source code for the app
Select Remote XNU Debugger from the debugger menu, and set the following fields in Debugger>Process options:
In Debugger>Debugger options, enable Suspend on library load/unload and set the Event condition field to:
In Debugger>Debugger options>Set specific options, set the following fields:
Note that we must enable the Debug UEFI option, and set the UEFI symbols option so the debugger can find DWARF info for the EFI app at runtime.
If the usernames on the host and VM are different, we will need a source path mapping:
Reboot the VM and enter the EFI Shell
Attach the debugger. After attaching IDA will detect the firmware images that have already been loaded:
Resume the OS and launch TestApp from the EFI Shell prompt:
At this point IDA will detect that the target app has been loaded, and suspend the process just before the entry point of TestApp.efi (because of step 5).
Now we can set a breakpoint somewhere in TestApp.efi and resume the OS. The debugger will be able to load source file and local variable information from TestApp.dll.dSYM:
IMPORTANT NOTE: You must wait until TestApp has been loaded into memory before setting any breakpoints. If you add a breakpoint in the database before attaching the debugger, IDA might not set the breakpoint at the correct address. This is a limitation in IDA that we must work around for now.
IDA Pro fully supports debugging native macOS applications.
Intel x86/64 debugging has been supported since IDA 5.6 (during OSX 10.5 Leopard), but due to IDA’s use of libc++ we can only officially support debugging on OSX 10.9 Mavericks and later. Apple Silicon arm64 debugging for macOS11 is also supported since IDA 7.6.
Note that this task is riddled with gotchas, and often times it demands precise workarounds that are not required for other platforms. In this tutorial we will purposefully throw ourselves into the various pitfalls of debugging on a Mac, in the hopes that learning things the hard way will ultimately lead to a smoother experience overall.
Begin by downloading samples:
which contains the sample applications used in this writeup.
Codesigning & Permissions
It is important to note that a debugger running on macOS requires special permissions in order to function properly. This means that the debugger itself must be codesigned in such a way that MacOS allows it to inspect other processes.
The main IDA Pro application is not codesigned in this way. Later on we’ll discuss why.
To quickly demonstrate this, let’s open a binary in IDA Pro and try to debug it. In this example we’ll be debugging the helloworld app from samples.zip:
on MacOSX 10.15 Catalina using IDA 7.5. Begin by loading the file in IDA:
Now go to menu Debugger>Select debugger and select Local Mac OS X Debugger:
Immediately IDA should print a warning message to the Output window:
This is because IDA is aware that it is not codesigned, and is warning you that attempting to debug the target application will likely fail. Try launching the application with shortcut F9. You will likely get this error message:
Codesigning IDA Pro might resolve this issue, but we have purposefully decided not to do this. Doing so would require refactoring IDA’s internal plugin directory structure so that it abides by Apple’s bundle structure guidelines. This would potentially break existing plugins as well as third-party plugins written by users. We have no plans to inconvenience our users in such a way.
Also note that running IDA as root will allow you to use the Local Mac OS X Debugger without issue, but this is not advisable.
A much better option is to use IDA’s mac debug server - discussed in detail in the next section.
Using the Mac Debug Server
A good workaround for the debugging restrictions on macOS is to use IDA’s debug server - even when debugging local apps on your mac machine. The mac debug server is a standalone application that communicates with IDA Pro via IPC, so we can ship it pre-codesigned and ready for debugging right out of the box:
Let’s try launching the server:
Now go back to IDA and use menu Debugger>Switch debugger to switch to remote debugging:
Now use Debugger>Process options to set the Hostname and Port fields to localhost and 23946.
(Note that the port number was printed by mac_server64 after launching it):
Also be sure to check the option Save network settings as default so IDA will remember this configuration.
Now go to _main in the helloworld disassembly, press F2 to set a breakpoint, then F9 to launch the process. Upon launching the debugger you might receive this prompt from the OS:
macOS is picky about debugging permissions, and despite the fact that mac_server is properly codesigned you still must explicitly grant it permission to take control of another process. Thankfully this only needs to be done once per login session, so macOS should shut up until the next time you log out (we discuss how to disable this prompt entirely in the Debugging Over SSH section below).
After providing your credentials the debugger should start up without issue:
Using a Launch Agent
To simplify using the mac server, save the following XML as com.hexrays.mac_server64.plist in ~/Library/LaunchAgents/:
Now mac_server64 will be launched in the background whenever you log in. You can connect to it from IDA at any time using the Remote Mac OS X Debugger option. Hopefully this will make local debugging on macOS almost as easy as other platforms.
Debugging System Applications
There are some applications that macOS will refuse to allow IDA to debug.
For example, load /System/Applications/Calculator.app/Contents/MacOS/Calculator in IDA and try launching the debugger. You will likely get this error message:
Despite the fact that mac_server64 is codesigned, it still failed to retrieve permission from the OS to debug the target app. This is because Calculator.app and all other apps in /System/Applications/ are protected by System Integrity Protection and they cannot be debugged until SIP is disabled. Note that the error message is a bit misleading because it implies that running mac_server64 as root will resolve the issue - it will not. Not even root can debug apps protected by SIP.
Disabling SIP allows IDA to debug applications like Calculator without issue:
The effects of SIP are also apparent when attaching to an existing process. Try using menu Debugger>Attach to process, with SIP enabled there will likely only be a handful of apps that IDA can debug:
Disabling SIP makes all system apps available for attach:
It is unfortunate that such drastic measures are required to inspect system processes running on your own machine, but this is the reality of MacOS. We advise that you only disable System Integrity Protection when absolutely necessary, or use a virtual machine that can be compromised with impunity.
Debugging System Libraries
With IDA you can debug any system library in /usr/lib/ or any framework in /System/Library/.
This functionality is fully supported, but surprisingly it is one of the hardest problems the mac debugger must handle. To demonstrate this, let’s try debugging the _getaddrinfo function in libsystem_info.dylib.
Consider the getaddrinfo application from samples.zip:
Try testing it out with a few hostnames:
Now load libsystem_info.dylib in IDA and set a breakpoint at _getaddrinfo:
Choose Remote Mac OS X Debugger from the Debugger menu and under Debugger>Process options be sure to provide a hostname in the Parameters field. IDA will pass this argument to the executable when launching it:
Before launching the process, use Ctrl+S to pull up the segment list for libsystem_info.dylib. Pay special attention to the __eh_frame and __nl_symbol_ptr segments. Note that they appear to be next to each other in memory:
This will be important later.
Finally, use F9 to launch the debugger and wait for our breakpoint at _getaddrinfo to be hit. We can now start stepping through the logic:
Everything appears to be working normally, but use Ctrl+S to pull up the segment information again. We can still see __eh_frame, but it looks like __nl_symbol_ptr has gone missing:
It is actually still present, but we find it at a much higher address:
Recall that we opened the file directly from the filesystem (/usr/lib/system/libsystem_info.dylib). However this is actually not the file that macOS loaded into memory. The libsystem_info image in process memory was mapped in from the dyld_shared_cache, and the library’s segment mappings were modified before it was inserted into the cache.
IDA was able to detect this situation and adjust the database so that it matches the layout in process memory. This functionality is fully supported, but it is not trivial. Essentially the debugger must split your database in half, rebase all code segments to one address, then rebase all data segments to a completely different address.
It is worth noting there is another approach that achieves the same result, but without so much complexity.
Debugging Modules in dyld_shared_cache
As an alternative for the above example, note that you can load any module directly from a dyld_shared_cache file and debug it. For example, open the shared cache in IDA:
When prompted, select the "single module" option:
Then choose the libsystem_info module:
Select the Remote Mac OS X Debugger and for Debugger>Process options use the exact same options as before:
Now set a breakpoint at _getaddrinfo and launch the process with F9.
After launching the debugger you might see this warning:
This is normal. Modules from the dyld_shared_cache will contain tagged pointers, and IDA patched the pointers when loading the file so that analysis would not be hindered by the tags. IDA is warning us that the patches might cause a discrepancy between the database and the process, but in this case we know it’s ok. Check Don’t display this message again and don’t worry about it.
Launching the process should work just like before, and we can start stepping through the function in the shared cache:
This time there was no special logic to map the database into process memory. Since we loaded the module directly from the cache, segment mappings already match what’s expected in the process. Thus only one rebasing operation was required (as apposed to the segment scattering discussed in the previous example).
Both techniques are perfectly viable and IDA goes out of its way to fully support both of them. In the end having multiple solutions to a complex problem is a good thing.
Debugging Objective-C Applications
When debugging macOS applications it is easy to get lost in some obscure Objective-C framework. IDA’s mac debugger provides tools to make debugging Objective-C code a bit less painful.
Consider the bluetooth application from samples.zip:
The app will print all devices that have been paired with your host via Bluetooth. Try running it:
Let’s try debugging this app. First consider the call to method +[IOBluetoothDevice pairedDevices]:
If we execute a regular instruction step with F7, IDA will step into the _objc_msgSend function in libobjc.A.dylib, which is probably not what we want here. Instead use shortcut Shift+O. IDA will automatically detect the address of the Objective-C method that is being invoked and break at it:
This module appears to be Objective-C heavy, so it might be a good idea to extract Objective-C type info from the module using right click -> Load debug symbols in the Modules window:
This operation will extract any Objective-C types encoded in the module, which should give us some nice prototypes for the methods we’re stepping in:
Let’s continue to another method call - but this time the code invokes a stub for _objc_msgSend that IDA has not analyzed yet, so its name has not been properly resolved:
In this case Shift+O should still work:
Shift+O is purposefully flexible so that it can be invoked at any point before a direct or indirect call to _objc_msgSend. It will simply intercept execution at the function in libobjc.A.dylib and use the arguments to calculate the target method address.
However, you must be careful. If you use this action in a process that does not call _objc_msgSend, you will lose control of the process. It is best to only use it when you’re certain the code is compiled from Objective-C and an _objc_msgSend call is imminent.
Decompiling Objective-C at Runtime
The Objective-C runtime analysis performed by Load debug symbols will also improve decompilation.
Consider the method -[IOBluetoothDevice isConnected]:
Before we start stepping through this method we might want to peek at the pseudocode to get a sense of how it works. Note that the Objective-C analysis created local types for the IOBluetoothDevice class, as well as many other classes:
This type info results in some sensible pseudocode:
We knew nothing about this method going in - but it’s immediately clear that device connectivity is determined by the state of an io_service_t handle in the IOBluetoothObject superclass, and we’re well on our way.
Debugging Over SSH
In this section we will discuss how to remotely debug an app on a mac machine using only an SSH connection. Naturally, this task introduces some unique complications.
To start, copy the mac_server binaries and the bluetooth app from samples.zip:
to the target machine:
Now ssh to the target machine and launch the mac_server:
Now open the bluetooth binary on the machine with your IDA installation, select Remote Mac OS X Debugger from the debugger menu, and for Debugger>Process options set the debugging parameters. Be sure to replace <remote user> and <remote ip> with the username and ip address of the target machine:
Try launching the debugger with F9. You might get the following error message:
This happened because debugging requires manual authentication from the user for every login session (via the Take Control prompt discussed under Using the Mac Debug Server, above).
But since we’re logged into the mac via SSH, the OS has no way of prompting you with the authentication window and thus debugging permissions are refused.
Note that mac_server64 might have printed this workaround:
But this is an extreme measure. As an absolute last resort you can launch the mac_server with your credentials in the environment variables, which should take care of authentication without requiring any interaction with the OS. However there is a more secure workaround.
In your SSH session, terminate the mac_server process and run the following command:
Edit taskport.plist and change the authenticate-user option to false:
Then apply the changes:
This will completely disable the debugging authentication prompt (even across reboots), which should allow you to use the debug server over SSH without macOS bothering you about permissions.
Dealing With Slow Connections
When debugging over SSH you might experience some slowdowns. For example you might see this dialog appear for several seconds when starting the debugger:
During this operation IDA is fetching function names from the symbol tables for all dylibs that have been loaded in the target process. It is a critical task (after all we want our stack traces to look nice), but it is made complicated by the sheer volume of dylibs loaded in a typical macOS process due to the dyld_shared_cache. This results in several megabytes of raw symbol names that mac_server must transmit over the wire every time the debugger is launched.
We can fix this by using the same trick that IDA’s Remote iOS Debugger uses to speed up debugging - by extracting symbol files from the dyld cache and parsing them locally. Start by downloading the ios_deploy utility from our downloads page, and copy it to the remote mac:
Then SSH to the remote mac and run it:
Copy mac_symbols.zip from the remote machine to your host machine and unzip it. Then open Debugger>Debugger options>Set specific options and set the Symbol path field:
Now try launching the debugger again, it should start up much faster.
Also keep the following in mind:
Use /var/db/dyld/dyld_shared_cache_i386 if debugging 32-bit apps
You must perform this operation after every macOS update. Updating the OS will update the dyld_shared_cache, which invalidates the extracted symbol files.
The ios_deploy utility simply invokes dyld_shared_cache_extract_dylibs_progress from the dsc_extractor.bundle library in Xcode. If you don’t want to use ios_deploy there are likely other third-party tools that do something similar.
Debugging arm64 Applications on Apple Silicon
IDA 7.6 introduced the ARM Mac Debugger, which can debug any application that runs natively on Apple Silicon.
On Apple Silicon, the same rules apply (see Codesigning & Permissions above). The Local ARM Mac Debugger can only be used when run as root, so it is better to use the Remote ARM Mac Debugger with the debug server (mac_server_arm64), which can debug any arm64 app out of the box (see Using the Mac Debug Server).
We have included arm64 versions of the sample binaries used in the previous examples. We encourage you to go back and try them. They should work just as well on Apple Silicon.
Debugging arm64e System Applications
Similar to Intel Macs, IDA cannot debug system apps on Apple Silicon until System Integrity Protection is disabled.
But here macOS introduces another complication. All system apps shipped with macOS are built for arm64e - and thus have pointer authentication enabled. This is interesting because ptruath-enabled processes are treated much differently within the XNU kernel. All register values that typically contain pointers (PC, LR, SP, and FP) will be signed and authenticated by PAC.
Thus, if a debugger wants to modify the register state of an arm64e process, it must know how to properly sign the register values. Only arm64e applications are allowed to do this (canonically, at least).
You may have noticed that IDA 7.6 ships with two versions of the arm64 debug server:
mac_server_arm64e is built specifically for the arm64e architecture, and thus will be able to properly inspect other arm64e processes. We might want to try running this version right away, but by default macOS will refuse to run any third-party software built for arm64e:
According to Apple, this is because the arm64e ABI is not stable enough to be used generically. In order to run third-party arm64e binaries you must enable the following boot arg:
After rebooting you can finally run mac_server_arm64e:
This allows you to debug any system application (e.g. /System/Applications/Calculator.app) without issue:
Also note that the arm64e ABI limitation means you cannot use the Local ARM Mac Debugger to debug system arm64e apps, since IDA itself is not built for arm64e. It is likely that Apple will break the arm64e ABI in the future and IDA might cease to work. We want to avoid this scenario entirely.
Using the Remote ARM Mac Debugger with mac_server_arm64e is a nice workaround. It guarantees ida.app will continue to work normally regardless of any breakages in the arm64e ABI, and we can easily ship new arm64e builds of the server to anybody who needs it.
Apple Silicon: TL;DR
To summarize:
Use mac_server_arm64 if you’re debugging third-party arm64 apps that aren’t protected by SIP
Use mac_server_arm64e if you’re feeling frisky and want to debug macOS system internals. You must disable SIP and enable nvram boot-args=-arm64e_preview_abi, then you can debug any app you want (arm64/arm64e apps, system/non-system apps, shouldn’t matter).
Support
If you have any questions about this writeup or encounter any issues with the debugger itself in your environment, don’t hesitate to contact us at [email protected].
Our Mac support team has years of experience keeping the debugger functional through rapid changes in the Apple developer ecosystem. It is likely that we can resolve your issue quickly.
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.
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 the mount phase to install DeveloperDiskImage.dmg, which contains the debugserver:
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:
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:
Use this path for the fields in Debugger>Process options...
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:
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:
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:
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:
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:
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.
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 target application will be a trivial helloworld program:
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 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.
The iOS debugger supports watchpoints. Now would be a good time to use one:
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:
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:
This leads us to the following logic in the dyld-733.6 source:
Here, fixupLoc (off_109CC00) and newValue (address of puts) are passed as the loc and target arguments for Arm64e::signPointer:
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:
Then overwrite the authenticated pointer to puts with a raw pointer to 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:
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
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.
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:
Then reopen the cache with:
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:
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:
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:
Now save the following xml as entitlements.plist:
Then use to codesign the server:
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:
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:
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:
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:
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.
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
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:
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:
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
This tutorial replaces the old iOS debugging tutorial, which is available .
$ alias ida64="/Applications/IDA\ Pro\ 7.5/ida64.app/Contents/MacOS/ida64"
$ ida64 helloworld
This program must either be codesigned or run as root to debug mac applications.
$ codesign -dvv /Applications/IDA\ Pro\ 7.5/idabin/dbgsrv/mac_server64
Executable=/Applications/IDA Pro 7.5/ida.app/Contents/MacOS/dbgsrv/mac_server64
Identifier=com.hexrays.mac_serverx64
Format=Mach-O thin (x86_64)
CodeDirectory v=20100 size=6090 flags=0x0(none) hashes=186+2 location=embedded
Signature size=9002
Authority=Developer ID Application: Hex-Rays SA (ZP7XF62S2M)
Authority=Developer ID Certification Authority
Authority=Apple Root CA
Timestamp=May 19, 2020 at 4:13:31 AM
$ /Applications/IDA\ Pro\ 7.5/idabin/dbgsrv/mac_server64
IDA Mac OS X 64-bit remote debug server(MT) v7.5.26. Hex-Rays (c) 2004-2020
Listening on 0.0.0.0:23946...
$ scp <IDA install dir>/dbgsrv/mac_server* user@remote:
$ scp bluetooth user@remote:
$ ssh user@remote
user@remote:~$ ./mac_server64
IDA Mac OS X 64-bit remote debug server(MT) v7.5.26. Hex-Rays (c) 2004-2020
Listening on 0.0.0.0:23946...
WARNING: The debugger could not acquire the necessary permissions from the OS to
debug mac applications. You will likely have to specify the proper credentials at
process start. To avoid this, you can set the MAC_DEBMOD_USER and MAC_DEBMOD_PASS
environment variables.
$ 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...
// 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";