Debugging Malware - Malware Analyst’s Cookbook and DVD: Tools and Techniques for Fighting Malicious Code (2011)

Malware Analyst’s Cookbook and DVD: Tools and Techniques for Fighting Malicious Code (2011)

Chapter 10. Debugging Malware

Debuggers are essential tools for malware analysis. They allow inspection of code at a more granular level than dynamic analysis and give full control over the malware’s run-time behaviors. Using debuggers, you can execute each instruction at your convenience instead of at the pace of a modern processor. In other words, you can execute the program in slow motion while studying its every action. You can also use a debugger to execute a few select functions instead of the entire program, which is helpful if you need to bypass anti-debugging code.

Many different debuggers and debugging tools are available to analysts. Some tasks require debugging in kernel mode, which is covered in Chapter 14. To debug programs in user mode, which is the focus of this chapter, you can use a GUI-based debugger, such as OllyDbg or Immunity Debugger. Both of these debuggers allow you to extend their features with existing plug-ins or ones that you create. For example, you can use OllyScript, which is an assembly-like language to develop plug-ins for OllyDbg. Immunity Debugger has a built-in Python interface and a strong API specifically designed for researching vulnerabilities and performing malware analysis. If you don’t require a GUI, you can use a pure Python framework such as pydbg or winappdbg. Using these tools, you can create your own handlers for events and exceptions, which enables you to control a program in an automated fashion.

Although this chapter begins with an introduction to using debuggers, it is important that you have a basic understanding of program flow, assembly language, CPU operations, and the Windows API. Furthermore, all of the tools discussed in this chapter actually execute the malware; therefore, you must take precautions to run these tools in a virtual machine or a devoted test environment.

Working with Debuggers

In this section, we’ll get you familiar with how to solve problems using Immunity Debugger and OllyDbg. For examples of using WinDbg, see Chapter 14. Immunity Debugger is based on the OllyDbg source code. Therefore, it looks and feels like OllyDbg and the two debuggers share a lot of the same underlying functionality and controls. Most of what you read in this section applies to both debuggers; however we choose to focus on Immunity Debugger because of its Python API. Before we get started, here is a list of resources you can use to find debugger plug-ins.

· Immunity Debugger forums: https://forum.immunityinc.com/board/show/14/immunity-debugger-repository/

· OllyDbg plugins on OpenRCE: http://www.openrce.org/downloads/browse/OllyDbg_Plugins

· OllyDbg plugins on Woodman: http://www.woodmann.com/collaborative/tools/index.php/Category:OllyDbg_Extensions

· Immunity Debugger downloads on Tuts 4 You: http://www.tuts4you.com/download.php?list.72

Also, this book does not cover anti-debugging tricks in detail. There are literally hundreds of different ways that malware can detect or prevent the use of debuggers. A majority of malware samples use at least one of those tricks. Here are a few resources you can use to defend yourself against anti-debugging tricks.

· The PhantOm plugin for OllyDbg: http://www.woodmann.com/collaborative/tools/index.php/PhantOm

· The hidedebug plugin for Immunity Debugger: (it ships with the debugger)

· The IDAStealth plugin for IDA Pro: http://newgre.net/idastealth

· Windows Anti-Debug Reference by Nicolas Falliere: http://www.symantec.com/connect/articles/windows-anti-debug-reference

· Anti-Unpacker Tricks by Peter Ferrie: http://pferrie.tripod.com/papers/unpackers.pdf

Recipe 11-1: Opening and Attaching to Processes

To begin using the debugger, you can attach it to an existing process or start a new process. In most cases, you’ll want to debug malware from the very start so you can control and observe its initial actions. If you attach to an existing process, you can control only its future actions because the initial ones have already executed. In other cases, however, the malware’s initial actions may be irrelevant to you, so it’s a decision you’ll want to make on a case-by-case basis.

Starting a New Process

If you start a new process, the debugger opens and pauses at the program’s entry point (its first instruction). The entry point is calculated by adding the ImageBase and AddressOfEntryPoint values from the PE header.

Note Some anti-debugger tricks including TLS entries can enable malware to execute code before your debugger initially pauses. In cases where the executable has TLS entries (Recipe 3-8 shows you how to check), you need to set a breakpoint before the program’s entry point before you start debugging. To do this, click Options ⇒ Debugging options ⇒Events ⇒ System breakpoint. Then use the PyCommand “!bpxep –tls” to set the new breakpoint. We will introduce how to use PyCommands later in the chapter.

If you need to supply arguments to the process when you start it, open the debugger and click File ⇒ Open. Then browse to the executable file in the GUI window and enter any required arguments in the Arguments field, as shown in Figure 11-1.

Figure 11-1: Supplying arguments to a process to debug

f1101.tif

Attaching to an Existing Process

To attach to an existing process, open the debugger and click File⇒ Attach. You’ll see a list of available processes, as shown in Figure 11-2. When you attach to a running process, the debugger suspends the process. This gives you time to inspect the process’s resources or figure out where to set breakpoints before you resume the process.

Figure 11-2: Selecting an existing process to debug

f1102.tif

Note If you started a new process, then the process will terminate when you close the debugger. However, if you attached to an existing process with the debugger, you can click File ⇒ Detach and then close the debugger without terminating the debugged process.

Recipe 11-2: Configuring a JIT Debugger for Shellcode Analysis

dvd1.eps

You can find supporting material for this recipe on the companion DVD.

Setting up a JIT (just-in-time) debugger is useful if you want to debug any process that encounters an unhandled exception (or critical error), but you don’t preemptively know which process that’s going to be. The JIT configuration exists in the registry at the following location: HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AeDebug\Debugger. If you place the path to your debugger in that registry key, the system will launch your debugger anytime it’s needed and automatically attach to the target process.

Instead of manually editing the registry, you can also click Options⇒ Just-In-Time Debugging ⇒ Make Immunity Debugger Just-In-Time Debugger, Figure 11-3 shows an example of this dialog.

Figure 11-3: Setting up just-in-time debugging

f1103.tif

One way you can leverage JIT debuggers for malware analysis is to load shellcode files. Debuggers can’t natively load shellcode for the same reason that you can’t double-click shellcode to execute it—there’s no PE header and Windows doesn’t know what do to with it. However, you can create a simple program that provides a wrapper around your shellcode and gives it a process context in which to execute. The code that follows is an example of such a program. It copies the content of your shellcode file from disk into memory, places a 0xCC byte (interrupt 3) at the start of the shellcode, and then uses inline assembly to jump to the shellcode and begin executing it. When the program reaches the 0xCC at the beginning, your JIT debugger will launch and you can debug the shellcode.

int main(int argc, char* argv[])

{

HANDLE hFile;

LPBYTE pSC;

DWORD dwSize;

if (argc != 2) {

printf("Usage: %s <sc file>\n", argv[0]);

return -1;

}

hFile = CreateFileA(argv[1],

GENERIC_READ, FILE_SHARE_READ, 0,

OPEN_EXISTING, 0, NULL);

if (hFile == INVALID_HANDLE_VALUE)

return -1;

dwSize = GetFileSize(hFile, NULL);

pSC = new BYTE[dwSize+1];

if (pSC != NULL) {

pSC[0] = '\xCC'; // INT 3

ReadFile(hFile, pSC+1, dwSize, &dwSize, NULL);

__asm jmp pSC;

}

CloseHandle(hFile);

return 0;

}

You can find a copy of the scloader program on the book’s DVD. Here’s the syntax:

C:\> scloader.exe win32_shellcode.bin

For more information on debugging shellcode, see Shellcoder’s Handbook: Discovering and Exploiting Security Holes, Chris Anley et al., Wiley Publishing.

Note Using a tool such as scloader is not the only way to get shellcode into your debugger. You can also use a tool such as David Zimmer’s Shellcode2Exe1 or Mario Vilas’ shellcode2exe.py2 to create an executable file from your shellcode.

1 http://labs.idefense.com/software/malcode.php#more_malcode+analysis+pack

2 http://breakingcode.wordpress.com/2010/01/18/quickpost-converting-shellcode-to-executable-files-using-inlineegg/

Recipe 11-3: Getting Familiar with the Debugger GUI

Once you have a process opened in the debugger, you may initially feel overwhelmed with all of the buttons, colors, and numbers. This recipe orients you to the basic GUI layout of the debugger. As shown in Figure 11-4, the default view has four major windows that show different information.

Figure 11-4: The debugger’s main GUI interface

f1104.eps

CPU Pane

The CPU pane shows a disassembly of all instructions in the currently selected module. A module in this case can be the process’s executable, a DLL, or any memory range accessible in the process. Here are a few lines from the CPU pane of Figure 11-4 to assist with the following discussion:

010073DF > 83B9 84000000 0E CMP DWORD PTR DS:[ECX+84], 0E

010073E6 .^ 76 F2 JBE SHORT NOTEPAD.010073DA

010073EB . 33C0 XOR EAX, EAX

Going from left to right, you first see the address in the process’s memory where a given instruction exists. The ordering shows lower addresses near the top of the CPU pane and higher addresses near the bottom. Next, depending on the instruction, you may see a character such as a carat (^), which indicates the direction of a jump, or a greater than sign (>), which indicates a jump destination. In the example from Figure 11-4, there is a conditional short jump instruction at 010073E6, which leads to 010073DA (a lower address) if taken; thus it shows the ^ character.

In the next column, you see one or more hex numbers that may or may not be separated by spaces. These are the opcodes and operands for the instructions. For example, opcode 83B9 stands for a comparison instruction (CMP) and it takes two operands: 84000000 and 0E. The previously mentioned conditional jump consists of a 1-byte opcode (76), which only requires one operand (F2). The instruction at 010073EB consists of a 2-byte opcode (33C0) and zero operands.

Here are some additional points to remember when familiarizing yourself with the CPU pane:

· Color coding: Immunity Debugger color codes instructions in the CPU pane. It uses red for instructions (JMP and CALL) that change the control flow, blue for constants and hard-coded numbers, white for registers, and yellow for lines that reference a memory address.

· Navigating: The value in the EIP register shows the next instruction to execute. If you “get lost” in the CPU pane by scrolling too far up or down and want to restore the current instruction, just double-click the address in the EIP register, or use Ctrl+G and type “EIP”.

· Patching code: Pressing the spacebar while your cursor is on an instruction allows you to type in your own assembly instructions and apply a “patch” to the program.

Register Pane

A register is the most basic unit of storage in the CPU. Each thread in a process has its own view of the CPU’s registers, which is called a context. When the CPU stops executing one thread to give another thread some processing time, it saves the previous thread’s context and then restores all of the values when it’s time to switch back. Table 11-1 shows a breakdown of the general-purpose registers on x86 systems. All of the 32-bit registers have a smaller 16-bit counterpart, but only some of them can be broken down even smaller into 8 bits.

Table 11-1: General-purpose Registers on x86 Systems

Table 11-1

Some of the general-purpose registers have special uses, which vary depending on which compiler you use. Here’s a quick primer:

· EAX: The extended accumulator register often stores the result of multiplication or division operations. It also frequently stores a function’s return value.

· ECX: The counter register frequently stores the number of times a loop should iterate.

· ESI and EDI: The source index and destination index, respectively, are often used in high-speed data transfer operations. You might see a pointer to the source (input buffer) placed in ESI and a pointer to the destination (output buffer) placed in EDI before memmove or memcpy.

· ESP: The stack pointer points to a currently executing program’s stack.

· EBP: Functions frequently use the frame pointer to locate their local variables (usually as an offset relative to EBP).

Aside from the general-purpose registers, the debugger’s Register pane also shows you information about the following registers:

· EIP: The instruction pointer contains the address of the next instruction to be executed.

· EFLAGS (abbreviated EFL in the register pane): This is a 32-bit register and each individual bit either controls some operation in the CPU or reflects the outcome of a previous operation.

Note You can change the value of all general-purpose registers by double-clicking them and entering a new value. You can toggle bits (turn them on or off) in the EFLAGS register by double-clicking them as well. The only register you can’t change by double-clicking is EIP. To change EIP, right-click your desired instruction and choose the Set New Origin Here menu option.

The debugger highlights registers if their values changed since the last instruction. Keeping track of which registers changed because of an instruction or set of instructions is critical to understanding behaviors at a low level. Here are a few rules that apply:

· The EIP register is highlighted after every instruction, even if the instruction does nothing, such as an NOP (no-operation). This is because the CPU must update EIP to point to the next instruction.

· Most, but not all, instructions will modify at least one of the general-purpose registers. The exceptions are instructions such as NOP and MOV EDI, EDI that do not actually cause a change.

· If you execute an entire function at once (see Recipe 11-5 regarding stepping over a function) the debugger will highlight all registers that changed.

Stack Pane

Programs use the stack for storing local variables, passing arguments to functions, and storing return addresses. Using a debugger to analyze the stack before calling a function can yield critical information about the number of arguments a function takes, the types of the arguments (like an address, integer, or character pointer), and the exact values of the arguments. Getting familiar with the stack pane is worth its weight in gold when reversing because it can help you discover the purpose of a function.

A program prepares to call functions by copying the function’s arguments onto the stack (via PUSH instructions). The following example program demonstrates the use of the stack pane in the upcoming discussion. You can find the example source code and a compiled copy of the program on the book’s DVD if you want to try this yourself.

#include "stdio.h"

int MYFUNC(int times, char * string){

int local;

for (local = 0; local <= times; local++){

printf("%d: %s\n", local, string);

}

return 99;

}

int _tmain(int argc, _TCHAR* argv[])

{

MYFUNC(10, "printme");

return 0;

}

As shown in Figure 11-5, the stack.exe program passes arguments to the target function by pushing them onto the stack in reverse order (the function’s first argument is pushed last). The CPU pane shows two PUSH instructions. The first value is a pointer to the ASCII string printme. It shows up as stack.00415748 in the debugger because the string is within the module named stack.exe at address 00415748. The second value is 0x0A (or 10 decimal).

Figure 11-5: The function’s arguments are transferred to the stack after executing the PUSH statements

f1105.eps

If you execute the two PUSH instructions in your debugger and pause when EIP is on the CALL instruction, as in Figure 11-5, then you should see something very similar to the image. At this time in the sample program, ESP (the stack pointer register) contains 0012FE94. Thus, on the top of the stack, you can find 0x0A—the target function’s first argument. At ESP+4, you can find a pointer to printme—the target function’s second argument. If the program took a third argument, you could find it at ESP+8.

Note A calling convention defines how functions accept arguments and if the caller or called function is responsible for removing the arguments from the stack after the function is done executing. The example we’ve shown is based on the stdcall calling convention. The Windows API uses stdcall and so do many C compilers. If you’re dealing with C++ code, or a program compiled with GCC, then you may observe parameters being passed to functions in different ways. For more information, see http://unixwiz.net/techtips/win32-callconv.html.

Dump Pane

You can use the Dump pane to inspect the contents of any valid memory location in the debugged process. If a register, stack location, or instruction in the CPU pane contains a valid memory address, you can navigate to the specified location by right-clicking the address and choosing the Follow in Dump option. Figure 11-6 shows an example of synchronizing the address in the EAX register with the dump pane display.

Figure 11-6: Using the follow in dump option on the EAX register

f1106.tif

Depending on which memory address you select and in which pane, you may have additional choices. If you right-click an instruction in the CPU pane and click Follow in Dump⇒ Selection, you’re taken to the current instruction’s address in the dump pane. Otherwise, if you select Follow in Dump⇒ Memory address or Follow in Dump⇒ Immediate Constant you’re taken to the address of one of the instruction’s operands.

In the dump pane, you can change the display format of the data. Right-click in the dump pane and you should see options such as hex, text, short, long, float, disassemble, and special. The hex format shown in Figure 11-7 shows each byte along with an ASCII (printable) version of those bytes.

Figure 11-7: The Dump pane in ASCII layout

f1107.tif

In some cases when you use the hex or ASCII layout, you’ll notice that the debugger underlines certain values in the dump pane. For example, as shown in Figure 11-8, the first six 32-bit values are underlined. This indicates that on the values contain an address that points to a known function, symbol, or a string. To explore the values, right-click and select the Long⇒Address option, as shown in Figure 11-9.

Figure 11-8: The underlined hex dump values indicate addresses with known values

f1108.tif

Figure 11-9: The dump pane after selecting Long ⇒ Address format

f1109.tif

Navigating to Addresses

By pressing Ctrl+G (Go) in the CPU pane, dump pane, or stack pane, you can make the debugger show you data at an address of your choice. Table 11-2 describes how you can navigate to different addresses. Although the table uses EAX as an example, you can use any register in your expressions, provided they contain a valid address.

Table 11-2: Expressions for Valid Addresses

Expression

Meaning

EIP

Go to the current instruction.

EIP+0xFF

Go to the current instruction plus hex value (255).

EAX

Go to the current address in EAX.

[EAX]

Go to the address pointed to by the current address in EAX (i.e., dereference the pointer in EAX).

[EAX+4]

Go to the address pointed to by the current address in EAX plus 4.

7C8286EE

Go to the absolute address 7C8286EE.

CopyFileA

Go to the address of CopyFileA in the process memory.

Note You can use the dump pane as a general-purpose hex editor as well. Navigate to the bytes you want to modify in the dump pane and just start typing over them. Be aware there is no undo for these changes.

Recipe 11-4: Exploring Process Memory and Resources

In the upper-right corner of the debugger window, you’ll see a sequence of single-letter buttons. Each button opens a window with data that you can use to inspect process resources. Table 11-3 shows a summary of the buttons.

Table 11-3: Buttons to Open Debugger Windows

Button

Description

l

Log messages (ALT+L)

e

Loaded executable modules (ALT+E)

m

Memory map (ALT+M)

t

Threads (no hotkey)

w

Windows (GUI processes only)

h

Open handles

c

CPU pane

k

Call stack

b

Breakpoints

z

Hardware breakpoints

Viewing Executable Modules

The Executable modules window of the debugger shows files that the debugged program has loaded into memory. You might use this window (an example is shown in Figure 11-10) for the following purposes:

· To verify which DLLs a process had loaded and the full path on disk to the DLLs.

· To determine exactly where a DLL resides in process memory.

· To determine which file contains the value you’re looking for. If you know the address of a function, string, or other variable, you can do a reverse lookup using the base and size fields of the executable modules window.

Figure 11-10: Executable modules window

f1110.tif

Enumerating Names

The names window shows functions that a program either imports or exports. You can use the names pane to find out exactly where the functions exist in the process’s memory. To access the names, right-click on the CPU pane and click Search for ⇒ Name or type Ctrl+N. You can look for names in the module currently displayed in the CPU pane or in all modules loaded into the process memory space (all DLLs). Figure 11-11 shows an example of locating a particular exported function by enumerating the names.

Figure 11-11: Names window

f1111.tif

Inspecting Handles

The handles pane displays details on all currently open handles. In particular, it shows the handle value, handle type, granted access, and object name. Many Windows API functions (such as ReadFile and RegSetValue) accept a handle value instead of the object name. Therefore, when you see a number such as 64 being passed to RegSetValue, you can look it up in the handles pane and see that 64 corresponds to something like REGISTRY\MACHINE\SOFTWARE\Microsoft\Windows. For more information on how you can use handles in your analysis, see Recipe 9-5.

Figure 11-12: The handles window

f1112.tif

Using the Memory Map Pane

The Memory map pane shows details on the allocated memory segments in the process. Each time a program loads a new module via the LoadLibrary API or allocates additional memory with VirtualAlloc, you’ll see new segments show up in the Memory pane. You can use this window to browse the permissions and types of data that exist at certain locations in a process, as shown in Figure 11-13.

Figure 11-13: The Memory map window

f1113.tif

As previously mentioned, when a new PE file is loaded into memory (whether it’s a DLL or the process’s own executable image), it could result in multiple new memory segments—one for the PE header and one for each of the PE sections. Figure 11-13 shows eight memory segments owned by the stack.exe program. The first one at 00400000 contains the program’s PE header. The next seven contain the program’s PE sections. If you want to compare the values in memory with the values on disk, take a look at Figure 11-14, which shows the names, sizes, and RVAs (relative virtual addresses) of sections in stack.exe’s PE header. You’ll notice that the actual sizes in the memory map are rounded up to the nearest multiple of 0x1000, which is the smallest page size.

Figure 11-14: Sections according to the section headers on disk

f1114.tif

If you double-click any memory segment, a window will open (similar to the Dump pane) that displays the segment’s contents in a format of your choice. You can also right-click in the memory map and select Search and then enter an ASCII, Unicode, or sequence of hex bytes to find anywhere in the process’s memory. Figure 11-15 shows a case-sensitive search for URL prefixes.

Figure 11-15: Searching for an ASCII string

f1115.tif

Recipe 11-5: Controlling Program Execution

This recipe describes various ways of controlling the execution of your debugged program. Each method can be controlled with a keyboard shortcut, as well as a button in the application’s GUI. Once you become experienced with debugging, you’ll find that your fingertips are almost always pressing one of the commands in this recipe.

Using Play/F9

The play command (F9) executes all instructions until an exception occurs, a breakpoint is reached, the program terminates, or until you pause it to regain control. If no breakpoint is set when you use F9, the process could infect your system and terminate before you get the chance to act. Therefore, you should use F9 with caution.

F7/Single Step-In and F8/Single Step-Over

You can execute a single instruction each time you use the single step (F7) command. The single step-over (F8) command is similar. When you use F8 and the current instruction is a CALL, all instructions in the called function execute. When you use F8 and the instruction is anything other than a CALL, then F8 will behave exactly like F7.

Execute Until Return

The execute until return command (Ctrl+F9) allows you to execute all instructions in the current function until it returns. This is useful if you stepped into a function that turns out to be uninteresting. Once you’ve reached the end of the function (i.e., a RET or RETN instruction), you can use either F7 or F8 to return to the calling function.

Execute Until User Code

The execute until user code command (Alt+F9) acts similarly to execute until return, except it can get you out of deeply nested sub-functions. This command pauses on address ranges instead of a particular function’s return instruction. For an example, see Figure 11-16. Imagine you’re debugging a program that calls ReadFile. You step into the call and end up inside kernel32.dll. Then you step into another call and end up inside ntdll.dll. At this point you are two modules deep. To immediately get back to the location where the program originally called ReadFile, you can use Alt+F9.

Figure 11-16: Using Alt+F9 to return to user code

f1116.eps

Note As an alternative to the execute until user code command, you can scroll down in the Stack pane and find a return address inside the module that you want to be debugging. Set a breakpoint (see next recipe) on the return address and use F9 to play until you’re out of the nested calls.

Using Set New Origin Here

Setting a new origin allows you to force execution of functions or blocks of code that don’t normally execute. For example, assume you want to debug a function that only executes when the malware receives a certain command. If the command and control server is unreachable (perhaps you’re debugging in a lab isolated from the Internet), the malware will never receive such a command. Thus, the function you want to debug will never execute without your intervention. In these cases, you can force the function to execute by manually re-setting EIP.

The biggest issue with manually setting a new origin at the start of a function is that you’ll skip over the code that is responsible for passing arguments to the function. This isn’t a problem if the function doesn’t take arguments, but if it does, then you also have to determine how many arguments the function takes and set up the stack. Otherwise, the function will take whatever values are currently on the stack and use them, which could cause the program to crash.

Recipe 11-6: Setting and Catching Breakpoints

Breakpoints are fundamental components of any debugger. They’ve already been mentioned many times throughout the chapter, but this recipe discusses them in greater detail. You can use breakpoints to pause the execution of a program when it reaches a particular instruction; when it calls an API function; or when it reads, writes, or executes from a given memory address or range. You can set different types of breakpoints in the CPU pane by right-clicking an instruction and selecting the breakpoint menu, as shown in Figure 11-17.

Figure 11-17: Accessing the Breakpoint menu

f1117.tif

Software Breakpoints

A software breakpoint replaces the byte at your breakpoint address with a 0xCC (INT 3). You can set a software breakpoint by clicking Breakpoint⇒ Toggle as shown in Figure 11-17 or by pressing F2. You won’t see the instruction actually change to INT 3 in the CPU pane because the debugger masks it. When the debugged program encounters an INT 3, the debugger’s exception handler will trigger and yield control to you. Before allowing the program to resume, the debugger replaces the 0xCC with the original byte.

The main advantage of software breakpoints is that you can set an unlimited number of them. Software breakpoints also have their disadvantages, such as the following:

· A malicious program can easily read the process memory looking for 0xCC and then change its behavior accordingly.

· If you set software breakpoints at the wrong place before or during an unpacking procedure, you can cause the program to crash unexpectedly. Consider code that reads every byte in its own memory and adds 1 to every byte to produce the unpacked byte. Instead of the original value plus 1, the software breakpoint would become 0xCD (0xCC + 1). Such an action would both destroy the breakpoint and the original value.

Hardware Breakpoints

A hardware breakpoint uses the CPU’s debug registers DR0-DR7. You can set hardware breakpoints to pause the program upon reading, writing, or executing a memory address. Unlike software breakpoints, hardware breakpoints do not modify the process’s memory, so you can use them more reliably with packed code. However, you can only set four hardware breakpoints at a time. Also, malware can detect if hardware breakpoints have been set by calling GetThreadContext with the CONTEXT_DEBUG_REGISTERS or CONTEXT_FULL flags.

Memory Breakpoints

Memory breakpoints can be useful when you find an interesting string or variable in the process’s memory but don’t know exactly which instruction(s) reference it. Using memory breakpoints, you can ask the debugger to pause when any instructions in the process (including loaded DLLs) read or write to the memory location.

The following list discusses the ways you can set memory breakpoints:

· To set a memory breakpoint on an instruction, right-click the desired address in the CPU pane and select either Breakpoint ⇒ Memory, On Access or Breakpoint⇒ Memory, On Write.

· To set a memory breakpoint on data in the Dump pane, highlight the group of bytes and right-click as described previously.

· To set a memory breakpoint for an entire section of memory, go to the memory map (Alt+M). Then right-click and choose either Set Break-On-Access, Set Memory Breakpoint On Access, or Set Memory Breakpoint On Write. Figure 11-18 shows how this menu will appear.

Figure 11-18: The memory map right-click menu

f1118.tif

Memory breakpoints work by enabling the PAGE_GUARD protection on the memory page. When the debugged process attempts to access a guarded page, the system fires a STATUS_GUARD_PAGE_VIOLATION3 exception. The debugger will catch this exception and pause the program so you can analyze it.

Setting Breakpoints Using Names/Symbols

Many debuggers allow you to set breakpoints on function names instead of addresses. For example, you can set a breakpoint on the Windows API function wsprintfW within user32.dll, instead of the address. The debugger translates the name into an address much like the GetProcAddress Windows API function. For this to work, the DLL containing the function you want to break on must already be loaded in the process—otherwise the lookup will fail.

To solve the problem of setting breakpoints on functions that aren’t currently loaded, you can configure the debugger to pause upon loading new modules. Click Options ⇒ Debugging Options ⇒ Events. Then select the Break on new module (DLL) checkbox and press play (F9). The debugger will pause when the debugged process loads a new DLL. When this happens, you can set a breakpoint on the desired function before allowing the program to resume. There is a Python script that uses this technique in an automatic manner from the cyberwart blog.4

Using the Command Box

Immunity Debugger’s command box allows you to enter commands such as bp for a software breakpoint or he for hardware breakpoint. Figure 11-19 shows how the authors set a software breakpoint on CreateFileW. Upon hitting play and catching the breakpoint, you can also see the parameters being sent to CreateFileW by looking at the Stack pane.

Figure 11-19: Using the command box to set breakpoints

f1119.tif

Note Typing help into the command box shows all of the possible commands that you can enter. Some other useful ones include d or dd to follow an address in the dump pane and various tracing, dump, stack, and window commands. The command box also serves as an interface to the Python scripts available in the installation directory C:\Program Files\Immunity Inc\Immunity Debugger\PyCommands, which we discuss more in the Immunity Debugger’s Python API section.

Practical Usage of a CreateFile Breakpoint

If you want to debug malware to examine its usage of a configuration file, you might set a breakpoint on CreateFileW and look on the stack until the FileName parameter points to the configuration file. Then you can set a breakpoint on ReadFile and/or WriteFile to inspect its input and output operations. In the case of ReadFile, you’ll see a pointer to the input buffer on the stack. You can follow that address in the Dump pane, step over (F8) the call to ReadFile, and now you’ll see the contents of the configuration file in the Dump pane. To break on the next instruction that accesses the file’s content, set a hardware on-access or memory on-access breakpoint at the start of the configuration file contents and then press play (F9). Using these few steps, you can pinpoint the exact location in the malware where the configuration file is parsed.

3 http://msdn.microsoft.com/en-us/library/aa366549%28VS.85%29.aspx

4 http://www.cyberwart.com/blog/2009/08/10/set-future-breakpoints

Recipe 11-7: Using Conditional Log Breakpoints

As mentioned in the previous recipe, when you set a breakpoint on an API, you can inspect the parameters sent to the API by looking on the stack. One problem you’ll likely run into is that some APIs may be called hundreds of times by other modules loaded in a process while you’re waiting for your malicious program to call the API. In this case, you’ll have to continue pressing play (F9) after each false positive, which is a very tedious process. Luckily, you can reduce the noise by using conditional breakpoints.

Defining the Conditions

Suppose you want to set a breakpoint on CreateFileW, but only pause the debugger when your process tries to open a file with write access. The second parameter to CreateFileW, named dwDesiredAccess, specifies the desired access. Examples include GENERIC_READ, GENERIC_WRITE, and GENERIC_ALL. These values can be combined with a logical OR. For instance, GENERIC_READ has the value 0x80000000 and GENERIC_WRITE has the value 0x40000000. If your malware calls CreateFileW with both read and write permissions, the dwDesiredAccess parameter will be 0xC0000000 (0x80000000|0x40000000 = 0xC0000000). When configuring the conditional breakpoint, you’ll want to check if the second parameter has the 0x40000000 bit set.

Setting the Breakpoint

To set a conditional breakpoint, navigate to the address of CreateFileW first. Then right-click the function’s first instruction and select Breakpoint ⇒ Conditional log (Shift+F4). The display (shown in Figure 11-20) allows you to define the condition using logical AND (&), OR (|), and equals (=) operators.

Figure 11-20: Options for a conditional log breakpoint

f1120.tif

To decide which values to place in the fields, make sure you understand the layout of the stack upon entering a function. Table 11-4 shows a quick review:

Table 11-4: Layout of the Stack upon Entering a Function

Layout

Description

ESP

Address of the top of the stack

[ESP]

Return address

[ESP+4]

First parameter to the function (file name)

[ESP+8]

Second parameter to the function (desired access)

As you can see in Figure 11-20, the condition is checking [ESP+8] which is the dwDesiredAccess parameter. The radio buttons allow you to control which actions to take when the breakpoint is triggered. The three possible actions are:

· Pause program: This action pauses execution of the program like a typical breakpoint.

· Log value of expression: This action allows logging of custom types and values. In Figure 11-20, you can see that the expression is [ESP+4], which is the first parameter to CreateFileW. Accordingly, the drop-down menu tells the debugger to decode [ESP+4] as a pointer to a Unicode string. As a result, when the breakpoint triggers, you’ll see the following message in the log window:

COND: FileName = 0100A900 "c:\myfile.txt"

· Log function arguments: This action dumps all function parameters (provided the debugger recognizes the API function) to the log window. This action is a just a pre-configured version of the previously described action.

7C810760 CALL to CreateFileW from notepad.01004ED8

FileName = "c:\myfile.txt"

Access = GENERIC_READ|GENERIC_WRITE

ShareMode = FILE_SHARE_READ

pSecurity = NULL

Mode = OPEN_ALWAYS

Attributes = NORMAL

hTemplateFile = NULL

Immunity Debugger’s Python API

Immunity Debugger has a built-in Python framework that you can use to extend the debugger’s functionality for malware analysis. This section discusses some of the existing Python plug-ins and presents a few new ones to get you familiar with the API. Also, Chapter 12 covers how to script the execution of malicious code for the purposes of decoding and decrypting. You can find documentation of the Python API in various online sources as well:

· Immunity Debugger Online API Reference: http://debugger.immunityinc.com/update/Documentation/ref/

· Intelligent Debugging for Vulnerability Analysis and Exploit Development by Damian Gomez: http://www.defcon.org/images/defcon-15/dc15-presentations/dc-15-gomez.pdf

· Starting to Write Immunity Debugger PyCommands Cheatsheet by Peter Van Eeckhoutte: http://www.corelan.be:8800/index.php/2010/01/26/starting-to-write-immunity-debugger-pycommands-my-cheatsheet/

Recipe 11-8: Debugging with Python Scripts and PyCommands

In this section, you will learn how to execute Python commands to set breakpoints, modify register values, read process’s memory, and search memory for strings. Although you have a multitude of ways to execute Python code in Immunity Debugger, this section covers only two of them—the Python shell and PyCommands.

Using the Python Shell

The Python shell is an interactive command shell that you can launch while debugging any process by clicking the icon shown in Figure 11-21.

Figure 11-21: Opening the Python shell

f1121.eps

You’ll be presented with a prompt that looks like this:

*** Immunity Debugger Python Shell v0.1 ***

Immlib instantiated as 'imm' PyObject

READY.

>>> type your python commands here

At the prompt, you can combine normal Python code with functions exposed by the Immunity Debugger API. Here are a few examples to get you started:

· To debug a new process and allow it to execute until the first call to CreateFileW, use the following:

>>> imm.openProcess("malware.exe")

0

>>> imm.setBreakpoint(imm.getAddress("kernel32.CreateFileW"))

0

>>> imm.Run()

1

· To execute the until CreateFileW finishes, and then print the return value, you can use the following:

>>> imm.runTillRet()

>>> regs = imm.getRegs()

>>> if regs['EAX'] == 0xFFFFFFFF:

>>> print "Invalid handle value!"

>>> else:

>>> print "The handle is: " + hex(regs['EAX'])

· To dump 0x8000 bytes of memory starting at address 0x1001000 to a file on disk, you can use the following:

>>> f = open("c:\dumped_01001000.mem", "wb")

>>> f.write(imm.readMemory(0x1001000, 0x8000))

>>> f.close()

· To list the loaded modules and their base addresses, use the following:

>>> mods = imm.getAllModules()

>>> for mod in mods:

>>> print "%08x" % mod.baseaddress, mod.name

>>>

5cb70000 shimeng.dll

7c800000 kernel32.dll

77c10000 msvcrt.dll

6f880000 acgenral.dll

7c900000 ntdll.dll

769c0000 userenv.dll

[REMOVED]

· To find and print all occurrences of a Unicode substring, use the following:

>>> strs = imm.Search(u"bot_")

>>> for addr in strs:

>>> buf = imm.readWString(addr).replace("\x00", "")

>>> print buf

>>>

bot_httpinject_enable

bot_httpinject_disable

bot_bc_remove

bot_bc_add

bot_update

bot_uninstall

· To search for all occurrences of an assembly instruction (PUSH 20000013 in the example) in a given module and disassemble the instructions around it, use the following:

>>> cmds = imm.searchCommandsOnModule(0x400000, "PUSH 20000013")

>>> for cmd in cmds:

>>> len = 0

>>> for c in range(0,5):

>>> addr = cmd[0] + len

>>> op = imm.Disasm(addr)

>>> print "0x%08x\t%s" % (addr, op.getDisasm())

>>> len += op.getSize()

>>>

0x00406fa8 PUSH 20000013

0x00406fad PUSH EBX

0x00406fae MOV DWORD PTR SS:[EBP-8],4

0x00406fb5 MOV DWORD PTR SS:[EBP+8],ESI

0x00406fb8 CALL DWORD PTR DS:[401360]

As you can see, there is a CALL instruction shortly after the PUSH that you searched for. To find the name of the function being called, you use the following Python commands.

>>> p = imm.readLong(0x401360)

>>> func = imm.getFunction(p)

>>> print func.getName()

WININET.HttpQueryInfoA

Using PyCommand Plug-ins

PyCommands are re-usable scripts that contain the same code that you would type into the Python shell. There is a pre-existing directory full of examples (see C:\Program Files\Immunity Inc\Immunity Debugger\PyCommands). Table 11-5 shows a summary of some malware-related plug-ins:

Table 11-5: Immunity Debugger PyCommand Plug-ins and Their Uses

Plug-in

Description

bpxep.py

Sets breakpoints on the entry point and TLS call back functions (see Recipe 11-1).

finddatatype.py

Scans a block of memory looking for strings, Unicode strings, linked lists, pointers, and “exploitable” types.

searchcrypt.py

Searches a process’s memory space for known cryptography constants.

search.py and searchcode.py

Searches a process’s memory space for assembly instructions or sets of instructions.

getevent.py

Gets more information on the last event that occurred, such as the address of the last instruction executed, the type of exception that occurred, and so on.

hookssl.py

Hooks the schannel.dll functions that browsers use for encrypting SSL traffic and dumps the captured data.

packets.py

Hooks ws2_32.dll network functions and prints the size of incoming/outgoing packets along with a binary and ASCII dump.

nohooks.py

Clears all hooks.

hidedebug.py

Prevents malware from detecting the debugger.

Executing PyCommands

To execute PyCommands, type a ! in Immunity Debugger’s command box followed by the name of the command. For example, if you want to execute the nohooks.py plug-in, you type !nohooks <arguments>. If the plug-ins require arguments, they typically display the proper syntax in the debugger’s log window. To install your own plug-ins, just create a new file named YourCommand.py and place it in the PyCommands directory; launch it by typing !YourCommand.

Recipe 11-9: Detecting Shellcode in Binary Files

dvd1.eps

You can find support material for this recipe on the companion DVD.

One of the interesting, malware-related tasks that you can accomplish with Immunity’s Python API is detecting streams of shellcode in arbitrary binary files. Imagine you come across a potentially malicious image file, office document, or data from a packet capture. If you suspect there may be shellcode in the file, but have no idea where the shellcode starts or ends, you can leverage a PyCommand on the DVD named scd.py (shellcode detect).

How the Script Works

Here is a brief explanation of how scd.py works:

1. You supply a path to the suspect file when launching scd.py.

2. The script uses imm.openProcess to start an instance of notepad.exe. This is just a dummy process used as a container for loading the shellcode.

3. It reads in the suspect file’s contents, allocates memory in the dummy process with imm.remoteVirtualAlloc, and transfers the file’s contents to the allocated region with imm.writeMemory.

4. It uses imm.disasm to disassemble the file’s contents looking for CALL or JMP instructions. Because you’re working with an arbitrary binary file, there may be hundreds of false positives. However, only shellcode would contain a CALL or JMP to a legitimate location where multiple other valid instructions exist. Figure 11-22 shows a diagram of the decisions that the script makes to limit false positives.

Figure 11-22: Decision tree for detecting shellcode

f1122.eps

Based on the preceding algorithm, scd.py will print a list of possible addresses that contain shellcode into its own debugger window. Here is the code for scd.py:

import immlib

import getopt, string

import immutils

import os

def usage(imm):

imm.Log("Usage: !scd -f FILETOCHECK")

def checkop(op):

instr = op.getDisasm()

junk = ["IN", "OUT", "LES", "FSUBR", "DAA",

"BOUND", "???", "AAM", "STD", "FIDIVR",

"FCMOVNE", "FADD", "LAHF", "SAHF", "CMC",

"FILD", "WAIT", "RETF", "SBB", "ADC",

"IRETD", "LOCK", "POP SS", "POP DS", "HLT",

"LEAVE", "ARPL", "AAS", "LDS", "SALC",

"FTST", "FIST", "PADD", "CALL FAR", "FSTP",

"AAA", "FIADD"]

for j in junk:

if instr.startswith(j):

return False

if op.isCall() or op.isJmp():

if op.getJmpAddr() > 0x7FFFFFFF:

return False

return True

def main (args):

imm = immlib.Debugger()

scfile = None

conditional = False

try:

opts, argo = getopt.getopt(args, "f:")

except getopt.GetoptError:

usage(imm)

return

for o,a in opts:

if o == "-f":

try:

scfile = a

except ValueError, msg:

return "Invalid argument: %s" % a

if scfile == None or not os.path.isfile(scfile):

usage(imm)

return

# Get something going so the context is valid

imm.openProcess("c:\\windows\\system32\\notepad.exe")

# Read file contents

buf = open(scfile, "rb").read()

cb = len(buf)

# Copy the contents to process memory

mem = imm.remoteVirtualAlloc(cb)

imm.writeMemory(mem, buf)

# Clarify the start and end of the buffer

start = mem

end = mem + cb

table = imm.createTable('Shellcode Detect',\

['Ofs', 'Abs', 'Op', 'Op2', 'Op3'])

while start < end:

# Disassemble the instruction

d = imm.disasm(start)

c = d.getSize()

# Skip anything that isn't a jump/call

if (not d.isCall()) and (not d.isJmp()):

start += c

continue

# Get the destination address of the jump/call

dest = d.getJmpAddr()

# The destination must land within the shellcode

# buffer or else we've just located a false positive

if dest < start or dest > end:

start += c

continue

# Disassemble the first 3 ops at destination

op2 = imm.disasm(dest)

op3 = imm.disasm(dest+op2.getSize())

op4 = imm.disasm(dest+op2.getSize()+op3.getSize())

# Use a simple validity check to reduce fp's

if checkop(op2) and checkop(op3) and checkop(op4):

table.add('', ['0x%x' % (start - mem),\

'0x%x' % start,\

'%s' % d.getDisasm(),\

'%s' % op2.getDisasm(),\

'%s' % op3.getDisasm()])

start += c

return "done"

Using scd.py

To use the script, copy it from the book’s DVD to your PyCommands directory. Then execute the following statement in the debugger’s command box:

!scd -f c:\bad.ppt

In the example, we passed the path to a malicious 230KB PowerPoint document. Figure 11-23 shows how the output appears. It contains the following columns:

· Ofs: Offset within the suspect file where possible shellcode exists.

· Abs: Absolute address within the process memory where the possible shellcode exists (this is the base address of the allocated memory plus the Ofs value).

· Op: A CALL or JMP instruction identified by the shellcode scanner. Only CALL or JMP instructions that lead to a valid destination are shown. Valid destinations include those between the base address of the allocated memory and the base address plus the size of the suspect file.

· Op2: A disassembly of the first instruction found at the destination address.

· Op3: A disassembly of the second instruction found at the destination address.

Figure 11-23: Shellcode detect output columns

f1123.tif

To interpret the results, look at the disassembly shown in the Op2 and Op3 columns. If both instructions appear to be valid and they seem to make sense contextually, then it’s very possible you’ve found some shellcode. The context is extremely important here, for example, because two instructions such as INC EDI and DEC EDI are valid, but they really don’t make sense when executed sequentially. This would be the equivalent of someone typing i+=1;i-=1; into their source code.

Although the scd.py script takes care of eliminating a large number of false positives (it reduced 230KB worth of data down to 30 possible shellcode locations), you still need to differentiate between shellcode and junk instructions to sort through the rest. As shown in Figure 11-23, the ~10 lines starting at absolute address 0x170E5F and continuing to 0x172012 are interesting. They are all JMP or CALL instructions to a location that make sense contextually. You can right-click any of these lines and copy the absolute address (from the Abs column) into your clipboard. Then over in the CPU pane, use Ctrl+G and paste in the address to bring up a more thorough disassembly of the surrounding instructions. By right-clicking the 0x170E5F line, which is a JMP to 0x170E91, you end up at the location shown in Figure 11-24.

As you can see, this led us directly to the shellcode. It required a few moments of visual inspection, but compared to the time it would take to visually inspect 230KB worth of binary data looking for a small chunk of shellcode, it’s time well spent. You could create a standalone tool using any stream disassembler (such as DiStorm see Recipe 6-9), but the next step after locating shellcode in a binary file is to load it into a debugger for analysis. With scd.py, the shellcode is already loaded and you can immediately start debugging it (this is another great time to use the set new origin feature discussed in Recipe 11-5).

Figure 11-24: Inspection of assembly instructions shows valid shellcode

f1124.tif

Recipe 11-10: Investigating Silentbanker’s API Hooks

dvd1.eps

You can find supporting material for this recipe on the companion DVD.

A debugger has full control over a debugged process, including the ability to inspect the process’s entire virtual memory space. As you learned in Recipe 11-9, the debugger also has a built-it disassembler that you can use to build tools. This recipe introduces a PyCommand that detects malicious API hooks in your debugged process. The idea is to give you a simple way to go from detection to debugging to fully understanding the purpose of an API hook. The script for this recipe is included on the book’s DVD, named findhooks.py.

How findhooks.py Differs from Rootkit Scanners

Rootkit scanners such as GMER (see Recipe 10-6) can check for API hooks system-wide, including those in kernel mode. However, these rootkit scanners don’t help you determine the purpose of the hook. For example, you may find that the HttpSendRequestW function is hooked within a browser, but this is only half of the story. You still need to determine the reason why malware hooked HttpSendRequestW. Here are a few reasonable explanations:

· The malware wants to monitor visited URLs and search engine queries.

· The malware wants to steal credentials for any websites a user logs into.

· The malware wants to steal credentials only from a few banking websites based in the UK.

· The malware wants to monitor visited URLs and steal credentials.

You can determine the reason(s) a particular malware sample hooks an API function by performing static analysis on the binary (using IDA Pro). Another way is to attach to the browser with a debugger, set a breakpoint on the hooked API function(s), trigger the breakpoint(s) by using the browser, and then step through the rootkit code to figure out what it does. The findhooks.py script that we present in this recipe is convenient because you can detect and debug a hook all without leaving the debugger’s GUI. However, we do not intend for findhooks.py to replace robust rootkit scanners like GMER. It is really just a proof-of-concept script that provides assistance with debugging.

How the Script Works

Here is a description of how the findhooks.py script works:

· It enumerates all symbols in the debugged process with imm.getAllSymbols. This function returns a dictionary with the module names (e.g., kernel32.dll) as the keys and another dictionary as the values. This other dictionary stores symbol addresses as the keys and symbol names (e.g., CreateFileW) as the values.

· For each symbol in each module, the script does a lookup on the symbol name using imm.getAddress and makes sure it can be located in the process’s memory. After the lookup, you have the addresses for the exported symbols (otherwise known as API functions).

· It disassembles the first instruction in each API function using imm.disasm and checks if the instruction is a CALL or JMP (using op.isCall and op.isJmp, respectively). If so, it gets the destination address with op.getJmpAddr.

· It checks if the destination address of the CALL or JMP is within the containing module. If not, then the API function is hooked.

The following is the code for the findhooks.py script.

import immlib

def isExternalToModule(imm, addr, dest):

'''is an address within range of a DLL'''

mod = imm.getModulebyAddress(addr)

if (dest < mod.getBaseAddress()) or \

(dest > mod.getBaseAddress()+mod.getSize()):

return True

return False

def main(args):

imm = immlib.Debugger()

table = imm.createTable('Rootkit Locator',\

['Function', 'Address', 'Opcode'])

# this allows us to enumerate all exports from all

# DLLs loaded in the process. we could alternately

# walk the LDR_MODULE list and use pefile to parse

# the PE header and find all exports

sym = imm.getAllSymbols()

# for each loaded DLL

for modname in sym.keys():

modsym = sym[modname]

# for each symbol in the DLL

for modaddr in modsym.keys():

mod = modsym[modaddr]

string = modname.split(".")[0] + "." + mod.name

# this works like GetProcAddress. if it succeeds,

# then we've found a valid export symbol

addr = imm.getAddress(string)

if addr == -1:

continue

# disassemble the function's 1st instruction

op = imm.disasm(addr)

instr = op.getDisasm()

# check for the most typical types of inline hooks

if op.isJmp() or op.isCall():

dest = op.getJmpAddr()

if isExternalToModule(imm, addr, dest):

table.add('', ['%s' % string,\

'0x%x' % addr, '%s' % instr])

# check for hooks of type "push 0x????????; retn"

elif op.isPush():

nextop = imm.disasm(addr + op.getSize())

if nextop.isRet():

call_dest = imm.readLong(addr+op.getSize()+1)

if isExternalToModule(imm, addr, call_dest):

table.add('', ['%s' % string,\

'0x%x' % addr, '%s' % instr])

Using findhooks.py

To use this debugger plug-in, copy findhook.py from the book’s DVD into your PyCommands directory. Then type !findhooks into the debugger’s command box without any arguments. Figure 11-25 shows an example of the script’s output. In the example, our debugger is attached to an Internet Explorer process infected with a sample of the Silent Banker trojan. Here is a description of the fields in the output window:

· Function: The name of the hooked API function and containing module.

· Address: The address of the hooked API function in memory.

· Opcode: The disassembly of the first instruction in the hooked function (the one that leads outside of the containing module).

Figure 11-25: Locating Silent Banker’s API hooks

f1125.tif

As you can see, there are several hooks in the IE process, but not all of them are malicious. You can usually distinguish between malicious and non-malicious hooks by examining the function name and where it leads. For example, ws2_32.WSAGetLastError is hooked, but it points at the kernel32.GetLastError function. This is just an instance of export forwarding. On the other hand,advapi32.CryptGenKey is hooked, but it points to an address at 01C10000. In fact, many of the hooked functions point somewhere in the 01000000–02000000 range. The code running in that memory range does not have an associated module name. Without a doubt, that’s where you can find Silent Banker.

Debugging the API Hooks

Now that you know which APIs are hooked, you can set a breakpoint on the hooked APIs and begin using the debugged process to visit websites, transfer FTP files, and so on. Of course, don’t log into anything with your real credentials or better yet—do your testing in a lab environment with InetSim (see Recipe 7-3) so there’s no possibility of data exfiltration.

Figure 11-26 shows a disassembly of code in the ws2_32.send hook. We got here by simply setting a breakpoint on send and then accessing a web page in IE. As you can see, the hook inspects outgoing packets for USER, PASS, and other strings exposed in plaintext protocols such as HTTP and FTP. If the malware reads data from a file on disk to see if it should target certain institutions, you’ll likely see it all happening inside this hook function.

Figure 11-26: The rootkit scans traffic for user names and passwords

f1126.tif

Using the technique described in this recipe, you can quickly detect hooked API functions in debugged processes. You may run into false positives (such as legitimate export forwarding) and the example script only detects inline hooks. However, you can extend it to detect other types of hooks without too much effort.

WinAppDbg Python Debugger

WinAppDbg (http://winappdbg.sourceforge.net/) is a Python module by Mario Vilas that allows you to easily instrument and debug Windows applications using Python scripts. You can create your own fully functional debugger based on WinAppDbg in just a few lines of source code. This opens doors for many interesting capabilities that you can execute entirely from the command line. Here is a description of WinAppDbg from the tool’s website:

It uses ctypes to wrap many Win32 API calls related to debugging, and provides a powerful abstraction layer to manipulate threads, libraries, and processes. It allows you to attach your script as a debugger, trace execution, hook API calls, handle events in your debugee, and set breakpoints of different kinds (code, hardware, and memory). Additionally it has no native code at all, making it easier to maintain or modify than other debuggers on Windows.

The next few recipes show you some ways that you can leverage the existing tools that ship with WinAppDbg and how you can design your own tools using the framework. If you’re looking for alternatives or additional information about pure Python debuggers for the Windows platform, see one of the following sources:

· Pedram Amini’s pydbg (http://pedram.redhive.com/PyDbg/docs/)

· Pedram Amini’s PaiMei reverse engineering framework (http://pedram.redhive.com/PaiMei/docs/)

Recipe 11-11: Manipulating Process Memory with WinAppDbg Tools

As previously mentioned, WinAppDbg is more than just a debugging framework. Mario provides a number of useful command-line Python scripts that you can use to investigate and interact with malware during an analysis. Table 11-6 shows a summary of the “auxiliary” tools that Mario provides.

Table 11-6: Auxiliary Tools for WinAppDbg

Tool Name

Description

pinject.py

Injects a DLL into a process of your choice.

plist.py

Lists active processes and their PIDs.

pmap.py

Shows the memory map of a process, including page permissions and the full path on disk to any mapped files that exist in the memory ranges.

pread.py

Reads process memory and outputs it to stdout or a file of your choice.

pwrite.py

Writes process memory (input can be hex digits on command line or a binary file).

ptrace.py

Traces the execution of a process—it can output a disassembly of instructions, and dump registers and stack contents prior to executing system calls (e.g., calls into kernel mode).

pkill.py

Terminates one or more processes.

pdebug.py

Command-line debugger with WinDbg-like syntax.

pfind.py

Searches the memory space of any user mode process for strings, hex patterns, or regular expressions.

A theoretical scenario demonstrates how to use these tools. Imagine there is a trojan running on your analysis machine and it decodes a URL for its command and control server into memory. Every 60 seconds, it attempts to resolve the hostname specified in the URL into an IP address and then tries to connect to it. Your goal is to make the trojan connect to a different server by finding and altering the URL in the trojan’s memory—without using any GUI tools and without disturbing the state of the process. To do this, you can use the following steps:

1. List the active processes on your lab machine with plist.py:

C:\Scripts> python plist.py

Process enumerator

by Mario Vilas (mvilas at gmail.com)

PID Filename

0 [System Idle Process]

4 [System Integrity Group]

460 cmd.exe

508 svchost.exe

580 jqs.exe

588 smss.exe

620 sqlservr.exe

664 csrss.exe

688 winlogon.exe

[REMOVED]

1744 yuapp.exe <= this is your malware

2. Search the Trojan’s memory space for http:// using pfind.py. This script takes the malware’s PID, the string to find, and an optional –v flag, which prints a hexdump of the memory that matched your search.

C:\Scripts> python pfind.py 1744 –s http:// -v

Process memory finder

by Mario Vilas (mvilas at gmail.com)

Found string #1 at process 1744, address 011913B0 (7 bytes)

011913B0: 68 74 74 70 3a 2f 2f 74 http://t

011913B8: 73 6f 2e 76 61 69 6c 72 so.vailr

Found string #1 at process 1744, address 017E7310 (7 bytes)

017E7310: 68 74 74 70 3a 2f 2f 61 http://a

017E7318: 64 2e 64 6f 75 62 6c 65 d.double

Found string #1 at process 1744, address 017E73E8 (7 bytes)

017E73E0: 00 00 00 00 0d f0 ad 0b ........

017E73E8: 68 74 74 70 3a 2f 2f 77 http://w

[REMOVED]

3. Print the entire URL with pread.py and determine how much space you have for replacing characters. In the command that follows, you supply the malware’s PID, the address of the first result identified by pfind.py, and the size of memory to read (64 bytes). The output shows that the URL requires 30 characters, but there is apparently some unused space after it. Without analyzing the code deeper, you can’t tell if the unused space belongs to another variable, so it’s risky to overwrite them.

C:\Scripts>python pread.py 1744 011913B0 64

Process memory reader

by Mario Vilas (mvilas at gmail.com)

Read 64 bytes from PID 1744

011913B0: 687474703a2f2f74736f652e7661696c http://tsoe.vail

011913C0: 726f61642e636f6d2f782e7068700000 road.com/x.php..

011913D0: 00000000000000000000000000000000 ................

011913E0: 00000000000000000000000000000000 ................

4. Overwrite the URL in memory using pwrite.py. You can enter hex values on the command line that you want to copy to the process memory, or you can supply a file on disk that contains the data to copy. The command that follows overwrites the URL with test.com/a.php, which is 746573742e636f6d2f612e70687000 in hex. Notice that the command adds a trailing NULL byte and 7 to the write address (so you don’t overwrite the http:// prefix):

C:\Scripts>python pwrite.py 1744 011913B0+7 \

746573742e636f6d2f612e70687000

Process memory writer

by Mario Vilas (mvilas at gmail.com)

Written 64 bytes to PID 1744

C:\Scripts>python pread.py 1744 011913B0 64

Process memory reader

by Mario Vilas (mvilas at gmail.com)

Read 64 bytes from PID 1744

011913B0: 687474703a2f2f746573742e636f6d2f http://test.com/

011913C0: 612e706870006f6d2f782e7068700000 a.php.om/x.php..

011913D0: 00000000000000000000000000000000 ................

011913E0: 00000000000000000000000000000000 ................

That’s it! You might notice the om/x.php still remains because the replacement URL was shorter than the original one. However, the NULL byte prevents the om/x.php from actually becoming part of the URL the next time the trojan attempts to connect to the site.

Recipe 11-12: Designing a Python API Monitor with WinAppDbg

dvd1.eps

You can find supporting material for this recipe on the companion DVD.

This recipe shows you how to create an API monitor based on the WinAppDbg framework. The online documentation5 for WinAppDbg contains several examples of building applications, so this recipe just covers the basic skeleton script for an API monitor and then discusses ways that you can customize it for malware analysis. The basic idea is to write a Python script that provides a wrapper around the debugger class. You’ll essentially execute malware inside the debugger, but there’s no GUI and it’s not interactive. Anything you want to do in terms of setting breakpoints, logging parameters, and reading/writing memory while the malware executes is all implemented into your reusable script.

The code that follows shows the skeleton for an API monitor that hooks CreateFileW. Inside the MyEventHandler class, you can place the names of any other Windows API functions that you’re interested in analyzing. In addition to the function’s name, you need to provide the number of arguments the function takes (which you can get from MSDN or the Windows header files). Then, you need to add handler functions that execute either before or after the API function that you’re hooking. These handler functions must follow specific naming conventions. A handler function that executes upon entering CreateFileW (useful to log parameters) must be named pre_CreateFileW. A handler function that executes upon exiting CreateFileW (useful to log return values) must be named post_CreateFileW.

Note As far as we know, there’s no maximum number of functions you can hook with the same script, but the more functions you hook, the slower the debugged program will execute. We’ve hooked nearly 200 functions without any issues.

from winappdbg import Debug, EventHandler

import sys

import os

class MyEventHandler( EventHandler ):

# Add the APIs you want to hook

apiHooks = {

'kernel32.dll' : [

( 'CreateFileW' , 7 ),

],

}

# The pre_ functions are called upon entering the API

def pre_CreateFileW(self, event, ra, lpFileName, dwDesiredAccess,

dwShareMode, lpSecurityAttributes, dwCreationDisposition,

dwFlagsAndAttributes, hTemplateFile):

fname = event.get_process().peek_string(lpFileName, \

fUnicode=True)

print "CreateFileW: %s" % (fname)

# The post_ functions are called upon exiting the API

def post_CreateFileW(self, event, retval):

if retval:

print 'Succeeded, handle value: %x' % (retval)

else:

print 'Failed!'

if __name__ == "__main__":

if len(sys.argv) < 2 or not os.path.isfile(sys.argv[1]):

print "\nUsage: %s <File to monitor> [arg1 arg2 ...]\n" % \

sys.argv[0]

sys.exit()

# Instance a Debug object, passing it the MyEventHandler instance

debug = Debug( MyEventHandler() )

try:

# Start a new process for debugging

p = debug.execv(sys.argv[1:], bFollow=True)

# Wait for the debugged process to finish

debug.loop()

# Stop the debugger

finally:

debug.stop()

The __main__ function creates an instance of the Debug object and passes it your MyEventHandler. It uses the execv method to launch the process that the user specified on the command line. The bFollow=True flag causes WinAppDbg to begin monitoring any child processes. WinAppDbg automatically places breakpoints on any API functions identified in your MyEventHandler class. When those breakpoints are triggered, the framework calls your pre_ and post_ handlers. This all happens in very much the same way as the conditional log breakpoints discussed in Recipe 11-7, except you have much more control over the conditions and the logging due to Python’s flexibility.

To test out the script, you can call it on the command line and specify the full path to a process to execute. If the process accepts any parameters, you can place them after the full path. In the example, you’re executing notepad.exe and passing it the name of a file to edit. The skeleton prints output to STDOUT so you can immediately begin seeing any calls that it makes toCreateFileW.

C:\>python simpleapi.py c:\windows\system32\notepad.exe c:\host.txt

CreateFileW: C:\WINDOWS\WindowsShell.Manifest

Succeeded (handle value: 48)

CreateFileW: c:\host.txt

Succeeded (handle value: 78)

Using the pymon.py API Monitor

Now that you’ve seen the basics of creating an API monitor, let’s take it a bit further. On the book’s DVD, you’ll find a script for WinAppDbg named pymon.py. Here are some of the features that we’ve built into pymon that we think make it a very useful tool:

· It monitors about 200 Windows API functions across 10+ DLLs (this isn’t many compared to other API monitors out there—we choose only the functions most likely to be informative.

· It outputs HTML reports and automatically highlights suspicious API calls.

· If the malware tries to delete files via DeleteFile or MoveFileEx, the script makes copies of the file to be deleted and places them in your output directory.

· It “follows” newly created child processes (this is just based on the bFollow=True feature of WinAppDbg).

· It attempts to track handle usage so that it prints meaningful object names rather than just handle values (i.e., it prints a file name rather than a number like 0x44).

· The HTML report shows a hexdump-style preview of binary data passed to API functions. For example, it shows the first 128 (this amount is configurable) bytes of data being written to a file. This also applies to data read from a file, data written or read from the registry, data transferred over the network, and data decrypted or encrypted with cryptography functions.

The automatic highlighting of suspicious activity is pymon’s best feature, in our opinion. Pre-populating lists of criteria that you classify as suspicious and immediately focusing on those behaviors in the HTML report can save a ton of time when analyzing malware. In the code that follows, we show you a few possibilities to get your ideas flowing. The first list,alert_file_content_write, produces an alert each time the malware makes a call to WriteFile, and the buffer of data to write contains one of the listed strings. It detects attempts to drop executable files, batch scripts, and autorun scripts.

#---------------------------------------------------------------------

# alert_file_content_write: Highlight attempts

# to write particular patterns.

#---------------------------------------------------------------------

alert_file_content_write = [

'This program cannot be run in DOS mode', # PE header string

'This program must be run under Win32', # PE header string

'Scripting.FileSystemObject', # WScript self-delete

# scripts

'@echo off', # BAT scripts

'net stop', # BAT scripts

'reg add', # BAT scripts

'Windows Registry Editor', # REG scripts

'[Autorun]', # Autorun scripts

]

The alert_file_write list is checked when malware calls CreateFile with a dwDesiredAccess parameter that specifies write access. In these cases, if the lpFileName parameter matches any item in the list, pymon produces an alert. You can populate the list with full paths, partial paths, extensions, files, named pipes, drives, and so on. Why would you want to set an alert on an entire drive? Maybe you’ve got a USB drive mounted as F: and a network share mounted as E:. When you run malware, if it writes to a file on either drive, you’ll know it has spreading capabilities.

#-------------------------------------------------------------------

# alert_file_write: Highlight attempts to write

# to files/directories that match

#-------------------------------------------------------------------

alert_file_write = [

'C:\\windows\\system32\\', # Writes to system dir

'\\\\.\\PhysicalDrive0', # Writes to physical drive

'.dll', # DLLs in any directory

'.exe', # EXEs in any directory

'.sys', # SYSs in any directory

'.bat', # BATs in any directory

'.reg', # REGs in any directory

'\\\\.\\PIPE\\SfcApi', # Attempts to disable WFP

'Autorun.inf', # Writes to autorun

]

The alert_file_read list is checked whenever malware attempts to open files with read permissions. In these conditions, you’re normally looking to produce alerts on files or directories that store sensitive information (such as passwords or cookies that banking trojans try to read) or anti-debugging criteria.

#---------------------------------------------------------------------

# alert_file_read: Highlight attempts to read

# files/directories that match

#---------------------------------------------------------------------

alert_file_read = [

'#SharedObjects', # Flash cookies

'\\Application Data\\Macromedia\\Flash Player',

# Flash cookies

'C:\\RECYCLER', # Accessing deleted files

'\\\\.\\SIWVID', # Anti-Debugging stuff

'\\\\.\\REGSYS', # ...

'\\\\.\\REGVXG',

'\\\\.\\FILEVXG',

'\\\\.\\FILEM',

'\\\\.\\TRW',

'\\\\.\\SICE',

'\\\\.\\NTICE',

'\\\\.\\ICEEXT',

'wcx_ftp.ini', # Total Commander passwords

'Ipswitch\\WS_FTP', # WS FTP passwords

'FlashFXP', # FlashFXP passwords

'SmartFTP', # SmartFTP passwords

'TurboFTP', # TurboFTP passwords

'\\Application Data\\Opera\\', # Opera passwords

'Cookies', # Cookies

'.pfx', # Certificates

]

The alert_reg_write list is checked whenever malware calls a function such as RegSetValue. If the key being modified matches a key in your list, pymon produces an alert. This is where you’d identify automatic startup locations, keys related to DLL injection, firewall modifications, services, and so on.

#---------------------------------------------------------------------

# alert_reg_write: Highlight attempts to write

# to registry keys that match

#---------------------------------------------------------------------

alert_reg_write = [

'HKEY_CLASSES_ROOT',

'Microsoft\\Windows\\CurrentVersion\\Run',

'FirewallPolicy\\StandardProfile\\AuthorizedApplications\\List',

'Image File Execution Options',

'Microsoft\\Windows NT\\CurrentVersion\\Winlogon\\Notify',

'ShellIconOverlayIdentifiers',

'InprocServer32',

'Software\\Microsoft\\Windows NT\\CurrentVersion\\Drivers32',

]

The alert_reg_content_write is similar to alert_file_content_write, except it applies to content being written to any value of any key in the registry. You can end up generating false positive alerts by adding common strings such as “http” to this list, so be careful. We’ve started it out with a list of extensions for executable files. Under which conditions would malware need to add data to the registry that contains the “.exe” string? We can’t think of any legitimate reasons, so we alert on them all. This is useful because there are so many automatic start locations. By specifying file extensions in the reg_alert_content_write list, you have a very good chance of catching any attempts to auto-start, without preemptively knowing which keys malware will use.

#---------------------------------------------------------------------

# alert_reg_content_write: Highlight attempts to

# write strings/patterns to registry

#---------------------------------------------------------------------

alert_reg_content_write = [

'.dll',

'.sys',

'.exe',

]

The alert_loaded_dll list is checked when malware calls a function like LoadLibrary. Unlike kernel32.dll, which contains functions for a variety of purposes, libraries such as pstorec.dll are only used for one thing—reading or writing to the protected storage. Therefore, if malware ever loads pstorec.dll, you know it’s likely going to attempt credential theft. Likewise, with sfc_os.dll—this library enables or disables Windows File Protection. If a process in user mode loads ntoskrnl.exe (the kernel executive module), it’s most likely gathering information to install a kernel-level rootkit.

#---------------------------------------------------------------------

# alert_loaded_dll: Highlight attempts to load particular DLLs

#---------------------------------------------------------------------

alert_loaded_dll = [

'pstorec.dll', # Accessing protected storage

'sfc_os.dll', # Accessing WFP services

'ntoskrnl.exe', # Trying to resolve exports for SSDT hook

]

In addition, pymon is configured to alert on the following indicators of malicious activity:

· Attempts to change file timestamps to dates in the past.

· Attempts to call CreateFile on itself (this usually means the malware is fetching other binaries or configuration information from its own file).

· Attempts to start or stop Windows services.

· Attempts to read or write from any other process besides its own.

Figure 11-27 shows an example pymon report. The real HTML output shows more detail, but we had to cut it short to fit on the page. You can see the name of the API function and the primary object on which the malware is trying to perform an operation. If the API takes binary data, such as the case for WriteFile and RegSetValueExA, you’ll see a hexdump preview of the data. If any of your alerts were triggered, pymon highlights the corresponding lines in yellow. Otherwise, if the API call succeeded, you’ll see it in light gray, and if it failed, you’ll see it in dark gray.

Figure 11-27: Pymon highlights suspicious behaviors automatically

f1127.tif

Based on the output in Figure 11-27, you can tell the malware drops WinCtrl32.dll into the system32 directory. It registers the DLL as a Winlogon notification package so that winlogon.exe loads the DLL when it starts. Then the malware tries to open a file named Wincl175.sys, but that attempt fails (you can tell it failed by the ffffffff return code which is INVALID_HANDLE_VALUE). Next, you can see the malware uses CreateProcessA to launch cmd.exe, which succeeds because the report shows the new process ID, thread ID, and so on. The cmd.exe process is instructed to delete one of the malware’s temporary files.

As you can see, pymon can be extremely helpful in exposing malware behaviors. This is just one example of an application that you can build by extending the WinAppDbg framework. The disadvantage to using pymon is that the malware is actually run in a debugger. Therefore, anti-debugging tricks can hinder your analysis. However, if you pass the bHostile=True flag to execvwhen starting the debugged process, WinAppDbg makes a few changes to prevent simple debugger detection, but it’s certainly not a complete defense.

5 http://sourceforge.net/apps/trac/winappdbg/wiki/Debugging