Special flags in system tables, which dwell in process memory and which an operation system sets, can be used to indicate that the process is being debugged. The states of these flags can be verified either by using specific API functions or examining the system tables in memory.
These techniques are the most commonly used by malware.
The function kernel32!IsDebuggerPresent() determines whether the current process is being debugged by a user-mode debugger such as OllyDbg or x64dbg. Generally, the function only checks the BeingDebugged flag of the Process Environment Block (PEB).
The following code can be used to terminate process if it is being debugged:
The function ntdll!NtQueryInformationProcess() can retrieve a different kind of information from a process. It accepts a ProcessInformationClass parameter which specifies the information you want to get and defines the output type of the ProcessInformation parameter.
It is possible to retrieve the port number of the debugger for the process using the ntdll!NtQueryInformationProcess(). There is a documented class ProcessDebugPort, which retrieves a DWORD value equal to 0xFFFFFFFF (decimal -1) if the process is being debugged.
A kernel structure called EPROCESS, which represents a process object, contains the field NoDebugInherit. The inverse value of this field can be retrieved using an undocumented class ProcessDebugFlags (0x1f). Therefore, if the return value is 0, a debugger is present.
The ntdll!NtQuerySystemInformation() function accepts a parameter which is the class of information to query. Most of the classes are not documented. This includes the SystemKernelDebuggerInformation (0x23) class, which has existed since Windows NT. The SystemKernelDebuggerInformation class returns the value of two flags: KdDebuggerEnabled in al, and KdDebuggerNotPresent in ah. Therefore, the return value in ah is zero if a kernel debugger is present.
For IsDebuggerPresent(): Set the BeingDebugged flag of the Process Environment Block (PEB) to 0. See BeingDebugged Flag Mitigation for further information.
For CheckRemoteDebuggerPresent() and NtQueryInformationProcess(): As CheckRemoteDebuggerPresent() calls NtQueryInformationProcess(), the only way is to hook the NtQueryInformationProcess() and set the following values in return buffers:
0 (or any value except -1) in case of a ProcessDebugPort query.
Non-zero value in case of a ProcessDebugFlags query.
0 in case of a ProcessDebugObjectHandle query.
The only way to mitigate these checks with RtlQueryProcessHeapInformation(), RtlQueryProcessDebugInformation() and NtQuerySystemInformation() functions is to hook them and modify the returned values:
RTL_PROCESS_HEAPS::HeapInformation::Heaps::Flags to HEAP_GROWABLE for RtlQueryProcessHeapInformation() and RtlQueryProcessDebugInformation().
SYSTEM_KERNEL_DEBUGGER_INFORMATION::DebuggerEnabled to 0 and SYSTEM_KERNEL_DEBUGGER_INFORMATION::DebuggerNotPresent to 1 for the NtQuerySystemInformation() function in case of a SystemKernelDebuggerInformation query.
The NtGlobalFlag field of the Process Environment Block (0x68 offset on 32-Bit and 0xBC on 64-bit Windows) is 0 by default. Attaching a debugger doesn’t change the value of NtGlobalFlag. However, if the process was created by a debugger, the following flags will be set:
The presence of a debugger can be detected by checking a combination of those flags.
This technique was originally described as an issue for TitanHide, a kernel driver to hide debuggers from detection. The detailed documentation for the structure KUSER_SHARED_DATA and its fields is available here.
Here is what the author of the issue wrote in the post regarding the features of the structure and its appropriate field:
“0x7ffe02d4 is actually 0x7ffe0000 + 0x2d4. 0x7ffe0000 is the fixed user mode address of the KUSER_SHARED_DATA structure that contains data that is shared between user mode and the kernel (though user mode doesn’t have write access to it). The struct has some interesting properties:
its address is fixed and has been in all Windows versions since it was introduced
its user mode address is the same in 32 bit and 64 bit mode
all offsets and sizes are strictly fixed, and new fields are only ever appended or added in place of unused padding space
Hence this program will work in 32 bit Windows 2000 and 64 bit Windows 10 without recompiling”.
Set the BeingDebugged flag to 0. This can be done by DLL injection. If you use OllyDbg or x32/64dbg as a debugger, you can choose various Anti-Debug plugins such as ScyllaHide.
Set the NtGlobalFlag to 0. This can be done by DLL injection. If you use OllyDbg or x32/64dbg as a debugger, you can choose various Anti-Debug plugins such as ScyllaHide.
For Heap Flags:
Set the Flags value to HEAP_GROWABLE, and the ForceFlags value to 0. This can be done by DLL injection. If you use OllyDbg or x32/64dbg as a debugger, you can choose various Anti-Debug plugins such as ScyllaHide.
For Heap Protection:
Manually patch 12 bytes for 32-bit and 20 bytes in a 64-bit environment after the heap. Hook kernel32!HeapAlloc() and patch the heap after its allocation.
For a possible mitigation, please check the link when the technique is described (with the issue for TitanHide) and also a draft code for patching kdcom.dllhere.