本環(huán)境是蛇矛實(shí)驗(yàn)室基于"火天網(wǎng)演攻防演訓(xùn)靶場"進(jìn)行搭建,通過火天網(wǎng)演中的環(huán)境構(gòu)建模塊,可以靈活的對目標(biāo)網(wǎng)絡(luò)進(jìn)行設(shè)計(jì)和配置,并且可以快速進(jìn)行場景搭建和復(fù)現(xiàn)驗(yàn)證工作。
背景概述
在安全研發(fā)的過程中,難免會遇到在用戶模式對抗 AV/EDR 的掛鉤,應(yīng)對的方法有很多,比如可以使用直接系統(tǒng)調(diào)用,還可以將殺軟的所掛的鉤子解除,還有一種就是尋找具有相同功能未被掛鉤的 API。本文將講述直接系統(tǒng)調(diào)用的原理,并對一些相關(guān)的項(xiàng)目進(jìn)行分析。
API 的調(diào)用過程分析
首先,我們編寫一段測試程序(x64)并使用相關(guān)工具對其調(diào)用流程進(jìn)行分析。
#include?#include? #include? int?main() { ????PROCESS_INFORMATION pi{}; ????STARTUPINFO si{ sizeof(si) }; ????CreateProcess(nullptr, nullptr, nullptr, nullptr, 0, 0, 0, nullptr, &si, &pi); ????system("pause"); ????return?0; }
使用 Process Monitor 工具進(jìn)行過濾監(jiān)測,可以看到 CreateProcess API 調(diào)用流程。
從上圖可以看到,當(dāng)我們在程序中調(diào)用 CreateProcess 之后,實(shí)際上是調(diào)用了用戶層下 kernel32.dll 這個(gè) DLL 中的 CreateProcessW ,從這個(gè)調(diào)用堆??梢钥闯?,在用戶模式下最終會調(diào)用 ntdll.dll 中的 NtCreateUserProcess 函數(shù),并通過這個(gè)函數(shù)進(jìn)入內(nèi)核。
使用 IDA 對這個(gè)函數(shù)進(jìn)行查看。
通過 IDA 中查看這個(gè)函數(shù)的實(shí)現(xiàn)可以看到,NtCreateUserProcess 函數(shù)的主體,函數(shù)以 rcx 寄存器作為參數(shù),然后將其值復(fù)制到 r10 寄存器中,由于在函數(shù)主體中后續(xù)沒有用到 r10 寄存器,所以這句沒有實(shí)際的作用,接下來,將 0C8h 賦值給 eax 寄存器,其中 0C8h 為系統(tǒng)調(diào)用號,在 Windows 中,系統(tǒng)調(diào)用通常使用特定的調(diào)用號來標(biāo)識,在這個(gè)例子中,調(diào)用號 0C8h 就表示 NtCreateUserProcess 系統(tǒng)調(diào)用,隨后執(zhí)行一條測試指令,檢查 ds:7FFE0308h 位置處的字節(jié)值是否為 1,這條指令是用來判斷 CPU 是否支持快速調(diào)用(即是否支持 syscall 指令),如果支持,則會使用 syscall 指令來執(zhí)行系統(tǒng)調(diào)用,否則會通過中斷調(diào)用(int 2eh) 指令來執(zhí)行系統(tǒng)調(diào)用進(jìn)入內(nèi)核。
使用 Native API
Native API 是一種用于訪問 Windows 操作系統(tǒng)內(nèi)部功能的 API。它位于高于應(yīng)用程序級別和內(nèi)核級別之間,可以讓開發(fā)人員訪問操作系統(tǒng)的一些底層功能。上文通過對用戶層的 API 的調(diào)用流程進(jìn)行分析,可以發(fā)現(xiàn),用戶層的大多數(shù) API 調(diào)用最終都會轉(zhuǎn)到 ntdll.dll 中去執(zhí)行,并通過相對應(yīng)的 Native API 中轉(zhuǎn)最終進(jìn)入內(nèi)核層執(zhí)行,通過 ntoskrnl.exe 實(shí)現(xiàn)具體的功能。
熟悉 Windows 編程的人應(yīng)該知道,Native API 一般是不直接對外公開的,它通常作為操作系統(tǒng)內(nèi)部的一個(gè)組件,并且只能由操作系統(tǒng)內(nèi)部的組件或應(yīng)用程序調(diào)用,所以在使用一些未文檔的化的 Native API 時(shí),需要通過網(wǎng)上公開的資料或者通過逆向取獲取其函數(shù)原型和相關(guān)參數(shù)。
下面使給出一段使用 Native API 的代碼(x64),注意 NtCreateThreadEx 32 位和 64 位函數(shù)原型不同,具體差別可自行上網(wǎng)查閱或者逆向。
#include?#include? // 定義 NtCreateThreadEx 函數(shù)指針 using?NtCreateThreadExT = NTSTATUS(NTAPI*)( ????OUT PHANDLE ThreadHandle, ????IN ACCESS_MASK DesiredAccess, ????IN LPVOID ObjectAttributes OPTIONAL, ????IN HANDLE ProcessHandle, ????IN PVOID StartRoutine, ????IN PVOID Argument OPTIONAL, ????IN ULONG CreateFlags, ????IN SIZE_T ZeroBits, ????IN SIZE_T StackSize, ????IN SIZE_T MaximumStackSize, ????IN LPVOID AttributeList OPTIONAL); EXTERN_C DWORD WINAPI ThreadProc(LPVOID param) { ????std::cout?<< GetCurrentThreadId() << std::endl; ????return?0; } int?main() { ????// 獲取 NtCreateThreadEx 函數(shù)的地址 ????auto?NtCreateThreadEx = (NtCreateThreadExT)GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtCreateThreadEx"); ????if?(NtCreateThreadEx == NULL) ????{ ????????std::cout?<< "get NtCreateThreadEx func address failed!"?<< std::endl; ????????return?-1; ????} ????HANDLE thread = NULL; ????NTSTATUS status = NtCreateThreadEx( ????????&thread, // 輸出線程句柄 ????????GENERIC_EXECUTE, // 指定線程的訪問權(quán)限 ????????NULL, // 指定線程安全描述符 ????????GetCurrentProcess(), // 指定線程所在的進(jìn)程句柄 ????????ThreadProc, // 指定線程函數(shù) ????????NULL, // 指定線程函數(shù)的參數(shù) ????????FALSE, // 指定是否在創(chuàng)建時(shí)掛起線程 ????????0, // 指定堆棧中保留的字節(jié)數(shù) ????????0, // 指定堆棧中分配的字節(jié)數(shù) ????????0, // 指定堆棧中總共保留的字節(jié)數(shù) ????????NULL?????????????????// 指定附加的參數(shù) ????); ????if?(status != 0) ????{ ????????printf("thread create failed: %d ", status); ????????return?-1; ????} ????std::cout?<< "thread created successfully "?<< std::endl; ????// 等待線程結(jié)束 ????WaitForSingleObject(thread, INFINITE); ????// 關(guān)閉線程句柄 ????CloseHandle(thread); ????system("pause"); ????return?0; }
上述代碼先定義了 NtCreateThreadEx 類型的函數(shù)指針,并通過 GetModuleHandleA + NtCreateThreadEx 來獲取 NtCreateThreadEx 的函數(shù)地址,之后通過使用 NtCreateThreadEx 創(chuàng)建一個(gè)線程,在創(chuàng)建的線程回調(diào)函數(shù)中,輸出了創(chuàng)建線程的 ID 號,最終待線程執(zhí)行完畢后,關(guān)閉了線程句柄。
執(zhí)行的結(jié)果如下:
在本節(jié)演示程序中,我們通過從已加載的 ntdll.dll 內(nèi)存中獲取NtCreateThreadEx 的函數(shù)地址,這能有效的繞過 AV/EDR 關(guān)于 Kernel32、KernelBase 的掛鉤,但是卻不能繞過 AV/EDR 對 ntdll的掛鉤。目前來說,大多數(shù) AV/EDR 在用戶模式下都是對 ntdll 中的相關(guān) API 進(jìn)行掛鉤了,所以接下來還需要了解直接系統(tǒng)調(diào)用,也就是通常說的重寫 R3 下的 API。
直接系統(tǒng)調(diào)用
前文中對 CreateProcess 的調(diào)用流程進(jìn)行了分析介紹了如何使用 Native API ,下面將解釋一下系統(tǒng)調(diào)用,在理解系統(tǒng)調(diào)用之前,首先需要了解一下現(xiàn)代操作系統(tǒng)的基礎(chǔ)結(jié)構(gòu),用戶模式(空間)和內(nèi)核模式(空間)。在 Windows 中,操作系統(tǒng)提供了一個(gè)內(nèi)核空間,用于處理所有的底層系統(tǒng)事務(wù),并提供一些系統(tǒng)服務(wù),而應(yīng)用程序運(yùn)行在用戶空間,并且不能直接訪問內(nèi)核空間的內(nèi)容。當(dāng)用戶空間的應(yīng)用程序需要訪問內(nèi)核空間的系統(tǒng)服務(wù)時(shí),就需要通過系統(tǒng)調(diào)用來實(shí)現(xiàn)。
系統(tǒng)調(diào)用是一種特殊的函數(shù),允許應(yīng)用程序訪問內(nèi)核空間。應(yīng)用程序通過調(diào)用系統(tǒng)調(diào)用函數(shù),將參數(shù)傳遞給內(nèi)核空間,內(nèi)核空間處理該請求并返回結(jié)果。
在 Windows 中,系統(tǒng)調(diào)用是通過向特定的內(nèi)核空間地址發(fā)送特殊的中斷來實(shí)現(xiàn)的。具體實(shí)現(xiàn)細(xì)節(jié)取決于 Windows 版本。例如,在 Windows 10 中,系統(tǒng)調(diào)用可以通過快速系統(tǒng)調(diào)用 (syscall) 指令或者傳統(tǒng)的中斷方式來實(shí)現(xiàn)。
綜上,系統(tǒng)調(diào)用是一種能夠讓用戶空間應(yīng)用程序訪問內(nèi)核空間系統(tǒng)服務(wù)的機(jī)制。通過系統(tǒng)調(diào)用,用戶空間應(yīng)用程序可以獲得更多的系統(tǒng)功能,例如讀寫磁盤、訪問網(wǎng)絡(luò)等。在 Windows 中,系統(tǒng)調(diào)用的實(shí)現(xiàn)方式為直接系統(tǒng)調(diào)用,它是通過執(zhí)行特殊的匯編指令來直接調(diào)用內(nèi)核空間的系統(tǒng)服務(wù)的。對于直接系統(tǒng)調(diào)用,與其它系統(tǒng)調(diào)用不同的是,它需要在編譯時(shí)預(yù)知調(diào)用的系統(tǒng)服務(wù)。換句話說,在編譯時(shí)就需要確定具體的系統(tǒng)服務(wù)的調(diào)用號,并在匯編代碼中顯式地使用它。例如在 NtCreateThreadEx 函數(shù)的匯編代碼中,我們可以看到如下代碼(win11):
在這段代碼中,mov 指令將 0C7h 的值存儲到了 eax 寄存器中,這個(gè)值就是調(diào)用號。然后 syscall 指令會使用 eax 寄存器中的值作為系統(tǒng)服務(wù)的調(diào)用號,從而調(diào)用內(nèi)核空間的系統(tǒng)服務(wù)。
總之,系統(tǒng)調(diào)用是用戶空間應(yīng)用程序訪問內(nèi)核空間系統(tǒng)服務(wù)的機(jī)制,而直接系統(tǒng)調(diào)用是 Windows 中系統(tǒng)調(diào)用的實(shí)現(xiàn)方式。
現(xiàn)如今,AV/EDR 在用戶層下通常通過掛鉤 Native API 用以監(jiān)控程序的執(zhí)行流程,進(jìn)而來判斷執(zhí)行的代碼是否是惡意的。為了繞過這些安全產(chǎn)品在用戶層的 API 掛鉤,可以采用直接系統(tǒng)調(diào)用,因?yàn)橹苯酉到y(tǒng)調(diào)用是指直接通過匯編指令調(diào)用內(nèi)核空間的系統(tǒng)服務(wù),而不會經(jīng)過用戶層的 API 函數(shù),所以能夠繞過一些在用戶層的 API 掛鉤的安全產(chǎn)品。
那如何實(shí)現(xiàn)直接系統(tǒng)調(diào)用呢?這里使用 Visual Studio 進(jìn)行直接系統(tǒng)調(diào)用的測試。
編寫的測試代碼:
首先新建匯編文件(.asm),并在其中編寫需要進(jìn)行直接系統(tǒng)調(diào)用的匯編代碼。
.code NtCreateThreadEx proc ????mov r10, rcx ????mov eax, 0C7h ????syscall ????ret NtCreateThreadEx endp end
如果想要 .asm 文件參與編譯,需要設(shè)置一下項(xiàng)目屬性,在 Build Customizations 中勾選 masm 的支持。
之后在 asm 文件 上右鍵設(shè)置其屬性 --- General --- Item Type --- Mircrosoft Macro Assembler
設(shè)置完畢后,開始編寫主程序的調(diào)用代碼。
#include?#include? EXTERN_C NTSTATUS NtCreateThreadEx( ????OUT PHANDLE ThreadHandle, ????IN ACCESS_MASK DesiredAccess, ????IN LPVOID ObjectAttributes OPTIONAL, ????IN HANDLE ProcessHandle, ????IN PVOID StartRoutine, ????IN PVOID Argument OPTIONAL, ????IN ULONG CreateFlags, ????IN SIZE_T ZeroBits, ????IN SIZE_T StackSize, ????IN SIZE_T MaximumStackSize, ????IN LPVOID AttributeList OPTIONAL); DWORD WINAPI ThreadProc(LPVOID prarm) { ????std::cout?<< "thead id:"?<< GetCurrentThreadId() << std::endl; ????return?0; } int?main() { ????HANDLE hproc = GetCurrentProcess(); ????HANDLE hthread = nullptr; ????// hthread = CreateThread(nullptr, 0, ThreadProc, nullptr, 0, nullptr); ????NtCreateThreadEx(&hthread, GENERIC_EXECUTE, nullptr, hproc, ThreadProc, nullptr, FALSE, 0, 0, 0, nullptr); ????WaitForSingleObject(hthread, INFINITE); ????CloseHandle(hthread); ????system("pause"); ????return?0; }
在匯編文件中下斷點(diǎn),調(diào)試運(yùn)行可以發(fā)現(xiàn),程序能夠走到我們自己編寫的直接系統(tǒng)調(diào)用中,并沒有走原始的 Native API。
執(zhí)行完畢后,程序運(yùn)行的結(jié)果如下。
使用重寫的 NtCreateThreadEx 創(chuàng)建的線程代碼被正常執(zhí)行了。
如果將上述代碼中 NtCreateThreadEx 函數(shù)換成 CreateThread ,通過 Process Monitor 工具對該程序 Thread Create 操作進(jìn)行監(jiān)控查看其調(diào)用堆棧。
可以看到,最終還是會轉(zhuǎn)到 NtCreateThreadEx 函數(shù)。
查看 NtCreateThreadEx 創(chuàng)建線程下的調(diào)用堆棧。
在這個(gè)調(diào)用堆棧中,并沒有發(fā)現(xiàn) NtCreateThreadEx 被調(diào)用,這是因?yàn)榇藭r(shí)調(diào)用的是程序中實(shí)現(xiàn)的 NtCreateThreadEx。
上述代碼通過對 NtCreateThreadEx 進(jìn)行直接系統(tǒng)調(diào)用,從而繞過了 AV/EDR 在用戶模式下對 Native API (這里是 NtCreateThreadEx )的掛鉤。
但需要說明的是在不同的 Windows 操作系統(tǒng)版本之間,系統(tǒng)調(diào)用號可能會有所不同。
例如上述代碼是在 win11 中實(shí)現(xiàn)的,自己查看 NtCreateThreadEx 的調(diào)用號為 0C7h,當(dāng)將其放到其他操作系統(tǒng)中,可能存在不同,比如在 win10 中測試發(fā)現(xiàn),NtCreateThreadEx 的調(diào)用號為 0C1h。
下圖為 win11(版本號22621.819) 中 NtCreateThreadEx 的調(diào)用號。
下圖為 win10(版本號19043.1110) 中 NtCreateThreadEx 的調(diào)用號。
不同操作系統(tǒng)間調(diào)用號的不同詳情可參考:
https://j00ru.vexillium.org/syscalls/nt/32/
https://j00ru.vexillium.org/syscalls/nt/64/
通過上面自己編寫個(gè) API 的直接系統(tǒng)調(diào)用可以看到,這個(gè)過程還是比較繁瑣,需要區(qū)分不同操作系統(tǒng)之間的 API 的調(diào)用號的不同,并且還需要去獲取 API 的函數(shù)原型。于是網(wǎng)上出現(xiàn)了各種方便用戶使用系統(tǒng)調(diào)用的優(yōu)秀項(xiàng)目,從本質(zhì)來說,要想使用直接系統(tǒng)調(diào)用,就是想辦法獲取相關(guān) Native API 的 stubs,我根據(jù)其實(shí)現(xiàn)方式的不同進(jìn)行了分類:
動(dòng)態(tài) SSN 號獲取
二次加載 ntdll 獲取 stubs(Dual-load ntdll)
讀取內(nèi)存中的 KnownDlls
從磁盤讀取 ntdll
下面介紹幾個(gè)比較優(yōu)秀的項(xiàng)目,并對其進(jìn)行簡單分析。
SysWhispers
SysWhispers 項(xiàng)目可以通過 python 腳本自動(dòng)生成 x64 版本系統(tǒng)調(diào)用 stubs。
項(xiàng)目地址:https://github.com/jthuraisamy/SysWhispers
使用介紹:根據(jù)項(xiàng)目說明文檔進(jìn)行測試,該項(xiàng)目最終會生成兩個(gè)文件 xxx.h 和 xxx.asm ,將其拷貝到項(xiàng)目中即可使用,具體在項(xiàng)目中使用這些函數(shù)參見上文 API 的調(diào)用過程。
這里以 NtCreateFile 作為演示。
py?.syswhispers.py?--functions NtCreateFile -o?syscalls
運(yùn)行之后發(fā)現(xiàn),同級目錄下發(fā)現(xiàn):
查看頭文件,發(fā)現(xiàn)其中聲明了調(diào)用相關(guān) Native API 使用到的數(shù)據(jù)結(jié)構(gòu)。
查看其生成 asm 文件內(nèi)容,可以發(fā)現(xiàn)其中是具體 API 的直接系統(tǒng)調(diào)用代碼 stubs,生成的匯編代碼首先會判斷系統(tǒng)版本進(jìn)而去選擇內(nèi)置的函數(shù)系統(tǒng)調(diào)用號,之后在進(jìn)行系統(tǒng)調(diào)用。
這段匯編代碼通過 PEB 檢查系統(tǒng)的主要版本、次要版本和構(gòu)建號,之后根據(jù)這些信息獲取正確的系統(tǒng)調(diào)用,如下圖所示。
Syswhispers2
在使用 Syswhispers 過程中可以發(fā)現(xiàn),該項(xiàng)目需要提前知道相關(guān)函數(shù)系統(tǒng)版本調(diào)用號進(jìn)行編寫,一般未將所有系統(tǒng)版本情況包含進(jìn)去,就會調(diào)用失敗,不具有通用性,所以原作者對其進(jìn)行改進(jìn),形成了 Syswhispers2 。
項(xiàng)目地址:https://github.com/jthuraisamy/SysWhispers2
作者在項(xiàng)目介紹文檔中指出了與 Syswhispers 項(xiàng)目的不同,其中最大的區(qū)別是不需要指定要支持哪個(gè)版本的 Windows 了。
Syswhispers2 項(xiàng)目的用法與 Syswhispers 一致,以生成 NtCreateUserProcess 的系統(tǒng) stubs 來說明。
py .syswhispers.py --functions NtCreateThreadEx -o syscalls -a x64 -l masm
下面對生成的關(guān)鍵代碼進(jìn)行分析。
生成的 syscallsstubs.std.x64.asm 中的代碼如下圖所示。
上述的代碼片段的主要功能是通過內(nèi)置預(yù)定義的 hash 值來獲取系統(tǒng)調(diào)用號,進(jìn)而進(jìn)行相關(guān)函數(shù)的系統(tǒng)調(diào)用。
其中關(guān)鍵的函數(shù)為 SW2_GetSyscallNumber ,該函數(shù)在 syscalls.c 文件中被定義與實(shí)現(xiàn)。
該函數(shù)中又調(diào)用了另一個(gè)關(guān)鍵函數(shù) SW2_PopulateSyscallList ,該函數(shù)將 Zw 開頭 Native API 名稱的 hash 值按照升序排序保存到 SW2_SyscallList.Entries 這個(gè)全局?jǐn)?shù)組中。其中獲取 Zw 開頭的 API 是通過 peb 的到 ntdll 基址后,遍歷其導(dǎo)出表得到的,排序算法使用的冒泡排序。
通過 peb 獲取已加載的 ntdll 在內(nèi)存中的基址。
遍歷內(nèi)存中 ntdll 的導(dǎo)出表,獲取 Zw 開頭的函數(shù)名稱,存儲到全局?jǐn)?shù)組 SW2_SyscallList.Entries 中。
對 SW2_SyscallList.Entries 這個(gè)全局?jǐn)?shù)組進(jìn)行冒泡升序排序。
生成的文件中還有一個(gè)匯編文件(syscallsstubs.rnd.x64.asm),其代碼片段如下。
上述代碼片段與 syscallsstubs.std.x64.asm 中代碼不同點(diǎn)在于,該段匯編代碼隱藏了 syscall 指令的出現(xiàn),Syswhispers2 項(xiàng)目使用 SW2_GetRandomSyscallAddress 函數(shù)生成了隨機(jī)的 syscall 指令地址,用于防止 syscall 指令在匯編代碼片段中出現(xiàn)。
在使用這個(gè)文件時(shí)需要注意,需要 #define RANDSYSCALL 聲明宏,以開啟 SW2_GetRandomSyscallAddress 。
其中 SW2_GetRandomSyscallAddress 函數(shù)在 syscalls.c 文件中定義與實(shí)現(xiàn)。
該段代碼通過特征碼定位的形式來獲取隨機(jī)一個(gè) Native API 的 syscall 指令地址,之后通過 call qword ptr [syscallAddress] 替換代碼中的 syscall 指令,從而繞過了 AV/EDR 對 syscall 指令的標(biāo)記。
SysWhispers3
SysWhispers3 項(xiàng)目在項(xiàng)目說明文檔中解釋了與 Syswhispers2 項(xiàng)目的不同之處。
下面對 SysWhispers3 項(xiàng)目生成的文件進(jìn)行簡單分析。
主要分析生成的匯編代碼文件和 syscalls.c 文件。
分析使用的生成腳本命令
py .syswhispers.py --functions NtCreateThreadEx -o syscalls -a x64 -c msvc -m jumper_randomized
對生成的代碼文件分析,一共生成了 3 個(gè)文件。
生成的 syscalls-asm.x64.asm 文件如下。
該段匯編代碼主要是隱藏了 syscall 指令的出現(xiàn),并隨機(jī)獲取其他 API stubs 中的 syscall 指令,之后通過使用 jmp syscall 地址對其進(jìn)行轉(zhuǎn)換,從而繞過了部分 AV/EDR 對 syscall 指令的標(biāo)記。
其中函數(shù)的調(diào)用地址是 SW3_GetRandomSyscallAddress 函數(shù)實(shí)現(xiàn)的,該函數(shù)在 syscalls.c 文件中定義并實(shí)現(xiàn)。
SW3_GetRandomSyscallAddress 函數(shù)的主要作用是得到一個(gè)隨機(jī)的 Native API 的 ????syscall 指令地址。剩下的幾個(gè)函數(shù)與 Syswhispers2 項(xiàng)目中大體類似,便不在這里分析了。
HellsGate
項(xiàng)目地址:https://github.com/am0nsec/HellsGate
HellsGate 主要思路是通過 PEB 獲取已加載的 ntdll 在內(nèi)存中的基地址,隨后通過解析其導(dǎo)出表,來定位 API 地址,之后通過特征碼來獲取函數(shù)的系統(tǒng)調(diào)用號,進(jìn)而實(shí)現(xiàn)系統(tǒng)調(diào)用。
下面分析一些關(guān)鍵代碼。
這段代碼片段就是 HellsGate 用來定位系統(tǒng)調(diào)用號的特征碼。
mov r10,rcx // 0x4c 0x8b 0xd1 mov eax,// 0xb8 xx xx 0x00 0x00
雖然 HellsGate 通過特征碼能夠準(zhǔn)確定位獲取函數(shù)的系統(tǒng)調(diào)用號,但也存在一定的局限性,使用它的前提是內(nèi)存中的 ntdll 必須是“干凈”的 ntdll ,也就是不能被 AV/EDR 掛鉤,一旦被掛鉤,比如 mov r10,rcx 被掛鉤了,那么其字節(jié)碼就變了,就找不到想要的函數(shù)系統(tǒng)調(diào)用號了。
Halo’s Gate
后續(xù)也出現(xiàn)了一些改進(jìn)方案,比如 Halo’s Gate,詳情可參考:https://blog.sektor7.net/#!res/2021/halosgate.md ,基本思路就是由于相鄰的系統(tǒng)調(diào)用有一定規(guī)律性(如下圖所示),所以只要定位相鄰的系統(tǒng)調(diào)用,就可以推導(dǎo)出想要函數(shù)的系統(tǒng)調(diào)用號。
HellsGatePoC
從 HellsGate 之門項(xiàng)目可以看到,它的局限性在于需要有一塊“干凈”的 ntdll ,不然,它無法獲取到想要的系統(tǒng)調(diào)用號。所以,又一個(gè)思路誕生了,該項(xiàng)目的主要思路是從磁盤中中獲取干凈的 ntdll,并將其映射到內(nèi)存中,進(jìn)而獲取到系統(tǒng)調(diào)用號,從而使用系統(tǒng)調(diào)用。
項(xiàng)目地址:https://github.com/N4kedTurtle/HellsGatePoC
更多的項(xiàng)目
當(dāng)然有關(guān)使用直接系統(tǒng)調(diào)用繞過用戶層掛鉤的項(xiàng)目遠(yuǎn)遠(yuǎn)不止這些,由于篇幅有限,本文并沒有將其全部都介紹一遍,下面我推薦2個(gè)我認(rèn)為優(yōu)秀的項(xiàng)目。
https://github.com/JustasMasiulis/inline_syscall
https://github.com/crummie5/FreshyCalls
有興趣的讀者可以去了解下。
經(jīng)過前文的分析,可以知道使用直接系統(tǒng)調(diào)用大致就是獲取 Native API 的 stubs 或者動(dòng)態(tài)的獲取系統(tǒng)調(diào)用號去構(gòu)造 stubs,雖然使用直接系統(tǒng)調(diào)用能夠有效的避免 AV/EDR 在用戶模式下的掛鉤,但是去獲取這個(gè) stubs 或者調(diào)用號的過程還是會被 AV/EDR 監(jiān)控的,并且在 Window Vista 之后,在內(nèi)核模式中,安全廠商的研發(fā)人員可以利用微軟提供的現(xiàn)成的內(nèi)核通知回調(diào)很輕松的監(jiān)控用戶模式下程序的各種動(dòng)作,所以在進(jìn)行防御規(guī)避的過程中,需要不斷去發(fā)掘出新的思路方法,或者將已知的各種規(guī)避技術(shù)相互結(jié)合來進(jìn)行欺騙和繞過。
并且隨著 Windows 版本的提升,微軟也已經(jīng)開始也有趨勢去處理調(diào)用號的問題,從下圖(x86)可以發(fā)現(xiàn),系統(tǒng)調(diào)用號并不一定是穩(wěn)定遞增的,所以從這個(gè)方面想,那種基于排序動(dòng)態(tài)獲取系統(tǒng)調(diào)用號的方法是否在未來還可用?以及是否有對應(yīng)的緩解措施去應(yīng)對這些變化,是作為一個(gè)安全研究員需要去不斷探索的事。
參考文獻(xiàn)
https://j00ru.vexillium.org/syscalls/nt/32/
https://j00ru.vexillium.org/syscalls/nt/64/
https://www.mdsec.co.uk/2020/12/bypassing-user-mode-hooks-and-direct-invocation-of-system-calls-for-red-teams/
https://blog.sektor7.net/#!res/2021/halosgate.md
https://teamhydra.blog/2020/09/18/implementing-direct-syscalls-using-hells-gate/
編輯:黃飛
?
評論
查看更多