概述
成都創(chuàng)新互聯憑借專業(yè)的設計團隊扎實的技術支持、優(yōu)質高效的服務意識和豐厚的資源優(yōu)勢,提供專業(yè)的網站策劃、網站建設、成都網站建設、網站優(yōu)化、軟件開發(fā)、網站改版等服務,在成都10余年的網站建設設計經驗,為成都數千家中小型企業(yè)策劃設計了網站。
在本文中,我們將主要介紹一種新型的進程注入方法,我們稱之為“Ctrl-Inject”,它利用控制臺應用程序中處理Ctrl信號的機制實現注入。在研究的過程中,我們在瀏覽MSDN時發(fā)現有一條關于Ctrl信號處理的相關評論:“這是一個與SetConsoleCtrlHandler函數( https://docs.microsoft.com/en-us/windows/console/setconsolectrlhandler )一起使用的函數,由應用程序定義??刂婆_進程使用此函數來處理進程收到的控制信號。當收到信號后,系統(tǒng)會在進程中啟動一個新的線程來執(zhí)行該函數?!边@也就意味著,每次我們觸發(fā)一個信號到一個基于控制臺的進程時,系統(tǒng)都會調用一個在新線程中調用的處理函數。正因如此,我們可以借助這一特點,來實現一個不同于以往的進程注入。
控制信號處理
當用戶或進程向基于控制臺的進程(例如cmd.exe或powershell.exe)發(fā)送Ctrl + C(或Break)信號時,系統(tǒng)進程csrss.exe將會在目標進程中創(chuàng)建一個新的線程來調用函數CtrlRoutine。CtrlRoutine函數負責包裝使用SetConsoleCtrlHandler的處理程序。接下來,我們深入研究一下CtrlRoutine,首先注意到了下面這段代碼:
該函數使用名為HandlerList的全局變量來存儲回調函數列表,在該函數中會循環(huán)執(zhí)行,直到其中一個處理程序返回TRUE(通知該信號已被處理)為止。為了使處理程序成功執(zhí)行,它必須滿足以下條件:1、函數指針必須正確編碼。處理程序列表中的每個指針都使用RtlEncodePointer進行編碼,并在執(zhí)行之前使用RtlDecodePointer API進行解碼。因此,未經編碼的指針很有可能會導致程序崩潰。2、指向有效的CFG(Control Flow Guard,控制流防護)目標。CFG通過驗證間接調用的目標是否為有效函數,來嘗試對間接調用進行保護。我們來看一下SetConsoleCtrlHandle,看看它如何設置一個Ctrl處理程序,以便我們以后可以模仿其方式。在下圖中,我們可以看到各個指針在添加到HandlerList之前是如何編碼的。
接下來,我們看到了一個名為SetCtrlHandler的內部函數調用。該函數更新了兩個變量:一個是HandlerList,用于添加一個新的指針;另一個全局變量是HandlerListLength,增加了它的長度以適應新的列表大小。
現在,由于HandlerList和HandlerListLength變量駐留在kernelbase.dll模塊中,并且該模塊會映射到所有進程的相同地址,所以我們可以在進程中找到它們的地址,然后使用WriteProcessMemory在遠程進程中更新它們的值。我們的工作還沒有完成,考慮到CFG和指針編碼的存在,我們需要找到一種方法來繞過它們。
繞過指針編碼
在Windows 10之前的版本中,我們需要理解指針編碼、解碼的工作原理,從而應對指針編碼保護。接下來,我們一起深入了解一下EncodePointer的工作原理。
開始,存在一個對NtQueryInformationProcess的調用,其定義如下:
NTSTATUS WINAPI NtQueryInformationProcess(
_In_HANDLE ProcessHandle,
_In_PROCESSINFOCLASS ProcessInformationClass,
_Out_ PVOIDProcessInformation,
_In_ULONGProcessInformationLength,
_Out_opt_ PULONG ReturnLength
);
根據上述定義,我們可以做出以下假設:1、ProcessHandle:當傳遞-1的值時,它代表引用調用進程的函數。2、ProcessInformationClass:該參數的值為0x24,這是一個未公開的值,要求內核檢索進程加密Cookie。Cookie本身駐留在EPROCESS結構中。在檢索加密Cookie后,我們可以看到幾個涉及輸入指針和加密Cookie的操作。具體為:
EncodedPointer = (OriginalPointer ^ SecretCookie) >> (SecretCookie & 0x1F)
一種繞過的方法,是使用CreateRemoteThread執(zhí)行RtlEncodePointer,并將NULL作為參數傳遞給它,如下所示:1) EncodedPointer = (0 ^ SecretCookie) >> (SecretCookie & 0x1F)2) EncodedPointer = SecretCookie >> (SecretCookie & 0x1F)這樣一來,返回值將被Cookie旋轉的值增加到31倍(在64位Windows 10環(huán)境上該值為63,即0x3f)。如果我們在目標進程上使用已知的編碼地址,就能夠暴力猜測出原始Cookie值。以下代碼展示了如何對Cookie進行暴力猜測:
在Windows 10及以上版本中,微軟非??犊貫槲覀兲峁┝艘唤M新的API,稱為RtlEncodeRemotePointer和RtlDecodeRemotePointer。顧名思義,我們傳遞一個進程句柄和一個指針,該API將會為目標進程返回一個有效的編碼后指針。此外,還有另一種提取Cookie的技術,請參考: https://github.com/changeofpace/Remote-Process-Cookie-for-Windows-7/blob/master/Remote%20Process%20Cookie%20for%20Windows%207/main.cpp 。
繞過CFG
到目前為止,我們已經將我們的代碼注入到目標進程,并修改了HandlerList和HandlerListLength的值。如果我們現在嘗試發(fā)送Ctrl+C信號來觸發(fā)代碼,該進程會引發(fā)異常,最終自行終止。其原因在于,CFG會注意到我們正在嘗試跳轉到一個非有效調用目標的指針。幸運的是,微軟對我們一直非常友善,他們發(fā)布了另外一個有用的API,名為SetProcessValidCallTargets。
WINAPI SetProcessValidCallTargets(
_In_HANDLEhProcess,
_In_PVOID VirtualAddress,
_In_SIZE_TRegionSize,
_In_ULONG NumberOfOffsets,
_Inout_ PCFG_CALL_TARGET_INFO OffsetInformation
);
簡而言之,我們傳遞進程句柄和指針后,該API會將其設置為有效的調用目標。此外,如果使用我們此前介紹過的( https://blog.ensilo.com/documenting-the-undocumented-adding-cfg-exceptions )未記錄的API也可以實現這一點。
觸發(fā)Ctrl+C事件
現在一切準備就緒,我們需要做的就是在目標進程上觸發(fā)Ctrl + C,以調用我們的代碼。有幾種方法可以觸發(fā)它。在這種情況下,我們可以使用SendInput的組合,來觸發(fā)系統(tǒng)范圍的Ctrl鍵按鍵,以及用于發(fā)送C鍵的PostMessage。同樣,也適用于隱藏或不可見的控制臺窗口。以下是觸發(fā)Ctrl-C信號的函數:
揭秘底層
從實質上來說,在這個進程注入技術中,我們將代碼注入到目標進程中,但是我們從不直接調用它。也就是說,我們從來沒有自己調用CreateRemoteThread或使用SetThreadContext改變執(zhí)行流。相反,我們正在讓csrss.exe為我們調用它,這樣一來就顯得是一個正常的行為,不會被懷疑。其原因在于,每次將Ctrl + C信號發(fā)送到基于控制臺的應用程序時,conhost.exe會調用類似于調用堆棧的內容,如下所示:
其中,CsrClientCallServer會傳遞一個唯一索引標識符(0x30401),然后將其傳遞給csrss.exe服務。在其中,會從調度表中調用一個名為SrvEndTask的函數。調用鏈具體如下:
在這個調用鏈的最后,我們看到了RtlCreateUserThread,它負責在目標進程上執(zhí)行我們的線程。注意:盡管Ctrl-Inject技術僅針對于控制臺應用程序,但也可能會在很多控制臺應用程序上被濫用,最值得注意的就是cmd.exe。
總結
現在,我們已經了解了這個新型的進程注入方法,掌握了該方法的工作原理以及其背后到底發(fā)生了什么。在最后,我們可以總結一下Ctrl-Inject技術。這種技術與傳統(tǒng)線程注入技術相比,主要優(yōu)點是遠程線程是由可信的Windows進程csrss.exe創(chuàng)建,這使得它得隱蔽性更強。但同樣存在缺點,就是這種方法僅適用于控制臺應用程序。
要進行這種進程注入技術,所需的步驟如下:1、將OpenProcess附加到控制臺進程。2、通過調用VirtualAllocEx,為惡意負載分配一個新的緩沖區(qū)。3、使用WriteProcessMemory將數據寫入分配的緩沖區(qū)。4、使用目標進程cookie將指針指向指定的緩沖區(qū)。通過調用帶有空指針的RtlEncodePointer并手動編碼指針或通過調用RtlEncodeRemotePointer來實現。5、通知遠程進程,新指針是可以使用SetProcessValidCallTargets的有效指針。6、最后,使用PostMessage和SendInput的組合觸發(fā)Ctrl + C信號。7、恢復原始處理程序列表。