一、簡介
有時候,我們的應用程序會遭受網絡犯罪分子使用Hook或ROP攻擊,所以必須找到有效的方法來保護它們。在本文中,我描述了一個案例:當一個局外人(第三方應用程序,惡意軟件或逆向工程師)在我們的應用程序中攔截系統調用以更改其行為或監控其性能時,如何檢測。
我還描述了針對以下攻擊類型的兩種保護方法:
· Hook需要將第三方代碼注入到目標應用程序中以更改內存頁面的權限并重寫源代碼
· ROP攻擊不需要任何代碼注入
您可以使用這些方法來保護自己的應用程序,或在設計主動網絡防御系統時采用這些方法來防止剛剛提到的各種攻擊,甚至是0 day攻擊。
二、Hooks
Hook用于多種目的,但網絡犯罪分子通常使用它們來改變應用程序或操作系統的行為并監視其性能。有各種各樣的鉤子,但本文只考慮兩種類型:
1. 修補導入地址表(IAT)
2. Splicing
為了hook一個函數,攻擊者需要在內存加載的應用程序中更改代碼。為了修補,他們必須在IAT中重寫地址。對于Splicing,他們需要更改JMP指令的地址為函數開始的地址。結果,應用程序代碼將被改變。
因此,為了檢測代碼更改,可以用FunctionForChecking(%necessary API%)替換所有函數調用??梢詰酶鞣N方法來驗證%necessary API%是否真的是所需要的或者是否已被第三方替換??梢赃@樣做:
· 檢查函數的第一個字節,以獲取識別異常行為的控制傳輸指令。
· 檢查函數的所有校驗和。更改的校驗和表示指令已被替換。
· 確保用于傳輸控制的地址位于函數所在的加載模塊中,不在第三方加載的模塊中。
不重寫源代碼就很難做到這一點。此外,對于Hook檢測,可以將分析過程中加載的模塊與原始模塊進行比較。這里有一個example。但是,這種檢測并不主動,因為它只能檢測已安裝的鉤子。
為了執行這樣的Hook,第三方代碼需要對內存進行寫操作。但是,要做到這一點,就要獲得寫入內存頁面的權限。只有在惡意軟件代碼調用VirtualProtect函數后才能獲得這些權限。換句話說,為了攔截在我們的應用程序中對WinAPI的調用,第三方代碼需要使用WinAPI本身。
因此,我們也可以攔截VirtualProtect并檢查它。如果VirtualProtect由一個已知模塊調用,那么我們稱之為原始VirtualProtect。我們可以通過在應用程序啟動時保存所有模塊的開始和結束地址并檢查調用模塊是否在我們的模塊列表中來實現。但是,定義模塊是否合法加載是相當困難的。此方法僅能防止使用遠程線程進行DLL注入。但是注入也可以通過使用庫來寫寄存器以及AppInit_DLLs和KnownDlls 。
出于攻擊檢測的目的,假設在將模塊加載到進程的地址空間后,不更改內存頁面的權限。
三、保護措施
代碼是通過實例化一個DLL來注入的,這需要創建一個遠程線程。在此過程中,DLL嘗試掛鉤MessageBox。在我們的例子中,使用mhook來安裝鉤子??梢栽谶@篇文章中了解更多關于mhook的信息。但是,當我嘗試使用IAT注入鉤子時,此方法也起作用,因為它也需要調用VirtualProtect。安裝鉤子后,我們的應用程序會顯示一條消息,而不是如下MessageBox寫的消息。
std::cout << "after pressing ENTER MessageBox will be shown\n";
getchar();
MessageBox(NULL, L"text", L"caption", 0);
在實施針對掛鉤的保護后,應用程序中的代碼如下所示:
HookDef hookDef;
if (hookDef.Init())ret
{ /* three lines above */ }
Init攔截和VirtualProtect和VirtualProtectEx。當使用mhook鉤住了所提到的函數時,就會陷入無限遞歸之中,因為mhook本身使用VirtualProtectEx進行掛鉤。因此,必須添加一個檢查,如果掛鉤函數是VirtualProtectEx,那么使用VirtualProtect來應用mhook。
之后,鉤子通過調用CaptureStackBackTrace來定義返回地址,以便了解從哪個模塊開始調用。如果它檢測到傳輸給VirtualProtect的地址屬于已經加載到內存的模塊,那么有人正試圖hook我們的應用程序。
if (GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS,
reinterpret_cast
&hmodule))
{
InformAboutHook(callerAddress);
}
最后,得到結果:
四、ROP攻擊
我已經在這篇文章中解釋了如何執行ROP攻擊。在此情形下,我使用了一個準備好的漏洞,可以從msvcp140.dll的gadget鏈中
調用VirtualProtect??梢栽谶@篇文章中了解如何執行此漏洞利用。為了執行它,我更改了gadget地址,因為加載msvcp140.dll的地址與最初的地址不同:
在上圖中,與原始ROP漏洞利用相比,gadget地址中的更改標記為紅色。地址是用小寫字母編寫的,每個地址都是4個字節。因此,可以看到我偏移了小工具20個gadget。
讓我們想象一下,在應用程序中,堆棧被上面的ROP鏈重寫。執行漏洞利用程序首先執行指令鏈,每個指令鏈都以ret指令結束,準備調用VirtualProtect。跳到VirtualProtect代碼是在最后的ret指令中執行的。
五、防止ROP攻擊
開發人員如何檢測應用程序中的ROP攻擊?很明顯,攻擊過程需要操縱一個堆棧。因此,檢測ROP攻擊的多種選擇方案都是基于堆棧已被改變這個假設。讓我們來看看:
Shadow Stack是創建第二個堆棧的方法,其中返回地址與原始堆棧重復,并且在返回函數之前從兩個調用堆棧中加載返回地址并對它們進行比較:如果記錄不同,則其中一個已被重寫。有趣的是,這種方法已經在處理器級別上實現 。
更改調用時使用
push %table_index%
jmp %function%
返回時使用
pop ebx
jmp table[%table_index%]
但是,這種方法不能檢測到攻擊;它能防止攻擊并阻止使其發生。
G-Free方法增加了序言prologues和結語epilogues,而不是改變指令。如果函數序言中的加密返回地址,在結語中對其進行解密后結果不匹配,則ret指令將不會執行或將不能正確執行。
stack canaries方法將已知值放置在堆棧上的緩沖區和控制數據之間。這些值在返回之前被檢查。如果返回地址已更改,那么這些值也已更改。這是StackGuard中一個堆棧實現的例子。
無論如何,這些解決方案都需要重新編譯二進制文件。在本文中,我想考慮另一種方法:最后分支記錄(LBR)。
六、最后分支記錄LBR
使用gadgets,攻擊者可以調用任何系統函數。但是,單個函數調用通常是不夠的;有必要用更多的邏輯來執行某些事情。然而,創建具有更復雜邏輯的ROP鏈需要更多時間,并受可用模塊以及激活的ASLR(地址空間布局隨機化)的限制。這就是為什么ROP經常被用作繞過一些保護措施然后執行惡意代碼。例如,你可以創建一個ROP鏈來為shellcode分配內存,調用VirtualProtect,并為這個shell代碼傳遞控制權。
因此,為了檢測ROP攻擊,可以攔截對系統函數的調用,并嘗試檢查控制流程如何轉移到此:通過調用指令(正常行為),還是ret指令(異常行為)。
但如何獲得跳轉地址?現代處理器嵌入了稱為最后分支記錄的機制。激活時,處理器將跳轉地址記錄到MSR(模型特定寄存器)中。記錄的分支數量(CPU從中跳轉到)取決于處理器的類型。例如,Intel Core i5-6200U記錄31個分支。無論如何,仍然可以擁有最后一個分支的索引。
這里可以找到運行LBR的MSR地址并讀取最后一個分支的索引。該文章中提到的硬編碼值取自英特爾的手冊(第1381頁)。此外,英特爾處理器已經為分支存儲應用了改進的功能 ,本文中未涉及此點。
七、LBR實現
必須使用內核模塊才能使用MSR。我還嘗試通過安裝Dr7寄存器字節創建調試過程來從用戶模式激活MSR,但此方法非常不穩定且受到限制。
我使用__writemsr 和__readmsrfunctions作為MSR輸入輸出。通過將第一個字節安裝到地址0x1d9來激活LBR。調用DeviceIoControl來進一步與用戶模式下的驅動程序進行通信:
IOCTL_ROPPROT_FN fn { (unsigned long long)addr };
IOCTL_ROPPROT_FN fromFnCalled { 0 };
DeviceIoControl(hDriverDevice, IOCTL_ROPPROT_CHECK_LBR, &fn, sizeof(fn), &fromFnCalled, sizeof(fromFnCalled), &dwReturn, NULL);
執行跳轉的指令地址將紀錄在FnCalled的返回值。
指令搜索的簡化版本如下所示:
do
{
toBr = __readmsr(MSR_LASTBRANCH_0_TO_IP + lastLbrIdx);
lastBr = __readmsr(MSR_LASTBRANCH_0_FROM_IP + lastLbrIdx);
if (toBr == checkedAddr)
{
*fromAddr = lastBr;
return ROPPROT_SUCCESS;
}
} while (lastLbrIdx--);
之后,調用者將比較返回地址的第一個字節與ret指令(在掛鉤函數中)的操作碼。
unsigned char byte = *(unsigned char*)fromFnCalled.addr;
if (IsRet(byte))
{
MessageBox(NULL, L"ROP attack detected!", L"Alert", 0);
Return false;
}
// call the original function
以下是它如何查找用戶:
在檢測到掛鉤的VirtualProtect后,系統通過使用帶有IOCTL_ROPPROT_CHECK_LBR的參數來調用DeviceIoControl來呼叫驅動程序??梢栽贒ebugView窗口中看到驅動程序消息。它從最后記錄的分支開始經過分支,并搜索掛鉤的VirtualProtect(0x402a60)傳輸給它的地址。當它找到地址時,它會定義跳轉的執行位置:0x77696930 (VirtualProtectStub)。由于它只是一個jmp指令,因此它會查看跳轉的VirtualProtectStub——0x6b7a22fc的執行位置并返回該地址。我們的應用程序檢查此地址的指令,如果是一個ret指令,就會發送一個警報。
在上面,可以看到最后一個gadget,它將esp地址更改為eax中VirtualProtectStub的地址。之后,ret指令重定向到VirtualProtectStub。右邊是關于ROP鏈中的這個gadget的記錄,它在溢出之后自動進入堆棧。
八、運行驅動的注意事項
如果想運行驅動程序,需要考慮以下幾個方面。對于驅動程序開發,我使用了Windows Driver Kit。此外,我使用Visual Studio 2015進行驅動程序編譯。
雖然驅動程序很可能會進行測試簽名,但需要允許從命令行運行此類驅動程序,然后重啟計算機:
bcdedit /set testsigning on
另外,需要預先禁用BIOS中的安全啟動。此外,需要在寄存器中添加a Debug Print Filter記錄,才能在DebugView中查看驅動程序的輸出。
九、總結
文中所描述的方法并不打算成為最佳的Hook和ROP檢測解決方案。他們只是可能的方法。例如,如果最后一個gadget選擇使用jmp指令而不是ret,那么保護措施就不會檢測到攻擊。在這種情況下,需要對記錄的分支執行更詳細的分析,或者考慮重新記錄的頻率。
而且,我的防御只是基于使用VirtualProtect,但還需要保護一系列關鍵函數,例如LoadLibrary,這是ROP漏洞利用可能用于動態加載的關鍵函數。所描述的ROP攻擊檢測方法的思想來自于包含在Microsoft EMET中的kBouncer。
至于Hook,需要考慮到所描述的保護措施可能會干擾某些使用VirtualProtect來保護其性能的保護器的工作。至于Microsoft EMET,它還在其Memory Protection功能中使用VirtualProtect hook。
從這里可以下載Hook和ROP攻擊檢測的例子:
hookdef_src
ropprot_src
1024你懂的国产日韩欧美_亚洲欧美色一区二区三区_久久五月丁香合缴情网_99爱之精品网站
責任編輯:韓希宇
免責聲明:
中國電子銀行網發布的專欄、投稿以及征文相關文章,其文字、圖片、視頻均來源于作者投稿或轉載自相關作品方;如涉及未經許可使用作品的問題,請您優先聯系我們(聯系郵箱:cebnet@cfca.com.cn,電話:400-880-9888),我們會第一時間核實,謝謝配合。