
Gray Hat Python - Immunity Debugger
Intro
Having considered the creation and use of the pure Python debugger in the form of PyDbg, it's time to study the Immunity Debugger, which consists of a full user interface and the most powerful Python library, for today, to develop exploits, detect vulnerabilities and analyze malicious code. Released in 2007, Immunity Debugger has a good combination of both dynamic debugging and static analysis capabilities. In addition, it has a fully customizable graphical interface implemented on pure Python. At the beginning of this chapter, we briefly introduce the Immunity Debugger and its user interface. Then, we will begin to gradually deepen the development of the exploit and some methods for automatically circumventing the anti-debugging techniques used in malware.
5.1 Installing the Immunity Debugger
Immunity Debugger is distributed and supported [1] for free, here is the link to download it: debugger.immunityinc.com
Just download and run the installer. If you have not installed Python 2.5 (approx. Per. As advised), then this is not a big problem, since the Immunity Debugger is bundled with the Python 2.5 installer (approx. Per. .1) that will be installed by the trader for you if the need arises. Immediately after installing and starting the Immunity Debugger, it will be ready to use.
5.2 Immunity Debugger 101
Let's take a quick look at the Immunity Debugger and its interface, and then move on to the immlib Python library, which allows you to write scripts for the debugger. At the first start, you will see the interface shown in Figure 5-1.

Fig. 5-1 : Immunity Debugger main interface
The main debugger interface consists of five main parts. In the upper left corner is the CPU window, where the assembler code is displayed. In the upper right corner there is a register window where general-purpose registers are displayed, as well as other processor registers. In the lower left corner there is a memory dump window where you can see the hexadecimal dump of any address space that you have selected. In the lower right corner is the stack window, which displays the corresponding calls to the stack; it also shows you the decoded function parameters in the form of symbolic information (for example, some native call to the Windows API function). The fifth element is a white command line panel located at the very bottom and designed to control the debugger using commands in the WinDbg style. Here you can execute PyCommands,
5.2.1 PyCommands
The main way to execute Python scripts in the Immunity Debugger is to use PyCommands [2] . PyCommands are Python scripts that are written to perform various tasks inside the Immunity Debugger, for example, scripts that perform: various hooks, static analysis, or any other debugging functionality. Each PyCommand must have a specific structure for its proper execution. The following code snippet shows the main PyCommand structure, which you can use as a template to create your own PyCommands.
from immlib import *
def main(args):
# Instantiate a immlib.Debugger instance
imm = Debugger()
return "[*] PyCommand Executed!"
Each PyCommand has two main components. The first component, you should have defined the main () function, which should take one parameter, which is a list of arguments passed to PyCommand. The second component is that main () should return a "string" when it finishes its execution. This line will update the "debugger status bar" (approx. Per. Located under the command line) when the script finishes execution.
When you want to run PyCommand, you should make sure that your script is saved in the PyCommands directory, which is located in the main installation directory of the Immunity Debugger. To execute a saved script, simply enter an exclamation mark followed by the script name in the debugger command line, like this:
!scriptname
As soon as you press ENTER, your script will start running.
5.2.2 PyHooks
Immunity Debugger comes with 13 different types of intercepts, each of which you can implement either as a separate script or as an internal PyCommand script. The following types of intercepts can be used:
BpHook / LogBpHook
When a breakpoint occurs, these types of intercepts are triggered. Both hooks behave the same, except that when BpHook is encountered, it actually stops the debugger, while LogBpHook does not interrupt its execution.
AllExceptHook
Any exception that occurs in the processor will cause this type of catch to be executed.
PostAnalysisHook
This interception is triggered after the debugger finishes analyzing the loaded module. This can be useful if you have some static analysis tasks that you want to perform automatically immediately after the module analysis is complete. It is important to note that the module (including the main executable) needs to be analyzed before you can decode functions and main blocks using immlib.
AccessViolationHook
This interception fires whenever an access violation occurs; it is most useful for intercepting information during fuzzing.
LoadDLLHook / UnloadDLLHook
This interception is triggered whenever a DLL loads / unloads.
CreateThreadHook / ExitThreadHook
This interception is triggered whenever a thread is created / destroyed.
CreateProcessHook / ExitProcessHook
This type of interception is triggered when the target process starts or exits.
FastLogHook / STDCALLFastLogHook
These two intercepts use a stub to transmit to the small body the interceptor code, which can log a specific value of a register or a piece of memory during the interception. These intercepts are useful for intercepting frequently called functions; we will discuss their use in Chapter 6.
To set PyHook, you can use the following template, which uses LogBpHook as an example:
from immlib import *
class MyHook( LogBpHook ):
def __init__( self ):
LogBpHook.__init__( self )
def run( regs ):
# Executed when hook gets triggered
We overload the LogBpHook class and make sure the run () function is defined. When the interception works, the run () function accepts, as a single argument, a list of all processor registers that were set at the time the hook was triggered, which allows us to view or change the current values as we see fit. The regs variable is a dictionary that we can use to access registers by name, like this:
regs["ESP"]
Now we can define hooks in several ways, using PyCommand and PyHooks. Thus, you can set the hooks either manually using PyCommand or automatically using PyHooks (located in the main installation directory of the Immunity Debugger). In the case of PyCommand, the interception will be set whenever PyCommand is executed. In the case of PyHooks, the interception will be triggered automatically each time the Immunity Debugger starts. Now let's move on to some examples of using immlib, the Immunity Debugger built-in Python library.
5.3 Exploit development
Detecting software vulnerabilities is just the beginning of a long and difficult journey ahead of you to get a reliable working exploit. Immunity Debugger has many design features that make it a little easier to go through its development path. We will develop some PyCommands that speed up the exploit development process, including a way to find instructions for receiving EIP, as well as filtering bytes that are not suitable for use in shell code. We will also use PyCommand! Findatidep, which is bundled with the Immunity Debugger, which helps bypass DEP (Data Execution Prevention) [3] . Let's start!
5.3.1 Search for exploit friendly instructions
After you have gained control on EIP, you need to transfer execution to the shell code. As a rule, you will have a register or a shift from the register, which will indicate the shell code. Your task is to find instructions somewhere in the executable file or in one of its loaded modules, which will transfer control to the desired address. The Immunity Debugger Python library makes this easy by providing a search interface that allows you to search for instructions of interest across the entire downloaded binary. Let's jot down a script on our knee that will receive the instruction and return all the addresses where this instruction occurs. Create a new findinstruction.py file and enter the following code.
findinstruction.py:
from immlib import *
def main(args):
imm = Debugger()
search_code = " ".join(args)
(#1): search_bytes = imm.Assemble( search_code )
(#2): search_results = imm.Search( search_bytes )
for hit in search_results:
# Retrieve the memory page where this hit exists
# and make sure it's executable
(#3): code_page = imm.getMemoryPagebyAddress( hit )
(#4): access = code_page.getAccess( human = True )
if "execute" in access.lower():
imm.log( "[*] Found: %s (0x%08x)" % ( search_code, hit ), address = hit )
return "[*] Finished searching for instructions, check the Log window."
In the beginning, we translate the received instructions into their binary equivalent (# 1) , and then use the Search () function to search for all instructions in the memory of the downloaded binary file (# 2) . Next, in the returned list, we sort through all the detected addresses to get the memory page where the instruction is located (# 3) , after which we make sure that the memory is marked as executable (# 4) . Then, for each instruction, in the memory executable page, we find its address and display it in the “Log” window . To use the script, just pass the instruction you are looking for as an argument, like this:
!findinstruction "instruction to search for"
After executing the script, with the following parameters:
!findinstruction jmp esp
You will see a result similar to Fig. 5-2.

Fig. 5-2 : Output of PyCommand! Findinstruction
Now we have a list of addresses that we can use to execute our shellcode, assuming that it can be run through the ESP register. In addition to the list of addresses, we now have a good tool to quickly find the addresses of the instructions we are interested in.
5.3.2 Filtering bad characters
When you send a string containing an exploit to the target system - there are some character sets that you cannot use in the shell code. For example, if we found a stack overflow when calling the strcpy () function, then our exploit cannot contain the NULL character (0x00), because strcpy () stops copying data as soon as it encounters a NULL value. Therefore, when writing exploits, they use shellcode encoders that decode and execute it after running the shell code. However, there are still some cases where you can have several characters filtered out or processed in some special way in vulnerable software, which can become a real nightmare to manually identify them.
Usually, when you put the shellcode in a vulnerable program, and it does not start (causing an access violation or a crash in the program until it is fully executed), you first need to make sure that it is copied to memory exactly the way you they wanted it. Immunity Debugger can make this easier. Look at Fig. 5-3, which shows the stack after overflow.

Fig. 5-3 : Immunity Debugger window stack after overflow
We see that the EIP register currently points to the ESP register. Four bytes of 0xCC will simply make the debugger stop, as if a breakpoint were installed there (remember? 0xCC is an INT3 instruction). Immediately after four INT3 instructions, at the offset ESP + 0x4, the shell code is located. It is there that you need to begin the study of memory, to make sure that our shell code is exactly the same as we sent it during our attack on the target system. To study the shellcode in memory, we just take the original as an ASCII string and compare it (byte-by-bit) with the shellcode located in the memory to make sure that the shellcode was loaded correctly. If we notice a difference, we output a bad byte that did not pass through the software filter in Log. After which, we can add processing of such a character to the shellcode encoder, before launching re-attack! To test the functionality of this tool, you can take the shellcode from Metasploit, or your own homework. Create a new filebadchar.py and enter the following code.
badchar.py:
from immlib import *
def main(args):
imm = Debugger()
bad_char_found = False
# First argument is the address to begin our search
address = int(args[0],16)
# Shellcode to verify
shellcode = ">>COPY AND PASTE YOUR SHELLCODE HERE<<"
shellcode_length = len(shellcode)
debug_shellcode = imm.readMemory( address, shellcode_length )
debug_shellcode = debug_shellcode.encode("HEX")
imm.log("Address: 0x%08x" % address)
imm.log("Shellcode Length : %d" % length)
imm.log("Attack Shellcode: %s" % canvas_shellcode[:512])
imm.log("In Memory Shellcode: %s" % id_shellcode[:512])
# Begin a byte-by-byte comparison of the two shellcode buffers
count = 0
while count <= shellcode_length:
if debug_shellcode[count] != shellcode[count]:
imm.log("Bad Char Detected at offset %d" % count)
bad_char_found = True
break
count += 1
if bad_char_found:
imm.log("[*****] ")
imm.log("Bad character found: %s" % debug_shellcode[count])
imm.log("Bad character original: %s" % shellcode[count])
imm.log("[*****] ")
return "[*] !badchar finished, check Log window."
In this script, we actually only use the readMemory () call from the Immunity Debugger library, and the rest of the script makes a simple string comparison. Now all you have to do is take your shellcode as an ASCII string (for example, if you have bytes 0xEB 0x09, then your string will look like EB09), insert it into the script and run the script as follows:
!badchar "Address to Begin Search"
In our previous example, we would start the search with ESP + 0x4, whose absolute address is 0x00AEFD4C, so we run PyCommand as follows:
!badchar 0x00AEFD4c
After starting, the script would immediately warn us of any problems with filtering bad characters and could significantly reduce the time spent debugging a failure in the shell code or reversing any filters that we might encounter.
5.3.3 DEP bypass
DEP is a security measure implemented in Microsoft Windows (XP SP2, 2003 and Vista) to prevent code from executing in memory areas such as heap and stack. This can interfere with shellcode execution on most exploits, because most exploits store their shellcodes on the heap or stack. However, there is a well-known trick [4]through which we can use native Windows API calls to disable DEP, for the current process in which we are running and in which it is allowed to safely transfer control to our shell code, regardless of whether it is stored on the stack or on the heap. The Immunity Debugger comes with PyCommand called findantidep.py. which determines the appropriate addresses for installing your exploit in such a way as to disable DEP and execute shellcode. Consider a little theory for disabling DEP. Then we move on to using the PyCommand script, which allows us to find the addresses of interest to us.
A Windows API call that can be used to disable DEP for the current process is an undocumented function NtSetInformationProcess () [5]which has the following prototype:
NTSTATUS NtSetInformationProcess(
IN HANDLE hProcessHandle,
IN PROCESS_INFORMATION_CLASS ProcessInformationClass,
IN PVOID ProcessInformation,
IN ULONG ProcessInformationLength );
To disable DEP, you need to call the NtSetInformationProcess () function with the parameters set: ProcessInformationClass to ProcessExecuteFlags (0x22) and ProcessInformation to MEM_EXECUTE_OPTION_ENABLE (0x2). The problem with a simple shellcode setup is that calling this function consists of a number of NULL parameters, which are problematic for most shellcodes. A technique to circumvent this limitation is to place the shell code in the middle of a function that will already call NtSetInformationProcess () on the stack with the necessary parameters. There is a known place in ntdll.dll that does this for us. Look at the ntdll.dll disassembler output for Windows XP SP2 obtained with the Immunity Debugger.
7C91D3F8 . 3C 01 CMP AL,1
7C91D3FA . 6A 02 PUSH 2
7C91D3FC . 5E POP ESI
7C91D3FD . 0F84 B72A0200 JE ntdll.7C93FEBA
...
7C93FEBA > 8975 FC MOV DWORD PTR SS:[EBP-4],ESI
7C93FEBD .^E9 41D5FDFF JMP ntdll.7C91D403
...
7C91D403 > 837D FC 00 CMP DWORD PTR SS:[EBP-4],0
7C91D407 . 0F85 60890100 JNZ ntdll.7C935D6D
...
7C935D6D > 6A 04 PUSH 4
7C935D6F . 8D45 FC LEA EAX,DWORD PTR SS:[EBP-4]
7C935D72 . 50 PUSH EAX
7C935D73 . 6A 22 PUSH 22
7C935D75 . 6A FF PUSH -1
7C935D77 . E8 B188FDFF CALL ntdll.ZwSetInformationProcess
Following this code, we see a comparison of AL with a value of 1, then the value 2 is placed in the ESI. If AL is 1, a conditional transition to 0x7C93FEBA is triggered. There, the value from ESI is moved to the EBP-4 stack variable (remember that ESI is still set to 2?). Then, the condition is checked at 0x7C91D403, which checks our variable on the stack (it is still 2) to make sure that it is not zero, after which the conditional transition to 0x7C935D6D is triggered. This is where the fun begins; it can be seen that the value 4 is pushed onto the stack, the EBP-4 variable (still equal to 2!) is loaded into the EAX register, then this value is pushed onto the stack, then the value 0x22 and the value -1 (-1, the process handle that tells the function call are pushed) that this is the current process in which you need to disable DEP), followed by a call to ZwSetInformationProcess (alias NtSetInformationProcess). So, in reality, what happened in this piece of code called the NtSetInformationProcess () function, with the following parameters:
NtSetInformationProcess( -1, 0x22, 0x2, 0x4 )
Perfect! This will disable DEP for the current process, but for this we need to transfer control to the address 0x7C91D3F8. Before we transfer control to this piece of code, we need to make sure that AL (low byte of EAX) is set to 1. After these conditions are met, we will be able to transfer control to the shell code, as in any other overflow, for example, using JMP ESP instructions. Thus, three addresses are needed:
- An address that sets AL to 1 and then returns;
- The address where the piece of code is located to disable DEP;
- The address to transfer control to the beginning of our shell code.
Usually you need to search for these addresses manually, but the developers of the exploits at Immunity created a small Python script findantidep.py , made in the form of a wizard, which will guide you through the process of finding these addresses. It even creates an exploit string that you can copy and paste into your exploit. This allows you to use the addresses found without any effort at all. Let's look at the findantidep.py script and then test it.
findantidep.py:
import immlib
import immutils
def tAddr(addr):
buf = immutils.int2str32_swapped(addr)
return "\\x%02x\\x%02x\\x%02x\\x%02x" % ( ord(buf[0]) , ord(buf[1]), ord(buf[2]), ord(buf[3]) )
DESC="""Find address to bypass software DEP"""
def main(args):
imm=immlib.Debugger()
addylist = []
mod = imm.getModule("ntdll.dll")
if not mod:
return "Error: Ntdll.dll not found!"
# Finding the First ADDRESS
(#1): ret = imm.searchCommands("MOV AL,1\nRET")
if not ret:
return "Error: Sorry, the first addy cannot be found"
for a in ret:
addylist.append( "0x%08x: %s" % (a[0], a[2]) )
ret = imm.comboBox("Please, choose the First Address [sets AL to 1]", addylist)
firstaddy = int(ret[0:10], 16)
imm.Log("First Address: 0x%08x" % firstaddy, address = firstaddy)
# Finding the Second ADDRESS
(#2): ret = imm.searchCommandsOnModule( mod.getBase(), "CMP AL,0x1\n PUSH 0x2\n POP ESI\n" )
if not ret:
return "Error: Sorry, the second addy cannot be found"
secondaddy = ret[0][0]
imm.Log( "Second Address %x" % secondaddy , address= secondaddy )
# Finding the Third ADDRESS
(#3): ret = imm.inputBox("Insert the Asm code to search for")
ret = imm.searchCommands(ret)
if not ret:
return "Error: Sorry, the third address cannot be found"
addylist = []
for a in ret:
addylist.append( "0x%08x: %s" % (a[0], a[2]) )
ret = imm.comboBox("Please, choose the Third return Address [jumps to shellcode]", addylist)
thirdaddy = int(ret[0:10], 16)
imm.Log( "Third Address: 0x%08x" % thirdaddy, thirdaddy )
(#4): imm.Log( 'stack = "%s\\xff\\xff\\xff\\xff%s\\xff\\xff\\xff\\xff" + "A" * 0x54 + "%s" + shellcode ' % ( tAddr(firstaddy), tAddr(secondaddy), tAddr(thirdaddy) ) )
So, first we find the commands that will set AL to 1 (# 1), then ask the user to select the appropriate address. After that, we will search for a set of instructions in ntdll.dll , which contain the DEP disable code (# 2). In the third step, we ask the user to enter instructions or instructions that will have to transfer control to the shell code (# 3), and provide the user with a list of addresses where these instructions can be found. The script ends with outputting the results to the Log window (# 4). Look at figures 5-4 - 5-6 to see how this process goes.

Fig. 5-4: First, select the address that will set AL to 1

Fig. 5-5: Then we introduce a set of instructions that will transfer control to the shell code

Fig. 5-6: Now select the address that will return from step (# 2) [/ CENTER]
And in the end you will see the output in the Log window, as shown here:
stack = "\x75\x24\x01\x01\xff\xff\xff\xff\x56\x31\x91\x7c\xff\xff\xff\xff" + "A" * 0x54 + "\x75\x24\x01\x01" + shellcode
Now you can simply copy and paste this output line into the exploit and add your shellcode. Using this script can help test existing exploits so that they can run successfully on a system with DEP enabled or create new exploits that support disabling DEP out of the box. This is a great example of taking hours of manual search, which has turned into a 30 second exercise. Now you can see how some simple Python scripts can help you develop more reliable and portable exploits in a short time. Let's move on to using immlib to bypass common anti-debugging procedures in malware.
5.4 Bypassing anti-debugging methods
Current types of malware are becoming more and more confused in their methods of infection, distribution and their ability to defend against analysis. In addition to common code obfuscation methods, such as using packers and cryptors, malware usually uses anti-debugging techniques, trying to prevent its analysis with a debugger to make it difficult to research. Using the Immunity Debugger and Python, you can create some simple scripts that circumvent some of these anti-debugging tricks to help the analyst investigate malware samples. Let's look at some of these most common anti-debugging methods and write some appropriate code to work around them.
5.4.1 IsDebuggerPresent
By far, the most common anti-debugging method is to use the IsDebuggerPresent () function exported from kernel32.dll. This function is called without parameters and returns 1 if there are attached debuggers to the current process or 0 if there are none. If we disassemble this function, we will see the following piece of code:
7C813093 >/$ 64:A1 18000000 MOV EAX,DWORD PTR FS:[18]
7C813099 |. 8B40 30 MOV EAX,DWORD PTR DS:[EAX+30]
7C81309C |. 0FB640 02 MOVZX EAX,BYTE PTR DS:[EAX+2]
7C8130A0 \. C3 RETN
This code loads the address from the TIB (Thread Information Block), which is always located at offset 0x18 from the FS register. From there, it loads the PEB (Process Environment Block), which is always located at offset 0x30 in the TIB. The third instruction sets EAX to the value from the BeingDebugged parameter, which is located at offset 0x2 in PEB. If there is a debugger attached to the process, this byte is set to 0x1. A simple workaround for this was published by Damian Gomez [6] from Immunity, which is just one Python string that can be contained in PyCommand or can be executed from the Python shell in the Immunity Debugger:
imm.writeMemory( imm.getPEBaddress() + 0x2, "\x00" )
This code simply resets the BeingDebugged flag in PEB, and now any malware that uses this check will be tricked into believing that there is no debugger attached.
5.4.2 Bypassing Process Enumeration
Malicious people also try to iterate over all running processes on the computer to determine if the debugger is running. For example, if you use the Immunity Debugger to research a virus, then ImmunityDebugger.exe will be registered as a running process. To iterate over running processes, the malware will use the Process32First () function to get the first registered process in the list of system processes, and then it will use Process32Next () to iterate over all remaining processes. Both of these function calls return a Boolean value that tells the calling code whether the function completed successfully or not, so we can just patch these two functions so that the EAX register is set to zero when the function returns the result. We will use the powerful built-in assembler Immunity Debugger to achieve this.
(#1): process32first = imm.getAddress("kernel32.Process32FirstW")
process32next = imm.getAddress("kernel32.Process32NextW")
function_list = [ process32first, process32next ]
(#2): patch_bytes = imm.Assemble( "SUB EAX, EAX\nRET" )
for address in function_list:
(#3): opcode = imm.disasmForward( address, nlines = 10 )
(#4): imm.writeMemory( opcode.address, patch_bytes )
First, we find the addresses of two functions sorting processes and save them in a list (# 1) . Then we translate some bytes into the corresponding opcodes, which set the EAX register to 0 and return control from the function; this will be our patch (# 2) . Next, we go through 10 instructions (# 3) , inside the Process32First and Process32Next functions. We do this because some advanced malicious programs will actually check the first few bytes of these functions to make sure that the function was not patched by the reverse engineer. We deceive them by patching 10 instructions below; however, if they verify the integrity of the entire function, they will discover us. After patching bytes in functions (# 4), both functions will return a false result no matter how they are called.
We looked at two examples of how you can use Python and the Immunity Debugger to create automated malware protection methods that attempt to detect the presence of an attached debugger. There are many more anti-debugging methods that can be used, so an infinite number of Python scripts will be written to handle them! The knowledge gained in this chapter will help you to enjoy a shorter exploit development time, as well as a new arsenal of tools to fight against malware.
Now let's move on to some interception methods that you can use during reversing.
References
[1] For debugger support and general discussions visit http://forum.immunityinc.com .
[2] For a full set of documentation on the Immunity Debugger Python library, refer to http://debugger.immunityinc.com/update/Documentation/ref/ .
[3] An in-depth explanation of DEP can be found at http://support.microsoft.com/kb/875352/EN-US/ .
[4] See Skape and Skywing's paper at http://www.uninformed.org/?v=2&a=4&t=txt .
[5] The NtSetInformationProcess () function definition can be found at http://undocumented.ntinternals.net/UserMode/Undocumented%20Functions/NT%20Objects/Process/NtSetInformationProcess.html .
[6] The original forum post is located at http://forum.immunityinc.com/index.php?topic=71.0 .