首页
社区
课程
招聘
[原创]常见进程注入的实现及内存dump分析——反射式DLL注入(上)
发表于: 2018-1-17 21:54 19352

[原创]常见进程注入的实现及内存dump分析——反射式DLL注入(上)

2018-1-17 21:54
19352

原理:这里只说注射器实现的功能:在目标进程开辟空间,将Payload写到开辟的空间去,最后调用DLL中的反射加载函数。

实现:

    前期实现就不多说,包括:获取目标进程PID,提升当前进程权限,在上一篇文章中已经给出了代码。

在上一篇文章《常见进程注入的实现及内存dump分析——经典DLL注入》中,介绍了经典DLL注入,虽然简单,但是是注入的基本原理,在上篇文章中没有提到的是:32位的注入程序要注入32位的进程中,不要试图注入64位进程,这种注入确实是可以实现,但是很麻烦,所以就放到最后去学习。
由于反射式DLL比较复杂,这篇文章只是注射器的实现和分析,源码参考自GitHub,同样,会在文章末尾贴上地址。
反射式DLL我理解就是让DLL自身不使用LoadLibraryA函数,将自身映射到目标进程内存中。

OS:Windows 10 PRO 1709

IDE:Visual Studio 2015 Community

语言:Visual C++

Dropper:注射器的实现

LPVOID lpBuffer = HeapAlloc(GetProcessHeap(), 0, dwLength);//创建缓冲区
if (ReadFile(hFile, lpBuffer, dwLength, &dwBytesRead, NULL) == false)//将DLL数据复制到缓冲区
	BreakForError("Failed to read the DLL file");
LPVOID lpBuffer = HeapAlloc(GetProcessHeap(), 0, dwLength);//创建缓冲区
if (ReadFile(hFile, lpBuffer, dwLength, &dwBytesRead, NULL) == false)//将DLL数据复制到缓冲区
	BreakForError("Failed to read the DLL file");
HANDLE hTargetProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);
HANDLE hTargetProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);
HANDLE hMoudle = LoadRemoteLibraryR(hTargetProcess, lpBuffer, dwLength, NULL);
HANDLE hMoudle = LoadRemoteLibraryR(hTargetProcess, lpBuffer, dwLength, NULL);
//获取加载器的地址(文件偏移)
DWORD dwReflectiveLoaderOffset = GetReflectiveLoaderOffset(lpBuffer);
//在目标进程分配内存(RWX)
LPVOID lpRemoteLibraryBuffer = VirtualAllocEx(hProcess, NULL, dwLength, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);

//写数据
WriteProcessMemory(hProcess, lpRemoteLibraryBuffer, lpBuffer, dwLength, NULL);
//线程函数的地址=基地址+文件偏移
LPTHREAD_START_ROUTINE lpReflectiveLoader = (LPTHREAD_START_ROUTINE)((ULONG_PTR)lpRemoteLibraryBuffer + dwReflectiveLoaderOffset);

//创建远程线程
hThread = CreateRemoteThread(hProcess, NULL, 1024 * 1024, lpReflectiveLoader, lpParameter, (DWORD)NULL, &dwThreadId);
//获取加载器的地址(文件偏移)
DWORD dwReflectiveLoaderOffset = GetReflectiveLoaderOffset(lpBuffer);
//在目标进程分配内存(RWX)
LPVOID lpRemoteLibraryBuffer = VirtualAllocEx(hProcess, NULL, dwLength, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);

//写数据
WriteProcessMemory(hProcess, lpRemoteLibraryBuffer, lpBuffer, dwLength, NULL);
//线程函数的地址=基地址+文件偏移
LPTHREAD_START_ROUTINE lpReflectiveLoader = (LPTHREAD_START_ROUTINE)((ULONG_PTR)lpRemoteLibraryBuffer + dwReflectiveLoaderOffset);

//创建远程线程
hThread = CreateRemoteThread(hProcess, NULL, 1024 * 1024, lpReflectiveLoader, lpParameter, (DWORD)NULL, &dwThreadId);
  1. 得到PE头的地址。
  2. 得到导出函数表结构体指针的地址。
  3. 获取导出表结构体的内存地址(RVA)。
  4. 找到导出表名称数组在内存中的地址(RVA)。
  5. 获取导出函数地址表在内存中的地址(RVA)。
  6. 获取导出函数序号表在内存中的地址(RVA)。
  7. 通过导出函数名来获取导出函数地址(RVA)。

//基址->在Dropper进程中开辟的堆空间的起始地址
UINT_PTR uiBaseAddress = (UINT_PTR)lpReflectiveDllBuffer;
//得到NT头的文件地址
UINT_PTR uiExportDir = (UINT_PTR)uiBaseAddress + ((PIMAGE_DOS_HEADER)uiBaseAddress)->e_lfanew;
//获得导出表结构体指针的地址
UINT_PTR uiNameArray = (UINT_PTR)&(((PIMAGE_NT_HEADERS)uiExportDir)->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]);
//该调用中,第一个参数即为导出表结构体映射到内存的相对虚拟地址
//结果为找到到导出表结构体的内存地址
uiExportDir = uiBaseAddress + Rva2Offset(((PIMAGE_DATA_DIRECTORY)uiNameArray)->VirtualAddress, uiBaseAddress);
//得到导出表名称数组在内存中的地址RVA
uiNameArray = uiBaseAddress + Rva2Offset(((PIMAGE_EXPORT_DIRECTORY)uiExportDir)->AddressOfNames, uiBaseAddress);
//得到导出函数地址表在内存中的地址RVA
UINT_PTR uiAddressArray = uiBaseAddress + Rva2Offset(((PIMAGE_EXPORT_DIRECTORY)uiExportDir)->AddressOfFunctions, uiBaseAddress);
//得到函数序号地址表在内存中的地址
UINT_PTR uiNameOrdinals = uiBaseAddress + Rva2Offset(((PIMAGE_EXPORT_DIRECTORY)uiExportDir)->AddressOfNameOrdinals, uiBaseAddress);
//导出函数的数量
DWORD dwCounter = ((PIMAGE_EXPORT_DIRECTORY)uiExportDir)->NumberOfNames;
while (dwCounter--)
{
	//这里需要将获取到的各表的RVA转化为各表实际的文件偏移
	char *cpExportedFunctionName = (char *)(uiBaseAddress + Rva2Offset((*(DWORD*)uiNameArray), uiBaseAddress));
	if (strstr(cpExportedFunctionName, "ReflectiveLoader") != NULL)
	{
		//获取地址表起始地址的实际位置
		uiAddressArray = uiBaseAddress + Rva2Offset(((PIMAGE_EXPORT_DIRECTORY)uiExportDir)->AddressOfFunctions, uiBaseAddress);
		//根据序号找到序号对应的函数地址
		uiAddressArray += (*(WORD*)(uiNameOrdinals) * sizeof(DWORD));
		// 返回ReflectiveLoader函数的文件偏移,即函数机器码的起始地址
		return Rva2Offset((*(DWORD*)uiAddressArray), uiBaseAddress);
	}
	uiNameArray += sizeof(DWORD);
	uiNameOrdinals += sizeof(WORD);
}
//基址->在Dropper进程中开辟的堆空间的起始地址
UINT_PTR uiBaseAddress = (UINT_PTR)lpReflectiveDllBuffer;
//得到NT头的文件地址
UINT_PTR uiExportDir = (UINT_PTR)uiBaseAddress + ((PIMAGE_DOS_HEADER)uiBaseAddress)->e_lfanew;
//获得导出表结构体指针的地址
UINT_PTR uiNameArray = (UINT_PTR)&(((PIMAGE_NT_HEADERS)uiExportDir)->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]);
//该调用中,第一个参数即为导出表结构体映射到内存的相对虚拟地址
//结果为找到到导出表结构体的内存地址
uiExportDir = uiBaseAddress + Rva2Offset(((PIMAGE_DATA_DIRECTORY)uiNameArray)->VirtualAddress, uiBaseAddress);
//得到导出表名称数组在内存中的地址RVA
uiNameArray = uiBaseAddress + Rva2Offset(((PIMAGE_EXPORT_DIRECTORY)uiExportDir)->AddressOfNames, uiBaseAddress);
//得到导出函数地址表在内存中的地址RVA
UINT_PTR uiAddressArray = uiBaseAddress + Rva2Offset(((PIMAGE_EXPORT_DIRECTORY)uiExportDir)->AddressOfFunctions, uiBaseAddress);
//得到函数序号地址表在内存中的地址
UINT_PTR uiNameOrdinals = uiBaseAddress + Rva2Offset(((PIMAGE_EXPORT_DIRECTORY)uiExportDir)->AddressOfNameOrdinals, uiBaseAddress);
//导出函数的数量
DWORD dwCounter = ((PIMAGE_EXPORT_DIRECTORY)uiExportDir)->NumberOfNames;
while (dwCounter--)
{
	//这里需要将获取到的各表的RVA转化为各表实际的文件偏移
	char *cpExportedFunctionName = (char *)(uiBaseAddress + Rva2Offset((*(DWORD*)uiNameArray), uiBaseAddress));
	if (strstr(cpExportedFunctionName, "ReflectiveLoader") != NULL)
	{
		//获取地址表起始地址的实际位置
		uiAddressArray = uiBaseAddress + Rva2Offset(((PIMAGE_EXPORT_DIRECTORY)uiExportDir)->AddressOfFunctions, uiBaseAddress);
		//根据序号找到序号对应的函数地址
		uiAddressArray += (*(WORD*)(uiNameOrdinals) * sizeof(DWORD));
		// 返回ReflectiveLoader函数的文件偏移,即函数机器码的起始地址
		return Rva2Offset((*(DWORD*)uiAddressArray), uiBaseAddress);
	}
	uiNameArray += sizeof(DWORD);
	uiNameOrdinals += sizeof(WORD);
}
DWORD Rva2Offset(DWORD dwRva, UINT_PTR uiBaseAddress)
{
	//得到nt头在内存中的实际地址
	PIMAGE_NT_HEADERS pNtHeaders = (PIMAGE_NT_HEADERS)(uiBaseAddress + ((PIMAGE_DOS_HEADER)uiBaseAddress)->e_lfanew);
	//获得节表
	PIMAGE_SECTION_HEADER pSectionHeader = (PIMAGE_SECTION_HEADER)((UINT_PTR)(&pNtHeaders->OptionalHeader) + pNtHeaders->FileHeader.SizeOfOptionalHeader);
	//不在任意块内
	if (dwRva < pSectionHeader[0].PointerToRawData)
		return dwRva;
	//通过遍历块,来找到相对偏移地址对应的文件偏移地址
	for (WORD wIndex = 0; wIndex < pNtHeaders->FileHeader.NumberOfSections; wIndex++)
	{
		if (dwRva >= pSectionHeader[wIndex].VirtualAddress && dwRva < (pSectionHeader[wIndex].VirtualAddress + pSectionHeader[wIndex].SizeOfRawData))
			return (dwRva - pSectionHeader[wIndex].VirtualAddress + pSectionHeader[wIndex].PointerToRawData);
			//\------------------块内偏移-------------------/	 \-----------块在文件中的偏移------------/
	}
}

DWORD Rva2Offset(DWORD dwRva, UINT_PTR uiBaseAddress)
{
	//得到nt头在内存中的实际地址
	PIMAGE_NT_HEADERS pNtHeaders = (PIMAGE_NT_HEADERS)(uiBaseAddress + ((PIMAGE_DOS_HEADER)uiBaseAddress)->e_lfanew);
	//获得节表
	PIMAGE_SECTION_HEADER pSectionHeader = (PIMAGE_SECTION_HEADER)((UINT_PTR)(&pNtHeaders->OptionalHeader) + pNtHeaders->FileHeader.SizeOfOptionalHeader);
	//不在任意块内
	if (dwRva < pSectionHeader[0].PointerToRawData)
		return dwRva;
	//通过遍历块,来找到相对偏移地址对应的文件偏移地址
	for (WORD wIndex = 0; wIndex < pNtHeaders->FileHeader.NumberOfSections; wIndex++)
	{
		if (dwRva >= pSectionHeader[wIndex].VirtualAddress && dwRva < (pSectionHeader[wIndex].VirtualAddress + pSectionHeader[wIndex].SizeOfRawData))
			return (dwRva - pSectionHeader[wIndex].VirtualAddress + pSectionHeader[wIndex].PointerToRawData);
			//\------------------块内偏移-------------------/	 \-----------块在文件中的偏移------------/
	}
}


回想我们注射器实现的过程中所调用的函数,与正常的注入似乎没有太大的区别,而且像CreateRemoteProcess这种危险函数杀软抓的很严,是可以被替换掉的,而且没有发现LoadLibraryA函数。但这个样本有明显的特征:解析PE结构,所以当我们遇到这种样本的时候,可以考虑为反射式DLL注入。
优点:没有使用获取LoadLibraryA函数。


GetReflectiveLoaderOffset  F5后的代码
从图中可以看到,有大量调用同一个函数的情况,并且有字符串比较。

Rva2Offset F5后的代码
这张图中,具有特征性的应该就是这些数字了,如果根据之前的想法,怀疑这是个反射式DLL注射,进入到这个函数的时候,可以去思考这是不是在解析PE文件。当然,如果动态跟踪的话,及时查看内存,也可以分辨的出来。
《Windows PE权威指南》
*测试时使用的DLL为该源码编译的DLL。


LPVOID lpBuffer = HeapAlloc(GetProcessHeap(), 0, dwLength);//创建缓冲区
if (ReadFile(hFile, lpBuffer, dwLength, &dwBytesRead, NULL) == false)//将DLL数据复制到缓冲区
	BreakForError("Failed to read the DLL file");
HANDLE hTargetProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);
HANDLE hMoudle = LoadRemoteLibraryR(hTargetProcess, lpBuffer, dwLength, NULL);
//获取加载器的地址(文件偏移)
DWORD dwReflectiveLoaderOffset = GetReflectiveLoaderOffset(lpBuffer);
//在目标进程分配内存(RWX)
LPVOID lpRemoteLibraryBuffer = VirtualAllocEx(hProcess, NULL, dwLength, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);

//写数据
WriteProcessMemory(hProcess, lpRemoteLibraryBuffer, lpBuffer, dwLength, NULL);
//线程函数的地址=基地址+文件偏移
LPTHREAD_START_ROUTINE lpReflectiveLoader = (LPTHREAD_START_ROUTINE)((ULONG_PTR)lpRemoteLibraryBuffer + dwReflectiveLoaderOffset);

//创建远程线程
hThread = CreateRemoteThread(hProcess, NULL, 1024 * 1024, lpReflectiveLoader, lpParameter, (DWORD)NULL, &dwThreadId);
//基址->在Dropper进程中开辟的堆空间的起始地址
UINT_PTR uiBaseAddress = (UINT_PTR)lpReflectiveDllBuffer;
//得到NT头的文件地址
UINT_PTR uiExportDir = (UINT_PTR)uiBaseAddress + ((PIMAGE_DOS_HEADER)uiBaseAddress)->e_lfanew;
//获得导出表结构体指针的地址
UINT_PTR uiNameArray = (UINT_PTR)&(((PIMAGE_NT_HEADERS)uiExportDir)->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]);
//该调用中,第一个参数即为导出表结构体映射到内存的相对虚拟地址
//结果为找到到导出表结构体的内存地址
uiExportDir = uiBaseAddress + Rva2Offset(((PIMAGE_DATA_DIRECTORY)uiNameArray)->VirtualAddress, uiBaseAddress);
//得到导出表名称数组在内存中的地址RVA
uiNameArray = uiBaseAddress + Rva2Offset(((PIMAGE_EXPORT_DIRECTORY)uiExportDir)->AddressOfNames, uiBaseAddress);
//得到导出函数地址表在内存中的地址RVA
UINT_PTR uiAddressArray = uiBaseAddress + Rva2Offset(((PIMAGE_EXPORT_DIRECTORY)uiExportDir)->AddressOfFunctions, uiBaseAddress);
//得到函数序号地址表在内存中的地址
UINT_PTR uiNameOrdinals = uiBaseAddress + Rva2Offset(((PIMAGE_EXPORT_DIRECTORY)uiExportDir)->AddressOfNameOrdinals, uiBaseAddress);
//导出函数的数量
DWORD dwCounter = ((PIMAGE_EXPORT_DIRECTORY)uiExportDir)->NumberOfNames;
while (dwCounter--)
{
	//这里需要将获取到的各表的RVA转化为各表实际的文件偏移
	char *cpExportedFunctionName = (char *)(uiBaseAddress + Rva2Offset((*(DWORD*)uiNameArray), uiBaseAddress));
	if (strstr(cpExportedFunctionName, "ReflectiveLoader") != NULL)
	{
		//获取地址表起始地址的实际位置
		uiAddressArray = uiBaseAddress + Rva2Offset(((PIMAGE_EXPORT_DIRECTORY)uiExportDir)->AddressOfFunctions, uiBaseAddress);
		//根据序号找到序号对应的函数地址
		uiAddressArray += (*(WORD*)(uiNameOrdinals) * sizeof(DWORD));
		// 返回ReflectiveLoader函数的文件偏移,即函数机器码的起始地址
		return Rva2Offset((*(DWORD*)uiAddressArray), uiBaseAddress);
	}
	uiNameArray += sizeof(DWORD);
	uiNameOrdinals += sizeof(WORD);
}

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

最后于 2019-1-11 19:08 被kanxue编辑 ,原因:
上传的附件:
收藏
免费 5
支持
分享
最新回复 (19)
雪    币: 5649
活跃值: (3767)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
最后一张图看不清?请问能上传原图吗
2018-1-18 00:43
0
雪    币: 285
活跃值: (1095)
能力值: ( LV13,RANK:405 )
在线值:
发帖
回帖
粉丝
3
任蝶飞 最后一张图看不清?请问能上传原图吗
附件中有原图,git上也上传了
2018-1-18 08:12
0
雪    币: 3
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
继续强势围观~~~
2018-1-18 08:58
0
雪    币: 36
活跃值: (1061)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
5
mark
2018-1-20 12:16
0
雪    币: 576
活跃值: (2035)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
n年前的内存注入
2018-1-20 14:42
0
雪    币: 285
活跃值: (1095)
能力值: ( LV13,RANK:405 )
在线值:
发帖
回帖
粉丝
7
kakasasa n年前的内存注入
我只是把我学的过程与大家分享,缓冲区溢出这么多年了,不还是再用吗
2018-1-20 16:32
0
雪    币: 3136
活跃值: (97)
能力值: ( LV9,RANK:165 )
在线值:
发帖
回帖
粉丝
8
支持
2018-1-23 10:11
0
雪    币: 300
活跃值: (2472)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
mark
2018-1-24 14:18
0
雪    币: 6818
活跃值: (153)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
2018-1-25 21:10
0
雪    币: 2359
活跃值: (288)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
11
必须支持
2018-1-26 07:49
0
雪    币: 0
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
12
异常了。
2018-2-3 15:16
0
雪    币: 42
活跃值: (193)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
13
mark
2018-2-4 08:24
0
雪    币: 6
活跃值: (35)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
14
试了一下直接调用拷贝到缓冲区里的导出函数代码可能会出错,如果导出函数里只是简单地定义变量什么的才可以,是不是没有修复重定位导致,小白不太懂
2018-2-5 21:10
0
雪    币: 285
活跃值: (1095)
能力值: ( LV13,RANK:405 )
在线值:
发帖
回帖
粉丝
15
夏船长 试了一下直接调用拷贝到缓冲区里的导出函数代码可能会出错,如果导出函数里只是简单地定义变量什么的才可以,是不是没有修复重定位导致,小白不太懂[em_31]
有重定位得修复啊,而且必须得修复啊
2018-2-5 22:37
0
雪    币: 13
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
16
kakasasa n年前的内存注入
大神,要不您也分享一点技术嘛,不需要最新的,几年前的也行
2018-8-30 09:35
0
雪    币: 11716
活跃值: (133)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
17
#寻宝大战#祝看雪19岁快乐!
2019-1-11 20:16
0
雪    币: 296
活跃值: (236)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
18
为什么注入win2000失败
2019-5-16 21:53
0
雪    币: 244
活跃值: (174)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
19
husterlong 为什么注入win2000失败
为什么还有2000这样的古董存在?
2019-10-24 18:15
0
雪    币: 2
活跃值: (37)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
20
       
HANDLE hTargetProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);
楼主 现在的游戏都有对这个 函数做hook的,估计这个函数执行就无效了
2022-4-18 19:05
0
游客
登录 | 注册 方可回帖
返回
//