如何检测外部进程通过OpenProcess打开我方进程句柄并获通知?
检测外部作弊器的进程句柄读取行为
一、用户态检测方案(无需内核驱动)
1. 未公开API句柄枚举与权限校验
Windows没有公开的全局句柄枚举函数,但可以通过NtQuerySystemInformation(需动态加载)获取系统所有句柄信息,再筛选指向目标游戏进程且带有PROCESS_VM_READ权限的句柄。
示例C++实现:
#include <windows.h> #include <winternl.h> #include <stdio.h> // 定义未公开API与结构体 typedef NTSTATUS(WINAPI* pNtQuerySystemInformation)( SYSTEM_INFORMATION_CLASS SystemInformationClass, PVOID SystemInformation, ULONG SystemInformationLength, PULONG ReturnLength ); typedef struct _SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX { PVOID Object; HANDLE UniqueProcessId; HANDLE HandleValue; ULONG GrantedAccess; USHORT CreatorBackTraceIndex; USHORT ObjectTypeIndex; ULONG HandleAttributes; ULONG Reserved; } SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX, *PSYSTEM_HANDLE_TABLE_ENTRY_INFO_EX; typedef struct _SYSTEM_HANDLE_INFORMATION_EX { ULONG NumberOfHandles; ULONG Reserved; SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX Handles[1]; } SYSTEM_HANDLE_INFORMATION_EX, *PSYSTEM_HANDLE_INFORMATION_EX; void ScanSuspiciousHandles(DWORD gamePid) { HMODULE ntdll = GetModuleHandle(L"ntdll.dll"); pNtQuerySystemInformation NtQuerySystemInfo = (pNtQuerySystemInformation)GetProcAddress(ntdll, "NtQuerySystemInformation"); ULONG bufSize = 0x10000; PVOID buf = malloc(bufSize); NTSTATUS status; // 动态调整缓冲区大小 while ((status = NtQuerySystemInfo((SYSTEM_INFORMATION_CLASS)0x10, buf, bufSize, &bufSize)) == STATUS_INFO_LENGTH_MISMATCH) { free(buf); buf = malloc(bufSize); } if (NT_SUCCESS(status)) { PSYSTEM_HANDLE_INFORMATION_EX handleInfo = (PSYSTEM_HANDLE_INFORMATION_EX)buf; for (ULONG i = 0; i < handleInfo->NumberOfHandles; i++) { auto& handle = handleInfo->Handles[i]; // ObjectTypeIndex=7对应进程对象(不同Windows版本可能需验证调整) if (handle.ObjectTypeIndex != 7) continue; // 跳过游戏进程自身的句柄 if ((DWORD)handle.UniqueProcessId == gamePid) continue; // 检查目标是否为游戏进程且拥有读权限 if ((DWORD)handle.Object == gamePid && (handle.GrantedAccess & PROCESS_VM_READ)) { printf("可疑进程[%d]持有游戏进程句柄,权限包含PROCESS_VM_READ\n", (DWORD)handle.UniqueProcessId); } } } free(buf); }
2. API挂钩监控OpenProcess/ReadProcessMemory
使用Detours或EasyHook等库,在用户态挂钩目标API,拦截对游戏进程的读取请求:
- 挂钩
OpenProcess:检测是否有进程以PROCESS_VM_READ权限打开游戏进程 - 挂钩
ReadProcessMemory:监控对游戏进程内存的读取操作
示例Detours挂钩OpenProcess的代码:
#include <windows.h> #include <detours.h> #include <stdio.h> #define GAME_PID 1234 // 替换为你的游戏进程ID typedef HANDLE(WINAPI* pOpenProcess)( DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwProcessId ); pOpenProcess OriginalOpenProcess = NULL; HANDLE WINAPI HookedOpenProcess(DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwProcessId) { if (dwProcessId == GAME_PID && (dwDesiredAccess & PROCESS_VM_READ)) { DWORD callerPid = GetCurrentProcessId(); printf("进程[%d]尝试以PROCESS_VM_READ权限打开游戏进程\n", callerPid); // 可选:拒绝请求,返回NULL // return NULL; } return OriginalOpenProcess(dwDesiredAccess, bInheritHandle, dwProcessId); } void InitHook() { DetourTransactionBegin(); DetourUpdateThread(GetCurrentThread()); OriginalOpenProcess = (pOpenProcess)DetourFindFunction("kernel32.dll", "OpenProcess"); DetourAttach(&(PVOID&)OriginalOpenProcess, HookedOpenProcess); DetourTransactionCommit(); }
3. 内存页保护陷阱
通过VirtualProtect给游戏敏感内存区域添加PAGE_GUARD属性,当外部进程读取该区域时触发异常,在游戏进程的异常处理中捕获并告警:
#include <windows.h> #include <stdio.h> #define SENSITIVE_MEM_ADDR 0x00000000 // 替换为游戏敏感内存地址 LONG WINAPI GuardPageExceptionHandler(PEXCEPTION_POINTERS ep) { if (ep->ExceptionRecord->ExceptionCode == EXCEPTION_GUARD_PAGE) { DWORD callerPid; GetWindowThreadProcessId(GetForegroundWindow(), &callerPid); printf("进程[%d]访问受保护的游戏内存区域\n", callerPid); // 移除保护,避免重复触发 VirtualProtect(ep->ExceptionRecord->ExceptionAddress, 4096, PAGE_READWRITE, NULL); return EXCEPTION_CONTINUE_EXECUTION; } return EXCEPTION_CONTINUE_SEARCH; } void SetupMemoryGuard() { SetUnhandledExceptionFilter(GuardPageExceptionHandler); DWORD oldProtect; VirtualProtect((PVOID)SENSITIVE_MEM_ADDR, 4096, PAGE_READWRITE | PAGE_GUARD, &oldProtect); }
二、关键注意事项
NtQuerySystemInformation是未公开API,不同Windows版本的结构体定义可能有变化,需做好兼容性测试- 全局API挂钩需要注入到所有潜在作弊进程中,可使用EasyHook等支持跨进程挂钩的库
- VAC等商业反作弊会结合句柄枚举、API挂钩与行为分析(如频繁读取特定内存区域的进程)提升检测准确率
内容的提问来源于stack exchange,提问作者boooba
相关产品推荐
相关产品推荐

