Anti-Debug: Object Handles
Contents
Object Handles
The following set of techniques represents the checks which use kernel objects handles to detect a debugger presence. Some WinAPI functions that accept kernel object handles as their parameters can behave differently under debugging or cause side-effects that emerge because of debuggers’ implementation. Moreover, there are specific kernel objecst that are created by the operation system when debugging begins.
1. OpenProcess()
Some debuggers can be detected by using the kernel32!OpenProcess() function on the csrss.exe process. The call will succeed only if the user for the process is a member of the administrators group and has debug privileges.
C/C++ Code
2. CreateFile()
When the CREATE_PROCESS_DEBUG_EVENT event occurs, the handle of the debugged file is stored in the CREATE_PROCESS_DEBUG_INFO structure. Therefore, debuggers can read the debug information from this file. If this handle is not closed by the debugger, the file won’t be opened with exclusive access. Some debuggers can forget to close the handle.
This trick uses kernel32!CreateFileW() (or kernel32!CreateFileA()) to exclusively open the file of the current process. If the call fails, we can consider that the current process is being run in the presence of a debugger.
C/C++ Code
3. CloseHandle()
If a process is running under a debugger and an invalid handle is passed to the ntdll!NtClose() or kernel32!CloseHandle() function, then the EXCEPTION_INVALID_HANDLE (0xC0000008) exception will be raised. The exception can be cached by an exception handler. If the control is passed to the exception handler, it indicates that a debugger is present.
C/C++ Code
4. LoadLibrary()
When a file is loaded to process memory using the kernel32!LoadLibraryW() (or kernel32!LoadLibraryA()) function, the LOAD_DLL_DEBUG_EVENT event occurs. The handle of the loaded file will be stored in the LOAD_DLL_DEBUG_INFO structure. Therefore, debuggers can read the debug information from this file. If this handle is not closed by the debugger, the file won’t be opened with exclusive access. Some debuggers can forget to close the handle.
To check for the debugger presence, we can load any file using kernel32!LoadLibraryA() and try to exclusively open it using kernel32!CreateFileA(). If the kernel32!CreateFileA() call fails, it indicates that the debugger is present.
C/C++ Code
5. NtQueryObject()
When a debugging session begins, a kernel object called “debug object” is created, and a handle is associated with it. Using the ntdll!NtQueryObject() function, it is possible to query for the list of existing objects, and check the number of handles associated with any debug object that exists.
However this technique can’t say for sure if the current process is being debugged right now. It only shows if the debugger is running on the system at all since the system’s boot.
C/C++ Code
Mitigations
The simplest way to mitigate these checks is to just manually trace the program till a check and then skip it (e.g. patch with NOPs or change the instruction pointer or change the Zero Flag after the check).
If you write an anti-anti-debug solution, you need to hook the listed functions and change return values after analyzing their input:
- ntdll!OpenProcess: Return NULL if the third argument is the handle of csrss.exe.
- ntdll!NtClose: You can check if it is possible to retrieve any information about the input handle using ntdll!NtQueryObject() and not throw an exception if the handle is invalid.
- ntdll!NtQueryObject: Filter debug objects from the results if the ObjectAllTypesInformation class is queried.
The following techniques should be handled without hooks:
- ntdll!NtCreateFile: Too generic to mitigate. However, if you write a plugin for a specific debugger, you can ensure that the handle of the debugged file is closed.
- kernel32!LoadLibraryW/A: No mitigation.