Gargoyle——內(nèi)存掃描逃逸技術(shù)
0x00 前言
Gargoyle是一種在非可執(zhí)行內(nèi)存中隱藏可執(zhí)行代碼的技術(shù)。在一些程序員定義的間隔,gargoyle將蘇醒,且一些ROP標記它自身為可執(zhí)行,且做一些事:
這個技術(shù)是針對32位的Windows闡述的。在本文中,我們將深入探討其實現(xiàn)細節(jié)。
0x01 實時內(nèi)存分析
執(zhí)行實時內(nèi)存分析是一個相當(dāng)大代價的操作,如果你使用Windows Defender,你可能在這個問題上就到頭了(谷歌的反惡意軟件服務(wù))。因為程序必須在可執(zhí)行的內(nèi)存中,用于減少計算負擔(dān)的一種常用技術(shù)是只限制可執(zhí)行代碼頁的分析。在許多進程中,這將數(shù)量級的減少要分析的內(nèi)存數(shù)量。
Gargoyle表明這是個有風(fēng)險的方式。通過使用Windows異步過程調(diào)用,讀寫內(nèi)存能被作為可執(zhí)行內(nèi)存來執(zhí)行一些任務(wù)。一旦它完成任務(wù),它回到讀寫內(nèi)存,直到定時器過期。然后重復(fù)循環(huán)。
當(dāng)然,沒有Windows API InvokeNonExecutableMemoryOnTimerEx。得到循環(huán)需要做一些操作。
0x02 Windows異步過程調(diào)用(APC)
異步編程使一些任務(wù)延遲執(zhí)行,在一個獨立的線程上下文中執(zhí)行。每個線程有它自己的APC隊列,并且當(dāng)一個線程進入alertable狀態(tài),Windows將從APC隊列中分發(fā)任務(wù)到等待的線程。
有一些方法來插入APC:
ReadFileEx
SetWaitableTimer
SetWaitableTimerEx
WriteFileEx
進入alertable狀態(tài)的方法:
SleepEx
SignalObjectAndWait
MsgWaitForMultipleObjectsEx
WaitForMultipleObjectsEx
WaitForSingleObjectEx
我們要使用的組合是用CreateWaitableTimer創(chuàng)建一個定時器,然后使用SetWaitableTimer插入APC隊列:
默認的安全屬性是fine,我們不想手動重置,并且我們不想要一個命名的定時器。因此對于CreateWaitableTimer所有的參數(shù)是0或者nullptr。這個函數(shù)返回一個HANDLE,表示我們新的定時器。接下來,我們必須配置它:
第一個參數(shù)是我們從CreateWaitableTimer得到的句柄。參數(shù)pDueTime是一個指向LARGE_INTEGER的指針,指定第一個定時器到期的時間。例如,我們簡單的設(shè)為0(立即過期)。lPeriod定義了過期間隔(毫秒級)。這個決定了gargoyle調(diào)用的頻率。
下個參數(shù)pfnCompletionRoutine將是我們要努力的主題。這是來自等待線程的Windows調(diào)用的地址。聽起來很簡單,除非在可執(zhí)行內(nèi)存中分發(fā)的APC沒有一個gargoyle代碼。如果我們將pfnCompletionRoutine指向gargoyle,我們將觸發(fā)數(shù)據(jù)執(zhí)行保護(DEP)。
取而代之,我們使用一些ROP gadget,將重定向執(zhí)行線程的棧到lpArgToCompletionRoutine指向的地址。當(dāng)ROP gadget執(zhí)行,指定的棧在調(diào)用gargoyle第一條指令前調(diào)用VirtualProtectEx來標記gargoyle為可執(zhí)行。
最后一個參數(shù)與在定時器到期后是否喚醒計算機有關(guān)。我們設(shè)置為false。
0x03 Windows數(shù)據(jù)執(zhí)行保護和VirtualProtectEx
最后是VirtualProtectEx,用來修改各種內(nèi)存保護屬性:
我們將在兩種上下文中調(diào)用VirtualProtectEx:在gargoyle完成執(zhí)行后(在我們觸發(fā)線程alertable之前)和在gargoyle開始執(zhí)行之前(在線程分發(fā)APC之后)??促Y料了解詳情。
在這個PoC中,我們將gargoyle,跳板,ROP gadget和我們的讀寫內(nèi)存都放進同一個進程中,因此第一個參數(shù)hProcess設(shè)置為GetCurrentProcess。下一個參數(shù)lpAddress與gargoyle的地址一致,dwSize與gargoyle的可執(zhí)行內(nèi)存大小一致。我們提供期望的保護屬性給flNewProtect。我們不關(guān)心老的保護屬性,但是不幸的是lpflOldProtect不是一個可選的參數(shù)。因此我們將將它設(shè)為一些空內(nèi)存。
唯一根據(jù)上下文不同的參數(shù)是flNewProtect。當(dāng)gargoyle進入睡眠,我們想修改它為PAGE_READWRITE或0x04。在gargoyle執(zhí)行前,我們想標記它為PAGE_EXECUTE_READ或0x20。
0x04 棧跳板
注意:如果你不熟悉x86調(diào)用約定,本節(jié)將比較難以理解。對于新人,可以參考我的文章x86調(diào)用約定。
通常,ROP gadget被用來對抗DEP,通過構(gòu)建調(diào)用VirtualProtectEx來標記棧為可執(zhí)行,然后調(diào)用到棧上的一個地址。這在利用開發(fā)中經(jīng)常很有用,當(dāng)一個攻擊者能寫非可執(zhí)行內(nèi)存??梢詫⒁欢〝?shù)量的ROP gadget放在一起做相當(dāng)多的事。
不幸的是,我們不能控制我們的alerted線程的上下文。我們能通過pfnCompletionRoutine控制eip,并且線程棧中的指針位于esp+4,即調(diào)用函數(shù)的第一個參數(shù)(WINAPI/__stdcall調(diào)用約定)。
幸運的是,我們已經(jīng)在APC入隊前就執(zhí)行了,因此我們能在我們的alerted線程中小心的構(gòu)建一個新的棧(棧跳板)。我們的策略是找到代替esp指向我們棧跳板的ROP gadget。下面的形式的就能工作:
有點詭異,因為函數(shù)通常不以pop esp/ret結(jié)束,但是由于可變長度的操作碼,Intel x86匯編過程會產(chǎn)生非常密集的可執(zhí)行內(nèi)存。不管怎樣,在32位的mshtml.dll的偏移7165405處有這么一個gadget:
注意:感謝Sascha Schirra的Ropper工具。
在我們調(diào)用SetWaitableTimer時,這個gadget將設(shè)置esp為我們放入lpArgToCompletionRoutine中的任何值。剩下的事就是將lpArgToCompletionRoutine指向一些構(gòu)造的棧內(nèi)存。棧跳板看起來如下:
我們設(shè)置lpArgToCompletionRoutine為void* VirtualProtectEx參數(shù),以便ROP gadget能ret到執(zhí)行VirtualProtectEx。當(dāng)VirtualProtectEx得到這個調(diào)用,esp將指向void* return_address。我們可以設(shè)置這個為我們的gargoyle。
0x05 gargoyle
讓我們暫停片刻,看下在我們創(chuàng)建定時器和啟動循環(huán)之前創(chuàng)建的讀寫Workspace。這個Workspace包含3個主要內(nèi)容:一些配置幫助gargoyle啟動自身,??臻g和StackTrampoline:
你已經(jīng)看見了StackTrampoline,和stack是一個內(nèi)存塊。SetupConfiguration:
在PoC的main.cpp中,SetupCOnfiguration這么設(shè)置:
非常簡單。簡單的指向多個Windows函數(shù)和一些有用的參數(shù)。
現(xiàn)在你有了Workspace的大概印象,讓我們回到gargoyle。一旦棧跳板被VirtualProtectEx調(diào)用,gargoyle將執(zhí)行。這一刻,esp指向old_protections,因為VirtualProtect使用WINAPI/__stdcall約定。
注意我們放入了一個參數(shù)(void* setup_config)在StackTrampoline的末尾。這是方便的地方,因為如果它是以__cdecl/__stdcall約定調(diào)用gargoyle的第一個參數(shù)。
這將使得gargoyle能在內(nèi)存中找到它的讀寫配置:
現(xiàn)在我們準備好了。Esp指向了Workspace.stack。我們在ebx中保存了Configuration對象。如果這是第一次調(diào)用gargoyle,我們將需要建立定時器。我們通過Configuration的initialized字段來檢查這個:
如果gargoyle已經(jīng)初始化了,我們跳過定時器創(chuàng)建。
注意,在reset_trampoline中,我們重定位了跳板中VirtualProtectEx的地址。在ROP gadget ret后,執(zhí)行VirtualProtectEx。當(dāng)它完成后,它在正常的函數(shù)執(zhí)行期間將破環(huán)棧上的地址。
你能執(zhí)行任意代碼。對于PoC,我們彈出一個對話框:
一旦我們完成了執(zhí)行,我們需要構(gòu)建調(diào)用到VirtualProtectEx,然后WaitForSingleObjectEx。我們實際上構(gòu)建了兩個調(diào)用到WaitForSingleObjectEx,因為APC將從第一個返回并繼續(xù)執(zhí)行。這啟動了我們定義的循環(huán)APC:
0x06 測試
PoC的代碼在github上,且你能簡單的測試,但是你必須安裝:
Visual studio 2015 Community,但是其他版本也能用
Netwide Assembler v2.12.02 x64,但是其他版本也能用。確保nasm.exe在你的路徑中。
克隆gargoyle:
- git clone https://github.com/JLospinoso/gargoyle.git
打開Gargoyle.sln并構(gòu)建。
你必須在和setup.pic相同的目錄運行g(shù)argoyle.exe。默認解決方案的輸出目錄是Debug或release。
每15秒,gargoyle將彈框。當(dāng)你點擊確定,gargoyle將完成VirtualProtectEx/WaitForSingleObjectEx調(diào)用。
有趣的是,使用Systeminternal的VMMap能驗證gargoyle的PIC執(zhí)行。如果消息框是激活的,gargoyle將被執(zhí)行。反之則沒有只想能夠。PIC的地址在執(zhí)行前使用stdout打印。