Table of Contents for
The IDA Pro Book, 2nd Edition

Version ebook / Retour

Cover image for bash Cookbook, 2nd Edition The IDA Pro Book, 2nd Edition by Chris Eagle Published by No Starch Press, 2011
  1. Cover
  2. The IDA Pro Book
  3. PRAISE FOR THE FIRST EDITION OF THE IDA PRO BOOK
  4. Acknowledgments
  5. Introduction
  6. I. Introduction to IDA
  7. 1. Introduction to Disassembly
  8. The What of Disassembly
  9. The Why of Disassembly
  10. The How of Disassembly
  11. Summary
  12. 2. Reversing and Disassembly Tools
  13. Summary Tools
  14. Deep Inspection Tools
  15. Summary
  16. 3. IDA Pro Background
  17. Obtaining IDA Pro
  18. IDA Support Resources
  19. Your IDA Installation
  20. Thoughts on IDA’s User Interface
  21. Summary
  22. II. Basic IDA Usage
  23. 4. Getting Started with IDA
  24. IDA Database Files
  25. Introduction to the IDA Desktop
  26. Desktop Behavior During Initial Analysis
  27. IDA Desktop Tips and Tricks
  28. Reporting Bugs
  29. Summary
  30. 5. IDA Data Displays
  31. Secondary IDA Displays
  32. Tertiary IDA Displays
  33. Summary
  34. 6. Disassembly Navigation
  35. Stack Frames
  36. Searching the Database
  37. Summary
  38. 7. Disassembly Manipulation
  39. Commenting in IDA
  40. Basic Code Transformations
  41. Basic Data Transformations
  42. Summary
  43. 8. Datatypes and Data Structures
  44. Creating IDA Structures
  45. Using Structure Templates
  46. Importing New Structures
  47. Using Standard Structures
  48. IDA TIL Files
  49. C++ Reversing Primer
  50. Summary
  51. 9. Cross-References and Graphing
  52. IDA Graphing
  53. Summary
  54. 10. The Many Faces of IDA
  55. Using IDA’s Batch Mode
  56. Summary
  57. III. Advanced IDA Usage
  58. 11. Customizing IDA
  59. Additional IDA Configuration Options
  60. Summary
  61. 12. Library Recognition Using FLIRT Signatures
  62. Applying FLIRT Signatures
  63. Creating FLIRT Signature Files
  64. Summary
  65. 13. Extending IDA’s Knowledge
  66. Augmenting Predefined Comments with loadint
  67. Summary
  68. 14. Patching Binaries and Other IDA Limitations
  69. IDA Output Files and Patch Generation
  70. Summary
  71. IV. Extending IDA’s Capabilities
  72. 15. IDA Scripting
  73. The IDC Language
  74. Associating IDC Scripts with Hotkeys
  75. Useful IDC Functions
  76. IDC Scripting Examples
  77. IDAPython
  78. IDAPython Scripting Examples
  79. Summary
  80. 16. The IDA Software Development Kit
  81. The IDA Application Programming Interface
  82. Summary
  83. 17. The IDA Plug-in Architecture
  84. Building Your Plug-ins
  85. Installing Plug-ins
  86. Configuring Plug-ins
  87. Extending IDC
  88. Plug-in User Interface Options
  89. Scripted Plug-ins
  90. Summary
  91. 18. Binary Files and IDA Loader Modules
  92. Manually Loading a Windows PE File
  93. IDA Loader Modules
  94. Writing an IDA Loader Using the SDK
  95. Alternative Loader Strategies
  96. Writing a Scripted Loader
  97. Summary
  98. 19. IDA Processor Modules
  99. The Python Interpreter
  100. Writing a Processor Module Using the SDK
  101. Building Processor Modules
  102. Customizing Existing Processors
  103. Processor Module Architecture
  104. Scripting a Processor Module
  105. Summary
  106. V. Real-World Applications
  107. 20. Compiler Personalities
  108. RTTI Implementations
  109. Locating main
  110. Debug vs. Release Binaries
  111. Alternative Calling Conventions
  112. Summary
  113. 21. Obfuscated Code Analysis
  114. Anti–Dynamic Analysis Techniques
  115. Static De-obfuscation of Binaries Using IDA
  116. Virtual Machine-Based Obfuscation
  117. Summary
  118. 22. Vulnerability Analysis
  119. After-the-Fact Vulnerability Discovery with IDA
  120. IDA and the Exploit-Development Process
  121. Analyzing Shellcode
  122. Summary
  123. 23. Real-World IDA Plug-ins
  124. IDAPython
  125. collabREate
  126. ida-x86emu
  127. Class Informer
  128. MyNav
  129. IdaPdf
  130. Summary
  131. VI. The IDA Debugger
  132. 24. The IDA Debugger
  133. Basic Debugger Displays
  134. Process Control
  135. Automating Debugger Tasks
  136. Summary
  137. 25. Disassembler/Debugger Integration
  138. IDA Databases and the IDA Debugger
  139. Debugging Obfuscated Code
  140. IdaStealth
  141. Dealing with Exceptions
  142. Summary
  143. 26. Additional Debugger Features
  144. Debugging with Bochs
  145. Appcall
  146. Summary
  147. A. Using IDA Freeware 5.0
  148. Using IDA Freeware
  149. B. IDC/SDK Cross-Reference
  150. Index
  151. About the Author

Dealing with Exceptions

Occasionally, programs expect to handle any exceptions generated during their execution. As we saw in Chapter 21, obfuscated programs often go so far as to intentionally generate exceptions as both an anti–control flow technique and an anti-debugging technique. Unfortunately, exceptions are often indicative of a problem, and the purpose of debuggers is to assist us in localizing problems. Therefore, debuggers typically want to handle all exceptions that occur when a program is running in order to help us find bugs.

When a program expects to handle its own exceptions, we need to prevent the debugger from intercepting such exceptions, or, at a minimum, once an exception is intercepted, we need a means to have the debugger forward the exception to the process at our discretion. Fortunately, IDA’s debugger has the capability to pass along individual exceptions as they occur or to automatically pass along all exceptions of a specified type.

Automated exception processing is configured via the Debugger ▸ Debugger Options command; the resulting dialog is shown in Figure 25-7.

The Debugger Setup dialog

Figure 25-7. The Debugger Setup dialog

In addition to allowing several events to be configured to automatically stop the debugger and a number of events to be automatically logged to IDA’s message window, the Debugger Setup dialog is used to configure the debugger’s exception-handling behavior. The Edit Exceptions button opens the Exceptions configuration dialog shown in Figure 25-8.

The Exceptions configuration dialog

Figure 25-8. The Exceptions configuration dialog

For each exception type known to the debugger, the dialog lists an operating system–specific exception code, the name of the exception, whether the debugger will stop the process or not (Stop/No), and whether the debugger will handle the exception or automatically pass the exception to the application (Debugger/Application). A master list of exceptions and default settings for handling each exception is contained in <IDADIR>/cfg/exceptions.cfg. In addition, the configuration file contains messages to be displayed whenever an exception of a given type occurs while the debugger is executing a process. Changes to the debugger’s default exception-handling behavior may be made by editing exceptions.cfg with a text editor. In exceptions.cfg, the values stop and nostop are used to indicate whether the debugger should suspend the process or not when a given exception occurs.

Exception handling may also be configured on a persession (that is, while you have a particular database open) basis by editing individual exceptions via the Exceptions configuration dialog. To modify the debugger’s behavior for a given exception type, right-click the desired exception in the Exceptions configuration dialog and select Edit. Figure 25-9 shows the resulting Exception editing dialog.

The Exception editing dialog

Figure 25-9. The Exception editing dialog

Two options, corresponding to the two configurable options in exceptions.cfg, may be configured for any exception. First, it is possible to specify whether the debugger should stop the process when an exception of the specified type occurs or whether execution should continue. Beware: Allowing the process to continue may result in an infinite exception-generation loop if you also elect to have the debugger handle the exception.

The second configuration option allows you to decide whether a given exception type should be passed to the application being debugged so the application can have a chance to process the exception using its own exception handlers. When the proper operation of an application depends on such exception handlers being executed, you should choose to pass the associated exception types to the application. This may be required when analyzing obfuscated code such as that generated by the tElock utility (which registers its own exception handlers) described in Chapter 21.

Unless you have configured IDA to continue execution and to pass a specific exception type to the application, IDA will pause execution and report exceptions to you as they occur. If you elect to continue execution of the program, IDA will then display the Exception Handling dialog shown in Figure 25-10.

The Exception Handling dialog

Figure 25-10. The Exception Handling dialog

At this point you have the option of changing the manner in which IDA handles the given exception type (Change exception definition), passing the exception on to the application (Yes), or allowing IDA to eat the exception (No). Passing the exception to the application allows the application to handle the exception using any configured exception handlers. If you choose No, IDA attempts to continue execution, which is likely to fail unless you have corrected the condition that was responsible for causing the exception.

A special circumstance arises when you are single stepping through code and IDA determines that the instruction you are about to execute will generate an exception, as is the case with an int 3, an icebp, or a popf that will set the trace flag; IDA displays the dialog shown in Figure 25-11.

The exception confirmation dialog

Figure 25-11. The exception confirmation dialog

In most cases, the Run option is the most suitable choice and results in the application seeing the behavior that it expects when a debugger is not attached (as noted in the dialog). In working through this dialog, you are simply acknowledging that an exception is about to be generated. If you choose Run, in short order you will then be notified that an exception has occurred, and when you continue execution, you will be presented with the Exception Handling dialog of Figure 25-10 to decide how the exception should be dealt with.

Determining how an application will handle an exception requires that we know how to trace exception handlers, which in turn requires that we know how to locate exception handlers. Ilfak discusses tracing Windows SEH handlers in a blog post titled “Tracing exception handlers.”[236] The basic idea is to locate any interesting exception handlers by walking the application’s list of installed exception handlers. For Windows SEH exceptions, a pointer to the head of this list may be found as the first dword in the thread environment block (TEB). The list of exception handlers is a standard linked-list data structure that contains a pointer to the next exception handler in the chain and a pointer to the function that should be called to handle any exception that is generated. Exceptions are passed down the list from one handler to another until a handler chooses to handle the exception and notify the operating system that the process may resume normal execution. If none of the installed exception handlers choose to handle the current exception, the operating system terminates the process or, when the process is being debugged, notifies the debugger that an exception has occurred within the debugged process.

Under the IDA debugger, TEBs are mapped to an IDA database section named TIB[NNNNNNNN], where NNNNNNNN is the eight-digit hexadecimal representation of the thread’s identification number. The following listing shows an example of the first dword in one such section:

TIB[000009E0]:7FFDF000 TIB_000009E0_ segment byte public 'DATA' use32
  TIB[000009E0]:7FFDF000 assume cs:TIB_000009E0_
  TIB[000009E0]:7FFDF000 ;org 7FFDF000h
 TIB[000009E0]:7FFDF000 dd offset dword_22FFE0

The first three lines show summary information about the segment, while the fourth line contains the first dword of the section, indicating that the first exception handler record may be found at address 22FFE0h (off-set dword_22FFE0). If no exception handlers were installed for this particular thread, the first dword in the TEB would contain the value 0FFFFFFFFh, indicating that the end of the exception handler chain had been reached. In this example, examining two dwords at address 22FFE0h shows the following:

Stack[000009E0]:0022FFE0 
dword_22FFE0 dd 0FFFFFFFFh     ; DATA XREF: TIB[000009E0]:7FFDF000↓o
Stack[000009E0]:0022FFE4              dd offset loc_7C839AA8

The first dword contains the value 0FFFFFFFFh, indicating that this is the last exception handler record in the chain. The second dword contains the address 7C839AA8h (offset loc_7C839AA8), indicating that the function at loc_7C839AA8 should be called to process any exceptions that may arise during the execution of the process. If we were interested in tracing the handling of any exceptions in this process, we might begin by setting a breakpoint at address 7C839AA8h.

Because it is a relatively simple task to walk the SEH chain, a useful feature for the debugger to implement would be a display of the chain of SEH handlers that are installed for the current thread. Given such a display, it should be easy to navigate to each SEH handler, at which point you may decide whether you want to insert a breakpoint within the handler or not. Unfortunately, this is another feature available in OllyDbg that is not available in IDA’s debugger. To address this shortcoming, we have developed an SEH Chain plug-in, which, when invoked from within the debugger, will display the list of exception handlers that are installed for the current thread. An example of this display is shown in Figure 25-12.

The SEH Chain display

Figure 25-12. The SEH Chain display

This plug-in utilizes the SDK’s choose2 function to display a nonmodal dialog that lists the current exception-handler chain. For each installed exception handler, the address of the exception-handler record (the two-dword list record) and the address of the corresponding exception handler are displayed. Double-clicking an exception handler jumps the active disassembly view (either IDA View-EIP or IDA View-ESP) to the address of the SEH handler function. The entire purpose of this plug-in is to simplify the process of locating exception handlers. The source code for the SEH Chain plug-in may be found on the website for this book.

The flip side of the exception-handling process is the manner in which an exception handler returns control (if it chooses to do so) to the application in which the exception occurred. When an exception-handler function is called by the operating system, the function is granted access to all of the CPU register’s contents as they were set at the moment the exception took place. In the process of handling the exception, the function may elect to modify one or more CPU register values prior to returning control to the application. The intent of this process is for an exception handler to be given the opportunity to repair the state of the process sufficiently so that the process may resume normal execution. If the exception handler determines that the process should be allowed to continue, the operating system is notified, and the process’s register values are restored, using any modifications made by the exception handler. As discussed in Chapter 21, some anti–reverse engineering utilities make use of exception handlers to alter a process’s flow of execution by modifying the saved value of the instruction pointer during the exception-handling phase. When the operating system returns control to the affected process, execution resumes at the address specified by the modified instruction pointer.

In his blog post on tracing exceptions, Ilfak discusses the fact that Windows SEH exception handlers return control to the affected process via the ntdll.dll function NtContinue (also known as ZwContinue). Since NtContinue has access to all of the process’s saved register values (via one of its arguments), it is possible to determine exactly where the process will resume execution by examining the value contained in the saved instruction pointer from within NtContinue. Once we know where the process is set to resume execution, we can set a breakpoint in order to avoid stepping through operating system code and to stop the process at the earliest opportunity once it resumes execution. The following steps outline the process we need to follow:

  1. Locate NtContinue and set a nonstopping breakpoint on its first instruction.

  2. Add a breakpoint condition to this breakpoint.

  3. When the breakpoint is hit, obtain the address of the saved registers by reading the CONTEXT pointer from the stack.

  4. Retrieve the process’s saved instruction pointer value from the CONTEXT record.

  5. Set a breakpoint on the retrieved address and allow execution to continue.

Using a process similar to the debugger-hiding script, we can automate all of these tasks and associate them with the initiation of a debugging session. The following code demonstrates launching a process in the debugger and setting a breakpoint on NtContinue:

static main() {
   auto func;
   RunTo(BeginEA());
   GetDebuggerEvent(WFNE_SUSP, −1);
   func = LocByName("ntdll_NtContinue");
   AddBpt(func);
   SetBptCnd(func, "bpt_NtContinue()");
}

The purpose of this code is simply to set a conditional breakpoint on the entry of NtContinue. The behavior of the breakpoint is implemented by the IDC function bpt_NtContinue, which is shown here:

static bpt_NtContinue() {
    auto p_ctx = Dword(ESP + 4);            //get CONTEXT pointer argument
    auto next_eip = Dword(p_ctx + 0xB8);    //retrieve eip from CONTEXT
    AddBpt(next_eip);                  //set a breakpoint at the new eip
    SetBptCnd(next_eip, "Warning(\"Exception return hit\") || 1");
          return 0;           //don't stop
  }

This function locates the pointer to the process’s saved register context information , retrieves the saved instruction pointer value from offset 0xB8 within the CONTEXT structure , and sets a breakpoint on this address . In order to make it clear to the user why execution has stopped, a breakpoint condition (which is always true) is added to display a message to the user . We choose to do this because the breakpoint was not set explicitly by the user, and the user may not correlate the event to the return from an exception handler.

This example represents a simple means of handling exception returns. Much more sophisticated logic could be added to the breakpoint function bpt_NtContinue. For example, if you suspect that an exception handler is manipulating the contents of debug registers, perhaps to prevent you from setting hardware breakpoints, you might opt to restore the values of the debug registers to known good values prior to returning control to the process being debugged.