一、简介

通过该技术可以不使用 LoadLibrary 将 dll 加载到内存。实现该技术的经典项目:stephenfewer/ReflectiveDLLInjection

git clone https://github.com/stephenfewer/ReflectiveDLLInjection.git

仓库中有两个项目:dll、inject,dll 项目实现了 ReflectiveLoader 函数并导出,该函数内部实现了将 PE 字节加载到内存的过程。inject 项目展示了如何在目标进程中调用 ReflectiveLoader 实现远程 LoadLibrary

NXJqVh

二、ReflectiveLoader 实现

ReflectiveLoader 实现过程共分 8 步:

s0ZsHo

2.1 计算 PE 镜像基址

这里使用了 VS 中提供的内部指令 _ReturnAddress 获取到一个镜像区域中的内存地址(caller 函数调用的下一条指令地址),然后向前查找直到找到 PE 头标志:

Hhh1fZ

caller 定义:

xDVd3M

参考:MSDN

2.2 从进程 PEB 中获取几个必要的函数地址

后面加载 IAT、修复重定向时必须要用到的几个函数:

  • LoadLibraryA:动态加载 dll
  • GetProcAddress:获取 API 地址
  • VirtualAlloc:分配内存
  • NtFlushInstructionCache:修复重定向之后,需要刷新指令缓存

这几个函数分别在 kernel32.dllntdll.dll 中,一般进程都会加载这两个 dll,所以直接从当前进程中获取这几个函数的地址:

首先先获取 PEB 基址:

hAmTUh

获取模块列表,并计算每个模块名字的 hash:

ZNDGde

如果是 KERNEL32 模块,则读取模块的函数导出目录信息:

qRgIt6

通过 hash 确定 LoadLibraryAGetProcAddressVirtualAlloc 三个 API 函数的地址:

Oq3Xwd

如果是 NTDLL 模块,一样的方式找到 NtFlushInstructionCache 函数地址:

NJ2RaG

2.3 载入镜像到新分配的内存

从 PE 头中获取镜像大小,然后分配同等大小的 RWX 内存,将 PE 头逐字节复制到新的内存位置:

eTIb2h

这步感觉没有必要,原区域已经是 RWX 权限了,而且直接分配 RWX 权限内存是非常敏感的操作,最好是先 RW,写入之后再改成 RX,而且只有 .text 扇区是需要 RX 权限的。

2.4 将所有节复制到新分配的内存

从 PE 头读取节地址信息,依次复制到新内存:

2K2fOM

2.5 处理导入表

获取导入表地址,并使用 uiValueC 变量遍历导入项:

pjwVKZ

使用前面获取的 LoadLibraryA 函数依次导入模块:

3MB0qM

接下来修复 IAT 表中的导入函数地址,如果是通过序号导入则手动解析模块导出函数地址,否则直接使用 GetProcAddress 函数得到地址:

HJzOcS

2.6 修复重定向

将镜像新的内存基址修复到 relocation 节:

lF3jwI

2.7 调用 DLLMain

OptionalHeader 中得到 DLLMain 地址,在修复过基址之后要调用 NtFlushInstructionCache 函数刷新指令缓存,然后再执行 DLLMain 函数:

pSXDnC

2.8 返回 DLLMain 地址

非必须的一步,将 DLLMain 地址作为 ReflectiveLoader 的返回值:

RUg7fo

三、LoadRemoteLibraryR 实现

inject 项目中,main 函数通过调用 LoadRemoteLibraryRReflectiveLoaderDLL 注入到目标进程:

2ZcDSL

LoadRemoteLibraryR 实现相对简单,通过普通的 dll 注入手法,利用 VirtualAllocEx 函数在目标进程内存空间分配 RWX 内存,调用 WriteProcessMemory 函数将 dll 写入到目标进程中,调用 CreateRemoteThread 创建远程线程执行 ReflectiveLoader

Z3a74O

四、内存特征

内存区域为 RWX 权限,并且区域开头为标准的 PE 头,即以 MZ 开头:

7pqGkm

五、参考链接