DebugPort 字段

一、通过检测 DebugPort 字段

当调试器与进程建立调试关系后,调试器会通过调试子系统来设置被调试进程的内核结构体 EPROCESS 中的 DebugPort 字段,该字段保存的是调试端口,调试器和被调试进程通过这个端口来进行通信。

因此,我们可以通过检测该调试端口,来判断目标进程是否处于被调试状态。首先,我们来观察一下这个 DebugPort 字段所处的位置,来直观的感受一下。

我们打开双机调试,然后在虚拟机中打开 notepad.exe 程序,通过下面命令查询其 EPROCESS 结构地址:

!process 0 0 notepad.exe

结果如下:

然后通过下面命令进行观察其 EPROCESS 结构:

dt nt!_EPROCESS ffffe107c29ef1c0

结果如下:

我们可以看到,在 0x578 偏移处看到了 DebugPort 字段,当然这个偏移不是固定的,不同版本的系统会有变化。

接下来,我们通过一些方法来检测 DebugPort 字段。

1 通过 CheckRemoteDebuggerPresent 函数来检测

这个函数不仅可以检测自己进程是否被调试,同时也可以检测系统中的其他线程是否处于被调试状态。

我们来研究研究这个函数的底层实现,在 x64dbg 中任意打开一个进程,跳转到这个函数的代码:

可以看到,CheckRemoteDebuggerPresent 函数其实是调用了 NtQueryInformationProcess 来对 DebugPort 字段进行的检测。

我们现在用这个函数来检测调试器:

/* 通过 CheckRemoteDebuggerPresent 函数检测 DebugPort 字段 */
BOOL isDebug = FALSE;
if (CheckRemoteDebuggerPresent(GetCurrentProcess(), &isDebug))
{
    if (isDebug)
    {
        printf("正在被调试\r\n");
    }
    else
    {
        printf("没有被调试\r\n");
    }
}

结果如下:

2 通过 NtQueryInformationProcess 函数来检测

我们从刚刚的分析中可以知道,CheckRemoteDebuggerPresent 函数底层其实是通过调用 NtQueryInformationProcess 来检测 DebugPort 字段的,那么我们肯定也可以通过这个更底层的函数来进行检测。而且,由于这个函数是微软未公开的一个函数,相比 CheckRemoteDebuggerPresent 函数来说,反调试中被干掉的难度显著增加。

这个未公开函数的原型如下:

// NtQueryInformationProcess 函数原型
NTSTATUS NtQueryInformationProcess(
    IN HANDLE ProcessHandle,    // 需查询的进程句柄
    IN PROCESSINFOCLASS ProcessInformationClass,    // 需查询的进程信息枚举类型
    OUT PVOID ProcessInformation,    // 输出缓冲区
    IN ULONG ProcessInformationLength,    // 输出缓冲区大小
    OUT PULONG ReturnLength OPTIONAL    // 实际返回大小
);

其中,第二个参数是一个枚举类型,而我们需要传入的参数就是 ProcessDebugPort(7),其实就是对应的十进制的 7,我们可以用 x64dbg 打开第一个检测程序,然后在 CheckRemoteDebuggerPresent 函数处理下一个断点,单步运行到其底层函数 NtQueryInformationProcess 处停下:

从图中可以看到,RDX 寄存器的值为 7,也就是传入 NtQueryInformationProcess 函数的第二个参数,再次应证了我们的想法。

接下来我们给出检测代码:

#include <stdio.h>
#include <Windows.h>

typedef enum _PROCESSINFOCLASS {
    ProcessBasicInformation,
    ProcessQuotaLimits,
    ProcessIoCounters,
    ProcessVmCounters,
    ProcessTimes,
    ProcessBasePriority,
    ProcessRaisePriority,
    ProcessDebugPort,
    ProcessExceptionPort,
    ProcessAccessToken,
    ProcessLdtInformation,
    ProcessLdtSize,
    ProcessDefaultHardErrorMode,
    ProcessIoPortHandlers,          // Note: this is kernel mode only
    ProcessPooledUsageAndLimits,
    ProcessWorkingSetWatch,
    ProcessUserModeIOPL,
    ProcessEnableAlignmentFaultFixup,
    ProcessPriorityClass,
    ProcessWx86Information,
    ProcessHandleCount,
    ProcessAffinityMask,
    ProcessPriorityBoost,
    ProcessDeviceMap,
    ProcessSessionInformation,
    ProcessForegroundInformation,
    ProcessWow64Information,
    ProcessImageFileName,
    ProcessLUIDDeviceMapsEnabled,
    ProcessBreakOnTermination,
    ProcessDebugObjectHandle,
    ProcessDebugFlags,
    ProcessHandleTracing,
    ProcessIoPriority,
    ProcessExecuteFlags,
    ProcessResourceManagement,
    ProcessCookie,
    ProcessImageInformation,
    MaxProcessInfoClass             // MaxProcessInfoClass should always be the last enum
} PROCESSINFOCLASS;

typedef NTSTATUS(NTAPI* PFN_NtQueryInformationProcess)(
	HANDLE ProcessHandle,    // 需查询的进程句柄
	DWORD ProcessInformationClass,    // 需查询的进程信息枚举类型
	PVOID ProcessInformation,    // 输出缓冲区
	ULONG ProcessInformationLength,    // 输出缓冲区大小
	PULONG ReturnLength    // 实际返回大小
	);

typedef struct _PROCESS_BASIC_INFORMATION {
    NTSTATUS ExitStatus;
    PVOID PebBaseAddress;    // PPEB PebBaseAddress;
    ULONG_PTR AffinityMask;
    LONG BasePriority;    // KPRIORITY BasePriority;
    ULONG_PTR UniqueProcessId;
    ULONG_PTR InheritedFromUniqueProcessId;
} PROCESS_BASIC_INFORMATION;
typedef PROCESS_BASIC_INFORMATION* PPROCESS_BASIC_INFORMATION;

// 定义函数指针
PFN_NtQueryInformationProcess NtQueryInformationProcess;

int main
{
    /* 通过 NtQueryInformationProcess 函数检测 DebugPort 字段 */
    // 从 ntdll.dll 中获取 NtQueryInformationProcess 函数的地址
    HMODULE hMod = LoadLibraryA("ntdll.dll");
    NtQueryInformationProcess = (PFN_NtQueryInformationProcess)GetProcAddress(hMod, "NtQueryInformationProcess");

    // 查询是否存在 DebugPort
    PVOID DebugPort = NULL;
    NtQueryInformationProcess(GetCurrentProcess(), ProcessDebugPort, &DebugPort, sizeof(PVOID), NULL);    // ProcessDebugPort(7)
    if (DebugPort != NULL)
    {
        printf("正在被调试,传回的 DebugPort 为 %p\r\n", DebugPort);
    }
    else
    {
        printf("没有被调试\r\n");
    }
}
	

运行结果如下:

我们能够看到当用调试器启动或附加进程的时候,被调试进程是能够检测到被调试的,但是通过 NtQueryInformationProcess 函数返回的 DebugPort 字段却是 FFFFFFFFFFFFFFFF,这是因为 DebugPort 是一个重要的内核字段,需要对用户层隐藏,因此如果进程没有被调试,则返回 NULL,如果正在被调试则返回 FFFFFFFFFFFFFFFF

posted @ 2025-02-12 22:15  lostin9772  阅读(4)  评论(0)    收藏  举报