Intro
Having considered creating and using a debugger on pure Python in the form of PyDbg, it is time to explore the Immunity Debugger, which consists of a full user interface and the most powerful Python library, to date, to develop exploits, detect vulnerabilities and analyze malicious code. Released in 2007, Immunity Debugger has a good mix of 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 will briefly introduce the Immunity Debugger and its user interface. Then we will begin a gradual deepening in the development of an exploit and some methods to automatically bypass anti-debugging techniques used in malware. Let's start by downloading Immunity Debugger and running it.
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 yet (approx. Trans. As was advised to you), then this is not a big problem, since Immunity Debugger comes bundled with the Python 2.5 installer .1), which will be installed as an after party for you if the need arises. Immediately after installing and running Immunity Debugger, it will be ready for 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. When you first start, you will see the interface shown in Figure 5-1.
Fig. 5-1 : The main interface Immunity Debugger
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 registers are displayed, as well as other processor registers. In the lower left corner there is a memory dump window, where you can see a hexadecimal dump of any address space you choose. In the lower right corner there is a stack window in which the corresponding stack calls are displayed; it also shows you the decoded parameters of functions in the form of symbolic information (for example, some native call to the Windows API function). The fifth element is the white command line panel located at the very bottom and intended to control the debugger using WinDbg-style commands. Here you can do PyCommands, which we will look at next.
5.2.1 PyCommands
The main way to run Python scripts in 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 implement: various interceptions, static analysis, or any other debugging function. Each PyCommand must have a specific structure for its proper execution. The following code snippet shows the basic PyCommand structure that you can use as a template to create your own PyCommands.
from immlib import * def main(args):
In each PyCommand there are two main components. The first component, you must have a main () function defined, which must take one parameter, which is a list of the arguments passed to PyCommand. The second component is that main () should return a “string” when it has completed its execution. This line will update the “debugger status bar” (approx. Trans. 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 Immunity Debugger installation directory. To execute a saved script, simply type an exclamation mark followed by the name of the script, on 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 hooks, each of which you can implement either as a separate script or as an internal PyCommand script. The following types of interception can be used:
BpHook / LogBpHook
When breykopint meets - these types of interceptions work. Both hooks behave the same, except that when BpHook is encountered, it actually stops the execution of the debugger, whereas LogBpHook does not interrupt its execution.
AllExceptHook
Any exception that occurs in the processor will cause this type of interception to occur.
PostAnalysisHook
This interception is triggered after the debugger has finished analyzing the loaded module. This can be useful if you have some static analysis tasks that you want to do automatically, immediately after the module analysis is completed. It is important to note that the module (including the main executable file) needs to be analyzed before you can decode the functions and the main blocks using immlib.
AccessViolationHook
This interception is triggered whenever an access violation occurs; it is most useful for intercepting information while performing fuzzing.
LoadDLLHook / UnloadDLLHook
This interception is triggered whenever a DLL is loaded / unloaded.
CreateThreadHook / ExitThreadHook
This interception is triggered whenever a stream is created / destroyed.
CreateProcessHook / ExitProcessHook
This type of interception is triggered when the target process starts or exits.
FastLogHook / STDCALLFastLogHook
These two interceptions use a stub to transfer execution of the interceptor code to a small body, which can log a specific value of a register or memory location during interception. These hooks 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 the LogBpHook as an example:
from immlib import * class MyHook( LogBpHook ): def __init__( self ): LogBpHook.__init__( self ) def run( regs ):
We overload the LogBpHook class and make sure that the run () function is defined. When interception is triggered, the run () function accepts, as a single argument, a list of all processor registers that were set at the moment the hook was triggered, which allows us to view or change the current values ​​as we see fit. The variable regs is a dictionary that we can use to access registers by name, like this:
regs["ESP"]
Now we can define captures in several ways, using PyCommand and PyHooks. Thus, you can install traps either manually using PyCommand, or automatically using PyHooks (located in the main Immunity Debugger installation directory). In the case of PyCommand, the interception will be set every time PyCommand is executed. In the case of PyHooks, interception will automatically trigger each time Immunity Debugger is launched. Now let's move on to some examples of using immlib, the Immunity Debugger built-in Python library.
5.3 Development exploit
Vulnerability detection in software is only the beginning of a long and difficult journey ahead for you to get a reliable working exploit. Immunity Debugger has many design features that allow you to go the way of its development a little easier. We will develop some PyCommands that accelerate the exploit development process, including how to find instructions for obtaining EIP, as well as filtering bytes that are not usable in a shell code. We will also use PyCommand! Findatidep, supplied with Immunity Debugger, which helps to bypass the Data Execution Prevention (DEP)
[3] . Let's start!
5.3.1 Search for exploit friendly instructions
After you get control on EIP, you need to transfer the execution to the shellcode. As a rule, you will have a register or offset from the register that will point to the shell code. Your task is to find the instruction, 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 on the entire downloaded binary file. Let's put a script on our knees that will receive instructions and return all the addresses where this instruction is found. Create a new file
findinstruction.py and enter the following code.
findinstruction.py:
from immlib import * def main(args): imm = Debugger() search_code = " ".join(args) (
At the beginning, we will translate the received instructions into their binary equivalent
(# 1) , and then we use the Search () function to search all the instructions in the loaded binary file
(# 2) . Next, in the returned list we search 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 executable memory page, we find its address and output it to the
“Log” window. To use the script, simply 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 : PyCommand output! 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 instructions of interest to us.
5.3.2 Bad Character Filtering
When you send a string containing an exploit to the target system, there are some character sets that you cannot use in the shellcode. For example, if we found a stack overflow when calling the strcpy () function, then our exploit cannot contain a NULL (0x00) character, because strcpy () stops copying data as soon as it encounters a NULL value. Therefore, when writing exploits, shellcode encoders are used, which, after launching the shellcode, decode and execute it. However, there are still some cases where you can have a few characters filtered out or processed in some special way in vulnerable software, which can be a real nightmare to define them manually.
Usually, when you put the shellcode in a vulnerable program, and it did not start (causing an access violation or a program crash, before it was fully executed), you first need to make sure that it was copied into memory exactly as you did. they wanted it. Immunity Debugger can facilitate this task. Look at pic. 5-3, which shows the stack after overflow.
Fig. 5-3 : Immunity Debugger stack window after overflow
We see that the EIP register is currently pointing to the ESP register. Four bytes of 0xCC will simply force the debugger to stop, as if there was a breakpoint installed (remember? 0xCC is an INT3 instruction). Immediately after the four INT3 instructions, the ESP + 0x4 offset is the shellcode. It is there that you need to start 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 examine the shellcode in memory, we simply take the original as an ASCII string and compare it (byte-by-bye) with the shellcode located in memory to verify that the shellcode was loaded correctly. If we notice the difference, we output the bad byte that did not pass through the program filter to the Log. After that, we can add processing of such a character to the shellcode coder, before launching a re-attack! To check the performance of this tool, you can take a shell code from Metasploit, or your own home-made. Create a new
badchar.py file and enter the following code.
badchar.py:
from immlib import * def main(args): imm = Debugger() bad_char_found = False
In this script, we really only use the readMemory () call from the Immunity Debugger library, and the rest of the script is a simple string comparison. Now all you need to do is take your shellcode as an ASCII string (for example, if you have 0xEB 0x09 bytes, then your string will look like EB09), paste it into the script and run the script as follows:
!badchar "Address to Begin Search"
In our previous example, we would start a search with ESP + 0x4, the absolute address of which is 0x00AEFD4C, so we start PyCommand as follows:
!badchar 0x00AEFD4c
After launch, the script would immediately warn us of any problems with filtering bad characters and could significantly reduce the time spent on debugging a shell code failure 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 running in memory areas such as heap and stack. This can interfere with shell-code execution in most exploits, because most exploits store their shell codes in a heap or stack. However, there is a well-known technique
[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 in a heap. Immunity Debugger comes with a PyCommand called findantidep.py. which defines the appropriate addresses to install your exploit, so that you disable DEP and execute the shellcode. Consider a small theory on disabling DEP. Then move on to using the PyCommand script, which allows you to find the addresses of interest.
The Windows API call that can be used to disable DEP for the current process is the undocumented NtSetInformationProcess () function
[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 specified parameters: ProcessInformationClass to ProcessExecuteFlags (0x22) and ProcessInformation to MEM_EXECUTE_OPTION_ENABLE (0x2). The problem with a simple shellcode setup is that the call to this function consists of a number of NULL parameters, which are problematic for most shell codes. A technique that allows you to bypass this restriction is to place the shellcode in the middle of a function that already on the stack will call NtSetInformationProcess () with the necessary parameters. There is a known place in
ntdll.dll that does it for us. Look at the
ntdll.dll disassembled 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 the comparison of AL with the value 1, then the value 2 is placed in ESI. If AL is 1, then the conditional transition to 0x7C93FEBA is triggered. There, the value from ESI is moved to the variable of the EBP-4 stack (remember that ESI is still set to 2?). Then the condition is checked at the address 0x7C91D403, which checks our variable in 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; you can see 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 is pushed and the value -1 (-1, the handle of the process that tells the function call that this is the current process in which you want to disable DEP), then the call to ZwSetInformationProcess (alias NtSetInformationProcess) follows. 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 (the low byte of EAX) is set to 1. After these conditions are met, we can transfer the control to the shell code, like in any other overflow, for example, using JMP ESP instructions. Thus, you need three addresses:
- The address that sets AL to 1, and then returns;
- The address where the piece of code is to disable DEP;
- The address to transfer control to the beginning of our shellcode.
Usually you need to search for these addresses manually, but the developers of the exploits in Immunity created a small Python-script
findantidep.py , executed as 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 found addresses without any effort at all. Let's look at the findantidep.py script and then try it out.
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!"
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 search for a set of instructions in ntdll.dll , which contain the DEP disable code (# 2). In the third step, ask the user to enter instructions or instructions that will have to transfer control to the shellcode (# 3), and provide the user with a list of addresses where these instructions can be found. The script ends with the output of the results in the Log window (# 4). Look at figures 5-4 - 5-6 to see how this process goes.
Fig.
5-4: First choose the address that will set AL to 1
Fig.
5-5: Then we enter 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 finally 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 to test existing exploits, so that they can successfully run on a system with DEP enabled or create new exploits that would support disabling DEP from the box. This is a great example of picking up hours of manual search, which 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
The current types of malware are becoming more and more entangled in their methods of infection, distribution and their ability to protect against analysis. In addition to common code obfuscation techniques, such as using packers and cryptors, malware usually uses anti-debugging techniques in an attempt to prevent its analysis with a debugger to make it difficult to study. Using the Immunity Debugger and Python, you can create some simple scripts that allow you to bypass some of these anti-debugging techniques to help the analyst research malware samples. Let's look at some of these most common anti-debug 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 debuggers attached to the current process or 0 if it is not. 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 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 Demian Gomez [6] of 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 zeroes the BeingDebugged flag in PEB, and now any malware that uses this check will be deceived into believing that there is no associated debugger.
5.4.2
The malware also tries 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 through the running processes, the malware will use the Process32First () function to get the first registered process in the system's process list, and then use Process32Next () to iterate through all the remaining processes. Both of these function calls return a boolean value that tells the caller whether the function succeeded or not, so we can simply patch these two functions so that the EAX register is set to zero when the result is returned by the function. We will use the built-in assembler Immunity Debugger to achieve this goal.Look at the following code:
(
First, we find the addresses of two functions that select the processes and save them to the list (# 1) . Then we translate some bytes into the corresponding opcodes, which will 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) , in the nutria of the functions Process32First and Process32Next. We do this because some advanced malware will actually check the first few bytes of these functions in order to make sure that the function was not patched by the reverse engineer. We deceive them by patching with 10 instructions below; true, 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 regardless of how they are called.
We looked at two examples of how you can use Python and Immunity Debugger to create automated ways to protect against malicious programs trying 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 cope with them! The knowledge gained in this chapter will help you enjoy a shorter exploit development time, as well as a new arsenal of tools to combat malware.
Now let's move on to some interception methods that you can use during reversing.
Links
[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 the 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 .