Anti-debugging Techniques Part II – Process Walking
Welcome to the second installment of Anti-Debugging Techniques. This week we will be playing with the idea of using the Windows API to do process listing and comparison in an effort to frustrate debuggers. We will of course be including a binary and source for your testing pleasure.
Before we get too far along, I’d also like to mention that in Part I, there was a mistake that would have allowed exploitation with or without a debugger attached. This has been fixed and new code and a fresh binary have been uploaded.
The Windows API allows us to enumerate processes currently running by using a combination of the CreateToolhelp32Snapshot, Process32FirstW and Process32NextW functions. The first step is to create a handle to the process snapshot and a variable to store PROCESSENTRY32 structure information. A call is made to Process32FirstW to get the first entry in the list and populate the structure. The PROCESSENTRY32 structure contains several pieces of information including the registered process name. We continue to walk the snapshot by calling Process32NextW.
In my example I have chosen to look for ImmunityDebug and OllyDbg. Of course adding to this list is quite simple.
Precompiled binary of below [17.5kb] or source [2.5kb]
/* Description This is a small vulnerable program that is making use of process walking techniques to prevent debugging. The program takes in an argument, prints out the process walk, compares strings and then either exits or does a vulnerable call to strcpy(); Author Saint patrick [saintpatrick@l1pht.com] */ #define UNICODE #define _UNICODE #include #include #include // Needed for Process32First and Process32Next #include #include #include #include BOOL foundDebug(); // Here we have a simple buffer overflow to exploit. int main(int argc, char *argv[]) { // Static Buffer @ 150 char buffer[150]; if (foundDebug()) { printf("Found debugger"); exit(0); } else { // Print something and then do the devil's dance printf("You're a sexy beast %s",argv[1]); strcpy(buffer,argv[1]); } return 0; } // Quite a bit of this code was ripped from a post by anonytmouse. Big thanks to him. // I've made changes necessary to illustrate it's use as a anti-debug trick BOOL foundDebug() { HANDLE hProcessSnap = NULL; BOOL result = FALSE; PROCESSENTRY32 pe32 = {0}; long lngTimeout = 1000; // 1 second default timeout LPTSTR exeFile[2]; // Stick whatever debuggers you want detected here. // Theses are not case sensitive (_tcsicmp does lowercase comparison), so feel free to do quick add. exeFile[0] = TEXT("ImmunityDebugger.exe"); exeFile[1] = TEXT("OLLYDBG.EXE"); // Take a snapshot of all processes in the system. hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if (hProcessSnap == INVALID_HANDLE_VALUE) return 0; // Fill in the size of the structure before using it. pe32.dwSize = sizeof(PROCESSENTRY32); // Walk the snapshot of the processes, and for each process, // display information. if (Process32First(hProcessSnap, &pe32)) { do { // Just quickly displaying the pe32.szExeFile property to help visualize wprintf(L"We found %s\n",pe32.szExeFile); // Iterate over the list. Adjust for statement to match array length. int i = 0; for (i;i<=1;i++) { // Do the comparison if (_tcsicmp( pe32.szExeFile, exeFile[i]) == 0) { result = TRUE; } } } while (Process32Next(hProcessSnap, &pe32)); } // Close the snapshot CloseHandle (hProcessSnap); return result; } |
The Bypass:
When I had chosen this technique I did not know how canned the bypass would be. My first naive attempt was to rename the debugger. Neither ImmunityDebugger or OllyDbg like this much and complain loudly. Good, I would have been quite disappointed had such a thing been successful.
So, looking at the !hidedebug pycommand options again I noticed an option for patching ZwQuerySystemInformation in order to remove Immunity from the list of processes. I chose this option and restarted the process. This ended up working. So, it’s that easy, but why?
After taking a look at the actual script and comments I began to think that ZwQuerySystemInformation was being used by all three major steps in this technique. I used arwin.c to locate the ZwQuerySystemInformation function in ntdll. Then ran back and set a breakpoint on both the Process32FirstW and ZwQuerySystemInformation functions. I ran the program and hit the first breakpoint, however resuming did not stop at the ZwQuerySystemInformation function.
I discovered that the patch is actually occurring during the process snapshot. This means that of our functions only CreateToolhelp32Snapshot directly interacts with ZwQuerySystemInformation and that my breakpoint was set too far into the program execution causing the miss.
Cool right? So, we can easily bypass this technique as well by patching the snapshot before walking it. Another approach is one that is described in Justin Seitz’s book Gray Hat Python. In his approach he obtains the addresses of the Process32FirstW and Process32NextW calls and patches those.
The comparison of these methods illustrates the concept of patching functions at different levels in the normal code path flow.

