Blog

Counter-Punching Anti-Debugging Methods with WinAppDbg and Python - Part 1

~ by Britton Manahan

Introduction

Advanced dynamic malware analysis through debugging can unlock invaluable insights into an unknown binary. However, sophisticated malware authors aren’t going to simply allow their programs to hand over their secrets to a debugger. Many of the anti-debugging techniques that have been around for over a decade are often layered together in an attempt to thwart analysis. However, more advanced techniques also exist, and are continuously being developed. Simply by understanding how these mechanics take place, reverse engineers can both detect and neutralize them.

WinAppDbg is a Python module for building powerful and customized Windows OS debugging functionality. It was created by Mario Vilas, is currently on version 1.6, and has excellent documentation. In this blog post, I will examine different anti-debugging techniques and methods for countering those techniques using WinAppDbg and Python. Due to the ingenuity of malware authors, this is far from a complete list. This post will serve as the first in a series of blog posts demonstrating the powerful capabilities of WinAppDbg. The complete source code for all examples is available on the Crypsis Github and unless explicitly stated, all code examples are for 32-bit binaries.

List of Libraries and Tools

IsDebuggerPresent()

The most well-known and obvious anti-debugging technique uses the Windows API function IsDebuggerPresent(). This function will return a boolean value indicating if a debugger is attached to the calling process.  The API hooking functionality of WinAppDbg makes it a simple task to ensure that this function always returns a value of true. First, you need to fill out the apiHooks property of the EventHandler class with the required information on IsDebuggerPresent(), and then define a post-callback function for it. The defined post-callback function will execute right before the debuggee process returns from the API function call.

The apiHooks property

#EventHandler
class MyEventHandler( EventHandler ):

#Tell Winappdbg which functions we want to hook with apiHooks
apiHooks = {
#Broken up per dll
"kernel32.dll":[
("IsDebuggerPresent",0)
]
}

IsDebuggerPresent() post-callback function


#post-callback function for IsDebuggerPresent()
#called immediately before returning from the API call
def post_IsDebuggerPresent(self,event,retval):
print "IsDebuggerPresent() called!"
print "Returning False!"

#Get the thread that made the API call
thread = event.get_thread()

#Set the register containing the return value, EAX, to 0
thread.set_register("Eax",0x0)

With these lines of code inside a WinAppDbg Python script, anytime the process being debugged is about to return from a call to IsDebuggerPresent(), the EAX register will be set equal to 0. This is equivalent to setting the function to always equal false because the 32-bit version of the Windows API uses the stdcall calling convention, which places the return value in EAX.

Peb.BeingDebugged

On a more granular level, IsDebuggerPresent() checks if the BeingDebugged flag is set in the Process Environment Block (PEB). The PEB is a Windows OS process data structure containing process specific information. The BeingDebugged flag exists at a two-byte offset from the start of the PEB.

Rekall output for first three fields of the PEB

Leveraging the fact that the Thread Information Block (TIB), which contains a pointer to the PEB, is accessible from the FS segment register for 32-bit programs, and the GS register for 64-bit programs, malware can easily determine the address of the PEB. Malicious code can then bypass calling IsDebuggerPresent() and directly check the BeingDebugged flag itself by applying the two byte offset to this address.

x86 assembly code for checking BeingDebugged flag

This manual check can easily be negated with WinAppDbg by clearing the BeingDebugged flag at its source. When debugging a process, the debugger is automatically alerted and passed a DEBUG_EVENT struct for the following events:

  • CREATE_PROCESS_DEBUG_EVENT
  • CREATE_THREAD_DEBUG_EVENT
  • EXCEPTION_DEBUG_EVENT
  • EXIT_PROCESS_DEBUG_EVENT
  • EXIT_THREAD_DEBUG_EVENT
  • LOAD_DLL_DEBUG_EVENT
  • OUTPUT_DEBUG_STRING_EVENT
  • RIP_EVENT
  • UNLOAD_DLL_DEBUG_EVENT

By defining the process creation notification method for the WinAppDbg Eventhandler class, we can execute code immediately when the process is generated. Also, WinAppDbg has methods specifically for resolving the debuggee’s PEB address, reading process memory (peek), and writing to process memory (poke).

Process Creation Event Function

    #CREATE_PROCESS_DEBUG_EVENT handler
def create_process( self, event ):
print "Process created!"

#Get the process object
process = event.get_process()

#Get the main_module object
main_module = process.get_main_module()

#Display the virtual address base the exe was loaded at
print "Main Module Loaded at: %08x" % main_module.get_base()

#Get the address of the PEB
peb = process.get_peb_address()
#Check if the BeingDebugged Flag is set
#The \x is an escape sequence that tells Python to handle the
#next two characters as hexadecimal characters
if process.peek(peb+2,1) == '\x01':
print "BeingDebugged flag is True!"
print "Setting BeingDebugged flag to False"
#Attempt to clear the BeingDebugged Flag
try:
process.poke(peb+2,'\x00')
#Validate that the Flag has been cleared
if process.peek(peb+2,1) == '\x00':
print "BeingDebugged flag successfully cleared!"
except:
print "ERROR!: Failed to clear BeingDebugged flag"

Now when the WinAppDbg script creates a new process, it will clear the BeingDebugged flag in the PEB before any of its code is able to execute.

NtGlobalFlag and Heap Flags

Two additional values related to heap allocations, NtGlobalFlag and process heap flags, can be manually checked for the presence of a debugger. For processes created by a debugger, additional flags are set in the NtGlobalFlag value of the PEB, which normally has a value of zero. These additional flags that are set in NtGloblalFlag include:

  • FLG_HEAP_ENABLE_TAIL_CHECK (0x10)
  • FLG_HEAP_ENABLE_FREE_CHECK (0x20)
  • FLG_HEAP_VALIDATE_PARAMETERS (0x40)

These flags not only determine the behavior of heap allocation, but affect the flags set in the header of any allocated heaps (For more details on this behavior, see section 2.2 of this whitepaper). While these flags could also be manually cleared in memory, a simpler method involves creating a registry key with the processes name before it launches. This key goes under the following path with a blank REG_SZ value named “GlobalFlag”:

 HKLM\Software\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\

We can easily automate the creation of this registry key and value with the _winreg Python library.

GlobalFlag registry key/value creation


Regpath = "Software\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options\\"
Subkey = Regpath + os.path.basename(filepath)
hKey = _winreg.CreateKey( _winreg.HKEY_LOCAL_MACHINE, Subkey )
_winreg.SetValueEx(hKey, "GlobalFlag", 0, REG_SZ, "")

 

Before registry key/value creation

After registry key/value creation

The before and after views into the NtGlobalFlag and heap flag values show the registry key in effect for the “mystery_malware.exe” process. The values utilized by malware for debugger detection are no longer present once the registry key is created for the process, and its GlobalFlag value is set to zero.

Hostile Mode

It’s important to note that WinAppDbg does have a setting titled “hostile mode” that will automatically attempt several anti-anti-debugging techniques, including clearing the BeingDebugged flag. However, as stated by the WinAppDbg source code:

When hostile mode is enabled, some things may not work as expected! This is because the anti-anti debug tricks may disrupt the behavior of the Win32 debugging APIs or WinAppDbg itself.”

This is a perfect example of the importance of detail and precision in reverse engineering. When applicable, I recommend copying and pasting the code for many of the additional anti-anti-debugging methods offered by hostile mode into your WinAppDbg scripts. This insight and ultimate control over everything taking place allows you to easily comment out any individual techniques as needed. Some additional anti-anti-debugging techniques implemented by hostile mode include:

  • The parent process is changed to explorer.exe
  • Exceptions are passed back to the debuggee by default
  • Checks for the “Ice Breakpoint” trick
  • Checks for attempts to read or clear the trap flag
  • Invalid handles are explicitly passed back to the debuggee

EXCEPTION_INVALID_HANDLE

Another anti-debug trick to look for involves the “EXCEPTION_INVALID_HANDLE” exception. Under normal operation, when the CloseHandle() Windows API function is passed an invalid handle value the function will return false (zero). However, when a process is being debugged an “EXCEPTION_INVALID_HANDLE” exception is thrown. The clear difference in behavior makes this a possible method for detecting the presence of a debugger.

Rather than allowing the exception to be thrown and passed back to the debuggee, a better approach is to prevent the exception from happening in the first place. This can be achieved by combining the Sysinternals tool handle.exe with a pre-callback function on CloseHandle(). Unlike the earlier example with the post-callback function that was created for IsDebuggerPresent(), this pre-callback function will engage when the function is first entered.  While the post-callback functions in WinAppDbg automatically have the function’s return value passed to them, the function’s return address is passed to pre-callback functions. Also, unlike the previous API hooking example, the CloseHandle() function takes a parameter. The first step is to update the apiHooks property with the additional kernel32.dll function.

Updated apiHooks property


#EventHandler
class MyEventHandler( EventHandler ):

#Tell WinAppDbg which functions we want to hook with the apiHooks property
apiHooks = {
#Broken up per dll
"kernel32.dll":[
("IsDebuggerPresent",0),
#New kernel32.dll function added with a single parameter
("CloseHandle",1)

]
}

Next define the pre-callback function for CloseHandle()

CloseHandle() pre-callback function


#pre callback function for CloseHandle()
#the handle parameter is the actual parameter for CloseHandle
def pre_CloseHandle( self, event, return_address, handle ):
print "CloseHandle() called..."

# get process object
process = event.get_process()

#get thread object
thread = event.get_thread()

#get cpu value context for thread
registers = thread.get_context()

#get the current value of the Esp register
Esp = registers["Esp"]

#get the debuggee PID
pid = str(process.get_pid())

#prepare the cmdline for getting current handles of the debuggee
cmdline = "C:\\tools\\sysinternals\\handle.exe -a -p " + pid
hfound = False

try:
#execute handle.exe
handle_process = Popen(cmdline, stdout=PIPE)
#wait for it to complete
handle_process.wait()
#retrieve the output, seperated by new lines
handle_out =handle_process.communicate()[0].split("\r\n")

#loop through the output line by line, after the program banner
for x in range(5,len(handle_out)-1):
#check if the handle value equals the CloseHandle() parameter
if str(handle) == handle_out[x].partition(":")[0].lstrip():
hfound = True
break
except Exception as e:
print "Exception while attempting to run handle.exe - %s" % e

#handle is valid, do nothing
if hfound == True:
print "Handle found in Debuggee Handle Table"
#invalid handle
else:
print "Handle NOT found in the Debuggee Handle Table"
print "Program may be attempting to detect presence of debugger"
print "Skipping function call"

#Clean up the stack
thread.set_register("Esp", Esp + 8)
#Set the next instruction to be executed to the return address
thread.set_register("Eip", return_address)
#Set the return value to zero
thread.set_register("Eax", 0x0)

The code within this defined pre-callback function will execute handle.exe with a filter on the debuggee PID. It will then check the handle value portion of this output against the value passed to CloseHandle(). If the parameter handle value is found in the debuggee handle table, then execution will continue without any changes. However, if the handle value is not found, then the function call will be skipped with a simulated return value of false. This is accomplished by setting the value of three CPU registers: ESP, EIP, and EAX.

  • ESP is increased by 8 to “clean up” the stack and revert it to its pre-call state.
  • EIP is set to the function’s return address that is automatically passed as a parameter to the pre-callback function.
  • EAX is set to zero in order to create a return value of false.

To verify the correct number of bytes that need to be added to ESP, a pre\post-callback function combo can be used to get the difference in the stack pointer at the beginning and end of the function call. It is crucial to remember that the stack is configured as a top-down data structure in memory, and space is cleared on it by increasing its pointer value.

CloseHandle() pre\post combo callback functions


#pre callback function for CloseHandle()
#the handle parameter is the actual parameter for CloseHandle
def pre_CloseHandle( self, event, return_address, handle ):
print "CloseHandle() called..."

# get process object
process = event.get_process()

#get thread object
thread = event.get_thread()

#get cpu value context for thread
registers = thread.get_context()

#get the current value of the Esp register
Esp = registers["Esp"]

#print current value of ESP
print "pre-ESP: %08x" % Esp

#post callback function for CloseHandle()
def post_CloseHandle( self, event, return_address ):
# get process object
process = event.get_process()

#get thread object
thread = event.get_thread()

#get cpu value context for thread
registers = thread.get_context()

#get the current value of the Esp register
Esp = registers["Esp"]

#print current value of ESP
print "post-ESP: %08x" % Esp

Difference in ESP at the beginning and end of CloseHandle()

This output shows the difference of 8 bytes at the beginning and end of the function call.

Execution Timing

The next anti-debugging technique that will be examined in this blog post takes advantage of the fact that heavy examination conducted by a debugger, especially instruction single-stepping, significantly impacts the rate of execution for the debuggee. Malware can use a wide range of specialized instructions and API calls to time the rate of execution. The difference in time between point A and point B in the program is compared to a threshold to determine if instructions are being examined by a debugger. The options for timing include three assembly level instructions and several Windows API functions.

To detect the use of the assembly instructions RDPMC, RDTSC, and INT 2AH, we can utilize WinAppDbg’s ability to disassemble memory sections. WinAppDbg supports several different disassembly libraries, but distrom appears to be the most stable option. Combining this with the single-stepping capabilities of WinAppDbg makes it possible to examine each instruction. It may seem irrational that we are turning on single-stepping in order to detect anti-debugging logic that is more effective when enabled, however, one may want the capabilities that single-stepping provides for additional functionality. This includes additional anti-anti-debugging logic and other process introspection that will be examined in future blog posts.

To enable single-stepping for a process being debugged by WinAppDbg, we need to update the process creation event handler.

Updated process creation event function


#Process Creation Event
def create_process( self, event ):
print "Process created!"

#turn on instruction single-stepping
event.debug.start_tracing( event.get_tid() )

#Get the process object
process = event.get_process()

Since the start_tracing() method of the debug object is being called at process creation, every instruction of the process will be seen by the script. Single-stepping is achieved by WinAppDbg, and debuggers in general, through use of the trap flag. When the trap flag is set in the FLAGS register, an interrupt execution is triggered after each instruction. The single_step() method of the EventHandler class, if defined, will execute for each instruction while tracing is enabled.

Single-step exception handler function


#function for single step exception
def single_step( self, event ):

#get process object
process = event.get_process()

#get thread object
thread = event.get_thread()

#get the current cpu context
registers = thread.get_context()

#get the current value of EIP
Eip = registers["Eip"]

#print Eip

#attempt to dissable the instructions at EIP
try:
#dissasemble the instructions at EIP
disassem = thread.disassemble_instruction(Eip)[2]
# mnemonic = disassem.split()[0].strip()
# operand1 = disassem.split()[1].replace(",","").strip()
if disassem == "RDTSC":
#get handle to the main exe module
main_module = process.get_main_module()
#calculate the offset from the main module base to the instruction call
offset = main_module.get_base() - Eip
print "Timing instruction RDTSC detected at: EIP=%08x Main_Module_Offset=%08x" % (Eip,offset)
#check for RDPMC
if disassem == "RDPMC":
main_module = process.get_main_module()
offset = main_module.get_base() - Eip
print "Timing instruction RDPMC detected at: EIP=%08x Main_Module_Offset=%08x" % (Eip,offset)
#check for INT 2ah
if disassem == "INT 0x2a":
main_module = process.get_main_module()
offset = main_module.get_base() - Eip
print "Timing instruction INT 0x2a detected at: EIP=%08x Main_Module_Offset=%08x" % (Eip,offset)
except:
pass

By including the current value of EIP and the offset from the base of the main module in the output, anytime any special timing instructions are encountered a malware analyst can easily determine the next steps required to defeat the anti-debugging mechanism that may be in place. Unlike previous examples, for execution timing I don’t attempt to actually defeat the mechanism in an automated fashion because of the number of variables involved in it, including:

  • The number of instructions being timed
  • The type of instructions being timed
  • The time threshold, or potential time threshold range
  • Registers involved

However, if the malware you’re examining ends execution very quickly after generating output similar to this:

Then it is extremely likely that execution timing is taking place and you have a great head start on defeating it.

The second set of methods for achieving execution timing all utilize different Windows API calls. By hooking each one of them with both a pre and post-callback function we can achieve the same type of output. Using GetTickCount() as an example, the callback functions could all be similar to the following format:

Pre\post-callback function combo


#pre-callback function for GetTickCount()
def pre_GetTickCount( self, event, return_address):

#Timing API notification message
print "Entering Timing API Call:\n\tGetTickCount()\n\tRA=%08x" % return_address

#post-callback function for GetTickCount()
def post_GetTickCount( self, event, retval):

#Timing API notification message
print "Returning from Timing API Call\n\tGetTickCount()\n\tRV=%d" % retval

Which will generate the following output:

Conclusion

In this blog post the implementation of several anti-anti-debugging techniques were explored using WinAppdbg and Python. Checking the BeingDebugged flag, either with IsDebuggerPresent() or manually, the NtGlobalFlag, and the heap flags all rely on the value of a property to determine the presence of a debugger. The other techniques, Closehandle() exceptions and execution timing, utilize differences in behavior that occur as a part of debugging. While this is only a fraction of the functionally that could be built, these examples highlight the power of combining debugging with a scripting language. Debugging is a powerful and essential tool for malware reverse engineering, and on-demand scripting can unlock its true potential.

Sources

Ferrie, Peter. “The ‘Ultimate’ Anti-Debugging Reference.” Anti-Reverseing.com, 4 May 2011, www.anti-reversing.com/Downloads/Anti-Reversing/The_Ultimate_Anti-Reversing_Reference.pdf.

Kulchytskyy, Oleg. “Anti Debugging Protection Techniques With Examples.” Anti Debugging Protection Techniques With Examples, Apriorit, 5 Sept. 2016, www.apriorit.com/dev-blog/367-anti-reverse-engineering-protection-techniques-to-use-before-releasing-software.

Ligh, Michael Hale., et al. The Art of Memory Forensics: Detecting Malware and Threats in Windows, Linux, and Mac Memory. Wiley, 2014.

Sikorski, Michael, and Andrew Honig. Practical Malware Analysis: the Hands-on Guide to Dissecting Malicious Software. No Starch Press, 2012.

Vilas, Mario. “WinAppDbg - Programming Reference.” Sourceforge.net, 20 Dec. 2013, www.winappdbg.sourceforge.net/doc/v1.5/reference/.

Back to All Posts