一、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 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 kd> dt _PEB ntdll!_PEB +0x000 InheritedAddressSpace : UChar +0x001 ReadImageFileExecOptions : UChar +0x002 BeingDebugged : UChar +0x003 BitField : UChar +0x003 ImageUsesLargePages : Pos 0, 1 Bit +0x003 IsProtectedProcess : Pos 1, 1 Bit +0x003 IsLegacyProcess : Pos 2, 1 Bit +0x003 IsImageDynamicallyRelocated : Pos 3, 1 Bit +0x003 SkipPatchingUser32Forwarders : Pos 4, 1 Bit +0x003 SpareBits : Pos 5, 3 Bits +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 AtlThunkSListPtr : Ptr32 Void +0x024 IFEOKey : Ptr32 Void +0x028 CrossProcessFlags : Uint4B +0x028 ProcessInJob : Pos 0, 1 Bit +0x028 ProcessInitializing : Pos 1, 1 Bit +0x028 ProcessUsingVEH : Pos 2, 1 Bit +0x028 ProcessUsingVCH : Pos 3, 1 Bit +0x028 ProcessUsingFTH : Pos 4, 1 Bit +0x028 ReservedBits0 : Pos 5, 27 Bits +0x02c KernelCallbackTable : Ptr32 Void +0x02c UserSharedInfoPtr : Ptr32 Void +0x030 SystemReserved : [1] Uint4B +0x034 AtlThunkSListPtr32 : Uint4B +0x038 ApiSetMap : Ptr32 Void +0x03c TlsExpansionCounter : Uint4B +0x040 TlsBitmap : Ptr32 Void +0x044 TlsBitmapBits : [2] Uint4B +0x04c ReadOnlySharedMemoryBase : Ptr32 Void +0x050 HotpatchInformation : 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 _RTL_CRITICAL_SECTION +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 ActiveProcessAffinityMask : 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 _ACTIVATION_CONTEXT_DATA +0x1fc ProcessAssemblyStorageMap : Ptr32 _ASSEMBLY_STORAGE_MAP +0x200 SystemDefaultActivationContextData : Ptr32 _ACTIVATION_CONTEXT_DATA +0x204 SystemAssemblyStorageMap : Ptr32 _ASSEMBLY_STORAGE_MAP +0x208 MinimumStackCommit : Uint4B +0x20c FlsCallback : Ptr32 _FLS_CALLBACK_INFO +0x210 FlsListHead : _LIST_ENTRY +0x218 FlsBitmap : Ptr32 Void +0x21c FlsBitmapBits : [4] Uint4B +0x22c FlsHighIndex : Uint4B +0x230 WerRegistrationData : Ptr32 Void +0x234 WerShipAssertPtr : Ptr32 Void +0x238 pContextData : Ptr32 Void +0x23c pImageHeaderHash : Ptr32 Void +0x240 TracingFlags : Uint4B +0x240 HeapTracingEnabled : Pos 0, 1 Bit +0x240 CritSecTracingEnabled : Pos 1, 1 Bit +0x240 SpareTracingBits : Pos 2, 30 Bits
BeingDebugged =0表示未调试,≠0表示调试。
NtGlobalFlag =0表示未调试,≠0表示调试。在 Windows NT中,有一组标志存储在全局变量NtGlobalFlag中,这在整个系统中是通用的。在启动时,NtGlobalFlag全局系统变量将使用系统注册表项中的值进行初始化:[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\GlobalFlag]
,要检查进程是否已使用调试器启动,请检查PEB结构中NtGlobal标志字段的值。此字段分别位于x32和x64系统的PEB 0x068和0x0bc偏移量。
ProcessHeap
标志名
说明
ProcessHeap->Flags
=2表示未调试,≠2表示调试。
ProcessHeap->ForceFlags
=0表示未调试,≠0表示调试。
CrossProcessFlags ULONG类型,每个位表达的含义不同,其中第四位为ProcessUsingVEH,可用来检测是否使用了VEH。
https://www.geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/pebteb/peb/crossprocessflags.htm
二、API系列 IsDebuggerPresent 1 BOOL IsDebuggerPresent () ;
返回0表示未调试,返回1表示调试,实际上是读取BeingDebugged的值。
CheckRemoteDebuggerPresent 1 2 3 4 BOOL CheckRemoteDebuggerPresent ( [in] HANDLE hProcess, [in, out] PBOOL pbDebuggerPresent ) ;
检测进程是否处于调试状态,且其不仅仅可以检测自身是否处于调试状态,还可以检测其他程序是否处于调试状态,底层为调用NtQueryInformationProcess(ProcessDebugPort)。
DbgBreakPoint
软件断点,int3。调试器附加后会停留在此函数上进行中断等待,将函数头部改写ret可让调试结束
DbgUiRemoteBreakin ntdll提供的用于在目标进程中创建远线程后下软件断点的函数,hook该函数可让调试器下断点时失败。
1 2 3 4 5 6 7 __kernel_entry NTSTATUS NtQueryInformationProcess ( [in] HANDLE ProcessHandle, [in] PROCESSINFOCLASS ProcessInformationClass, [out] PVOID ProcessInformation, [in] ULONG ProcessInformationLength, [out, optional] PULONG ReturnLength ) ;
ProcessInformationClass为一个宏,用于调试检测的值如下:
值
含义
补充
ProcessDebugPort(0x7)
检索一个 DWORD_PTR 值,该值是进程的调试器的端口号。 非零值指示进程在环 3 调试器的控制下运行。
无
ProcessDebugObjectHandle(0x1E)
调试器会为被调试的进程创建一个调试对象,该参数能获取调试对象的句柄,不为0则为调试。
无
ProcessDebugFlags(0x1F)
调试标志。调试时为0,未调试为1。
无
1 2 3 4 5 6 7 __kernel_entry NTSTATUS NtQueryInformationThread ( [in] HANDLE ThreadHandle, [in] THREADINFOCLASS ThreadInformationClass, [in, out] PVOID ThreadInformation, [in] ULONG ThreadInformationLength, [out, optional] PULONG ReturnLength ) ;
检查当前线程上下文的Dr寄存器。
1 2 3 4 5 6 7 8 9 10 11 12 WOW64_CONTEXT context = { 0 }; context.ContextFlags = WOW64_CONTEXT_ALL; if (NT_SUCCESS (NtQueryInformationThread (NtCurrentThread, ThreadWow64Context, &context, sizeof (context), NULL ))){ if (context.Dr0 || context.Dr1 || context.Dr2 || context.Dr3 || context.Dr6 || context.Dr7) { DEBUG_LOG (LL_ERR, "Debugger detected via: ThreadWow64Context" ); return true ; } } return false ;
可查询当前系统是否为调试模式。
1 2 3 4 5 6 __kernel_entry NTSTATUS NtQuerySystemInformation ( [in] SYSTEM_INFORMATION_CLASS SystemInformationClass, [in, out] PVOID SystemInformation, [in] ULONG SystemInformationLength, [out, optional] PULONG ReturnLength ) ;
调试状态下 SYSTEM_KERNEL_DEBUGGER_INFORMATION.DebuggerEnabled
值和KernelDebuggerNotPresent
值为1。
NtQueryObject 可查询内核对象。
1 2 3 4 5 6 7 __kernel_entry NTSYSCALLAPI NTSTATUS NtQueryObject ( [in, optional] HANDLE Handle, [in] OBJECT_INFORMATION_CLASS ObjectInformationClass, [out, optional] PVOID ObjectInformation, [in] ULONG ObjectInformationLength, [out, optional] PULONG ReturnLength ) ;
通过参数二传入ObjectAllTypesInformation
获取所有对象后,判断是否存在调试对象。
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 if (NtQueryObject (NULL , ObjectAllInformation, p_Memory, objSize, NULL ) != 0 ) { goto main_end; } p_ObjectAllInfo = (POBJECT_ALL_INFORMATION)p_Memory; p_ObjInfoLocation = (PUCHAR)p_ObjectAllInfo->ObjectTypeInformation; for (UINT i = 0 ; i < p_ObjectAllInfo->NumberOfObjects; i++) { p_ObjectTypeInfo = (POBJECT_TYPE_INFORMATION)p_ObjInfoLocation; if (wcscmp (L"DebugObject" , p_ObjectTypeInfo->TypeName.Buffer) == 0 ) { if (p_ObjectTypeInfo->TotalNumberOfObjects > 0 ) { cout << "发现调试器!" << endl; break ; } else { cout << "没有调试器" << endl; break ; } } p_ObjInfoLocation = (PUCHAR)p_ObjectTypeInfo->TypeName.Buffer; p_ObjInfoLocation += p_ObjectTypeInfo->TypeName.MaximumLength; ULONG_PTR tmp = ((ULONG_PTR)p_ObjInfoLocation) & -(int )sizeof (void *); if ((ULONG_PTR)tmp != (ULONG_PTR)p_ObjInfoLocation) { tmp += sizeof (void *); } p_ObjInfoLocation = ((unsigned char *)tmp); }
配合NtCreateDebugObject
函数进行检测。首先创建一个调试对象,然后再查询该调试对象类型的对象总数,如果数量为1则表示没有调试,如果数量>1则表示有其他调试器再调试进程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 char checkDebugObj_4037F0 () { int v1; int v2[6 ]; char v3; _DWORD v4[1025 ]; v3 = 1 ; v1 = 0 ; memset (v4, 0 , 4096 ); v2[0 ] = 24 ; memset (&v2[1 ], 0 , 20 ); NtCreateDebugObject_404650 (&v1, 0x1F000F , v2, 0 ); NtQueryObject_4045D0 (v1, ObjectTypeInformation, v4, 0x1000 , 0 ); if ( v4[3 ] == 1 ) v3 = 0 ; NtwClose_404560 (v1); return v3; }
可以通过给线程设置为ThreadHideFromDebugger
使调试器无法捕获被隐藏线程的异常信息。
1 2 3 4 5 6 NTSYSAPI NTSTATUS ZwSetInformationThread ( [in] HANDLE ThreadHandle, [in] THREADINFOCLASS ThreadInformationClass, [in] PVOID ThreadInformation, [in] ULONG ThreadInformationLength ) ;
NtGetContextThread、NtSetContextThread 1 2 3 4 5 6 7 8 9 BOOL GetThreadContext ( [in] HANDLE hThread, [in, out] LPCONTEXT lpContext ) ;BOOL SetThreadContext ( [in] HANDLE hThread, [in] const CONTEXT *lpContext ) ;
基于硬件断点的原理,可以将CR0-CR3寄存器清空,并且检查TF标志位。
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 typedef NTSTATUS (NTAPI* pfnNtGetContextThread) ( _In_ HANDLE ThreadHandle, _Out_ PCONTEXT pContext ) ;typedef NTSTATUS (NTAPI* pfnNtSetContextThread) ( _In_ HANDLE ThreadHandle, _In_ PCONTEXT pContext ) ;int main () { HMODULE hNtDll = LoadLibrary (TEXT ("ntdll.dll" )); auto fnNtGetContextThread = (pfnNtGetContextThread)GetProcAddress (hNtDll, "NtGetContextThread" ); auto fnNtSetContextThread = (pfnNtSetContextThread)GetProcAddress (hNtDll, "NtSetContextThread" ); CONTEXT ctx{}; fnNtGetContextThread (GetCurrentThread (),&ctx); ctx.Dr0 = 0 ; ctx.Dr1 = 0 ; ctx.Dr2 = 0 ; ctx.Dr3 = 0 ; ctx.EFlags &= 0x0FFFFFFF ; fnNtSetContextThread (GetCurrentThread (),&ctx); getchar (); }
OutputDebugStringA 1 2 3 void OutputDebugStringA ( [in, optional] LPCSTR lpOutputString ) ;
可以通过构造部分特殊字符串使得调试器崩溃。恶意代码常尝试利用OllyDbg1.1的格式化字符串漏洞,为OutputDebugString函数提供一个%s字符串的参数,让OllyDbg崩溃。因此,需要注意程序中可疑的OutputDebugString调用,例如OutputDebugString(“%s%s%s%s%s%s%s%s%s”)。如果执行了这个调用,OllyDbg将会崩溃。
1 2 CHAR szFakeFormat[] = { '%' , 's' , '%' , 's' , '%' , 's' , '%' , 's' , '%' , 's' , '%' , 's' , '%' , 's' , '%' , 's' , '%' , 's' , '%' , 's' , '%' , 's' , 0x0 }; OutputDebugStringA (szFakeFormat);
也可以通过设置错误码,检测调试器。在有调试器存在和没有调试器存在时,OutputDebugString函数表现会有所不同。最明显的不同是, 如果有调试器存在,其后的GetLastError()的返回值为零。
1 2 3 4 5 6 SetLastError (1234 ); OutputDebugString ("" );tmpD=GetLastError (); if (tmpD==1234 ) return false ; return true ;
TimeCheck 由于断点的存在导致断点前后存在时间差,可以使用GetTickCount或者QueryPerformanceCounter、rdtsc指令,或者等待事件的方法检测。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 bool CheckSystemTime () { auto bRet = false ; BOOLEAN bAdjustPrivRet; auto ntStatus =RtlAdjustPrivilege (SE_SYSTEMTIME_PRIVILEGE, TRUE, FALSE, &bAdjustPrivRet); if (NT_SUCCESS (ntStatus)) { auto hEvent = CreateEventA (NULL , FALSE, FALSE, NULL ); if (IS_VALID_HANDLE (hEvent)) { if (NT_SUCCESS (NtSetSystemInformation (SystemTimeSlipNotification, &hEvent, sizeof (hEvent)))) { if (WaitForSingleObject (hEvent, 1 ) == WAIT_OBJECT_0) bRet = true ; } CloseHandle (hEvent); } } return bRet; }
NtSetDebugFilterState 函数ntdll!DbgSetDebugFilterState()和ntdll!NtSetDebugFilterState()只设置一个标志寄存器,如果内核模式的调试器存在,将被检查。因此,如果一个内核调试器被连接到系统上,这些函数将成功。
然而,这些函数也可能因为一些用户模式的调试器引起的副作用而成功。这些功能需要管理员的权限。
1 2 3 4 5 6 7 bool CheckDebugFilterState () { auto ntStatus = NtSetDebugFilterState (0 , 0 , TRUE); if (!NT_SUCCESS (ntStatus)) return true ; return false ; }
1 2 3 4 5 6 BOOL HeapSetInformation ( [in, optional] HANDLE HeapHandle, [in] HEAP_INFORMATION_CLASS HeapInformationClass, [in] PVOID HeapInformation, [in] SIZE_T HeapInformationLength ) ;
值
说明
HeapCompatibilityInformation(0)
启用堆功能。 仅支持低碎片堆 (LFH)
。 但是,应用程序不需要启用 LFH,因为系统根据需要使用 LFH 来服务内存分配请求。 Windows XP 和 Windows Server 2003: 默认情况下,LFH 未启用。 若要为指定的堆启用 LFH,请将 HeapInformation 参数指向的变量设置为 2。 为堆启用 LFH 后,无法禁用它。 不能为使用 HEAP_NO_SERIALIZE 创建的堆或固定大小的堆启用 LFH。 如果在 Windows 或 Microsoft 应用程序验证程序调试工具中使用堆调试工具,则无法启用 LFH。在任何调试器下运行进程时,将自动为进程中的所有堆启用某些堆调试选项。 这些堆调试选项阻止使用 LFH。 若要在调试器下运行时启用低碎片堆,请将_NO_DEBUG_HEAP环境变量设置为 1。
1 2 3 4 5 6 7 bool CheckHeapSetInformation () { ULONG uHeapInfo = 2 ; if (!g_winapiApiTable->HeapSetInformation (g_winapiApiTable->GetProcessHeap (), HeapCompatibilityInformation, &uHeapInfo, sizeof (uHeapInfo))) return true ; return false ; }
NtSystemDebugControl 1 2 3 4 5 6 7 8 inline bool CheckSystemDebugControl () { auto dwReturnLength = 0UL ; auto ntStatus = g_winapiApiTable->NtSystemDebugControl (SysDbgBreakPoint, NULL , 0 , NULL , 0 , NULL ); DEBUG_LOG (LL_SYS, "NtSystemDebugControl completed! Status: %p Return length: %u" , ntStatus, dwReturnLength); return (ntStatus != STATUS_DEBUGGER_INACTIVE); }
RtlCaptureStackBackTrace 1 2 3 4 5 6 NTSYSAPI WORD RtlCaptureStackBackTrace ( [in] DWORD FramesToSkip, [in] DWORD FramesToCapture, [out] PVOID *BackTrace, [out, optional] PDWORD BackTraceHash ) ;
该函数可以捕获当前调用堆栈的栈回溯。
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 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 #pragma once #include <vector> #include <string> #include <DbgHelp.h> #pragma comment(lib,"Dbghelp.lib" ) void wchar2strstring (std::string & szDst, WCHAR * wchart) { wchar_t * wtext = wchart; DWORD dwNmu = WideCharToMultiByte (CP_OEMCP, NULL , wtext, -1 , NULL , 0 , NULL , FALSE); char * psTest; psTest = new char [dwNmu]; WideCharToMultiByte (CP_OEMCP, NULL , wtext, -1 , psTest, dwNmu, NULL , FALSE); szDst = psTest; delete []psTest; } void ShowTraceStack (std::vector<std::string>& vInfo) { vInfo.clear (); enum { MAX_STACK_FRAMES = 16 }; void * arrBackTrace[MAX_STACK_FRAMES] = { 0 }; HANDLE hProcess = GetCurrentProcess (); SymInitialize (hProcess, NULL , TRUE); typedef USHORT (*PFUNRtlCaptureStackBackTrace) ( ULONG FramesToSkip, ULONG FramesToCapture, PVOID* BackTrace, PULONG BackTraceHash ) ; HMODULE hNt = LoadLibrary (_T("NtDll.dll" )); PFUNRtlCaptureStackBackTrace pCaptureStackBackTrace = (PFUNRtlCaptureStackBackTrace) GetProcAddress (hNt, ("RtlCaptureStackBackTrace" )); if (!pCaptureStackBackTrace) { vInfo.push_back ("RtlCaptureStackBackTrace load failed" ); return ; } CString strStackInfo; WORD wFrames = pCaptureStackBackTrace (0 , MAX_STACK_FRAMES, arrBackTrace, NULL ); TCHAR szBuffer[sizeof (SYMBOL_INFOW) + MAX_SYM_NAME * sizeof (TCHAR) ] ; DWORD64 dwAddress = 0 ; DWORD64 dwDisplacement = 0 ; PSYMBOL_INFOW pSymbol = NULL ; DWORD dwDisplacement2 = 0 ; IMAGEHLP_LINEW64 ilLine; for (WORD wIndex = 0 ; wIndex < wFrames; ++wIndex) { dwAddress = (DWORD64)(arrBackTrace[wIndex]); dwDisplacement = 0 ; pSymbol = (PSYMBOL_INFOW)szBuffer; pSymbol->SizeOfStruct = sizeof (SYMBOL_INFOW); pSymbol->MaxNameLen = MAX_SYM_NAME; dwDisplacement2 = 0 ; ilLine.SizeOfStruct = sizeof (IMAGEHLP_LINEW64); if (TRUE == SymFromAddrW (hProcess, dwAddress, &dwDisplacement, pSymbol) && TRUE == SymGetLineFromAddrW64 (hProcess, dwAddress, &dwDisplacement2, &ilLine)) { std::string sname, sfile; wchar2strstring (sname, pSymbol->Name); wchar2strstring (sfile, ilLine.FileName); strStackInfo.Format (_T("FUN:%s() File:%s:[%d]" ), sname.c_str (), sfile.c_str (), ilLine.LineNumber); } else { strStackInfo.Format (("query error: %d" ), GetLastError ()); } vInfo.push_back (strStackInfo.GetBuffer ()); } FreeLibrary (hNt); } void PrintfStackInfo () { printf ("===========start===========\n" ); std::vector<std::string> mVStackList; ShowTraceStack (mVStackList); for (int i = 0 ; i < mVStackList.size (); i++) { printf ("%s\n" , mVStackList[i].c_str ()); } printf ("===========end===========\n" ); }
三、异常捕获 CloseHandle、NtGetContextThread 可以通过传入错误参数的手段让调试器抛出异常后检测调试。调试情况下,一个非法的句柄会引发0xC00000008。
非调试情况下。
因此可以使用异常捕获来判断是否被调试。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include <iostream> #include <Windows.h> int main () { __try { CloseHandle ((HANDLE)672368 ); printf ("ok" ); } __except(EXCEPTION_EXECUTE_HANDLER) { printf ("debugger" ); } getchar (); }
VS调试模式下运行。
正常运行。
NtGetContextThread
同理。
其他可用函数还有
NtQueryInformationProcess
CloseWindow
通过创建一个互斥体对象,利用SetHandleInformation将互斥体对象句柄 标志改为HANDLE_FLAG_PROTECT_FROM_CLOSE,然后关闭句柄,如果是在调试器状态下,它会抛出EXCEPTION_EXECUTE_HANDLER异常,只要捕获到异常那么就表示程序被调试。
值
说明
HANDLE_FLAG_PROTECT_FROM_CLOSE(0x2)
如果设置了此标志,则调用 CloseHandle 函数不会关闭对象句柄。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 inline bool CheckCloseHandle2 () { auto hMutex = CreateMutexA (NULL , FALSE, "ntdil.dli" ); if (IS_VALID_HANDLE (hMutex)) { if (SetHandleInformation (hMutex, HANDLE_FLAG_PROTECT_FROM_CLOSE, HANDLE_FLAG_PROTECT_FROM_CLOSE)) { __try { CloseHandle (hMutex); } __except (HANDLE_FLAG_PROTECT_FROM_CLOSE) { return true ; } } } return false ; }
SetUnhandledExceptionFilter、UnhandledExcepFilter 通过设置一个SEH函数,然后抛出一个异常,使其跳转到其他地方进行检查调试。如果有调试器时,默认由调试器接管异常。
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 #include <iostream> #include <Windows.h> using namespace std;BOOL bIsBeinDbg = TRUE; LONG WINAPI UnhandledExcepFilter (PEXCEPTION_POINTERS pExcepPointers) { bIsBeinDbg = FALSE; return EXCEPTION_CONTINUE_EXECUTION; } int main () { LPTOP_LEVEL_EXCEPTION_FILTER Top = SetUnhandledExceptionFilter (UnhandledExcepFilter); RaiseException (EXCEPTION_FLT_DIVIDE_BY_ZERO, 0 , 0 , NULL ); if (bIsBeinDbg == TRUE) { cout << "发现调试器!" << endl; } else { cout << "没有调试器!" << endl; } main_end: getchar (); return 0 ; }
1 2 3 4 5 6 7 8 9 10 PRTL_DEBUG_INFORMATION NTAPI* RtlCreateQueryDebugBuffer ( IN ULONG Size, IN BOOLEAN EventPair ) ;NTSTATUS NTAPI* RtlQueryProcessDebugInformation ( IN ULONG ProcessId, IN ULONG DebugInfoClassMask, IN OUT PRTL_DEBUG_INFORMATION DebugBuffer) ;
ntdll!RtlQueryProcessDebugInformation()
函数可用于从请求进程的进程内存中读取某些字段,包括堆标志。
1 2 3 4 5 6 7 8 9 bool Check() { ntdll::PDEBUG_BUFFER pDebugBuffer = ntdll::RtlCreateQueryDebugBuffer(0, FALSE); if (!SUCCEEDED(ntdll::RtlQueryProcessDebugInformation(GetCurrentProcessId(), ntdll::PDI_HEAPS | ntdll::PDI_HEAP_BLOCKS, pDebugBuffer))) return false; ULONG dwFlags = ((ntdll::PRTL_PROCESS_HEAPS)pDebugBuffer->HeapInformation)->Heaps[0].Flags; return dwFlags & ~HEAP_GROWABLE; }
1 2 3 NTSTATUS NTAPI RtlQueryProcessHeapInformation ( IN OUT PRTL_DEBUG_INFORMATION Buffer ) ;
ntdll!RtlQueryProcessHeapInformation()
函数可用于从当前进程的进程内存中读取堆标志
1 2 3 4 5 6 7 8 9 ool Check () { ntdll::PDEBUG_BUFFER pDebugBuffer = ntdll::RtlCreateQueryDebugBuffer (0 , FALSE); if (!SUCCEEDED (ntdll::RtlQueryProcessHeapInformation ((ntdll::PRTL_DEBUG_INFORMATION)pDebugBuffer))) return false ; ULONG dwFlags = ((ntdll::PRTL_PROCESS_HEAPS)pDebugBuffer->HeapInformation)->Heaps[0 ].Flags; return dwFlags & ~HEAP_GROWABLE; }
四、注册表 GlobalFlagsClear 可执行文件可以包含IMAGE_LOAD_CONFIG_DIRECTORY
结构,该结构包含系统加载程序的其他配置参数。默认情况下,此结构不会内置于可执行文件中,但可以使用修补程序添加它。此结构具有GlobalFlagsClear
,该字段指示应重置 PEB 结构的 NtGlobalFlag
标志字段的哪些标志。该字段具有非零值,表示存在隐藏的调试器。
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 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 typedef struct _IMAGE_LOAD_CONFIG_DIRECTORY { DWORD Size; DWORD TimeDateStamp; WORD MajorVersion; WORD MinorVersion; DWORD GlobalFlagsClear; DWORD GlobalFlagsSet; DWORD CriticalSectionDefaultTimeout; ULONGLONG DeCommitFreeBlockThreshold; ULONGLONG DeCommitTotalFreeThreshold; ULONGLONG LockPrefixTable; ULONGLONG MaximumAllocationSize; ULONGLONG VirtualMemoryThreshold; ULONGLONG ProcessAffinityMask; DWORD ProcessHeapFlags; WORD CSDVersion; WORD DependentLoadFlags; ULONGLONG EditList; ULONGLONG SecurityCookie; ULONGLONG SEHandlerTable; ULONGLONG SEHandlerCount; ULONGLONG GuardCFCheckFunctionPointer; ULONGLONG GuardCFDispatchFunctionPointer; ULONGLONG GuardCFFunctionTable; ULONGLONG GuardCFFunctionCount; DWORD GuardFlags; IMAGE_LOAD_CONFIG_CODE_INTEGRITY CodeIntegrity; ULONGLONG GuardAddressTakenIatEntryTable; ULONGLONG GuardAddressTakenIatEntryCount; ULONGLONG GuardLongJumpTargetTable; ULONGLONG GuardLongJumpTargetCount; ULONGLONG DynamicValueRelocTable; ULONGLONG CHPEMetadataPointer; ULONGLONG GuardRFFailureRoutine; ULONGLONG GuardRFFailureRoutineFunctionPointer; DWORD DynamicValueRelocTableOffset; WORD DynamicValueRelocTableSection; WORD Reserved2; ULONGLONG GuardRFVerifyStackPointerFunctionPointer; DWORD HotPatchTableOffset; DWORD Reserved3; ULONGLONG EnclaveConfigurationPointer; ULONGLONG VolatileMetadataPointer; ULONGLONG GuardEHContinuationTable; ULONGLONG GuardEHContinuationCount; ULONGLONG GuardXFGCheckFunctionPointer; ULONGLONG GuardXFGDispatchFunctionPointer; ULONGLONG GuardXFGTableDispatchFunctionPointer; ULONGLONG CastGuardOsDeterminedFailureMode; ULONGLONG GuardMemcpyFunctionPointer; } IMAGE_LOAD_CONFIG_DIRECTORY, *PIMAGE_LOAD_CONFIG_DIRECTORY; PIMAGE_NT_HEADERS GetImageNtHeaders (PBYTE pImageBase) { PIMAGE_DOS_HEADER pImageDosHeader = (PIMAGE_DOS_HEADER)pImageBase; return (PIMAGE_NT_HEADERS)(pImageBase + pImageDosHeader->e_lfanew); } PIMAGE_SECTION_HEADER FindRDataSection (PBYTE pImageBase) { static const std::string rdata = ".rdata" ; PIMAGE_NT_HEADERS pImageNtHeaders = GetImageNtHeaders (pImageBase); PIMAGE_SECTION_HEADER pImageSectionHeader = IMAGE_FIRST_SECTION (pImageNtHeaders); int n = 0 ; for (; n < pImageNtHeaders->FileHeader.NumberOfSections; ++n) { if (rdata == (char *)pImageSectionHeader[n].Name) { break ; } } return &pImageSectionHeader[n]; } void CheckGlobalFlagsClearInProcess () { PBYTE pImageBase = (PBYTE)GetModuleHandle (NULL ); PIMAGE_NT_HEADERS pImageNtHeaders = GetImageNtHeaders (pImageBase); PIMAGE_LOAD_CONFIG_DIRECTORY pImageLoadConfigDirectory = (PIMAGE_LOAD_CONFIG_DIRECTORY)(pImageBase + pImageNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG].VirtualAddress); if (pImageLoadConfigDirectory->GlobalFlagsClear != 0 ) # 内存中检查IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG { std::cout << "Stop debugging program!" << std::endl; exit (-1 ); } } void CheckGlobalFlagsClearInFile () { HANDLE hExecutable = INVALID_HANDLE_VALUE; HANDLE hExecutableMapping = NULL ; PBYTE pMappedImageBase = NULL ; __try { PBYTE pImageBase = (PBYTE)GetModuleHandle (NULL ); PIMAGE_SECTION_HEADER pImageSectionHeader = FindRDataSection (pImageBase); TCHAR pszExecutablePath[MAX_PATH]; DWORD dwPathLength = GetModuleFileName (NULL , pszExecutablePath, MAX_PATH); if (0 == dwPathLength) __leave; hExecutable = CreateFile (pszExecutablePath, GENERIC_READ, FILE_SHARE_READ, NULL , OPEN_EXISTING, 0 , NULL ); if (INVALID_HANDLE_VALUE == hExecutable) __leave; hExecutableMapping = CreateFileMapping (hExecutable, NULL , PAGE_READONLY, 0 , 0 , NULL ); if (NULL == hExecutableMapping) __leave; pMappedImageBase = (PBYTE)MapViewOfFile (hExecutableMapping, FILE_MAP_READ, 0 , 0 , pImageSectionHeader->PointerToRawData + pImageSectionHeader->SizeOfRawData); if (NULL == pMappedImageBase) __leave; PIMAGE_NT_HEADERS pImageNtHeaders = GetImageNtHeaders (pMappedImageBase); PIMAGE_LOAD_CONFIG_DIRECTORY pImageLoadConfigDirectory = (PIMAGE_LOAD_CONFIG_DIRECTORY)(pMappedImageBase + (pImageSectionHeader->PointerToRawData + (pImageNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG].VirtualAddress - pImageSectionHeader->VirtualAddress))); if (pImageLoadConfigDirectory->GlobalFlagsClear != 0 ) { std::cout << "Stop debugging program!" << std::endl; exit (-1 ); } } __finally { if (NULL != pMappedImageBase) UnmapViewOfFile (pMappedImageBase); if (NULL != hExecutableMapping) CloseHandle (hExecutableMapping); if (INVALID_HANDLE_VALUE != hExecutable) CloseHandle (hExecutable); } }
SystemBoot 可以通过枚举系统启动项中是否存在debug相关字符串判断是否处于调试模式。
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 CheckDebugBoot () { auto hKey = HKEY (nullptr ); auto dwRegOpenRet = RegOpenKeyExA (HKEY_LOCAL_MACHINE, xorstr ("System\\CurrentControlSet\\Control" ).crypt_get (), 0 , KEY_READ, &hKey); if (dwRegOpenRet != ERROR_SUCCESS) { return false ; } char szBootOptions[1024 ] = { 0 }; auto dwLen = DWORD (sizeof (szBootOptions) - sizeof (CHAR)); auto dwRegGetRet = RegGetValueA (hKey, NULL , xorstr ("SystemStartOptions" ).crypt_get (), RRF_RT_REG_SZ, NULL , szBootOptions, &dwLen); if (dwRegGetRet != ERROR_SUCCESS) { g_winapiApiTable->RegCloseKey (hKey); return false ; } auto szLowerOptions =szLower (szBootOptions); if (strstr (szLowerOptions.c_str (), xorstr ("debug" ).crypt_get ())) { RegCloseKey (hKey); return true ; } RegCloseKey (hKey); return false ; }
五、堆检测 HeapTail 如果处于调试中,堆尾部也会留下痕迹。标志HEAP_TAIL_CHECKING_ENABLED 将会在分配的堆块尾部生成两个0xABABABAB。如果需要额外的字节来填充堆尾,HEAP_FREE_CHECKING_ENABLED标志则会生成0xFEEEFEEE。
1 2 3 4 5 6 7 8 9 10 11 12 bool CheckHeapTail () { DWORD flag[] = { 0xabababab , 0xabababab }; auto buff = reinterpret_cast <DWORD_PTR>(g_nmApp->DynamicWinapiInstance ()->NTHelper ()->Alloc (32 )); auto temp = buff + 32 ; auto dwRet = memcmp ((LPVOID)temp, (LPVOID)flag, 8 ); g_nmApp->DynamicWinapiInstance ()->NTHelper ()->Free (reinterpret_cast <PVOID>(buff)); return dwRet == 0 ; }
六、符号检查 Syser 调试工具通常会使用内核驱动,因此如果尝试是否可以打开一些调试器所用到的设备,就可判断是否存在调试器。
\\.\Syser
\\.\SyserBoot
\\.\SyserDbgMsg
////../images/Anti//SICE
////../images/Anti//SIWVID
////../images/Anti//SIWVIDSTART
////../images/Anti//NTICE
////../images/Anti//ICEEXT
////../images/Anti//TRW
////../images/Anti//TRWDEBUG
七、ETC 是一种通过直接检测当前程序、窗口等等中是否含有常见反调试软件,如果有则停止程序。
检测调试器窗口—–>FindWindow()
检测调试器进程——>CreatToolhelp32Snapshot()
检查计算机名称是否为“TEST”,“ANALYSIS”等——->GetComputerName()
检查程序运行路径是否存在“TEST”,“SAMPLE”等名称——->GetCommandLine()
检测虚拟机是否处于运行状态(查看虚拟机特有的进程名称—->VMWareService.exe,VMWareTray.exe,VMWareUser.exe等)
枚举每个进程下的一些特殊文件。
检查每个进程的资源。
等等…..
八、其他 KUSER_SHARED_DATA 无论是在 32 位系统内存分布,还是在 64 位系统内存分布中,我们知道高地址空间分配给系统内核使用,低地址空间分配给用户进程使用。
事实上,用户空间和内核空间其实有一块共享区域(KUSER_SHARED_DATA),大小为 4 KB。它们的内存地址虽然不一样,但是它们都是有同一块物理内存映射出来的,KdDebuggerEnabled 就在存放这一块内存里。
对于 32 位系统和 64 位系统来说,这块共享区域对应的内核地址范围以及对应用户空间的地址范围如下表所示:
mode
kernel start
kernel end
user start
user end
x86
0xFFDF0000
0xFFDF0FFF
0x7FFE0000
0x7FFE0FFF
x64
0xFFFFF78000000000
0xFFFFF78000000FFF
0x7FFE0000
0x7FFE0FFF
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 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 dt _KUSER_SHARED_DATA // win11前 nt!_KUSER_SHARED_DATA +0x000 TickCountLowDeprecated : Uint4B +0x004 TickCountMultiplier : Uint4B +0x008 InterruptTime : _KSYSTEM_TIME +0x014 SystemTime : _KSYSTEM_TIME +0x020 TimeZoneBias : _KSYSTEM_TIME +0x02c ImageNumberLow : Uint2B +0x02e ImageNumberHigh : Uint2B +0x030 NtSystemRoot : [260] Wchar +0x238 MaxStackTraceDepth : Uint4B +0x23c CryptoExponent : Uint4B +0x240 TimeZoneId : Uint4B +0x244 LargePageMinimum : Uint4B +0x248 AitSamplingValue : Uint4B +0x24c AppCompatFlag : Uint4B +0x250 RNGSeedVersion : Uint8B +0x258 GlobalValidationRunlevel : Uint4B +0x25c TimeZoneBiasStamp : Int4B +0x260 NtBuildNumber : Uint4B +0x264 NtProductType : _NT_PRODUCT_TYPE +0x268 ProductTypeIsValid : UChar +0x269 Reserved0 : [1] UChar +0x26a NativeProcessorArchitecture : Uint2B +0x26c NtMajorVersion : Uint4B +0x270 NtMinorVersion : Uint4B +0x274 ProcessorFeatures : [64] UChar +0x2b4 Reserved1 : Uint4B +0x2b8 Reserved3 : Uint4B +0x2bc TimeSlip : Uint4B +0x2c0 AlternativeArchitecture : _ALTERNATIVE_ARCHITECTURE_TYPE +0x2c4 BootId : Uint4B +0x2c8 SystemExpirationDate : _LARGE_INTEGER +0x2d0 SuiteMask : Uint4B +0x2d4 KdDebuggerEnabled : UChar // <<<<<<<<< 划重点 +0x2d5 MitigationPolicies : UChar +0x2d5 NXSupportPolicy : Pos 0, 2 Bits +0x2d5 SEHValidationPolicy : Pos 2, 2 Bits +0x2d5 CurDirDevicesSkippedForDlls : Pos 4, 2 Bits +0x2d5 Reserved : Pos 6, 2 Bits +0x2d6 Reserved6 : [2] UChar +0x2d8 ActiveConsoleId : Uint4B +0x2dc DismountCount : Uint4B +0x2e0 ComPlusPackage : Uint4B +0x2e4 LastSystemRITEventTickCount : Uint4B +0x2e8 NumberOfPhysicalPages : Uint4B +0x2ec SafeBootMode : UChar +0x2ed VirtualizationFlags : UChar +0x2ee Reserved12 : [2] UChar +0x2f0 SharedDataFlags : Uint4B +0x2f0 DbgErrorPortPresent : Pos 0, 1 Bit +0x2f0 DbgElevationEnabled : Pos 1, 1 Bit +0x2f0 DbgVirtEnabled : Pos 2, 1 Bit +0x2f0 DbgInstallerDetectEnabled : Pos 3, 1 Bit +0x2f0 DbgLkgEnabled : Pos 4, 1 Bit +0x2f0 DbgDynProcessorEnabled : Pos 5, 1 Bit +0x2f0 DbgConsoleBrokerEnabled : Pos 6, 1 Bit +0x2f0 DbgSecureBootEnabled : Pos 7, 1 Bit +0x2f0 DbgMultiSessionSku : Pos 8, 1 Bit +0x2f0 DbgMultiUsersInSessionSku : Pos 9, 1 Bit +0x2f0 DbgStateSeparationEnabled : Pos 10, 1 Bit +0x2f0 SpareBits : Pos 11, 21 Bits +0x2f4 DataFlagsPad : [1] Uint4B +0x2f8 TestRetInstruction : Uint8B +0x300 QpcFrequency : Int8B +0x308 SystemCall : Uint4B +0x30c SystemCallPad0 : Uint4B +0x310 SystemCallPad : [2] Uint8B +0x320 TickCount : _KSYSTEM_TIME +0x320 TickCountQuad : Uint8B +0x320 ReservedTickCountOverlay : [3] Uint4B +0x32c TickCountPad : [1] Uint4B +0x330 Cookie : Uint4B +0x334 CookiePad : [1] Uint4B +0x338 ConsoleSessionForegroundProcessId : Int8B +0x340 TimeUpdateLock : Uint8B +0x348 BaselineSystemTimeQpc : Uint8B +0x350 BaselineInterruptTimeQpc : Uint8B +0x358 QpcSystemTimeIncrement : Uint8B +0x360 QpcInterruptTimeIncrement : Uint8B +0x368 QpcSystemTimeIncrementShift : UChar +0x369 QpcInterruptTimeIncrementShift : UChar +0x36a UnparkedProcessorCount : Uint2B +0x36c EnclaveFeatureMask : [4] Uint4B +0x37c TelemetryCoverageRound : Uint4B +0x380 UserModeGlobalLogger : [16] Uint2B +0x3a0 ImageFileExecutionOptions : Uint4B +0x3a4 LangGenerationCount : Uint4B +0x3a8 Reserved4 : Uint8B +0x3b0 InterruptTimeBias : Uint8B +0x3b8 QpcBias : Uint8B +0x3c0 ActiveProcessorCount : Uint4B +0x3c4 ActiveGroupCount : UChar +0x3c5 Reserved9 : UChar +0x3c6 QpcData : Uint2B +0x3c6 QpcBypassEnabled : UChar +0x3c7 QpcShift : UChar +0x3c8 TimeZoneBiasEffectiveStart : _LARGE_INTEGER +0x3d0 TimeZoneBiasEffectiveEnd : _LARGE_INTEGER +0x3d8 XState : _XSTATE_CONFIGURATION
1 2 3 4 5 6 7 8 9 10 11 12 bool CheckSharedUserData () { auto pUserSharedData = (KUSER_SHARED_DATA *)0x7FFE0000 ; BOOLEAN KdDebuggerEnabled = (pUserSharedData->KdDebuggerEnabled & 0x1 ) == 0x1 ; BOOLEAN KdDebuggerNotPresent = (pUserSharedData->KdDebuggerEnabled & 0x2 ) == 0x0 ; if (KdDebuggerEnabled || !KdDebuggerNotPresent) return true ; return false ; }