Skip to content

Heap Flags

Warning

The current page still doesn't have a translation for this language.

You can read it through Google Translate.

Besides, you can also help to translate it: Contributing.

关于Heap flags

Heap flags包含有两个与NtGlobalFlag一起初始化的标志: FlagsForceFlags. 这两个字段的值不仅会受调试器的影响, 还会由windows版本而不同, 字段的位置也取决于windows的版本.

  • Flags字段:
    • 在32位Windows NT, Windows 2000和Windows XP中, Flags位于堆的0x0C偏移处. 在32位Windows Vista及更新的系统中, 它位于0x40偏移处.
    • 在64位Windows XP中, Flags字段位于堆的0x14偏移处, 而在64位Windows Vista及更新的系统中, 它则是位于0x70偏移处.
  • ForceFlags字段:
    • 在32位Windows NT, Windows 2000和Windows XP中, ForceFlags位于堆的0x10偏移处. 在32位Windows Vista及更新的系统中, 它位于0x44偏移处.
    • 在64位Windows XP中, ForceFlags字段位于堆的0x18偏移处, 而在64位Windows Vista及更新的系统中, 它则是位于0x74偏移处.

在所有版本的Windows中, Flags字段的值正常情况都设为HEAP_GROWABLE(2), 而ForceFlags字段正常情况都设为0. 然而对于一个32位进程(64位程序不会有此困扰), 这两个默认值, 都取决于它的宿主进程(host process)的subsystem版本(这里不是指所说的比如win10的linux子系统). 只有当subsystem3.51及更高的版本, 字段的默认值才如前所述. 如果是在3.10-3.50版本之间, 则两个字段的HEAP_CREATE_ALIGN_16 (0x10000)都会被设置. 如果版本低于3.10, 那么这个程序文件就根本不会被运行.

如果某操作将FlagsForgeFlags字段的值分别设为20, 但是却未对subsystem版本进行检查, 那么就可以表明该动作是为了隐藏调试器而进行的.

当调试器存在时, 在Windows NT, Windows 2000和32位Windows XP系统下, Flags字段会设置以下标志:

HEAP_GROWABLE (2)
HEAP_TAIL_CHECKING_ENABLED (0x20)
HEAP_FREE_CHECKING_ENABLED (0x40)
HEAP_SKIP_VALIDATION_CHECKS (0x10000000)
HEAP_VALIDATE_PARAMETERS_ENABLED (0x40000000)

在64位Windows XP系统, Windows Vista及更新的系统版本, Flags字段则会设置以下标志(少了HEAP_SKIP_VALIDATION_CHECKS (0x10000000)):

HEAP_GROWABLE (2)
HEAP_TAIL_CHECKING_ENABLED (0x20)
HEAP_FREE_CHECKING_ENABLED (0x40)
HEAP_VALIDATE_PARAMETERS_ENABLED (0x40000000)

而对于ForgeFlags字段, 正常情况则会设置以下标志:

HEAP_TAIL_CHECKING_ENABLED (0x20)
HEAP_FREE_CHECKING_ENABLED (0x40)
HEAP_VALIDATE_PARAMETERS_ENABLED (0x40000000)

因为NtGlobalFlag标志的关系, heap也会设置一些标志位

  • 如果在NtGlobalFlag字段中有设置FLG_HEAP_ENABLE_TAIL_CHECK标志, 那么在heap字段中就会设置HEAP_TAIL_CHECKING_ENABLED标志.
  • 如果在NtGlobalFlag字段中有设置FLG_HEAP_ENABLE_FREE_CHECK标志, 那么在heap字段中就会设置FLG_HEAP_ENABLE_FREE_CHECK标志.
  • 如果在NtGlobalFlag字段中有设置FLG_HEAP_VALIDATE_PARAMETERS标志, 那么在heap字段中就会设置HEAP_VALIDATE_PARAMETERS_ENABLED标志(在Windows NTWindows 2000中还会设置HEAP_CREATE_ALIGN_16 (0x10000)标志).

heap flags同样也如上节的NtGlobalFlag那样, 不过它受到注册表HKLM\Software\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\<filename>位置的PageHeapFlags"键的控制.

获取heap位置

有多种方法能获知heap的位置, 方法之一就是kernel32GetProcessHeap()函数, 当然也可以用以下的32位汇编代码来检测32位环境(实际上就有一些壳避免使用该api函数, 直接查询PEB):

mov eax, fs:[30h] ;Process Environment Block
mov eax, [eax+18h] ;get process heap base

或使用以下64位代码来检测64位环境

push 60h
pop rsi
gs:lodsq ;Process Environment Block
mov eax, [rax+30h] ;get process heap base

或使用以下32位代码检测64位环境

mov eax, fs:[30h] ;Process Environment Block
;64-bit Process Environment Block
;follows 32-bit Process Environment Block
mov eax, [eax+1030h] ;get process heap base

另外一种方法则是使用kernel32GetProcessHeaps()函数, 其实它只是简单的转给了ntdllRtlGetProcessHeaps()函数, 这个函数会返回属于当前进程的堆的数组, 而数组的第一个堆, 就跟kernel32GetProcessHeap()函数所返回的是一样的.

这个过程可以用32位代码检测32位windows环境来实现:

push 30h
pop esi
fs:lodsd ;Process Environment Block
;get process heaps list base
mov esi, [esi+eax+5ch]
lodsd

同上, 用64位代码检测64位windows环境的代码是:

push 60h
pop rsi
gs:lodsq ;Process Environment Block
;get process heaps list base
mov esi, [rsi*2+rax+20h]
lodsd

或使用32位代码检测64位window环境:

mov eax, fs:[30h] ;Process Environment Block
;64-bit Process Environment Block
;follows 32-bit Process Environment Block
mov esi, [eax+10f0h] ;get process heaps list base
lodsd

检测Flags字段

那么显然, 检测调试器我们就可以从检测那几个FlagsForgeFlags的标志位入手.

先看Flags字段的检测代码, 用32位代码检测32位windows环境, 且subsystem版本在3.10-3.50之间:

call GetVersion
cmp al, 6
cmc
sbb ebx, ebx
and ebx, 34h
mov eax, fs:[30h] ;Process Environment Block
mov eax, [eax+18h] ;get process heap base
mov eax, [eax+ebx+0ch] ;Flags
;neither HEAP_CREATE_ALIGN_16
;nor HEAP_SKIP_VALIDATION_CHECKS
and eax, 0effeffffh
;HEAP_GROWABLE
;+ HEAP_TAIL_CHECKING_ENABLED
;+ HEAP_FREE_CHECKING_ENABLED
;+ HEAP_VALIDATE_PARAMETERS_ENABLED
cmp eax, 40000062h
je being_debugged

32位代码检测32位windows环境, 且subsystem3.51及更高版本:

call GetVersion
cmp al, 6
cmc
sbb ebx, ebx
and ebx, 34h
mov eax, fs:[30h] ;Process Environment Block
mov eax, [eax+18h] ;get process heap base
mov eax, [eax+ebx+0ch] ;Flags
;not HEAP_SKIP_VALIDATION_CHECKS
bswap eax
and al, 0efh
;HEAP_GROWABLE
;+ HEAP_TAIL_CHECKING_ENABLED
;+ HEAP_FREE_CHECKING_ENABLED
;+ HEAP_VALIDATE_PARAMETERS_ENABLED
;reversed by bswap
cmp eax, 62000040h
je being_debugged

64位代码检测64位windows环境(64位进程不必受subsystem版本困扰):

push 60h
pop rsi
gs:lodsq ;Process Environment Block
mov ebx, [rax+30h] ;get process heap base
call GetVersion
cmp al, 6
sbb rax, rax
and al, 0a4h
;HEAP_GROWABLE
;+ HEAP_TAIL_CHECKING_ENABLED
;+ HEAP_FREE_CHECKING_ENABLED
;+ HEAP_VALIDATE_PARAMETERS_ENABLED
cmp d [rbx+rax+70h], 40000062h ;Flags
je being_debugged

用32位代码检测64位windows环境:

push 30h
pop eax
mov ebx, fs:[eax] ;Process Environment Block
;64-bit Process Environment Block
;follows 32-bit Process Environment Block
mov ah, 10h
mov ebx, [ebx+eax] ;get process heap base
call GetVersion
cmp al, 6
sbb eax, eax
and al, 0a4h
;Flags
;HEAP_GROWABLE
;+ HEAP_TAIL_CHECKING_ENABLED
;+ HEAP_FREE_CHECKING_ENABLED
;+ HEAP_VALIDATE_PARAMETERS_ENABLED
cmp [ebx+eax+70h], 40000062h
je being_debugged

如果是直接通过KUSER_SHARED_DATA结构的NtMajorVersion字段(位于2G用户空间的0x7ffe026c偏移处)获取该值(在所有32位/64位版本的Windows都可以获取该值), 可以进一步混淆kernel32GetVersion()函数调用操作.

检测ForgeFlags字段

当然另一个方法就是检测ForgeFlags字段, 以下是32位代码检测32位Windows环境, subsystem版本在3.10-3.50之间:

call GetVersion
cmp al, 6
cmc
sbb ebx, ebx
and ebx, 34h
mov eax, fs:[30h] ;Process Environment Block
mov eax, [eax+18h] ;get process heap base
mov eax, [eax+ebx+10h] ;ForceFlags
;not HEAP_CREATE_ALIGN_16
btr eax, 10h
;HEAP_TAIL_CHECKING_ENABLED
;+ HEAP_FREE_CHECKING_ENABLED
;+ HEAP_VALIDATE_PARAMETERS_ENABLED
cmp eax, 40000060h
je being_debugged

32位代码检测32位windows环境, 且subsystem3.51及更高版本:

call GetVersion
cmp al, 6
cmc
sbb ebx, ebx
and ebx, 34h
mov eax, fs:[30h] ;Process Environment Block
mov eax, [eax+18h] ;get process heap base
;ForceFlags
;HEAP_TAIL_CHECKING_ENABLED
;+ HEAP_FREE_CHECKING_ENABLED
;+ HEAP_VALIDATE_PARAMETERS_ENABLED
cmp [eax+ebx+10h], 40000060h
je being_debugged

64位代码检测64位windows环境(64位进程不必受subsystem版本困扰):

push 60h
pop rsi
gs:lodsq ;Process Environment Block
mov ebx, [rax+30h] ;get process heap base
call GetVersion
cmp al, 6
sbb rax, rax
and al, 0a4h
;ForceFlags
;HEAP_TAIL_CHECKING_ENABLED
;+ HEAP_FREE_CHECKING_ENABLED
;+ HEAP_VALIDATE_PARAMETERS_ENABLED
cmp d [rbx+rax+74h], 40000060h
je being_debugged
用32位代码检测64位windows环境:

call GetVersion
cmp al, 6
push 30h
pop eax
mov ebx, fs:[eax] ;Process Environment Block
;64-bit Process Environment Block
;follows 32-bit Process Environment Block
mov ah, 10h
mov ebx, [ebx+eax] ;get process heap base
sbb eax, eax
and al, 0a4h
;ForceFlags
;HEAP_TAIL_CHECKING_ENABLED
;+ HEAP_FREE_CHECKING_ENABLED
;+ HEAP_VALIDATE_PARAMETERS_ENABLED
cmp [ebx+eax+74h], 40000060h
je being_debugged

参考链接