关于反射dll注入的一些概念和内容可以在我的文章里找到

dll的加载

loadlibrary

我们知道loadlibrary在kernelbase.dll中有很多个,loadlibraryA、loadlibraryW、loadlibraryExA、loadlibraryExW,实际上最终调用的都是loadlibraryExW,loadlibraryA是用于当传入的字符串为ascii编码时将字符串转换成unicode编码再传给loadlibraryW的,loadlibraryW再调用loadlibraryExW进行dll加载。

贴一段reactos的loadlibraryExW源码,在我的Win11下使用ida观察到的loadlibraryExW的逻辑和reactos中的基本一致,直接看带注释的reactos源码更方便理解,该函数检查dwflag的值进行一些分支行为,初始化unicode_string结构体,搜索dll路径,加载dll,最终返回dll句柄,在实际的windows中会多出一些安全检查。

LoadLibraryExW(LPCWSTR lpLibFileName,
               HANDLE hFile,
               DWORD dwFlags)
{
    UNICODE_STRING DllName;
    HINSTANCE hInst;
    NTSTATUS Status;
    PWSTR SearchPath;
    ULONG DllCharacteristics = 0;
    BOOL FreeString = FALSE;

    /* Check for any flags LdrLoadDll might be interested in */
    if (dwFlags & DONT_RESOLVE_DLL_REFERENCES)
    {
        /* Tell LDR to treat it as an EXE */
        DllCharacteristics = IMAGE_FILE_EXECUTABLE_IMAGE;
    }

    /* Build up a unicode dll name from null-terminated string */
    RtlInitUnicodeString(&DllName, (LPWSTR)lpLibFileName);

    /* Lazy-initialize BasepExeLdrEntry */
    if (!BasepExeLdrEntry)
        LdrEnumerateLoadedModules(0, BasepLocateExeLdrEntry, NtCurrentPeb()->ImageBaseAddress);

    /* Check if that module is our exe*/
    if (BasepExeLdrEntry && !(dwFlags & LOAD_LIBRARY_AS_DATAFILE) &&
        DllName.Length == BasepExeLdrEntry->FullDllName.Length)
    {
        /* Lengths match and it's not a datafile, so perform name comparison */
        if (RtlEqualUnicodeString(&DllName, &BasepExeLdrEntry->FullDllName, TRUE))
        {
            /* That's us! */
            return BasepExeLdrEntry->DllBase;
        }
    }

    /* Check for trailing spaces and remove them if necessary */
    if (DllName.Buffer[DllName.Length/sizeof(WCHAR) - 1] == L' ')
    {
        RtlCreateUnicodeString(&DllName, (LPWSTR)lpLibFileName);
        while (DllName.Length > sizeof(WCHAR) &&
            DllName.Buffer[DllName.Length/sizeof(WCHAR) - 1] == L' ')
        {
            DllName.Length -= sizeof(WCHAR);
        }
        DllName.Buffer[DllName.Length/sizeof(WCHAR)] = UNICODE_NULL;
        FreeString = TRUE;
    }

    /* Compute the load path */
    SearchPath = BaseComputeProcessDllPath((dwFlags & LOAD_WITH_ALTERED_SEARCH_PATH) ?
                                           DllName.Buffer : NULL,
                                           NULL);
    if (!SearchPath)
    {
        /* Getting DLL path failed, so set last error, free mem and return */
        BaseSetLastNTError(STATUS_NO_MEMORY);
        if (FreeString) RtlFreeUnicodeString(&DllName);
        return NULL;
    }

    _SEH2_TRY
    {
        if (dwFlags & LOAD_LIBRARY_AS_DATAFILE)
        {
            /* If the image is loaded as a datafile, try to get its handle */
            Status = LdrGetDllHandleEx(0, SearchPath, NULL, &DllName, (PVOID*)&hInst);
            if (!NT_SUCCESS(Status))
            {
                /* It's not loaded yet - so load it up */
                Status = BasepLoadLibraryAsDatafile(SearchPath, DllName.Buffer, &hInst);
            }
            _SEH2_YIELD(goto done;)
        }

        /* Call the API Properly */
        Status = LdrLoadDll(SearchPath,
                            &DllCharacteristics,
                            &DllName,
                            (PVOID*)&hInst);
    }
    _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
    {
        Status = _SEH2_GetExceptionCode();
    } _SEH2_END;


done:
    /* Free SearchPath buffer */
    RtlFreeHeap(RtlGetProcessHeap(), 0, SearchPath);

    /* Free DllName string if it was dynamically allocated */
    if (FreeString) RtlFreeUnicodeString(&DllName);

    /* Set last error in failure case */
    if (!NT_SUCCESS(Status))
    {
        DPRINT1("LoadLibraryExW(%ls) failing with status %lx\n", lpLibFileName, Status);
        BaseSetLastNTError(Status);
        return NULL;
    }

    /* Return loaded module handle */
    return hInst;
}

微软文档中有关dwflag的含义

DONT_RESOLVE_DLL_REFERENCES0x00000001

如果使用此值,并且可执行模块是 DLL,则系统不会调用 DllMain 来处理进程和线程初始化和终止。 此外,系统不会加载由指定模块引用的其他可执行模块。

注意 不要使用此值;它仅用于向后兼容性。 如果计划仅访问 DLL 中的数据或资源,请使用 LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVELOAD_LIBRARY_AS_IMAGE_RESOURCE 或两者兼有。 否则,请使用 LoadLibrary 函数将库加载为 DLL 或可执行模块。

 

LOAD_IGNORE_CODE_AUTHZ_LEVEL0x00000010

如果使用此值,则系统不会检查 appLocker 规则 ,也不会对 DLL 应用 软件限制策略。 此操作仅适用于正在加载的 DLL,不适用于其依赖项。 建议在安装过程中必须运行提取的 DLL 的安装程序中使用此值。

Windows Server 2008 R2 和 Windows 7:在安装了KB2532445的系统上 ,调用方必须以“LocalSystem”或“TrustedInstaller”身份运行;否则,系统将忽略此标志。 有关详细信息,请参阅 https://support.microsoft.com/kb/2532445帮助和支持知识库中的“通过在运行 Windows 7 或 Windows Server 2008 R2 的计算机上使用 Office 宏来规避 AppLocker 规则”。

Windows Server 2008、Windows Vista、Windows Server 2003 和 Windows XP:AppLocker 在 Windows 7 和 Windows Server 2008 R2 中引入。

LOAD_LIBRARY_AS_DATAFILE0x00000002

如果使用此值,系统会将文件映射到调用进程的虚拟地址空间,就好像它是数据文件一样。 不执行任何操作来执行或准备执行映射的文件。 因此,不能使用此 DLL 调用 GetModuleFileNameGetModuleHandleGetProcAddress 等函数。 使用此值会导致写入只读内存以引发访问冲突。 如果要仅加载 DLL 以从其中提取消息或资源,请使用此标志。

此值可与 LOAD_LIBRARY_AS_IMAGE_RESOURCE一起使用。 有关详细信息,请参阅“备注”。

LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE0x00000040

LOAD_LIBRARY_AS_DATAFILE类似,不同之处在于 DLL 文件是使用调用进程的独占写入访问权限打开的。 其他进程在使用时无法打开 DLL 文件进行写入访问。 但是,DLL 仍可由其他进程打开。

此值可与 LOAD_LIBRARY_AS_IMAGE_RESOURCE一起使用。 有关详细信息,请参阅“备注”。

Windows Server 2003 和 Windows XP:在 Windows Vista 之前不支持此值。

LOAD_LIBRARY_AS_IMAGE_RESOURCE0x00000020

如果使用此值,系统将文件映射到进程的虚拟地址空间作为图像文件。 但是,加载程序不会加载静态导入或执行其他通常的初始化步骤。 如果要仅加载 DLL 以从其中提取消息或资源,请使用此标志。

除非应用程序依赖于具有图像内存中布局的文件,否则此值应与 LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVELOAD_LIBRARY_AS_DATAFILE一起使用。 有关详细信息,请参阅“备注”部分。

Windows Server 2003 和 Windows XP:在 Windows Vista 之前不支持此值。

LOAD_LIBRARY_SEARCH_APPLICATION_DIR0x00000200

如果使用此值,则会搜索应用程序的安装目录来查找 DLL 及其依赖项。 不搜索标准搜索路径中的目录。 此值不能与 LOAD_WITH_ALTERED_SEARCH_PATH结合使用。

Windows 7、Windows Server 2008 R2、Windows Vista 和 Windows Server 2008:此值需要安装 KB2533623

Windows Server 2003 和 Windows XP:不支持 此值。

LOAD_LIBRARY_SEARCH_DEFAULT_DIRS0x00001000

此值是 LOAD_LIBRARY_SEARCH_APPLICATION_DIRLOAD_LIBRARY_SEARCH_SYSTEM32LOAD_LIBRARY_SEARCH_USER_DIRS的组合。 不搜索标准搜索路径中的目录。 此值不能与 LOAD_WITH_ALTERED_SEARCH_PATH结合使用。

此值表示应用程序在其 DLL 搜索路径中应包含的建议最大目录数。

Windows 7、Windows Server 2008 R2、Windows Vista 和 Windows Server 2008:此值需要安装 KB2533623

Windows Server 2003 和 Windows XP:不支持 此值。

LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR0x00000100

如果使用此值,则包含 DLL 的目录会暂时添加到搜索 DLL 依赖项的目录列表的开头。 不搜索标准搜索路径中的目录。

lpFileName 参数必须指定完全限定的路径。 此值不能与 LOAD_WITH_ALTERED_SEARCH_PATH结合使用。

例如,如果 Lib2.dll 只是 c:\Dir1 中 C:\Dir1\Lib1.dll, loading Lib1.dll with this value causes the system to search for Lib2.dll 的依赖项。 若要在 C:\Dir1 中搜索 Lib2.dll 以及 DLL 搜索路径中的所有目录,请结合使用此值与 LOAD_LIBRARY_SEARCH_DEFAULT_DIRS

Windows 7、Windows Server 2008 R2、Windows Vista 和 Windows Server 2008:此值需要安装 KB2533623

Windows Server 2003 和 Windows XP:不支持 此值。

LOAD_LIBRARY_SEARCH_SYSTEM320x00000800

如果使用此值,%windows%\system32 搜索 DLL 及其依赖项。 不搜索标准搜索路径中的目录。 此值不能与 LOAD_WITH_ALTERED_SEARCH_PATH结合使用。

Windows 7、Windows Server 2008 R2、Windows Vista 和 Windows Server 2008:此值需要安装 KB2533623

Windows Server 2003 和 Windows XP:不支持 此值。

LOAD_LIBRARY_SEARCH_USER_DIRS0x00000400

如果使用此值,则使用 AddDllDirectory 添加的目录或 SetDllDirectory 函数将搜索 DLL 及其依赖项。 如果已添加多个目录,则未指定搜索目录的顺序。 不搜索标准搜索路径中的目录。 此值不能与 LOAD_WITH_ALTERED_SEARCH_PATH结合使用。

Windows 7、Windows Server 2008 R2、Windows Vista 和 Windows Server 2008:此值需要安装 KB2533623

Windows Server 2003 和 Windows XP:不支持 此值。

LOAD_WITH_ALTERED_SEARCH_PATH0x00000008

如果使用此值并 lpFileName 指定绝对路径,则系统会使用“备注”部分中讨论的备用文件搜索策略来查找指定模块导致加载的关联可执行模块。 如果使用此值,lpFileName 指定相对路径,则行为是未定义的。

如果未使用此值,或者如果 lpFileName 未指定路径,则系统会使用“备注”部分中讨论的标准搜索策略来查找指定模块导致加载的关联可执行模块。

此值不能与任何 LOAD_LIBRARY_SEARCH 标志合并。

LOAD_LIBRARY_REQUIRE_SIGNED_TARGET0x00000080

指定在加载时必须检查二进制图像的数字签名。

此值需要 Windows 8.1、Windows 10 或更高版本。

LOAD_LIBRARY_SAFE_CURRENT_DIRS0x00002000

如果使用此值,则仅当 DLL 位于安全加载列表中的目录下时,才允许从当前目录加载 DLL 执行。

DONT_RESOLVE_DLL_REFERENCES0x00000001

如果使用此值,并且可执行模块是 DLL,则系统不会调用 DllMain 来处理进程和线程初始化和终止。 此外,系统不会加载由指定模块引用的其他可执行模块。

注意 不要使用此值;它仅用于向后兼容性。 如果计划仅访问 DLL 中的数据或资源,请使用 LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVELOAD_LIBRARY_AS_IMAGE_RESOURCE 或两者兼有。 否则,请使用 LoadLibrary 函数将库加载为 DLL 或可执行模块。

 

LOAD_IGNORE_CODE_AUTHZ_LEVEL0x00000010

如果使用此值,则系统不会检查 appLocker 规则 ,也不会对 DLL 应用 软件限制策略。 此操作仅适用于正在加载的 DLL,不适用于其依赖项。 建议在安装过程中必须运行提取的 DLL 的安装程序中使用此值。

Windows Server 2008 R2 和 Windows 7:在安装了KB2532445的系统上 ,调用方必须以“LocalSystem”或“TrustedInstaller”身份运行;否则,系统将忽略此标志。 有关详细信息,请参阅 https://support.microsoft.com/kb/2532445帮助和支持知识库中的“通过在运行 Windows 7 或 Windows Server 2008 R2 的计算机上使用 Office 宏来规避 AppLocker 规则”。

Windows Server 2008、Windows Vista、Windows Server 2003 和 Windows XP:AppLocker 在 Windows 7 和 Windows Server 2008 R2 中引入。

LOAD_LIBRARY_AS_DATAFILE0x00000002

如果使用此值,系统会将文件映射到调用进程的虚拟地址空间,就好像它是数据文件一样。 不执行任何操作来执行或准备执行映射的文件。 因此,不能使用此 DLL 调用 GetModuleFileNameGetModuleHandleGetProcAddress 等函数。 使用此值会导致写入只读内存以引发访问冲突。 如果要仅加载 DLL 以从其中提取消息或资源,请使用此标志。

此值可与 LOAD_LIBRARY_AS_IMAGE_RESOURCE一起使用。 有关详细信息,请参阅“备注”。

LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE0x00000040

LOAD_LIBRARY_AS_DATAFILE类似,不同之处在于 DLL 文件是使用调用进程的独占写入访问权限打开的。 其他进程在使用时无法打开 DLL 文件进行写入访问。 但是,DLL 仍可由其他进程打开。

此值可与 LOAD_LIBRARY_AS_IMAGE_RESOURCE一起使用。 有关详细信息,请参阅“备注”。

Windows Server 2003 和 Windows XP:在 Windows Vista 之前不支持此值。

LOAD_LIBRARY_AS_IMAGE_RESOURCE0x00000020

如果使用此值,系统将文件映射到进程的虚拟地址空间作为图像文件。 但是,加载程序不会加载静态导入或执行其他通常的初始化步骤。 如果要仅加载 DLL 以从其中提取消息或资源,请使用此标志。

除非应用程序依赖于具有图像内存中布局的文件,否则此值应与 LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVELOAD_LIBRARY_AS_DATAFILE一起使用。 有关详细信息,请参阅“备注”部分。

Windows Server 2003 和 Windows XP:在 Windows Vista 之前不支持此值。

LOAD_LIBRARY_SEARCH_APPLICATION_DIR0x00000200

如果使用此值,则会搜索应用程序的安装目录来查找 DLL 及其依赖项。 不搜索标准搜索路径中的目录。 此值不能与 LOAD_WITH_ALTERED_SEARCH_PATH结合使用。

Windows 7、Windows Server 2008 R2、Windows Vista 和 Windows Server 2008:此值需要安装 KB2533623

Windows Server 2003 和 Windows XP:不支持 此值。

LOAD_LIBRARY_SEARCH_DEFAULT_DIRS0x00001000

此值是 LOAD_LIBRARY_SEARCH_APPLICATION_DIRLOAD_LIBRARY_SEARCH_SYSTEM32LOAD_LIBRARY_SEARCH_USER_DIRS的组合。 不搜索标准搜索路径中的目录。 此值不能与 LOAD_WITH_ALTERED_SEARCH_PATH结合使用。

此值表示应用程序在其 DLL 搜索路径中应包含的建议最大目录数。

Windows 7、Windows Server 2008 R2、Windows Vista 和 Windows Server 2008:此值需要安装 KB2533623

Windows Server 2003 和 Windows XP:不支持 此值。

LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR0x00000100

如果使用此值,则包含 DLL 的目录会暂时添加到搜索 DLL 依赖项的目录列表的开头。 不搜索标准搜索路径中的目录。

lpFileName 参数必须指定完全限定的路径。 此值不能与 LOAD_WITH_ALTERED_SEARCH_PATH结合使用。

例如,如果 Lib2.dll 只是 c:\Dir1 中 C:\Dir1\Lib1.dll, loading Lib1.dll with this value causes the system to search for Lib2.dll 的依赖项。 若要在 C:\Dir1 中搜索 Lib2.dll 以及 DLL 搜索路径中的所有目录,请结合使用此值与 LOAD_LIBRARY_SEARCH_DEFAULT_DIRS

Windows 7、Windows Server 2008 R2、Windows Vista 和 Windows Server 2008:此值需要安装 KB2533623

Windows Server 2003 和 Windows XP:不支持 此值。

LOAD_LIBRARY_SEARCH_SYSTEM320x00000800

如果使用此值,%windows%\system32 搜索 DLL 及其依赖项。 不搜索标准搜索路径中的目录。 此值不能与 LOAD_WITH_ALTERED_SEARCH_PATH结合使用。

Windows 7、Windows Server 2008 R2、Windows Vista 和 Windows Server 2008:此值需要安装 KB2533623

Windows Server 2003 和 Windows XP:不支持 此值。

LOAD_LIBRARY_SEARCH_USER_DIRS0x00000400

如果使用此值,则使用 AddDllDirectory 添加的目录或 SetDllDirectory 函数将搜索 DLL 及其依赖项。 如果已添加多个目录,则未指定搜索目录的顺序。 不搜索标准搜索路径中的目录。 此值不能与 LOAD_WITH_ALTERED_SEARCH_PATH结合使用。

Windows 7、Windows Server 2008 R2、Windows Vista 和 Windows Server 2008:此值需要安装 KB2533623

Windows Server 2003 和 Windows XP:不支持 此值。

LOAD_WITH_ALTERED_SEARCH_PATH0x00000008

如果使用此值并 lpFileName 指定绝对路径,则系统会使用“备注”部分中讨论的备用文件搜索策略来查找指定模块导致加载的关联可执行模块。 如果使用此值,lpFileName 指定相对路径,则行为是未定义的。

如果未使用此值,或者如果 lpFileName 未指定路径,则系统会使用“备注”部分中讨论的标准搜索策略来查找指定模块导致加载的关联可执行模块。

此值不能与任何 LOAD_LIBRARY_SEARCH 标志合并。

LOAD_LIBRARY_REQUIRE_SIGNED_TARGET0x00000080

指定在加载时必须检查二进制图像的数字签名。

此值需要 Windows 8.1、Windows 10 或更高版本。

LOAD_LIBRARY_SAFE_CURRENT_DIRS0x00002000

如果使用此值,则仅当 DLL 位于安全加载列表中的目录下时,才允许从当前目录加载 DLL 执行。

dll路径搜索顺序

微软官方文档中的描述:

如果启用了安全 DLL 搜索模式,则搜索顺序如下所示:

  1. DLL 重定向。

  2. API 集。

  3. SxS 清单重定向。

  4. Loaded-module 列表。

  5. 已知 DLL。

  6. Windows 11 版本 21H2 (10.0;内部版本 22000)及更高版本。 进程的包依赖项关系图。 这是应用程序的包以及应用程序包清单 <Dependencies> 部分中指定为 <PackageDependency> 的任何依赖项。 依赖项按它们在清单中显示的顺序进行搜索。

  7. 从中加载应用程序的文件夹。

  8. 系统文件夹。 使用 GetSystemDirectory 函数检索此文件夹的路径。

  9. 16 位系统文件夹。 没有可获取此文件夹的路径的函数,但搜索该函数。

  10. Windows 文件夹。 使用 GetWindowsDirectory 函数获取此文件夹的路径。

  11. 当前文件夹。

  12. PATH 环境变量中列出的目录。 这不包括由 应用路径 注册表项指定的每个应用程序路径。 计算 DLL 搜索路径时,不使用 应用路径 密钥。

如果 禁用安全 DLL 搜索模式,则搜索顺序相同,不同之处在于 当前文件夹 从序列中的位置 11 移动到位置 8(步骤 7 之后)。应用程序从中加载的文件夹。

ldrloaddll

ldrloaddll和ldrploaddll都是一些标志位和路径检查、初始化之类的措施,关键的操作在LdrpLoadDllInternal中,该函数的主要工作流程为:

ldrpProcessWork的流程如下,根据dll的标志位会进入不同的功能分支,而一般的加载都是进入右边这三类分支,这三类分支均进行一些检查之后去调用LdrpMapDllNtFileName来加载dll。

同样通过一系列检查以及对dll文件的打开还有内存操作之后,流程来到了LdrpMapDllWithSectionHandle,该函数将dll文件加载到内存镜像,然后将通过LdrpInsertDataTableEntry其挂载到InMemoryOrderLinks和InLoadOrderLinks上,至此在ldr的链表当中已经可以看到新的dll。

至此在ldr的链表当中已经可以看到新的dll。

新的问题:windbg与processhacker能在上链之前就发现dll

这里我写了一个简单的使用loadlibraryW加载Dll1.dll的小程序来做测试,在测试过程中我是用sxe ld来对我要加载的dll进行监测,windbg命中中断时的调用栈如下

a2hooks是我的杀软hook直接无视,可以看到这个调用栈还在执行dll文件映射到内存的阶段,但此时windbg已经能够检测到新的dll的加载了,虽然在!dlls中是看不到的,但是.imgscan可以发现目标dll,并且此时processhacker中也是可以看到目标dll的

说明windbg和processhacker都不是依赖遍历ldr来检测进程加载的dll的,那么他依赖的是什么呢?

VAD(Virtual Address Descriptor 虚拟地址描述符)树

Windows 内存管理器使用虚拟地址描述符树 (VAD) 来描述进程分配的内存范围。当进程使用 VirutalAlloc 分配内存时,内存管理器会在 VAD 树中创建一个条目。相应的页目录和页表条目只有在进程尝试引用该内存页面时才会创建,这可以为分配大量内存但访问稀疏的进程节省大量内存。

通过eprocess就可以找到vad树的根节点VadRoot,偏移量要看具体系统中的具体情况。

在windbg中也可以通过!vad直接列出vad树上的所有节点。

vad节点的数据结构为_MMVAD

typedef struct _MMVAD
{
    _MMVAD_SHORT Core;               // +0x000  VAD的核心结构,包含虚拟地址范围、保护属性、AVL树节点等基础信息
    union {
        // 匿名联合体,包含一些扩展信息或标志位,具体内容随Windows版本不同
    } u2;                           // +0x040
    struct _SUBSECTION* Subsection; // +0x048  指向映射的文件子段,间接关联到ControlArea和文件对象
    struct _MMPTE* FirstPrototypePte;   // +0x050  第一个原型页表项的指针,代表映射的起始页表项
    struct _MMPTE* LastContiguousPte;   // +0x058  最后一个连续页表项的指针,表示连续映射的结束页表项
    LIST_ENTRY ViewLinks;           // +0x060  双向链表,用于管理视图之间的链接
    struct _EPROCESS* VadsProcess; // +0x070  该VAD所属的进程对象指针
    union {
        // 另一个匿名联合体或结构体,包含额外标志或辅助信息,随版本不同
    } u4;                           // +0x078
    struct _FILE_OBJECT* FileObject; // +0x080  指向该VAD对应的文件对象,直接获取被映射文件(如DLL)信息
} MMVAD, *PMMVAD;

这里重点关注的是包含了controlarea的subsection

typedef struct _SUBSECTION {
    struct _CONTROL_AREA* ControlArea;     // +0x000 指向对应的 ControlArea 结构,管理整个映射区域的共享信息(文件映射核心)
    struct _MMPTE* SubsectionBase;          // +0x008 该子段中第一个页表项的起始地址
    struct _SUBSECTION* NextSubsection;     // +0x010 指向下一个子段,形成链表或树形结构
    union {
        struct _MI_FILE_EXTENTS* FileExtents;           // +0x018 文件扩展信息(描述文件映射范围)
        struct _RTL_AVL_TREE GlobalPerSessionHead;      // +0x018 会话全局树根(多用途,根据上下文)
        struct _MI_PER_SESSION_PROTOS* SessionDriverProtos; // +0x018 另一种会话驱动保护结构指针
    };
    union {
        /* 未命名联合体,包含若干标志和属性 */
    } u;                                    // +0x020
    unsigned int StartingSector;            // +0x024 映射文件开始的扇区号(文件偏移量,扇区为单位)
    unsigned int NumberOfFullSectors;       // +0x028 文件映射中完整扇区的数量
    unsigned int PtesInSubsection;          // +0x02c 该子段内的页表项数量
    union {
        /* 未命名联合体,包含更多页表相关信息 */
    } u1;                                   // +0x030
    unsigned int UnusedPtes;                 // +0x034 未使用的页表项数量
    unsigned int AlignmentNoAccessPtes;     // +0x034(与上字段共用偏移)对齐相关的不可访问页表项数
} SUBSECTION, *PSUBSECTION;

到了controlarea这里就可以读取到包含file_object的filepointer了,其类型是_EX_FAST_REF。

typedef struct _CONTROL_AREA {
    struct _SEGMENT* Segment;                  // +0x000 指向管理该映射区域的内存段结构
    LIST_ENTRY ListHead;                       // +0x008 用于链接 ControlArea 结构的双向链表
    unsigned __int64 NumberOfSectionReferences; // +0x018 当前 ControlArea 的引用计数(section 对象的引用数)
    unsigned __int64 NumberOfPfnReferences;      // +0x020 物理页框引用数,管理页面的映射计数
    unsigned __int64 NumberOfMappedViews;         // +0x028 由该 ControlArea 映射的视图数量
    unsigned __int64 NumberOfUserReferences;       // +0x030 用户模式对该 ControlArea 的引用数
    union {                                       // +0x038 未命名联合体,包含状态标志等(版本相关)
        // 省略详细字段
    } u;
    union {                                       // +0x03c 另一个未命名联合体,包含额外状态或标志
        // 省略详细字段
    } u1;
    _EX_FAST_REF FilePointer;                    // +0x040 指向关联的 FILE_OBJECT(文件对象),是一个带引用计数的快速引用结构
    int ControlAreaLock;                         // +0x048 控制区锁,保护 ControlArea 结构体的并发访问
    unsigned int ModifiedWriteCount;             // +0x04c 修改写计数,表示有多少未写回的页
    struct _MI_CONTROL_AREA_WAIT_BLOCK* WaitList; // +0x050 等待该 ControlArea 操作完成的等待队列
    union {                                       // +0x058 未命名联合体,版本相关或扩展标志
        // 省略详细字段
    } u2;
    unsigned __int64 LockedPages;                // +0x068 被锁定的页面数量
    _EX_PUSH_LOCK FileObjectLock;                // +0x070 保护文件对象的推锁(轻量级锁)
} CONTROL_AREA, *PCONTROL_AREA;

_EX_FAST_REF是一个用来同时存储指针和少量标志/引用计数的特殊结构,他的低4位保存引用计数,实际指针需要把低4位清零才能得到准确地址,清除低4位后才能得到正确的file_object地址。

typedef struct _EX_FAST_REF {
    union {
        void* Object;        // +0x000 实际指针地址,低4位可能用作引用计数等标志
        struct {
            unsigned __int64 RefCnt : 4;   // +0x000 低4位,表示引用计数(4 bit)
            unsigned __int64 Pointer : 60; // 剩余60位,表示指针的高位部分(地址,通常16字节对齐)
        };
        unsigned __int64 Value;    // +0x000 整个64位值,包括指针和引用计数
    };
} EX_FAST_REF, *PEX_FAST_REF;

通过windbg双端内核调试,先使用!gflag +ksl开启记录内核栈调用信息,然后使用sxe ld来对目标dll的加载做中断,观察调用栈

[0x0]   nt!DebugService2+0x5   0xffffd001ca262628   0xfffff8021434db86   
[0x1]   nt!DbgLoadUserImageSymbols+0x2a   0xffffd001ca262630   0xfffff802147aeb73   
[0x2]   nt!MiLoadUserSymbols+0x7f   0xffffd001ca262680   0xfffff802146ccb37   
[0x3]   nt!MiMapViewOfImageSection+0x9c7   0xffffd001ca2626e0   0xfffff80214619c2d   
[0x4]   nt!MiMapViewOfSection+0x33d   0xffffd001ca262830   0xfffff8021461f7e1   
[0x5]   nt!NtMapViewOfSection+0x2e1   0xffffd001ca2629a0   0xfffff8021436a263   
[0x6]   nt!KiSystemServiceCopyEnd+0x13   0xffffd001ca262a90   0x7fff84f337ca   
[0x7]   ntdll!NtMapViewOfSection+0xa   0x80b517f658   0x7fff84ee070e   
[0x8]   ntdll!LdrpMapViewOfSection+0xbe   0x80b517f660   0x7fff84ee0255   
[0x9]   ntdll!LdrpMapImage+0x75   0x80b517f700   0x7fff84ee0125   
[0xa]   ntdll!LdrpMapDllWithSectionHandle+0x2d   0x80b517f7a0   0x7fff84edefd8   
[0xb]   ntdll!LdrpMapDllNtFileName+0x130   0x80b517f7e0   0x7fff84ee263c   
[0xc]   ntdll!LdrpMapDllSearchPath+0x1c8   0x80b517f8b0   0x7fff84ed8e5c   
[0xd]   ntdll!LdrpProcessWork+0x70   0x80b517fa90   0x7fff84ec0911   
[0xe]   ntdll!LdrpLoadDllInternal+0x14d   0x80b517fae0   0x7fff84ec05ca   
[0xf]   ntdll!LdrpLoadDll+0xf2   0x80b517fb60   0x7fff84ebaf86   
[0x10]   ntdll!LdrLoadDll+0x96   0x80b517fd00   0x7fff8217ef1b   
[0x11]   KERNELBASE!LoadLibraryExW+0x17b   0x80b517fe00   0x7ff6282e11e3   
[0x12]   Project1!main+0x33   0x80b517fe70   0x7fff00000000   
[0x13]   0x7fff00000000   0x80b517fe78   0x80b529b780   
[0x14]   0x80b529b780   0x80b517fe80   0x7ff627c34000   
[0x15]   0x7ff627c34000   0x80b517fe88   0x0   

顺便贴一张NtMapviewOfSection的流程图

!vad可以看到在vad树上已经有目标dll的节点了

找到Subsection.ControlArea.FilePointer的值,去掉低4位用_FILE_OBJECT解析,在FileName就可以看到文件名了

这里我们把filename给修改掉,改成dll2.dll,这个时候lm就看不到这个模块了,说明windbg是通过遍历vad树中节点的fileObject来获得模块信息的

为什么反射注入dll在进程模块里面看不到

通过传统远程进程注入的dll在进程的模块中是可以看到该dll的信息的

而通过反射注入的dll在进程模块中是没有该模块的信息的,但是在内存中可以找到该dll

正常loadlibrary流程当中会通过LdrLoadDll将加载的dll注册到peb的ldr中,同时这个过程的文件内存镜像映射是通过NtMapViewOfSection来完成的,所以通过工具才能查看到进程加载的模块,而反射dll注入过程中并没有使用loadlibrary来加载反射dll本身,通过手动逐字节进行内存镜像映射,只是使用了loadlibrary来加载反射dll所需要的依赖dll。

反射注入的reflectiveLoader实现步骤:

  1. 定位去掉reflectiveLoader的真实dll的基址

  2. 读取进程的peb,遍历ldr来找到所需的dll和函数va

  3. 利用virtualalloc申请一段新的虚拟内存,手动逐字节将每个段映射过去

  4. 读取导入表,通过loadlibrary把所需dll加载,然后根据函数序号或者名称把所需的函数地址从导入的dll找到,写入iat表

  5. 检查重定向表,进行重定向操作

  6. 查找dll入口,调用入口函数

这样一来ldr上和vad中都没有和dll相关的信息,所以反射注入dll通过processhacker或者是windbg都在进程模块中看不到,但是windbg的imgscan还是可以扫出来,因为最后阶段的dll没做混淆,他的头部还是符合pe文件的。