NtGlobalFlag
在 32 位机器上,NtGlobalFlag
字段位于PEB
的0x68
的偏移处,64 位机器则是在偏移0xBC
位置,该字段的默认值为 0。当调试器正在运行时,该字段会被设置为一个特定的值
该字段包含有一系列的标志位,由调试器创建的进程会设置以下标志位:
这里NtGlobalFlag
的汇编代码如下,如果返回值为0x70
则程序处于调试状态
那么可以用代码进行判断如下,若返回为TRUE则处于调试状态,若返回FALSE则未处于调试状态
这里直接用编译器打开,处于调试状态
正常打开则处于未调试状态
IsDebuggerPresent
IsDebuggerPresent
这个API位于kernel32.dll
里面,首先通过0x18偏移找到TEB,再通过0x30找到PEB
然后在PEB的0x2偏移找到BeingDebugged
,这个值用来存储是否处于调试状态,PEB结构如下
0:023> dt 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 IsImageDynamicallyRelocated : Pos 2, 1 Bit
+0x003 SkipPatchingUser32Forwarders : Pos 3, 1 Bit
+0x003 IsPackagedProcess : Pos 4, 1 Bit
+0x003 IsAppContainer : Pos 5, 1 Bit
+0x003 IsProtectedProcessLight : Pos 6, 1 Bit
+0x003 IsLongPathAwareProcess : Pos 7, 1 Bit
+0x004 Padding0 : [4] UChar
+0x008 Mutant : Ptr64 Void
+0x010 ImageBaseAddress : Ptr64 Void
+0x018 Ldr : Ptr64 _PEB_LDR_DATA
+0x020 ProcessParameters : Ptr64 _RTL_USER_PROCESS_PARAMETERS
+0x028 SubSystemData : Ptr64 Void
+0x030 ProcessHeap : Ptr64 Void
+0x038 FastPebLock : Ptr64 _RTL_CRITICAL_SECTION
+0x040 AtlThunkSListPtr : Ptr64 _SLIST_HEADER
+0x048 IFEOKey : Ptr64 Void
+0x050 CrossProcessFlags : Uint4B
+0x050 ProcessInJob : Pos 0, 1 Bit
+0x050 ProcessInitializing : Pos 1, 1 Bit
+0x050 ProcessUsingVEH : Pos 2, 1 Bit
+0x050 ProcessUsingVCH : Pos 3, 1 Bit
+0x050 ProcessUsingFTH : Pos 4, 1 Bit
+0x050 ProcessPreviouslyThrottled : Pos 5, 1 Bit
+0x050 ProcessCurrentlyThrottled : Pos 6, 1 Bit
+0x050 ProcessImagesHotPatched : Pos 7, 1 Bit
+0x050 ReservedBits0 : Pos 8, 24 Bits
+0x054 Padding1 : [4] UChar
+0x058 KernelCallbackTable : Ptr64 Void
+0x058 UserSharedInfoPtr : Ptr64 Void
+0x060 SystemReserved : Uint4B
+0x064 AtlThunkSListPtr32 : Uint4B
+0x068 ApiSetMap : Ptr64 Void
+0x070 TlsExpansionCounter : Uint4B
+0x074 Padding2 : [4] UChar
+0x078 TlsBitmap : Ptr64 Void
+0x080 TlsBitmapBits : [2] Uint4B
+0x088 ReadOnlySharedMemoryBase : Ptr64 Void
+0x090 SharedData : Ptr64 Void
+0x098 ReadOnlyStaticServerData : Ptr64 Ptr64 Void
+0x0a0 AnsiCodePageData : Ptr64 Void
+0x0a8 OemCodePageData : Ptr64 Void
+0x0b0 UnicodeCaseTableData : Ptr64 Void
+0x0b8 NumberOfProcessors : Uint4B
+0x0bc NtGlobalFlag : Uint4B
+0x0c0 CriticalSectionTimeout : _LARGE_INTEGER
+0x0c8 HeapSegmentReserve : Uint8B
+0x0d0 HeapSegmentCommit : Uint8B
+0x0d8 HeapDeCommitTotalFreeThreshold : Uint8B
+0x0e0 HeapDeCommitFreeBlockThreshold : Uint8B
+0x0e8 NumberOfHeaps : Uint4B
+0x0ec MaximumNumberOfHeaps : Uint4B
+0x0f0 ProcessHeaps : Ptr64 Ptr64 Void
+0x0f8 GdiSharedHandleTable : Ptr64 Void
+0x100 ProcessStarterHelper : Ptr64 Void
+0x108 GdiDCAttributeList : Uint4B
+0x10c Padding3 : [4] UChar
+0x110 LoaderLock : Ptr64 _RTL_CRITICAL_SECTION
+0x118 OSMajorVersion : Uint4B
+0x11c OSMinorVersion : Uint4B
+0x120 OSBuildNumber : Uint2B
+0x122 OSCSDVersion : Uint2B
+0x124 OSPlatformId : Uint4B
+0x128 ImageSubsystem : Uint4B
+0x12c ImageSubsystemMajorVersion : Uint4B
+0x130 ImageSubsystemMinorVersion : Uint4B
+0x134 Padding4 : [4] UChar
+0x138 ActiveProcessAffinityMask : Uint8B
+0x140 GdiHandleBuffer : [60] Uint4B
+0x230 PostProcessInitRoutine : Ptr64 void
+0x238 TlsExpansionBitmap : Ptr64 Void
+0x240 TlsExpansionBitmapBits : [32] Uint4B
+0x2c0 SessionId : Uint4B
+0x2c4 Padding5 : [4] UChar
+0x2c8 AppCompatFlags : _ULARGE_INTEGER
+0x2d0 AppCompatFlagsUser : _ULARGE_INTEGER
+0x2d8 pShimData : Ptr64 Void
+0x2e0 AppCompatInfo : Ptr64 Void
+0x2e8 CSDVersion : _UNICODE_STRING
+0x2f8 ActivationContextData : Ptr64 _ACTIVATION_CONTEXT_DATA
+0x300 ProcessAssemblyStorageMap : Ptr64 _ASSEMBLY_STORAGE_MAP
+0x308 SystemDefaultActivationContextData : Ptr64 _ACTIVATION_CONTEXT_DATA
+0x310 SystemAssemblyStorageMap : Ptr64 _ASSEMBLY_STORAGE_MAP
+0x318 MinimumStackCommit : Uint8B
+0x320 SparePointers : [4] Ptr64 Void
+0x340 SpareUlongs : [5] Uint4B
+0x358 WerRegistrationData : Ptr64 Void
+0x360 WerShipAssertPtr : Ptr64 Void
+0x368 pUnused : Ptr64 Void
+0x370 pImageHeaderHash : Ptr64 Void
+0x378 TracingFlags : Uint4B
+0x378 HeapTracingEnabled : Pos 0, 1 Bit
+0x378 CritSecTracingEnabled : Pos 1, 1 Bit
+0x378 LibLoaderTracingEnabled : Pos 2, 1 Bit
+0x378 SpareTracingBits : Pos 3, 29 Bits
+0x37c Padding6 : [4] UChar
+0x380 CsrServerReadOnlySharedMemoryBase : Uint8B
+0x388 TppWorkerpListLock : Uint8B
+0x390 TppWorkerpList : _LIST_ENTRY
+0x3a0 WaitOnAddressHashTable : [128] Ptr64 Void
+0x7a0 TelemetryCoverageHeader : Ptr64 Void
+0x7a8 CloudFileFlags : Uint4B
+0x7ac CloudFileDiagFlags : Uint4B
+0x7b0 PlaceholderCompatibilityMode : Char
+0x7b1 PlaceholderCompatibilityModeReserved : [7] Char
+0x7b8 LeapSecondData : Ptr64 _LEAP_SECOND_DATA
+0x7c0 LeapSecondFlags : Uint4B
+0x7c0 SixtySecondEnabled : Pos 0, 1 Bit
+0x7c0 Reserved : Pos 1, 31 Bits
+0x7c4 NtGlobalFlag2 : Uint4B
这里如果用vc6的话会提示没有这个API
需要自己定义这个API
直接运行处于调试状态
直接运行则不会显示处于调试状态
NtQueryInformationProcess
NtQueryInformationProcess
是微软未公开的一个API,目前只能够通过一些结构的名字和逆向的方式来推断用途
// NtQueryInformationProcess 函数原型
__kernel_entry NTSTATUS NtQueryInformationProcess(
IN HANDLE ProcessHandle, // 进程句柄
IN PROCESSINFOCLASS ProcessInformationClass, // 检索的进程信息类型
OUT PVOID ProcessInformation, // 接收进程信息的缓冲区指针
IN ULONG ProcessInformationLength, // 缓冲区指针大小
OUT PULONG ReturnLength // 实际接收的进程信息大小
);
// PROCESSINFOCLASS 结构体原型
typedef enum _PROCESSINFOCLASS
{
ProcessBasicInformation, // 0x0
ProcessQuotaLimits,
ProcessIoCounters,
ProcessVmCounters,
ProcessTimes,
ProcessBasePriority,
ProcessRaisePriority,
ProcessDebugPort, // 0x7
ProcessExceptionPort,
ProcessAccessToken,
ProcessLdtInformation,
ProcessLdtSize,
ProcessDefaultHardErrorMode,
ProcessIoPortHandlers,
ProcessPooledUsageAndLimits,
ProcessWorkingSetWatch,
ProcessUserModeIOPL,
ProcessEnableAlignmentFaultFixup,
ProcessPriorityClass,
ProcessWx86Information,
ProcessHandleCount,
ProcessAffinityMask,
ProcessPriorityBoost,
ProcessDeviceMap,
ProcessSessionInformation,
ProcessForegroundInformation,
ProcessWow64Information, // 0x1A
ProcessImageFileName, // 0x1B
ProcessLUIDDeviceMapsEnabled,
ProcessBreakOnTermination,
ProcessDebugObjectHandle, // 0x1E
ProcessDebugFlags, // 0x1F
ProcessHandleTracing,
ProcessIoPriority,
ProcessExecuteFlags,
ProcessResourceManagement,
ProcessCookie,
ProcessImageInformation,
ProcessCycleTime,
ProcessPagePriority,
ProcessInstrumentationCallback,
ProcessThreadStackAllocation,
ProcessWorkingSetWatchEx,
ProcessImageFileNameWin32,
ProcessImageFileMapping,
ProcessAffinityUpdateMode,
ProcessMemoryAllocationMode,
ProcessGroupInformation,
ProcessTokenVirtualizationEnabled,
ProcessConsoleHostProcess,
ProcessWindowInformation,
ProcessHandleInformation,
ProcessMitigationPolicy,
ProcessDynamicFunctionTableInformation,
ProcessHandleCheckingMode,
ProcessKeepAliveCount,
ProcessRevokeFileHandles,
ProcessWorkingSetControl,
ProcessHandleTable,
ProcessCheckStackExtentsMode,
ProcessCommandLineInformation,
ProcessProtectionInformation,
ProcessMemoryExhaustion,
ProcessFaultInformation,
ProcessTelemetryIdInformation,
ProcessCommitReleaseInformation,
ProcessDefaultCpuSetsInformation,
ProcessAllowedCpuSetsInformation,
ProcessSubsystemProcess,
ProcessJobMemoryInformation,
ProcessInPrivate,
ProcessRaiseUMExceptionOnInvalidHandleClose,
ProcessIumChallengeResponse,
ProcessChildProcessInformation,
ProcessHighGraphicsPriorityInformation,
ProcessSubsystemInformation,
ProcessEnergyValues,
ProcessActivityThrottleState,
ProcessActivityThrottlePolicy,
ProcessWin32kSyscallFilterInformation,
ProcessDisableSystemAllowedCpuSets,
ProcessWakeInformation,
ProcessEnergyTrackingState,
ProcessManageWritesToExecutableMemory,REDSTONE3
ProcessCaptureTrustletLiveDump,
ProcessTelemetryCoverage,
ProcessEnclaveInformation,
ProcessEnableReadWriteVmLogging,
ProcessUptimeInformation,
ProcessImageSection,
ProcessDebugAuthInformation,
ProcessSystemResourceManagement,
ProcessSequenceNumber,
ProcessLoaderDetour,
ProcessSecurityDomainInformation,
ProcessCombineSecurityDomainsInformation,
ProcessEnableLogging,
ProcessLeapSecondInformation,
ProcessFiberShadowStackAllocation,
ProcessFreeFiberShadowStackAllocation,
MaxProcessInfoClass
} PROCESSINFOCLASS;
ProcessDebugPort
未公开的ntdll
的NtQueryInformationProcess()
函数接受一个信息类的参数用于查询。ProcessDebugPort(7)
是其中的一个信息类,kernel32
的CheckRemoteDebuggerPresent()
函数内部通过调用NtQueryInformationProcess()
来检测调试,而NtQueryInformationProcess
内部则是查询EPROCESS
结构体的DebugPort
字段,当进程正在被调试时,返回值为0xffffffff
,实现代码如下
typedef NTSTATUS(NTAPI* pfnNtQueryInformationProcess)(
_In_ HANDLE ProcessHandle,
_In_ UINT ProcessInformationClass,
_Out_ PVOID ProcessInformation,
_In_ ULONG ProcessInformationLength,
_Out_opt_ PULONG ReturnLength
);
bool NtQuery()
{
pfnNtQueryInformationProcess NtQueryInformationProcess = NULL; // 存放 ntdll 中 NtQueryInformationProcess 函数地址
NTSTATUS status; // NTSTATUS 错误代码,0:执行成功
DWORD isDebuggerPresent = -1; // 如果当前被调试,则 = ffffffff
HMODULE hNtDll = LoadLibrary(TEXT("ntdll.dll")); // ntdll 模块句柄
if (hNtDll)
{
// 取 NtQueryInformationProcess 函数地址
NtQueryInformationProcess = (pfnNtQueryInformationProcess)GetProcAddress(hNtDll, "NtQueryInformationProcess");
// 取地址成功
if (NtQueryInformationProcess)
{
// NtQueryInformationProcess 检测调试器
status = NtQueryInformationProcess(GetCurrentProcess(), ProcessDebugPort, &isDebuggerPresent, sizeof(DWORD), NULL);
// NtQueryInformationProcess 执行成功
if (status == 0 && isDebuggerPresent != 0)
{
// 输出
printf("status = %d\n", status);
printf("isDebuggerPresent = %x\n", isDebuggerPresent);
printf("当前处于调试状态\n");
getchar();
return 0;
}
}
}
// 输出
printf("status = %d\n", status);
printf("isDebuggerPresent = %x\n", isDebuggerPresent);
printf("当前未处于调试状态\n");
}
当isDebuggerPresent
的值为-1的时候处于调试状态
为0的时候则为正常启动
ProcessDebugObjectHandle
ProcessDebugObjectHandle
位于0x1E偏移,当status
不为0、isDebuggerPresent
不等于0时则处于调试状态
status = NtQueryInformationProcess(GetCurrentProcess(), 0x1E, &isDebuggerPresent, sizeof(DWORD), NULL);
ProcessDebugFlags
ProcessDebugFlags (0x1f)
类返回EPROCESS
结构体的NoDebugInherit
的相反数,当调试器存在时,返回值为0,不存在时则返回1
status = NtQueryInformationProcess(GetCurrentProcess(), 0x1F, &isDebuggerPresent, sizeof(DWORD), NULL);
if (status == 0 && isDebuggerPresent == 0)
父进程
我们一般正常模式启动程序其父进程一般都是explorer.exe
(不考虑服务进程),而当我们处于调试状态则父进程为调试器进程,那么我们就可以通过ntdll.dll
里面的NtQueryInformationProcess
来进行判断,实现代码如下
bool CheckParentProcess()
{
pfnNtQueryInformationProcess NtQueryInformationProcess = NULL; // 存放 ntdll 中 NtQueryInformationProcess 函数地址
HMODULE hNtDll = LoadLibrary(TEXT("ntdll.dll")); // ntdll 模块句柄
if (hNtDll)
{
struct PROCESS_BASIC_INFORMATION {
ULONG ExitStatus; // 进程返回码
PPEB PebBaseAddress; // PEB地址
ULONG AffinityMask; // CPU亲和性掩码
LONG BasePriority; // 基本优先级
ULONG UniqueProcessId; // 本进程PID
ULONG InheritedFromUniqueProcessId; // 父进程PID
}ProcInfo;
NtQueryInformationProcess = (pfnNtQueryInformationProcess)GetProcAddress(hNtDll, "NtQueryInformationProcess");
NtQueryInformationProcess(
GetCurrentProcess(),
ProcessBasicInformation, // 需要查询进程的基本信息
&ProcInfo,
sizeof(ProcInfo),
NULL);
DWORD ExplorerPID = 0;
DWORD CurrentPID = ProcInfo.InheritedFromUniqueProcessId;
GetWindowThreadProcessId(FindWindow(L"DebugPrint", NULL), &ExplorerPID);
return ExplorerPID == CurrentPID ? false : true;
}
}
实现效果如下
KernelDebuggerEnabled
NtQuerySystemInformation
被 ntdll.dll
导出,当第一个参数传入 0x23 (SystemInterruptInformation)
时,会返回一个 SYSTEM_KERNEL_DEBUGGER_INFORMATION
结构,里面的成员KdKdDebuggerEnable
和 KdDebuggerNotPresent
标志系统是否启用内核调试
typedef struct _SYSTEM_KERNEL_DEBUGGER_INFORMATION {
BOOLEAN KernelDebuggerEnabled;
BOOLEAN KernelDebuggerNotPresent;
} SYSTEM_KERNEL_DEBUGGER_INFORMATION, * PSYSTEM_KERNEL_DEBUGGER_INFORMATION;
typedef NTSTATUS(WINAPI* pNtQuerySystemInformation)(IN UINT SystemInformationClass, OUT PVOID SystemInformation, IN ULONG SystemInformationLength, OUT PULONG ReturnLength);
bool CheckSystemKernelDebuggerInformation()
{
pNtQuerySystemInformation NtQuerySystemInformation = NULL; // 存放 ntdll 中 NtQueryInformationProcess 函数地址
HMODULE hNtDll = LoadLibrary(TEXT("ntdll.dll")); // ntdll 模块句柄
if (hNtDll)
{
NtQuerySystemInformation = (pNtQuerySystemInformation)GetProcAddress(hNtDll, "NtQuerySystemInformation");
struct _SYSTEM_KERNEL_DEBUGGER_INFORMATION {
BOOLEAN KernelDebuggerEnabled;
BOOLEAN KernelDebuggerNotPresent;
}DebuggerInfo = { 0 };
NtQuerySystemInformation(
(SYSTEM_INFORMATION_CLASS)0x23, // 查询信息类型
&DebuggerInfo, // 输出查询信息
sizeof(DebuggerInfo), // 查询类型大小
NULL); // 实际返回大小
// 通过是否开启内核调试器知道当前系统有没有被调试
return DebuggerInfo.KernelDebuggerEnabled;
}
因为这里检测的是否启用内核调试,这里直接运行是不处于调试状态
使用调试模式启动win10运行则显示处于调试状态
ThreadHideFromDebugger
通过ZwSetInformationThread
函数,设置 ThreadHideFromDebugger
此参数将使这条线程对调试器隐藏,即调试器收不到调试信息
typedef enum THREAD_INFO_CLASS {
ThreadHideFromDebugger = 17
};
typedef NTSTATUS(NTAPI* ZW_SET_INFORMATION_THREAD)(
IN HANDLE ThreadHandle,
IN THREAD_INFO_CLASS ThreadInformaitonClass,
IN PVOID ThreadInformation,
IN ULONG ThreadInformationLength);
void ZSIT_DetachDebug()
{
ZW_SET_INFORMATION_THREAD ZwSetInformationThread;
ZwSetInformationThread = (ZW_SET_INFORMATION_THREAD)GetProcAddress(LoadLibrary(L"ntdll.dll"), "ZwSetInformationThread");
ZwSetInformationThread(GetCurrentThread(), ThreadHideFromDebugger, NULL, NULL);
}
这里调用函数之后为了证明程序运行成功,这里加上一行输出语句
然后尝试使用od附加,这里可以看到是没有DebugPrint.exe
这个进程的
那么这里为了更明显一点,首先看下QQ的PID是3872对应十六进制为F20是能够对应上的
然后计算DebugPrint.exe
的十六进制为5D8,在进程里面是看不到的