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