Debugger Inception – The Scriptable Debugger Within The Debugger

I recently began working on a toolset to aid with analyzing binary protocols and I decided to use it as an exercise to get more familiar with the Immunity Debugger. I have been using Windbg for a while now, however, I was constantly reading articles discussing how great Immunity Debugger is for exploit development and I had been meaning to take the time to become more familiar with it.

The first step in my plan involved breaking on the recv() related functions in Winsock and re-calling the function with the PEEK option selected to check for a cookie. This would allow for further analysis of a particular piece of unknown data in relation to the location within the assembly.

When I began developing my Immunity Debugger plugin, I quickly realized a large portion of the functionality that I was expecting from the Immunity Debugger API was not there. This included such things as starting/stopping individual threads, and making debuggee procedure calls.

I decided that I really wanted to work with Immunity Debugger, as a lot of the existing plugins are really useful for what I was trying to achieve, but the gaps in the API were a pretty huge hindrance to my project; therefore, I decided to implement the missing features myself.

In order to implement the missing features I needed to find a way to call the Win32 API functions that are responsible for the missing functionality. Since Immunity Debugger plugins are written in Python, the best way I could think of to call the Win32 API functions was to use the ctypes library. The ctypes library provides an interface for calling into native assembly code from Python. The library contains functionality that is needed to marshal data to and from Python classes into c structures and handlers that are loading DLL’s and calling functions contained within.
An example of using ctypes to call into the Win32 Native API function MessageBox is as follows:

ctypes.windll.user32.MessageBoxA(0, 'The message', 'The title', 0)

To communicate with the debuggee using the Win32 interface we require access to the HANDLE process. This handle was opened by Immunity Debugger when the initial command to attach to the target process was given. The handle is returned from a call to the OpenProcess() function and contains a value between 0 and 0xffffffff which represents an entry in a table in the kernel that contains structures associated with the process. By gaining the value of this handle, we can pass it to the various debugging-related functions in order to perform actions against the process. The method that I used to locate the value of this HANDLE was pretty simple. I basically copied a technique that I’ve used in the past for find-sock shellcode. We begin by looping through from 0 towards 0xffff in order to enumerate possible HANDLE values for the immunity debugger process. However since these values could be any type of HANDLE (we need HPROCESS in this case), such as file descriptors or thread handles, we need some way to determine if we’ve found the appropriate HPROCESS handle for the debuggee.

As it turns out, the official method for this is to use the function NtQueryObject(). However, looking at the MSDN page for this function it involves the use of the OBJECT_INFORMATION_CLASS structure, as well as four other parameters. While this isn’t technically difficult, it is a hassle that is not necessarily needed in this case. I found that I could simply call the GetProcessId() function in kernel32.dll in order to check if we have an HPROCESS handle. This function is typically used to retrieve the process id of the process described by the handle. If the function returns 0 (indicating failure) then the handle is not an HPROCESS type; therefore, we can disregard the handle. Calling the GetProcessId() function through ctypes was trivial, and this allowed me to implement the function below:

Now that I had a way to retrieve the process handle I set to work implementing the missing functionality. However, after implementing 3-4 of the functions that I would need in order to implement my research I realized that I was just implementing a large chunk of Pedram’s PyDbg library from scratch. Since I was already familiar with the PyDBG library and had a large amount of existing code written with it, I decided that it might make more sense to simply get PyDbg itself loading inside my plugin.

The process of loading PyDbg inside my plugin was actually pretty simple. The pydbg class has an attribute named h_process that stores the handle of the debuggee. In order for PyDbg to be capable of interacting with the debuggee, the h_process attribute must contain the handle. Therefore, I could simply instantiate the pydbg class and then set this attribute to the handle retrieved by the get_handle function from earlier.

The final step required for PyDbg to function was to populate the pid attribute of the pydbg class. Obviously this attribute is meant to contain the process ID of the debuggee. To populate the pid attribute I used the GetProcessId() function from earlier, called using ctypes.

dbg.pid = windll.kernel32.GetProcessId(handle)

With these steps completed, PyDbg performs as if it attached to the process itself. All the functionality I tested seems to work perfectly. It is fairly easy to modify any existing PyDbg code to work this way inside immdbg. One point of issue though is that the Immunity Debugger currently uses Python version 2.7. However, the latest build of PyDbg on Pedram’s Google code site uses Python 2.5. The only restriction is that a compiled version of pydasm (.pyd) is provided. The 2.7 linked version of the pydasm binary can be dowloaded, or compiled by hand and placed in the PyDbg directory. With this in place, everything works fine.

As far as my personal project, I have migrated to a combination of pinpy (python bindings-to-pin tool that I wrote last year) used with with idapython and scapy. These three technologies provide a much better platform for me to develop my ideas. However, I’m sure that I will be using PyDbg inside immdbg in the near future.

Some of the individuals posting to this site, including the moderators, work for Cisco Systems. Opinions expressed here and in any corresponding comments are the personal opinions of the original authors, not of Cisco. The content is provided for informational purposes only and is not meant to be an endorsement or representation by Cisco or any other party. This site is available to the public. No information you consider confidential should be posted to this site. By posting you agree to be solely responsible for the content of all information you contribute, link to, or otherwise upload to the Website and release Cisco from any liability related to your use of the Website. You also grant to Cisco a worldwide, perpetual, irrevocable, royalty-free and fully-paid, transferable (including rights to sublicense) right to exercise all copyright, publicity, and moral rights with respect to any original content you provide. The comments are moderated. Comments will appear as soon as they are approved by the moderator.