查询父进程 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");
}