基础方案和原理
内含 demo,编译环境:gcc (x86_64-posix-seh-rev0, Built by MinGW-W64 project) 8.1.0
1. IsDebuggerPresent
代码示例:
#include<stdio.h>
#include<windows.h>
#include<iostream>
using namespace std;
int main()
{
if (IsDebuggerPresent())
{
cout << "Stop debugging program!" << std::endl;
exit(-1);
}
return 0;
}
通过调试器,查看 IsDebuggerPresent源码可以看到:
0:000> u kernelBase!IsDebuggerPresent L3
KERNELBASE!IsDebuggerPresent:
00007ffb`ebf208d0 65488b042560000000 mov rax,qword ptr gs:[60h]
00007ffb`ebf208d9 0fb64002 movzx eax,byte ptr [rax+2]
00007ffb`ebf208dd c3 ret
32为:
0:000< u kernelbase!IsDebuggerPresent L3
KERNELBASE!IsDebuggerPresent:
751ca8d0 64a130000000 mov eax,dword ptr fs:[00000030h]
751ca8d6 0fb64002 movzx eax,byte ptr [eax+2]
751ca8da c3 ret
详细 PEB TEB结构可以参考附件内容。
原理
+0x060 ProcessEnvironmentBlock : Ptr64 _PEB gs 指向程序的_PEB结构,其中 _PEB 偏移 0x2 位置是: +0x002 BeingDebugged : UChar
所以该函数原理就是检查 PEB 下的 BeingDebugged 标志位。被调试是1 , 否则0。
PEB
你可以在代码中通过下面获取进程控制块地址:
PVOID GetPEB()
{
#ifdef _WIN64
return (PVOID)__readgsqword(0x0C * sizeof(PVOID)); # PVOID 大小由编译器系统环境决定 4 or 8 bytes
#else
return (PVOID)__readfsdword(0x0C * sizeof(PVOID));
#endif
}
如果程序是32位的,但是运行在64位系统上,遇到 WOW64 “天堂门”技术,可以通过下面代码,获取到单独创建的PEB结构: 你可以参考Get 32bit PEB of another process from a x64 process 同样的需求: 64位进程需要获取运行在WOW64中32位程序的PEB环境。
// WOW64
PVOID GetPEB64()
{
PVOID pPeb = 0;
#ifndef _WIN64
if (IsWin8OrHigher()) # S
{
BOOL isWow64 = FALSE;
typedef BOOL(WINAPI *pfnIsWow64Process)(HANDLE hProcess, PBOOL isWow64);
pfnIsWow64Process fnIsWow64Process = (pfnIsWow64Process)
GetProcAddress(GetModuleHandleA("Kernel32.dll"), "IsWow64Process");
if (fnIsWow64Process(GetCurrentProcess(), &isWow64))
{
if (isWow64)
{
pPeb = (PVOID)__readfsdword(0x0C * sizeof(PVOID));
pPeb = (PVOID)((PBYTE)pPeb + 0x1000);
}
}
}
#endif
return pPeb;
}
BYPASS
直接给标志位置0:
mov eax, dword ptr fs:[0x30]
mov byte ptr ds:[eax+2]
// x64
DWORD64 dwpeb = __readgsqword(0x60);
*((PBYTE)(dwpeb + 2)) = 0;
注意检查 TLS 回调
TLS在程序运行前以及运行了,记得检查是否由TLS回调函数,隐藏了反调试如:
#pragma section(".CRT$XLY", long, read)
__declspec(thread) int var = 0xDEADBEEF;
VOID NTAnopPI TlsCallback(PVOID DllHandle, DWORD Reason, VOID Reserved)
[backcolor=var(--color-canvas-subtle)]{
var = 0xB15BADB0;
if (IsDebuggerPresent())
{
MessageBoxA(NULL, "Stop debugging program!", "Error", MB_OK | MB_ICONERROR);
TerminateProcess(GetCurrentProcess(), 0xBABEFACE);
}
}
__declspec(allocate(".CRT$XLY"))PIMAGE_TLS_CALLBACK g_tlsCallback = TlsCallback;
2.NtGlobalFlag
在 Windows NT 中,有一组标志存储在全局变量 NtGlobalFlag 中,这在整个系统中是通用的。在启动时,NtGlobalFlag 全局系统变量将使用系统注册表项中的值进行初始化: [HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\GlobalFlag]
要检查进程是否已使用调试器启动,请检查 PEB 结构中 NtGlobal 标志字段的值。此字段分别位于 x32 和 x64 系统的PEB 0x068和0x0bc偏移量。
0:000> dt _PEB NtGlobalFlag @$peb
ntdll!_PEB
+0x068 NtGlobalFlag : 0x70
// x64
0:000> dt _PEB NtGlobalFlag @$peb
ntdll!_PEB
+0x0bc NtGlobalFlag : 0x70
NtGlobalFlag 和 IMAGE_LOAD_CONFIG_DIRECTORY
可执行文件可以包含IMAGE_LOAD_CONFIG_DIRECTORY结构,该结构包含系统加载程序的其他配置参数。默认情况下,此结构不会内置于可执行文件中,但可以使用修补程序添加它。此结构具有GlobalFlagsClear ,该字段指示应重置 PEB 结构的 NtGlobal 标志字段的哪些标志。如果可执行文件最初是在没有上述结构的情况下创建的,或者 GlobalFlagsClear = 0,则在磁盘或内存中,该字段将具有非零值,表示存在隐藏的调试器。下面的代码示例检查正在运行的进程内存和磁盘上的全局标记清除字段,从而说明一种流行的反调试技术:
代码片段
3. 堆标志和 ForceFlags
在PEB中包含两个特殊标志位结构:
0:000> dt _PEB ProcessHeap @$peb
ntdll!_PEB
+0x018 ProcessHeap : 0x00440000 Void
0:000> dt _HEAP Flags ForceFlags 00440000
ntdll!_HEAP
+0x040 Flags : 0x40000062
+0x044 ForceFlags : 0x40000060
x64:
0:000> dt _PEB ProcessHeap @$peb
ntdll!_PEB
+0x030 ProcessHeap : 0x0000009d`94b60000 Void
0:000> dt _HEAP Flags ForceFlags 0000009d`94b60000
ntdll!_HEAP
+0x070 Flags : 0x40000062
+0x074 ForceFlags : 0x40000060
判断条件:
如果 堆标志字段未设置HEAP_GROWABLE(0x00000002)标志,则正在调试进程。
如果ForceFlags值不为 0,则正在调试进程。
但在实际运行中根据操作系统不同可能会有一些偏差,下面是一个demo:
int GetHeapFlagsOffset(bool x64)
{
return x64 ?
IsVistaOrHigher() ? 0x70 : 0x14: //x64 offsets
IsVistaOrHigher() ? 0x40 : 0x0C; //x86 offsets
}
int GetHeapForceFlagsOffset(bool x64)
{
return x64 ?
IsVistaOrHigher() ? 0x74 : 0x18: //x64 offsets
IsVistaOrHigher() ? 0x44 : 0x10; //x86 offsets
}
void CheckHeap()
{
PVOID pPeb = GetPEB();
PVOID pPeb64 =