C++中如何通过文件映射对象句柄获取对应对象名称
问题背景
- 开发目标为解密某进程映射到内存中的媒体文件,目前已成功Hook
CreateFileW()与MapViewOfFile()函数,其余进程读写函数的Hook逻辑已调试通过、运行正常。 - 现存异常:其他进程通过当前Hook链路中的文件向其他文件执行写入操作时,会出现运行失败的问题。
- 待实现逻辑:需要在Hook的
MapViewOfFile()函数内部校验映射对象名称,识别到目标对象MYOBJECT后,对内存缓冲区中的数据做解密更新。 - 当前卡点:
MapViewOfFile()的Hook回调中仅持有传入的文件映射对象句柄hFileMappingObject,无法直接获取该句柄对应的对象名称,现有Hook代码框架如下:
LPVOID WINAPI MapViewHooked( __in HANDLE hFileMappingObject, __in DWORD dwDesiredAccess, __in DWORD dwFileOffsetHigh, __in DWORD dwFileOffsetLow, __in SIZE_T dwNumberOfBytesToMap ) { // string pszobjectnamefrom handle=some_function(hFileMappingObject);// 此处逻辑卡住 // if(pszobjectnamefrom=="MYOBJECT") { // 拿到缓冲区后执行解密逻辑 // } }
实现方案
通过句柄获取文件映射对象名称
Windows未提供公开的用户态API直接从句柄获取内核对象名称,可调用ntdll.dll导出的原生系统函数NtQueryObject实现,该函数虽未在官方文档中公开,但从Win2000到最新Win11版本的调用逻辑保持稳定,具体实现步骤如下:
- 动态加载
ntdll.dll,获取NtQueryObject的函数地址,提前定义好函数需要的枚举、结构类型 - 第一次调用
NtQueryObject,传入信息类为ObjectNameInformation,缓冲区参数传NULL,获取存储对象名称需要的缓冲区长度 - 按返回的长度申请堆内存,第二次调用
NtQueryObject将对象名称信息写入缓冲区,从返回的OBJECT_NAME_INFORMATION结构中读取Unicode格式的对象名 - 注意:不要使用
ObjectAllTypesInformation等需要遍历全系统句柄的信息类,会引发性能问题甚至Hook死锁;名称读取完成后及时释放申请的缓冲区。
对应实现代码如下:
// 提前定义原生API所需结构 typedef enum _OBJECT_INFORMATION_CLASS { ObjectNameInformation = 1, } OBJECT_INFORMATION_CLASS; typedef struct _UNICODE_STRING { USHORT Length; USHORT MaximumLength; PWSTR Buffer; } UNICODE_STRING, *PUNICODE_STRING; typedef struct _OBJECT_NAME_INFORMATION { UNICODE_STRING Name; } OBJECT_NAME_INFORMATION, *POBJECT_NAME_INFORMATION; typedef NTSTATUS(NTAPI* pNtQueryObject)( HANDLE Handle, OBJECT_INFORMATION_CLASS ObjectInformationClass, PVOID ObjectInformation, ULONG ObjectInformationLength, PULONG ReturnLength ); // 从句柄获取对象名的工具函数 std::wstring GetObjectNameFromHandle(HANDLE hObject) { pNtQueryObject NtQueryObject = (pNtQueryObject)GetProcAddress(GetModuleHandleW(L"ntdll.dll"), "NtQueryObject"); if (!NtQueryObject) return L""; ULONG retLen = 0; NTSTATUS status = NtQueryObject(hObject, ObjectNameInformation, NULL, 0, &retLen); if (retLen == 0) return L""; POBJECT_NAME_INFORMATION pNameInfo = (POBJECT_NAME_INFORMATION)HeapAlloc(GetProcessHeap(), 0, retLen); if (!pNameInfo) return L""; std::wstring objName; status = NtQueryObject(hObject, ObjectNameInformation, pNameInfo, retLen, &retLen); if (status >= 0 && pNameInfo->Name.Buffer != NULL && pNameInfo->Name.Length > 0) { objName.assign(pNameInfo->Name.Buffer, pNameInfo->Name.Length / sizeof(WCHAR)); } HeapFree(GetProcessHeap(), 0, pNameInfo); return objName; }
匹配目标对象后的解密逻辑实现
拿到对象名完成匹配后,按以下逻辑执行解密,避免引发访问异常或执行流错误:
- 所有分支必须优先调用原始的
MapViewOfFile()函数,传入全部收到的参数,拿到真实的映射内存基址,禁止在调用原函数前直接操作内存。 - 非目标对象的分支直接返回原函数的执行结果,不要篡改参数、返回值,这是之前跨文件写入失败的核心原因——非目标对象的执行流被Hook逻辑拦截,没有透传原函数的正确返回值。
- 匹配到
MYOBJECT对象后,根据dwNumberOfBytesToMap确定解密长度:如果该参数为0,调用VirtualQuery()查询映射段的完整区域长度。 - 调用
VirtualProtect()将待解密的映射内存区域临时修改为PAGE_READWRITE属性,记录原保护属性,在映射基址上原地执行解密逻辑,解密完成后恢复内存的原保护属性即可。
完整Hook逻辑框架参考:
// 保存原始MapViewOfFile函数地址 LPVOID(WINAPI* OriginalMapViewOfFile)(HANDLE, DWORD, DWORD, DWORD, SIZE_T) = MapViewOfFile; LPVOID WINAPI MapViewHooked( __in HANDLE hFileMappingObject, __in DWORD dwDesiredAccess, __in DWORD dwFileOffsetHigh, __in DWORD dwFileOffsetLow, __in SIZE_T dwNumberOfBytesToMap ) { // 先调用原函数拿到真实映射地址 LPVOID pMapBase = OriginalMapViewOfFile(hFileMappingObject, dwDesiredAccess, dwFileOffsetHigh, dwFileOffsetLow, dwNumberOfBytesToMap); if (!pMapBase) return NULL; // 读取对象名做匹配 std::wstring objName = GetObjectNameFromHandle(hFileMappingObject); if (objName.find(L"MYOBJECT") != std::wstring::npos) { SIZE_T decryptLen = dwNumberOfBytesToMap; if (decryptLen == 0) { MEMORY_BASIC_INFORMATION mbi = {0}; VirtualQuery(pMapBase, &mbi, sizeof(mbi)); decryptLen = mbi.RegionSize; } // 临时修改内存保护属性 DWORD oldProtect = 0; VirtualProtect(pMapBase, decryptLen, PAGE_READWRITE, &oldProtect); // 此处调用自己实现的解密函数,直接在pMapBase指向的内存上原地更新 // DecryptBuffer(pMapBase, decryptLen); // 恢复原保护属性 VirtualProtect(pMapBase, decryptLen, oldProtect, &oldProtect); } return pMapBase; }
注意:如果创建文件映射时传入的名称带
\Sessions\X\BaseNamedObjects\这类系统路径前缀,做字符串匹配时不要只做全字匹配,判断名称包含目标字符串即可,避免漏判。
内容的提问来源于stack exchange,提问作者Deepak Pathak
相关产品推荐
相关产品推荐

