如何查找程序的Shadow(隐藏)模块?现有方法失效求改进
可靠的Shadow模块地址查找方案
问题背景
原本通过遍历内存区域、对比模块大小与主模块(League of Legends.exe)大小且地址不同的方式判定Shadow模块,但卸载DLL重新注入后该方法失效。推测Shadow模块大小发生了变化(主模块大小未变),怀疑是DLL中执行的VirtualAlloc、memcpy等内存操作导致Shadow模块大小改变,进而使查找函数失效。
相关日志
[2023-01-22 13:56:32] ----------------- MFX.dll injected in to the League process ----------------- [2023-01-22 13:56:32] MinHook Initalized!... [2023-01-22 13:56:32] WndProc Hook... [2023-01-22 13:56:32] MAIN MODULE SIZE [2023-01-22 13:56:32] 52838400[2023-01-22 13:56:32] [2023-01-22 14:20:39] ----------------- MFX.dll injected in to the League process -----------------
当前查找函数代码
bool MEM::GetShadowModule(std::uintptr_t& base_address) { /* The `shadow module` has the same size as the `league` module, but has a different base */ /* Get base address of `League of Legends.exe` */ /* Check if `League of Legends.exe` is loaded */ if (!LeagueBase) return false; /* Get Module information */ MODULEINFO module_info = { 0 }; if (!GetModuleInformation(GetCurrentProcess(), (HMODULE)LeagueBase, &module_info, sizeof(MODULEINFO))) return false; /* Get module size */ std::uintptr_t module_size = (std::uintptr_t)module_info.SizeOfImage; MEMORY_BASIC_INFORMATION memory_info = { 0 }; /* Start at end of league base */ std::uintptr_t current_address = LeagueBase + module_size; /* Loop until we find the shadow module */ while (VirtualQuery((LPCVOID)current_address, &memory_info, sizeof(MEMORY_BASIC_INFORMATION32))) { /* Check if the size is the same as the league module */ if (memory_info.RegionSize == module_size && memory_info.BaseAddress != (LPVOID)LeagueBase) { /*We found shadow module base address Set the base address*/ base_address = current_address; return true; } /* Move to the next region */ current_address += memory_info.RegionSize; } return false; }
内存操作代码
auto GateWay = VirtualAlloc(NULL, ByteSize + 5, MEM_RESERVE | MEM_COMMIT, //Create a Gateway PAGE_EXECUTE_READWRITE); memcpy(GateWay, (PVOID)HookAddress, ByteSize); // Copy Original Bytes to GateWay
更可靠的Shadow模块查找方法
1. 校验模块特征哈希
Shadow模块是主模块的副本,可计算主模块关键区域的哈希值(如PE头、代码段前10KB),遍历内存时对匹配大致大小的区域计算对应哈希,与主模块哈希一致则判定为目标模块。此方法不受内存区域大小对齐变化的影响。
2. 验证PE结构合法性
遍历内存区域时,按PE文件格式逐层校验:
- 检查起始地址的
IMAGE_DOS_HEADER.e_magic是否为0x5A4D(MZ标识) - 跳转至
IMAGE_NT_HEADERS,验证Signature是否为0x00004550(PE标识) - 对比PE头中的
SizeOfImage、NumberOfSections、节表名称等字段与主模块是否完全匹配
3. 内存属性+特征结合筛选
Shadow模块与主模块的内存属性高度一致,可结合以下条件缩小范围:
- 过滤掉自己DLL通过
VirtualAlloc分配的内存区域(记录分配地址范围,遍历中直接跳过) - 匹配内存区域的
Protect属性与主模块对应段一致(如代码段为PAGE_EXECUTE_READ) - 遍历范围扩展至整个进程地址空间,而非仅从主模块末尾开始
4. 优化现有遍历逻辑
- 不要严格匹配
RegionSize与主模块SizeOfImage,允许±4KB的对齐误差 - 找到候选区域后,验证其起始地址是否符合模块对齐规则(通常为0x1000或0x10000,与主模块对齐方式一致)
内容的提问来源于stack exchange,提问作者Pandorax
相关产品推荐
相关产品推荐

