PEB结构体 FS段寄存器指向TEB结构体,TEB中偏移为0x30的地方存储PEB结构体。 使用FS:[0x30]
获取PEB结构体地址。
TEB结构体:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 kd> dt _teb nt!_TEB +0x000 NtTib : _NT_TIB +0x01c EnvironmentPointer : Ptr32 Void +0x020 ClientId : _CLIENT_ID //进程的pid +0x028 ActiveRpcHandle : Ptr32 Void +0x02c ThreadLocalStoragePointer : Ptr32 Void +0x030 ProcessEnvironmentBlock : Ptr32 _PEB //进程PEB +0x034 LastErrorValue : Uint4B +0x038 CountOfOwnedCriticalSections : Uint4B +0x03c CsrClientThread : Ptr32 Void +0x040 Win32ThreadInfo : Ptr32 Void +0x044 User32Reserved : [26 ] Uint4B +0x0ac UserReserved : [5 ] Uint4B +0x0c0 WOW32Reserved : Ptr32 Void +0x0c4 CurrentLocale : Uint4B +0x0c8 FpSoftwareStatusRegister : Uint4B +0x0cc SystemReserved1 : [54 ] Ptr32 Void +0x1a4 ExceptionCode : Int4B +0x1a8 ActivationContextStack : _ACTIVATION_CONTEXT_STACK +0x1bc SpareBytes1 : [24 ] UChar +0x1d4 GdiTebBatch : _GDI_TEB_BATCH +0x6b4 RealClientId : _CLIENT_ID +0x6bc GdiCachedProcessHandle : Ptr32 Void +0x6c0 GdiClientPID : Uint4B +0x6c4 GdiClientTID : Uint4B +0x6c8 GdiThreadLocalInfo : Ptr32 Void +0x6cc Win32ClientInfo : [62 ] Uint4B +0x7c4 glDispatchTable : [233 ] Ptr32 Void +0xb68 glReserved1 : [29 ] Uint4B
PEB结构体:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 +0x000 InheritedAddressSpace : UChar +0x001 ReadImageFileExecOptions : UChar +0x002 BeingDebugged : UChar +0x003 SpareBool : UChar +0x004 Mutant : Ptr32 Void +0x008 ImageBaseAddress : Ptr32 Void +0x00c Ldr : Ptr32 _PEB_LDR_DATA +0x010 ProcessParameters : Ptr32 _RTL_USER_PROCESS_PARAMETERS +0x014 SubSystemData : Ptr32 Void +0x018 ProcessHeap : Ptr32 Void +0x01c FastPebLock : Ptr32 _RTL_CRITICAL_SECTION +0x020 FastPebLockRoutine : Ptr32 Void +0x024 FastPebUnlockRoutine : Ptr32 Void +0x028 EnvironmentUpdateCount : Uint4B +0x02c KernelCallbackTable : Ptr32 Void +0x030 SystemReserved : [1 ] Uint4B +0x034 AtlThunkSListPtr32 : Uint4B +0x038 FreeList : Ptr32 _PEB_FREE_BLOCK +0x03c TlsExpansionCounter : Uint4B +0x040 TlsBitmap : Ptr32 Void +0x044 TlsBitmapBits : [2 ] Uint4B +0x04c ReadOnlySharedMemoryBase : Ptr32 Void +0x050 ReadOnlySharedMemoryHeap : Ptr32 Void +0x054 ReadOnlyStaticServerData : Ptr32 Ptr32 Void +0x058 AnsiCodePageData : Ptr32 Void +0x05c OemCodePageData : Ptr32 Void +0x060 UnicodeCaseTableData : Ptr32 Void +0x064 NumberOfProcessors : Uint4B +0x068 NtGlobalFlag : Uint4B +0x070 CriticalSectionTimeout : _LARGE_INTEGER +0x078 HeapSegmentReserve : Uint4B +0x07c HeapSegmentCommit : Uint4B +0x080 HeapDeCommitTotalFreeThreshold : Uint4B +0x084 HeapDeCommitFreeBlockThreshold : Uint4B +0x088 NumberOfHeaps : Uint4B +0x08c MaximumNumberOfHeaps : Uint4B +0x090 ProcessHeaps : Ptr32 Ptr32 Void +0x094 GdiSharedHandleTable : Ptr32 Void +0x098 ProcessStarterHelper : Ptr32 Void +0x09c GdiDCAttributeList : Uint4B +0x0a0 LoaderLock : Ptr32 Void +0x0a4 OSMajorVersion : Uint4B +0x0a8 OSMinorVersion : Uint4B +0x0ac OSBuildNumber : Uint2B +0x0ae OSCSDVersion : Uint2B +0x0b0 OSPlatformId : Uint4B +0x0b4 ImageSubsystem : Uint4B +0x0b8 ImageSubsystemMajorVersion : Uint4B +0x0bc ImageSubsystemMinorVersion : Uint4B +0x0c0 ImageProcessAffinityMask : Uint4B +0x0c4 GdiHandleBuffer : [34 ] Uint4B +0x14c PostProcessInitRoutine : Ptr32 void +0x150 TlsExpansionBitmap : Ptr32 Void +0x154 TlsExpansionBitmapBits : [32 ] Uint4B +0x1d4 SessionId : Uint4B +0x1d8 AppCompatFlags : _ULARGE_INTEGER +0x1e0 AppCompatFlagsUser : _ULARGE_INTEGER +0x1e8 pShimData : Ptr32 Void +0x1ec AppCompatInfo : Ptr32 Void +0x1f0 CSDVersion : _UNICODE_STRING +0x1f8 ActivationContextData : Ptr32 Void +0x1fc ProcessAssemblyStorageMap : Ptr32 Void +0x200 SystemDefaultActivationContextData : Ptr32 Void +0x204 SystemAssemblyStorageMap : Ptr32 Void +0x208 MinimumStackCommit : Uint4B
BeingDebugged(+0x2) 程序在运行在调试器中时值为1,否则为0
IsDebuggerPresent()
API获取PEB.BeingDebugged
值,其代码为:
1 2 3 4 mov eax ,dword ptr fs :[0x18 ] mov eax ,dword ptr ds :[eax +0x30 ] movzx eax ,byte ptr ds :[eax +2 ] retn
这里插一句,如果你在网上查找关于TEB的资料,会发现所有人都会说fs指向TEB结构体的起始地址。那么获取TEB起始地址为什么不直接用fs的值,而是要用fs:[0x18]
呢? 实际上是因为FS寄存器并非直接指向TEB结构体的地址,而是它持有SDT的索引,而该索引持有实际的TEB地址 。
SDT位于内核内存区域,其地址在GDTR(全局描述符表寄存器)中。
而fs:[0x18]=TEB.NtTib.Self=address of TEB=fs:0
,即fs:[0x18]
处存储实际TEB结构体的地址(这是TEB中的一个成员)。
ProcessHeap(+0x18) 用kernel32
的GetProcessHeap()
函数或直接取偏移。
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偏移处。
非调试状态下,Flags值为2,ForceFlags为0。想要绕过,更改这些值即可。
注意:将运行中的进程附加到调试器时,不会出现以上特征。
NtGlobalFlag(+0x68) 在 32 位机器上, NtGlobalFlag字段位于PEB(进程环境块)0x68的偏移处, 64 位机器则是在偏移0xBC位置。该字段的默认值为0。当调试器正在运行时, 该字段会被设置为0x70。 由调试器创建的进程会设置以下标志位:
1 2 3 FLG_HEAP_ENABLE_TAIL_CHECK (0x10) FLG_HEAP_ENABLE_FREE_CHECK (0x20) FLG_HEAP_VALIDATE_PARAMETERS (0x40)
NtGlobalFlag(0x70)由以上三个值按位或得到。NtGlobalFlag
的3个标志位只有当程序是由调试器创建, 而非由调试器附加上去 的进程时, 才会被设置。
绕过
手动修改标志位的值:0x70改为0即可
在 Ollydbg 中使用hide-debug
插件
在 Windbg 禁用调试堆的方式启动程序 (windbg -hd program.exe
)
1 2 3 4 5 6 7 NTSTATUS WINAPI NtQueryInformationProcess ( _In_ HANDLE ProcessHandle, _In_ PROCESSINFOCLASS ProcessInformationClass, _Out_ PVOID ProcessInformation, _In_ ULONG ProcessInformationLength, _Out_opt_ PULONG ReturnLength ) ;
参数ProcessHandle是我们要查询的进程的句柄。 参数ProcessInformationClass是一个枚举类型,通过传入特定的值,系统就会把相关的信息保存在ProcessInformation中。 参数ProcessInformation就是我们用来保存查询结果的缓冲区。 参数ProcessInformationLength是我们缓冲区的长度。 参数ReturnLength是调用函数后最终返回的数据长度。
ProcessDebugPort(0x7) ProcessInformationClass参数被设置为ProcessDebugPort(0x7)时,调用NtQueryInformationProcess()函数可以获得调试端口。 若非调试,则返回值(dwDebugPort)值为0,否则为0xffffffff。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 BOOL IsDebugger () { BOOL isDebugger = FALSE; DWORD dwDebugPort = 0 ; HMODULE hDll = NULL ; pNtQueryInformationProcess NtQueryInformationProcess = NULL ; hDll = LoadLibrary ("ntdll。dll" ); if (hDll) { NtQueryInformationProcess = (pNtQueryInformationProcess)GetProcAddress (hDll, "NtQueryInformationProcess" ); if (NtQueryInformationProcess) { NtQueryInformationProcess (GetCurrentProcess (), ProcessDebugPort, &dwDebugPort, sizeof (dwDebugPort), NULL ); if (dwDebugPort != 0 ) isDebugger = TRUE; } FreeLibrary (hDll); } return isDebugger; }
ProcessDebugObjectHandle(0x1e) 调试进程时生成调试对象。 函数第二个参数值为ProcessDebugObjectHandle(0x1e)时,调用函数后通过第三个参数获取调试对象的句柄。进程处于调试状态时,句柄值存在,否则为NULL。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 BOOL IsDebugger () { BOOL isDebugger = FALSE; HANDLE hDebugObj = NULL ; HMODULE hDll = NULL ; pNtQueryInformationProcess NtQueryInformationProcess = NULL ; hDll = LoadLibrary("ntdll。dll" ); if (hDll) { NtQueryInformationProcess = (pNtQueryInformationProcess)GetProcAddress(hDll, "NtQueryInformationProcess" ); if (NtQueryInformationProcess) { NtQueryInformationProcess(GetCurrentProcess(), ProcessDebugObjectHandle, &hDebugObj, sizeof (hDebugObj), NULL ); if (hDebugObj != NULL ) isDebugger = TRUE; } FreeLibrary(hDll); } return isDebugger; }
ProcessDebugFlags(0x1f) 函数第二个参数值为ProcessDebugFlags(0x1f)时,调用函数后通过第三个参数获取调试标志的值。为0表示进程处于调试状态,为1表示非调试。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 BOOL IsDebugger () { BOOL isDebugger = FALSE; HMODULE hDll = NULL ; pNtQueryInformationProcess NtQueryInformationProcess = NULL ; hDll = LoadLibrary ("ntdll。dll" ); if (hDll) { NtQueryInformationProcess = (pNtQueryInformationProcess)GetProcAddress (hDll, "NtQueryInformationProcess" ); if (NtQueryInformationProcess) { NtQueryInformationProcess (GetCurrentProcess (), ProcessDebugFlags, &isDebugger, sizeof (isDebugger), NULL ); isDebugger = !isDebugger; } FreeLibrary (hDll); } return isDebugger; }
CheckRemoteDebuggerPresent kernel32的CheckRemoteDebuggerPresent()函数用于检测指定进程是否正在被调试。 Remote在单词里是指同一个机器中的不同进程。
1 2 3 4 BOOL WINAPI CheckRemoteDebuggerPresent ( _In_ HANDLE hProcess, _Inout_ PBOOL pbDebuggerPresent ) ;
如果调试器存在 (通常是检测自己是否正在被调试), 该函数会将pbDebuggerPresent指向的值设为0xffffffff。
绕过 1 2 3 4 5 6 7 8 9 10 11 12 13 int main (int argc, char *argv[]) { BOOL isDebuggerPresent = FALSE; if (CheckRemoteDebuggerPresent (GetCurrentProcess (), &isDebuggerPresent )) { if (isDebuggerPresent ) { std::cout << "Stop debugging program!" << std::endl; exit (-1 ); } } return 0 ; }
我们可以直接修改isDebuggerPresent的值或修改跳转条件来绕过 (注意不是CheckRemoteDebuggerPresent的 izhi, 它的返回值是用于表示函数是否正确执行)。
但如果要针对CheckRemoteDebuggerPresent这个 api 函数进行修改的话,首先要知道CheckRemoteDebuggerPresent内部其实是通过调用NtQueryInformationProcess来完成功能的,所以就需要对NtQueryInformationProcess的返回值进行修改。
ZwSetInformationThread 等同于 NtSetInformationThread,通过为线程设置 ThreadHideFromDebugger,可以禁止线程产生调试事件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include <Windows。h> #include <stdio。h> typedef DWORD (WINAPI* ZW_SET_INFORMATION_THREAD) (HANDLE, DWORD, PVOID, ULONG) ;#define ThreadHideFromDebugger 0x11 VOID DisableDebugEvent (VOID) { HINSTANCE hModule; ZW_SET_INFORMATION_THREAD ZwSetInformationThread; hModule = GetModuleHandleA ("Ntdll" ); ZwSetInformationThread = (ZW_SET_INFORMATION_THREAD)GetProcAddress (hModule, "ZwSetInformationThread" ); ZwSetInformationThread (GetCurrentThread (), ThreadHideFromDebugger, 0 , 0 ); } int main () { printf ("Begin\n" ); DisableDebugEvent (); printf ("End\n" ); return 0 ; }
如果处于调试状态,执行完ZwSetInformationThread(GetCurrentThread(), ThreadHideFromDebugger, 0, 0);
,程序就会退出
绕过 注意到该处 ZwSetInformationThread 函数的第 2 个参数为 ThreadHideFromDebugger,其值为 0x11。调试执行到该函数时,若发现第 2 个参数值为 0x11,跳过或者将 0x11 修改为其他值即可
1 2 3 4 5 6 7 8 NTSTATUS NTAPI NtSetInformationThread ( _In_ HANDLE ThreadHandle, _In_ THREADINFOCLASS ThreadInformationClass, _In_reads_bytes_(ThreadInformationLength) PVOID ThreadInformation, _In_ ULONG ThreadInformationLength ) ;
参数一:当前线程的句柄,可以设置为NtCurrentThread 参数二:可以设置为ThreadHideFromDebugger标志 参数三:NULL 参数四:NULL
调用方法:NtSetInformationThread(handle.get(), ThreadHideFromDebugger, nullptr, 0);
ThreadHideFromDebugger值为0x11。 设置此参数将使这条线程对调试器“隐藏”。 即调试器收不到调试信息,因而就会出现当调试器对被调试进程下断点,线程执行到被下断点代码的时候就会卡死这种现象。
时钟检测 rdtsc rdtsc指令可以返回系统重启以来的时钟数,并且会将它作为一个64位的值放到EDX:EAX中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 BOOL IsDebugger() { BOOL IsDebugger = FALSE __asm { push eax push ecx xor eax , eax xor ecx , ecx rdtsc mov ecx , eax rdtsc sub eax , ecx cmp eax , 0xFFF jb NotDebugger mov ecx , 1 mov IsDebugger, ecx NotDebugger: pop ecx pop eax } }
GetTickCount GetTickCount函数可以返回最近系统重启时间与当前时间相差的毫秒数。
1 2 3 4 5 6 7 8 9 10 11 12 13 BOOL IsDebugger () { BOOL bIsDebugger = FALSE; DWORD dwFirst = 0 , dwSecond = 0 ; dwFirst = GetTickCount (); dwSecond = GetTickCount (); if (dwSecond - dwFirst > 0x1A ) bIsDebugger = TRUE; return bIsDebugger; }
在函数头部加上这个禁止键盘输入的函数,然后在函数尾部恢复键盘输入。感觉这个好有意思hhhhhhh
其他
使用FindWindows查看窗口是否是调试器
检测代码段是否有0xCC就是int 3断点
计算整个代码段中的CRC,判断程序是否被更改
符号检测主要针对一些使用了驱动的调试器或监视器,这类调试器在启动后会创建相应的驱动链接符号,以用于应用层与其驱动的通信
检测当前桌面中是否存在特定的调试窗口来判断是否存在调试器,不能判断该调试器是否正在调试该程序
特征码检测枚举当前正在运行的进程,并在进程的内存空间中搜索特定调试器的代码片段
一个进程只能同时被1个调试器调试,那么就可以将程序以调试方式启动,然后利用系统的调试机制防止被其他调试器调试
INT 2D
:一种调试时不会触发,正常运行才会触发的断点,正常运行到int 2d处将会触发异常执行SHE,调试时,单步执行会直接直接运行到程序终止
遇到新的再补充(x
参考资料:用户层反调试技术总结 CTF-wiki 调试陷阱ThreadHideFromDebugger的另一种对抗方法 Windows最全反调试知识汇总 https://hitworld.github.io/posts/e7c3b7f1/