Kate Li (Taiwan)的部落格

首頁

理解與回避

作者 alkan 時間 2020-03-19
all

我真正喜歡的領域之一是RedTeam和BlueTeam之間的“猫捉老鼠”遊戲,它們都迫使對方提高自己的遊戲水准。我們經常看到一些很棒的工具被發佈來幫助防禦者檢測惡意軟件或外殼程式碼的執行,並且知道這些防禦能力在執行成功的pentest或RedTeam任務時是如何發揮作用的非常重要。

最近我遇到了一個很棒的帖子“防守隊員也用圖表思考!”,可以在SpectreOps的部落格上找到。這篇文章是“通過一個專注於檢測過程注入的案例研究,研究資料獲取、數據質量和資料分析”系列文章的開始。如果你沒讀過,我強烈建議你讀。

本文討論的工具之一是“Get InjectedThread”,它是一個Powershell腳本,能够枚舉正在運行的行程,並顯示它認為是行程注入受害者的任何行程的資訊。這個工具可以在GitHub上找到。

當我看到這個工具的時候,我想到了一件事,那就是如果我在訂婚的時候遇到它,我會如何繞過檢測。另外,出於對Windows安全性這一領域的興趣,我真的希望在這種檢測科技發展的時候有一個良好的起點,可以通過Get InjectedThread的下一次反覆運算,或者通過集成同一方法的其他工具。這篇文章將通過一些不同的技巧來幫助我們理解如何繞過這種分析。

Get-InjectedThread

我們能避免被發現嗎?

通常,當試圖在另一個行程中執行程式碼時,會使用VirtualAllocEx->WriteProcessMemory->CreateRemoteThread鏈。讓我們快速查看Get InjectedThread的實際操作,方法是首先向行程中注入外殼程式碼,然後運行Get InjectedThread:

VirtualAllocEx WriteProcessMemory CreateRemoteThread Get-InjectedThread Get-InjectedThread

…我說過注射線程很棒,不是嗎:)。

在這裡,我們看到注入到cmd.exe中的外殼程式碼被捕獲並顯示給用戶,表明有可疑之處。

獲取InjectedThread的方法是分析系統上運行的線程。然後枚舉與線程的起始地址相關聯的記憶體,如果發現記憶體缺少MEM_IMAGE標誌,則該工具訓示線程可能是從動態分配的記憶體(很可能是從VirtualAllocEx調用或類似調用)運行的,而不是從DLL或EXE生成的。。。很酷。

Get-InjectedThread MEM_IMAGE VirtualAllocEx

現在,讓我們回顧一下程式碼,看看這些檢查是如何執行的:

function Get-InjectedThread { ... $hSnapshot = CreateToolhelp32Snapshot -ProcessId 0 -Flags 4 $Thread = Thread32First -SnapshotHandle $hSnapshot do { $proc = Get-Process -Id $Thread.th32OwnerProcessId if($Thread.th32OwnerProcessId -ne 0 -and $Thread.th32OwnerProcessId -ne 4) { $hThread = OpenThread -ThreadId $Thread.th32ThreadID -DesiredAccess $THREAD_ALL_ACCESS -InheritHandle $false if($hThread -ne 0) { $BaseAddress = NtQueryInformationThread -ThreadHandle $hThread $hProcess = OpenProcess -ProcessId $Thread.th32OwnerProcessID -DesiredAccess $PROCESS_ALL_ACCESS -InheritHandle $false if($hProcess -ne 0) { $memory_basic_info = VirtualQueryEx -ProcessHandle $hProcess -BaseAddress $BaseAddress $AllocatedMemoryProtection = $memory_basic_info.AllocationProtect -as $MemProtection $MemoryProtection = $memory_basic_info.Protect -as $MemProtection $MemoryState = $memory_basic_info.State -as $MemState $MemoryType = $memory_basic_info.Type -as $MemType if($MemoryState -eq $MemState::MEM_COMMIT -and $MemoryType -ne $MemType::MEM_IMAGE) { ...

閱讀上面的內容,有很多方面都很突出。首先是:

$BaseAddress = NtQueryInformationThread -ThreadHandle $hThread

此命令負責檢索正在運行的線程的入口地址。在注入外殼程式碼時,這通常是提供給CreateRemoteThread調用的地址。例如:

CreateRemoteThread threadHandle = CreateRemoteThread( processHandle, NULL, 0, BASE_ADDRESS, NULL, CREATE_SUSPENDED, NULL );

第二個有趣的電話是:

$memory_basic_info = VirtualQueryEx -ProcessHandle $hProcess -BaseAddress $BaseAddress

此行正在調用Win32函數VirtualQueryEx,該函數返回與運行線程的基址相關聯的記憶體分配資訊。記憶體保護、長度、標誌等資訊。。

VirtualQueryEx

然後在以下命令中使用此資訊:

if($MemoryState -eq $MemState::MEM_COMMIT -and $MemoryType -ne $MemType::MEM_IMAGE)

這裡我們看到最後一個檢查,看看線程的基址是否有MEM_COMMIT標誌,是否缺少MEM_IMAGE標誌。如果這是真的,那麼很有可能這是一個從動態記憶體注入並運行的線程,然後該工具將突出顯示該線程。

MEM_COMMIT MEM_IMAGE

考慮到這一點,讓我們看看是否有什麼方法可以繞過這些檢查,並在評估期間保持警惕。

通過加載庫注入DLL

第一種避免被捕獲的方法是將外殼程式碼添加到DLL中,然後使用LoadLibraryA作為入口點。通過這樣做,我們可以繞過以下檢查,因為我們的入口點實際上在MEM_映射標記記憶體中:

LoadLibraryA MEM_IMAGE if($MemoryState -eq $MemState::MEM_COMMIT -and $MemoryType -ne $MemType::MEM_IMAGE)

要瞄準LoadLibraryA,我們需要完成以下步驟:

LoadLibraryA LoadLibraryA LoadLibraryA

看起來像這樣:

讓我們通過從注入的DLL調用MessageBoxA來嘗試一下:

MessageBoxA

在這裡,我們看到我們已經成功地將外殼程式碼注入到啟動訊息方塊的cmd.exe中。更重要的是,在運行Get InjectedThread的過程中我們沒有被接收。

Get-InjectedThread

當然,這種技術有一個明顯的弱點,那就是我們需要在磁片上放置一個DLL來執行注入,新增我們的外殼程式碼被發現的機會。。。不過,這是一個很好的起點。

我們看看能不能做點別的。

SetThreadContext

我們現在知道,一個好的方法是將線程的入口地址設定為包含MEM_IMAGE標誌的記憶體區域。但是,如果我們在開始之前更新線程的入口點,這是否足以逃避檢測呢?

MEM_IMAGE

在本例中,我們將使用以下步驟,利用SetThreadContext調用將執行重定向到注入的外殼程式碼:

SetThreadContext MEM_IMAGE

這裡的理論是,我們的線程的基址將是MEM_映射標記的記憶體區域的基址,即使我們從未實際從這個地址執行程式碼。然後通過設定rip寄存器指向外殼程式碼,我們實現了執行,同時希望繞過Get InjectedThread。我們的代碼如下:

MEM_IMAGE rip Get-InjectedThread

運行我們的示例,我們看到有第二種執行外殼程式碼的方法,將線程注入notepad.exe:

以回報為導向。。。urm,螺紋

好吧,所以這個有點左外野。。。在本例中,我們將利用現有MEM_映射二進位檔案的指令將執行傳遞給外殼程式碼(如ROP),囙此:

MEM_IMAGE ThreadProc

我們知道,我們將使用CreateRemoteThread調用開始執行線程,該調用獲取要執行的ThreadProc的地址。我們還知道可以將可選參數傳遞給ThreadProc。

CreateRemoteThread ThreadProc ThreadProc

在x64行程上,參數將在rcx寄存器中傳遞,那麼我們尋找一個小工具:

rcx jmp rcx

這樣,我們可以將ThreadProc地址設定為jmp rcx小工具,並將外殼程式碼地址作為參數傳遞。這再次滿足了確保線程的基址位於記憶體的MEM_映射部分的要求,囙此應該有助於避免檢測。

ThreadProc jmp rcx MEM_IMAGE

下麵是如何實現這一點的示例:

當執行時,我們發現我們的外殼程式碼可以運行而不被標記:

所以我們有了它,一些不同的方法來生成我們的線程,同時隱藏真正的開始地址。希望這證明是有用的,如果你遇到類似的檢測科技。如果您有任何方法可以使用Get InjectedThreads檢測到上面的示例,那麼聽到它們將是非常棒的!

Get-InjectedThreads

更多閱讀