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

如何查找程序的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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 01:00:53