Windows内核中根据PID查找兄弟进程和父进程

在Windows内核驱动开发中,根据进程ID(PID)查找相关进程(如兄弟进程和父进程)是一个常见需求。这涉及到对Windows内核进程管理机制的理解和使用。下面我将详细介绍实现这一功能的方法。

基本概念

在Windows内核中:

  • 父进程:创建当前进程的进程
  • 兄弟进程:由同一个父进程创建的其他进程
  • 进程关系:通过EPROCESS结构中的信息可以获取

方法实现

1. 查找父进程

/*
 * 根据PID查找父进程信息
 * @param ProcessId 目标进程的PID
 * @return 父进程的PID,失败返回0
 */
HANDLE GetParentProcessId(HANDLE ProcessId)
{
    NTSTATUS status = STATUS_SUCCESS;
    PEPROCESS Process = NULL;
    HANDLE ParentPid = 0;
    
    // 通过PID获取EPROCESS结构
    status = PsLookupProcessByProcessId(ProcessId, &Process);
    if (!NT_SUCCESS(status))
    {
        KdPrint(("PsLookupProcessByProcessId failed: 0x%08X\n", status));
        return 0;
    }
    
    // 获取父进程PID
    // 注意: 这里使用未公开的字段,可能随Windows版本变化
    #if (NTDDI_VERSION >= NTDDI_WIN10)
        // Windows 10+
        ParentPid = PsGetProcessInheritedFromUniqueProcessId(Process);
    #else
        // Windows 8/7 - 使用偏移访问InheritedFromUniqueProcessId字段
        // 注意: 这些偏移可能随系统版本不同而变化
        ParentPid = *(HANDLE*)((PUCHAR)Process + FIELD_OFFSET(EPROCESS, InheritedFromUniqueProcessId));
    #endif
    
    // 释放引用
    ObDereferenceObject(Process);
    
    return ParentPid;
}

2. 查找兄弟进程 (使用ZwQuerySystemInformation)

/*
 * 根据PID查找所有兄弟进程
 * @param ProcessId 目标进程的PID
 * @param SiblingPids 用于存储兄弟进程PID的数组
 * @param MaxCount 数组最大容量
 * @param ActualCount 实际找到的兄弟进程数量
 * @return NTSTATUS 操作状态
 */
NTSTATUS FindSiblingProcesses(
    HANDLE ProcessId,
    PHANDLE SiblingPids,
    ULONG MaxCount,
    PULONG ActualCount
)
{
    NTSTATUS status;
    HANDLE parentPid;
    PVOID buffer = NULL;
    ULONG bufferSize = 0x10000; // 初始缓冲区大小
    PSYSTEM_PROCESS_INFORMATION procInfo = NULL;
    ULONG count = 0;
    
    if (ActualCount) *ActualCount = 0;
    
    // 获取父进程ID
    parentPid = GetParentProcessId(ProcessId);
    if (parentPid == 0)
    {
        return STATUS_NOT_FOUND;
    }
    
    // 分配内存用于存储进程信息
    buffer = ExAllocatePoolWithTag(PagedPool, bufferSize, 'CORP');
    if (buffer == NULL)
    {
        return STATUS_INSUFFICIENT_RESOURCES;
    }
    
    // 查询系统进程信息
    status = ZwQuerySystemInformation(
        SystemProcessInformation,
        buffer,
        bufferSize,
        NULL
    );
    
    // 如果缓冲区不足,重新分配更大的缓冲区
    if (status == STATUS_INFO_LENGTH_MISMATCH)
    {
        ExFreePoolWithTag(buffer, 'CORP');
        bufferSize *= 2;
        buffer = ExAllocatePoolWithTag(PagedPool, bufferSize, 'CORP');
        if (buffer == NULL)
        {
            return STATUS_INSUFFICIENT_RESOURCES;
        }
        
        status = ZwQuerySystemInformation(
            SystemProcessInformation,
            buffer,
            bufferSize,
            NULL
        );
    }
    
    if (!NT_SUCCESS(status))
    {
        ExFreePoolWithTag(buffer, 'CORP');
        return status;
    }
    
    // 遍历进程信息
    procInfo = (PSYSTEM_PROCESS_INFORMATION)buffer;
    while (TRUE)
    {
        HANDLE currentPid = (HANDLE)procInfo->UniqueProcessId;
        HANDLE currentParentPid = GetParentProcessId(currentPid);
        
        // 如果当前进程的父进程是目标进程的父进程,且不是目标进程自身
        if (currentParentPid == parentPid && currentPid != ProcessId)
        {
            // 将兄弟进程ID添加到结果数组
            if (SiblingPids && count < MaxCount)
            {
                SiblingPids[count] = currentPid;
            }
            count++;
        }
        
        // 移动到下一个进程信息
        if (procInfo->NextEntryOffset == 0)
            break;
            
        procInfo = (PSYSTEM_PROCESS_INFORMATION)((PUCHAR)procInfo + procInfo->NextEntryOffset);
    }
    
    // 释放缓冲区
    ExFreePoolWithTag(buffer, 'CORP');
    
    // 返回实际找到的兄弟进程数量
    if (ActualCount) *ActualCount = count;
    
    return STATUS_SUCCESS;
}

3. 查找兄弟进程 (使用进程枚举回调)

// 进程枚举上下文结构
typedef struct _PROCESS_ENUM_CONTEXT {
    HANDLE ParentPid;       // 父进程ID
    HANDLE TargetPid;       // 目标进程ID
    PHANDLE SiblingPids;    // 兄弟进程ID数组
    ULONG MaxCount;         // 数组最大容量
    ULONG Count;            // 当前找到的兄弟进程数量
} PROCESS_ENUM_CONTEXT, *PPROCESS_ENUM_CONTEXT;

// 进程枚举回调函数
BOOLEAN ProcessEnumCallback(
    PEPROCESS Process,
    PVOID Context
)
{
    PPROCESS_ENUM_CONTEXT enumContext = (PPROCESS_ENUM_CONTEXT)Context;
    HANDLE processPid = PsGetProcessId(Process);
    HANDLE parentPid = 0;
    
    // 获取当前进程的父进程ID
    #if (NTDDI_VERSION >= NTDDI_WIN10)
        parentPid = PsGetProcessInheritedFromUniqueProcessId(Process);
    #else
        parentPid = *(HANDLE*)((PUCHAR)Process + FIELD_OFFSET(EPROCESS, InheritedFromUniqueProcessId));
    #endif
    
    // 检查是否为兄弟进程
    if (parentPid == enumContext->ParentPid && processPid != enumContext->TargetPid)
    {
        // 添加到结果数组
        if (enumContext->SiblingPids && enumContext->Count < enumContext->MaxCount)
        {
            enumContext->SiblingPids[enumContext->Count] = processPid;
        }
        enumContext->Count++;
    }
    
    // 返回TRUE继续枚举
    return TRUE;
}

/*
 * 使用进程枚举查找兄弟进程
 * @param ProcessId 目标进程的PID
 * @param SiblingPids 用于存储兄弟进程PID的数组
 * @param MaxCount 数组最大容量
 * @param ActualCount 实际找到的兄弟进程数量
 * @return NTSTATUS 操作状态
 */
NTSTATUS FindSiblingProcessesEx(
    HANDLE ProcessId,
    PHANDLE SiblingPids,
    ULONG MaxCount,
    PULONG ActualCount
)
{
    NTSTATUS status;
    PEPROCESS Process = NULL;
    HANDLE parentPid = 0;
    PROCESS_ENUM_CONTEXT context = {0};
    
    if (ActualCount) *ActualCount = 0;
    
    // 获取目标进程的EPROCESS结构
    status = PsLookupProcessByProcessId(ProcessId, &Process);
    if (!NT_SUCCESS(status))
    {
        return status;
    }
    
    // 获取父进程ID
    #if (NTDDI_VERSION >= NTDDI_WIN10)
        parentPid = PsGetProcessInheritedFromUniqueProcessId(Process);
    #else
        parentPid = *(HANDLE*)((PUCHAR)Process + FIELD_OFFSET(EPROCESS, InheritedFromUniqueProcessId));
    #endif
    
    // 释放进程对象引用
    ObDereferenceObject(Process);
    
    if (parentPid == 0)
    {
        return STATUS_NOT_FOUND;
    }
    
    // 初始化枚举上下文
    context.ParentPid = parentPid;
    context.TargetPid = ProcessId;
    context.SiblingPids = SiblingPids;
    context.MaxCount = MaxCount;
    context.Count = 0;
    
    // 枚举所有进程
    status = PsEnumerateProcesses(ProcessEnumCallback, &context);
    
    // 返回找到的兄弟进程数量
    if (ActualCount) *ActualCount = context.Count;
    
    return status;
}

4. 使用示例

VOID TestProcessRelations(HANDLE PID)
{
    HANDLE parentPid = 0;
    HANDLE siblingPids[100] = {0};
    ULONG count = 0;
    NTSTATUS status;
    
    // 获取父进程ID
    parentPid = GetParentProcessId(PID);
    if (parentPid != 0)
    {
        DbgPrint("Process %u's parent PID: %u\n", (ULONG)(ULONG_PTR)PID, (ULONG)(ULONG_PTR)parentPid);
    }
    else
    {
        DbgPrint("Failed to get parent PID for process %u\n", (ULONG)(ULONG_PTR)PID);
    }
    
    // 获取兄弟进程
    status = FindSiblingProcesses(PID, siblingPids, 100, &count);
    if (NT_SUCCESS(status))
    {
        DbgPrint("Found %u sibling processes for PID %u:\n", count, (ULONG)(ULONG_PTR)PID);
        for (ULONG i = 0; i < min(count, 100); i++)
        {
            DbgPrint("  Sibling PID: %u\n", (ULONG)(ULONG_PTR)siblingPids[i]);
        }
    }
    else
    {
        DbgPrint("Failed to find sibling processes: 0x%08X\n", status);
    }
}

注意事项与说明

1. 使用未公开API的风险

上述代码使用了一些未公开的API和结构体偏移,如EPROCESS内部字段:

// 这些访问可能在不同Windows版本中失效
ParentPid = *(HANDLE*)((PUCHAR)Process + FIELD_OFFSET(EPROCESS, InheritedFromUniqueProcessId));

在正式产品中,应考虑动态确定这些偏移,或采用更可靠的替代方法。

2. 版本适配

Windows内核结构随系统版本变化,应根据目标系统版本调整代码:

#if (NTDDI_VERSION >= NTDDI_WIN10_RS3)
    // Windows 10 RS3及更高版本的代码
#elif (NTDDI_VERSION >= NTDDI_WIN10)
    // Windows 10初始版本的代码
#elif (NTDDI_VERSION >= NTDDI_WINBLUE)
    // Windows 8.1的代码
#else
    // 较旧Windows版本的代码
#endif

3. 安全性考虑

处理进程信息时需注意安全性:

  1. 权限检查:确保调用代码有足够权限访问目标进程
  2. 引用计数:正确管理ObDereferenceObject调用,避免引用泄漏
  3. 内存管理:分配的池内存必须释放,避免内存泄漏

4. Windows 10+ 的推荐方式

在Windows 10及更高版本中,可以使用更稳定的API:

// 获取父进程ID (Windows 10+)
HANDLE GetParentProcessIdWin10(HANDLE ProcessId)
{
    NTSTATUS status;
    PEPROCESS Process;
    HANDLE ParentPid = 0;
    
    status = PsLookupProcessByProcessId(ProcessId, &Process);
    if (NT_SUCCESS(status))
    {
        ParentPid = PsGetProcessInheritedFromUniqueProcessId(Process);
        ObDereferenceObject(Process);
    }
    
    return ParentPid;
}

5. 更完整的兄弟进程搜索实现

以下是一个更完整的兄弟进程搜索实现,包含更多错误处理和优化:

NTSTATUS EnumerateSiblingProcesses(
    IN HANDLE TargetProcessId,
    IN PVOID OutputBuffer,
    IN ULONG OutputBufferSize,
    OUT PULONG ReturnLength
)
{
    NTSTATUS status = STATUS_SUCCESS;
    HANDLE parentProcessId = NULL;
    PSIBLINGS_INFO siblingInfo = (PSIBLINGS_INFO)OutputBuffer;
    ULONG requiredSize = sizeof(SIBLINGS_INFO);
    ULONG siblingCount = 0;
    PVOID processInfoBuffer = NULL;
    ULONG processInfoSize = 0x20000;
    PSYSTEM_PROCESS_INFORMATION processInfo = NULL;
    PSYSTEM_PROCESS_INFORMATION currentProcess = NULL;
    
    // 参数检查
    if (!OutputBuffer || OutputBufferSize < sizeof(SIBLINGS_INFO) || !ReturnLength)
    {
        return STATUS_INVALID_PARAMETER;
    }
    
    *ReturnLength = 0;
    
    // 获取目标进程的父进程ID
    parentProcessId = GetParentProcessId(TargetProcessId);
    if (parentProcessId == NULL)
    {
        return STATUS_PROCESS_NOT_IN_JOB;
    }
    
    // 分配缓冲区并查询系统进程信息
    processInfoBuffer = ExAllocatePoolWithTag(PagedPool, processInfoSize, 'forP');
    if (!processInfoBuffer)
    {
        return STATUS_INSUFFICIENT_RESOURCES;
    }
    
    status = ZwQuerySystemInformation(
        SystemProcessInformation,
        processInfoBuffer,
        processInfoSize,
        NULL
    );
    
    if (status == STATUS_INFO_LENGTH_MISMATCH)
    {
        ExFreePoolWithTag(processInfoBuffer, 'forP');
        processInfoSize *= 2;
        processInfoBuffer = ExAllocatePoolWithTag(PagedPool, processInfoSize, 'forP');
        if (!processInfoBuffer)
        {
            return STATUS_INSUFFICIENT_RESOURCES;
        }
        
        status = ZwQuerySystemInformation(
            SystemProcessInformation,
            processInfoBuffer,
            processInfoSize,
            NULL
        );
    }
    
    if (!NT_SUCCESS(status))
    {
        ExFreePoolWithTag(processInfoBuffer, 'forP');
        return status;
    }
    
    // 第一次遍历:计算所需空间
    currentProcess = (PSYSTEM_PROCESS_INFORMATION)processInfoBuffer;
    while (TRUE)
    {
        HANDLE currentPid = (HANDLE)currentProcess->UniqueProcessId;
        HANDLE currentParentPid = GetParentProcessId(currentPid);
        
        if (currentParentPid == parentProcessId && currentPid != TargetProcessId)
        {
            siblingCount++;
            requiredSize += sizeof(PROCESS_BASIC_INFO);
        }
        
        if (currentProcess->NextEntryOffset == 0)
            break;
            
        currentProcess = (PSYSTEM_PROCESS_INFORMATION)((PUCHAR)currentProcess + currentProcess->NextEntryOffset);
    }
    
    // 返回所需缓冲区大小
    *ReturnLength = requiredSize;
    
    // 检查缓冲区是否足够大
    if (OutputBufferSize < requiredSize)
    {
        ExFreePoolWithTag(processInfoBuffer, 'forP');
        return STATUS_BUFFER_TOO_SMALL;
    }
    
    // 初始化输出结构
    RtlZeroMemory(siblingInfo, requiredSize);
    siblingInfo->ParentProcessId = parentProcessId;
    siblingInfo->NumberOfSiblings = siblingCount;
    
    // 再次遍历:填充兄弟进程信息
    PPROCESS_BASIC_INFO processBasicInfo = siblingInfo->ProcessInfo;
    ULONG processIndex = 0;
    
    currentProcess = (PSYSTEM_PROCESS_INFORMATION)processInfoBuffer;
    while (TRUE)
    {
        HANDLE currentPid = (HANDLE)currentProcess->UniqueProcessId;
        HANDLE currentParentPid = GetParentProcessId(currentPid);
        
        if (currentParentPid == parentProcessId && currentPid != TargetProcessId)
        {
            processBasicInfo[processIndex].ProcessId = currentPid;
            
            // 复制进程名称
            if (currentProcess->ImageName.Buffer)
            {
                ULONG nameLength = min(
                    currentProcess->ImageName.Length, 
                    sizeof(processBasicInfo[processIndex].ImageName) - sizeof(WCHAR)
                );
                
                RtlCopyMemory(
                    processBasicInfo[processIndex].ImageName,
                    currentProcess->ImageName.Buffer,
                    nameLength
                );
            }
            
            processIndex++;
        }
        
        if (currentProcess->NextEntryOffset == 0)
            break;
            
        currentProcess = (PSYSTEM_PROCESS_INFORMATION)((PUCHAR)currentProcess + currentProcess->NextEntryOffset);
    }
    
    // 释放资源
    ExFreePoolWithTag(processInfoBuffer, 'forP');
    
    return STATUS_SUCCESS;
}

实现细节说明

1. 进程信息结构

上述代码示例使用了以下结构:

// 定义进程基本信息结构
typedef struct _PROCESS_BASIC_INFO {
    HANDLE ProcessId;                    // 进程ID
    WCHAR ImageName[MAX_PATH];           // 进程镜像名称
} PROCESS_BASIC_INFO, *PPROCESS_BASIC_INFO;

// 定义兄弟进程信息结构
typedef struct _SIBLINGS_INFO {
    HANDLE ParentProcessId;              // 父进程ID
    ULONG NumberOfSiblings;              // 兄弟进程数量
    PROCESS_BASIC_INFO ProcessInfo[1];   // 兄弟进程信息数组(可变长度)
} SIBLINGS_INFO, *PSIBLINGS_INFO;

2. 内部系统API

代码中使用了以下未公开的内核API和结构:

// 系统信息类型枚举
typedef enum _SYSTEM_INFORMATION_CLASS {
    SystemProcessInformation = 5,
    // ...其他信息类型
} SYSTEM_INFORMATION_CLASS;

// 进程信息结构
typedef struct _SYSTEM_PROCESS_INFORMATION {
    ULONG NextEntryOffset;
    ULONG NumberOfThreads;
    LARGE_INTEGER SpareLi1;
    LARGE_INTEGER SpareLi2;
    LARGE_INTEGER SpareLi3;
    LARGE_INTEGER CreateTime;
    LARGE_INTEGER UserTime;
    LARGE_INTEGER KernelTime;
    UNICODE_STRING ImageName;
    KPRIORITY BasePriority;
    HANDLE UniqueProcessId;
    HANDLE InheritedFromUniqueProcessId;
    ULONG HandleCount;
    ULONG SessionId;
    ULONG_PTR PageDirectoryBase;
    // ...更多字段
} SYSTEM_PROCESS_INFORMATION, *PSYSTEM_PROCESS_INFORMATION;

// ZwQuerySystemInformation函数声明
NTSYSCALLAPI
NTSTATUS
NTAPI
ZwQuerySystemInformation(
    _In_ SYSTEM_INFORMATION_CLASS SystemInformationClass,
    _Out_writes_bytes_opt_(SystemInformationLength) PVOID SystemInformation,
    _In_ ULONG SystemInformationLength,
    _Out_opt_ PULONG ReturnLength
);

兼容性和稳定性改进建议

  1. 使用官方API:尽可能使用Microsoft公开记录的API,而不是依赖内部结构

  2. 动态偏移计算:如果必须访问内部结构,考虑在驱动加载时动态计算偏移量

  3. 版本检测:添加操作系统版本检测,针对不同Windows版本使用不同实现

  4. 错误处理:实现全面的错误处理,确保资源始终正确释放

  5. 应用示例:在生产环境中部署前进行全面测试

结论

本文详细介绍了在Windows内核中根据PID查找父进程和兄弟进程的方法。通过访问进程管理相关的内核结构,可以构建进程之间的关系图。这种功能在系统监控、安全软件和进程管理工具中非常有用。

请注意,内核开发涉及到系统底层操作,错误的实现可能导致系统不稳定。在生产环境中使用前,应该进行全面的测试,并考虑不同Windows版本的兼容性问题。

你可能感兴趣的:(windows驱动内核开发,windows内核,父进程,兄弟进程)