查询父进程 PID

一、通过查询父进程 PID

当我们的程序是通过在桌面或文件目录下通过鼠标双击打开的时候,该程序的父进程是 exploere.exe。为了更加深入的了解该原理,我们有必要详细了解一下一个进程的创建过程。

在 Windows 系统中,无论我们使用哪种方式打开一个新的程序,它都使用一套标准的流程来创建一个新进程。这个过程比较复杂,一般分为如下 6 个阶段:

  • 阶段 1:在父进程的用户空间中打开要执行的映像文件,确定其名称、类型和系统对它的设置选项。
  • 阶段 2:进入父进程的内核空间,为新进程创建 EPROCESS 结构、进程地址空间、KPROCESS 结构和 PEB。
  • 阶段 3:创建内核初始线程,但是创建时指定了挂起(suspend)标志,它并不会立刻开始运行。
  • 阶段 4:通知子系统服务程序。对于 Windows 程序,通知 Windows 子系统服务进程,即 CSRSS(负责处理与用户界面相关的任务,例如窗口管理、用户输入等),子系统会做必要的设置和登记(例如分配窗口句柄、初始化用户界面资源等),最后进程管理器(KiThreadStartup 等函数以及这些函数使用的数据结构泛称为进程管理器)会调用 PspUserThreadStartup 例程准备启动该线程。
  • 阶段 5:初始线程开始在内核空间中执行。
  • 阶段 6:通过 APC 机制,在新进程自己的用户空间中执行初始化动作。这一步最重要的工作就是通过 NTDLL.DLL 中的加载器,加载进程所依赖的 DLL 文件。

在上面的 6 个阶段中,前 4 个阶段都是在父进程或者子系统服务进程中完成的。这样做的原因是新进程创建之初,进程内的设施还不完善,执行某些任务可能有困难或者不可行。

我们通过 windbg 打开一个可执行程序,在执行完前 5 个阶段后,通过 APC 机制,正在新进程自己的用户空间中执行初始化动作(如加载进程所依赖的 DLL),并在 ntdll!LdrpDoDebuggerBreak 函数断下来:

由于我们是通过 windbg 打开文件的,所以该新进程初始阶段的前 4 个阶段都是在 windbg 中完成的,因此它的父进程也就是 windbg。

因此,如果一个进程的父进程不是 exploere.exe,那么它很可能处于调试状态,进一步判断父进程是否是一些常见的调试软件,就可以确定其处于调试状态。

1 通过 NtQueryInformationProcess 函数查询父进程 PID

我们可以在 NtQueryInformationProcess 函数中指定枚举常量 ProcessBasicInformation(0) 来查询一个进程的基本信息,其中就包括父进程的 PID:

#include <stdio.h>
#include <Windows.h>
#include <TlHelp32.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()
{
    /* 查询父进程 PID 判断是否被调试 */
    NtQueryInformationProcess = (PFN_NtQueryInformationProcess)
        GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtQueryInformationProcess");

    PROCESS_BASIC_INFORMATION basicInfo = { 0 };
    DWORD realSize = 0;
    NtQueryInformationProcess(GetCurrentProcess(), ProcessBasicInformation,
        &basicInfo, sizeof(basicInfo), &realSize);

    // 获取进程快照
    HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    if (hSnapshot == INVALID_HANDLE_VALUE)
    {
        printf("创建进程快照失败\r\n");
        return 0;
    }

    PROCESSENTRY32 pe32 = { 0 };
    // 初始化进程条目结构(不初始化无法查询)
    pe32.dwSize = sizeof(PROCESSENTRY32);
    ;
    if (Process32First(hSnapshot, &pe32))
    {
        do
        {
            if (wcscmp(L"explorer.exe", pe32.szExeFile) == 0)
            {
                if (pe32.th32ProcessID != basicInfo.InheritedFromUniqueProcessId)
                {
                    printf("程序可能被调试了\r\n");
                }
                else
                {
                    printf("程序没有被调试\r\n");
                }
                break;
            }
        } while (Process32Next(hSnapshot, &pe32));
    }
    else
    {
        printf("无法获取进程快照信息\r\n");
    }
    
    system("pause");
}
posted @ 2025-02-22 10:51  lostin9772  阅读(4)  评论(0)    收藏  举报