You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现其他进程打开本进程句柄时的通知?现有代码问题排查

检测其他进程打开本进程句柄的实现方案及问题分析

能否实现该需求?

可以实现,但你的现有代码逻辑存在根本性错误,混淆了进程句柄的信号状态与句柄创建事件的概念。

当前代码失效的核心原因

你的Program A中,WaitForSingleObject和RegisterWaitForSingleObject的作用是等待进程终止信号:只有当目标进程(此处为Program A自身)退出时,进程句柄才会变为「有信号状态」,等待操作才会触发。而其他进程调用OpenProcess打开你的进程,只是在该进程的句柄表中创建了一个新的句柄实例,完全不会改变原进程句柄的信号状态,因此你的等待永远不会被触发。

正确的实现方案

方案1:用户态ETW监控(推荐,无需驱动)

通过ETW(Event Tracing for Windows)监听内核级的进程句柄打开事件,这是用户态下最可靠、高效的方法。核心是订阅Microsoft-Windows-Kernel-Process提供者的ProcessHandleOpen事件,当有进程打开你的进程句柄时,会实时收到事件通知。

简化版实现代码:

#include <windows.h>
#include <evntrace.h>
#include <evntcons.h>
#include <stdio.h>

// ETW事件回调函数,处理句柄打开事件
VOID WINAPI EtwHandleOpenCallback(PEVENT_RECORD EventRecord) {
    if (EventRecord->EventHeader.EventDescriptor.Id == EVENT_ID_PROCESS_HANDLE_OPEN) {
        PROCESS_HANDLE_OPEN_EVENT_DATA* eventData = (PROCESS_HANDLE_OPEN_EVENT_DATA*)EventRecord->UserData;
        // 检查被打开的进程是否是当前进程
        if (eventData->ProcessId == GetCurrentProcessId()) {
            printf("检测到进程 %lu 打开了本进程的句柄\n", eventData->CreatorProcessId);
        }
    }
}

int main() {
    // 初始化ETW会话属性
    const size_t propsSize = sizeof(EVENT_TRACE_PROPERTIES) + MAX_PATH * sizeof(WCHAR);
    EVENT_TRACE_PROPERTIES* sessionProps = (EVENT_TRACE_PROPERTIES*)malloc(propsSize);
    if (!sessionProps) {
        printf("内存分配失败\n");
        return 1;
    }
    ZeroMemory(sessionProps, propsSize);
    sessionProps->Wnode.BufferSize = propsSize;
    sessionProps->Wnode.Flags = WNODE_FLAG_TRACED_GUID;
    sessionProps->LogFileMode = EVENT_TRACE_REAL_TIME_MODE;
    sessionProps->LoggerNameOffset = offsetof(EVENT_TRACE_PROPERTIES, LoggerName);
    sessionProps->LogFileNameOffset = offsetof(EVENT_TRACE_PROPERTIES, LogFileName);

    TRACEHANDLE loggerHandle = 0;
    // 启动ETW会话
    if (StartTrace(&loggerHandle, L"ProcessHandleMonitor", sessionProps) != ERROR_SUCCESS) {
        printf("启动ETW会话失败,错误码:%d\n", GetLastError());
        free(sessionProps);
        return 1;
    }

    // 订阅内核进程提供者
    const GUID processProviderGuid = { 0x22FB2CD6, 0x0E7B, 0x422B, {0xA0, 0xC7, 0x2F, 0xAD, 0x1F, 0xD0, 0xE7, 0x16} };
    if (EnableTraceEx2(loggerHandle, &processProviderGuid, EVENT_CONTROL_CODE_ENABLE_PROVIDER,
        TRACE_LEVEL_INFORMATION, 0, 0, 0, NULL) != ERROR_SUCCESS) {
        printf("订阅提供者失败,错误码:%d\n", GetLastError());
        StopTrace(loggerHandle, L"ProcessHandleMonitor");
        free(sessionProps);
        return 1;
    }

    // 开始处理ETW事件
    EVENT_TRACE_LOGFILE traceConfig;
    ZeroMemory(&traceConfig, sizeof(EVENT_TRACE_LOGFILE));
    traceConfig.LoggerName = L"ProcessHandleMonitor";
    traceConfig.ProcessTraceMode = PROCESS_TRACE_MODE_REAL_TIME | PROCESS_TRACE_MODE_EVENT_RECORD;
    traceConfig.EventRecordCallback = EtwHandleOpenCallback;

    printf("开始监控进程句柄打开事件...(按Ctrl+C退出)\n");
    if (ProcessTrace(&traceConfig, 1, NULL, NULL) != ERROR_SUCCESS) {
        printf("处理事件失败,错误码:%d\n", GetLastError());
    }

    // 清理资源
    StopTrace(loggerHandle, L"ProcessHandleMonitor");
    free(sessionProps);
    return 0;
}

注意:该程序需要以管理员权限运行,编译时需链接advapi32.lib和ntdll.lib。

方案2:轮询枚举进程句柄(简单但低效)

定期调用NtQuerySystemInformation枚举系统中所有打开的句柄,筛选出指向当前进程的句柄,通过对比前后两次枚举的结果,发现新增的句柄。这种方法存在延迟,且需要较高权限,适合简单测试场景。

对Program B的优化建议

你的Program B硬编码了PID,实际使用时应通过命令行参数传入目标PID,同时记得关闭打开的句柄避免资源泄漏:

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

int main(int argc, char* argv[]) {
    if (argc < 2) {
        printf("使用方式:%s <目标进程PID>\n", argv[0]);
        return 1;
    }

    DWORD targetPid = atoi(argv[1]);
    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, targetPid);
    if (hProcess == NULL) {
        printf("打开进程失败,错误码:%d\n", GetLastError());
        return 1;
    }

    printf("成功打开进程 %lu 的句柄\n", targetPid);
    CloseHandle(hProcess); // 关闭句柄,避免泄漏
    return 0;
}

内容的提问来源于stack exchange,提问作者getunstuckaq

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 01:20:41