NtGlobalFlag
Malware reads the NtGlobalFlag field of the PEB (offset 0x68/0xBC) to detect if the process was launched under a debugger via the 0x70 heap flag combination.
When a process is created by a debugger, the Windows loader sets three heap-validation flags in the NtGlobalFlag field of the Process Environment Block (PEB):
| Flag | Value |
|---|---|
FLG_HEAP_ENABLE_TAIL_CHECK | 0x10 |
FLG_HEAP_ENABLE_FREE_CHECK | 0x20 |
FLG_HEAP_VALIDATE_PARAMETERS | 0x40 |
The combined value of 0x70 is therefore a reliable debugger indicator. The field sits at PEB offset 0x68 on 32-bit Windows and 0xBC on 64-bit Windows. It is not modified when a debugger attaches to an already-running process, so this check specifically targets processes spawned inside a debugger.
How it works
#include <windows.h>
#include <winternl.h>
BOOL NtGlobalFlagDebugged(void)
{
#ifdef _WIN64
PPEB pPeb = (PPEB)__readgsqword(0x60);
DWORD ntgf = *(DWORD *)((BYTE *)pPeb + 0xBC);
#else
PPEB pPeb = (PPEB)__readfsdword(0x30);
DWORD ntgf = *(DWORD *)((BYTE *)pPeb + 0x68);
#endif
return (ntgf & 0x70) != 0;
}Alternatively, malware uses NtQueryInformationProcess to retrieve the PEB pointer indirectly before reading the field, hiding the API name behind dynamic resolution.
In assembly (x86):
mov eax, fs:[30h] ; PEB pointer
mov eax, [eax + 68h] ; NtGlobalFlag
and eax, 70h
jnz debugger_detectedDetection & bypass
During debugging:
- Use a debugger plugin (ScyllaHide, TitanHide) that patches
NtGlobalFlagto0before the check executes. - Manually zero the field: in x64dbg open the dump at
gs:[60h]+0xBCand write00 00 00 00. - If the process must start under the debugger, patch the check with a
NOPsled or force the branch to the non-detected path.
Static / automated detection:
- YARA: search for the string
"NtGlobalFlag"or the byte sequence68h/BChcombined with PEB access patterns. - CAPA rule U0111 flags reads at
peb+0x68/peb+0xBCfollowed by a bitwise AND against0x70. - Frida/dynamic instrumentation: hook reads of those PEB offsets and return
0.