首頁>技術>

主要內容

針對程序行為的監控需求,以往很多安全軟體都是採用的Hook技術攔截關鍵的系統呼叫,來實現對惡意軟體程序建立的攔截。但在x64架構下,系統核心做了很多安全檢測措施,特別是類似於KDP這樣的技術,使得Hook方法不再有效。為此OS推出了基於回撥實現的行為監控方案。本文藉助IDA逆向分析該技術的實現原理並給出了關鍵資料結構及呼叫鏈,透過雙機核心除錯驗證了該資料結構以及呼叫鏈的正確性。

涉及到的內容如下:

1、核心物件及核心物件管理;2、程序回撥;3、核心除錯;4、Windbg雙擊除錯;

0 引言

近年來,各種惡意軟體新變種層出不窮,攻擊方法、手段多種多樣,造成了巨大的經濟損失。作為防守的第一個環節就是能夠識別出惡意程序建立的動作,而程序建立監控技術是為了能夠讓安全軟體有機會攔截到此動作的技術。安全軟體根據匹配演算法判斷是否准許該程序建立,以此達到保護使用者資料安全的目的。x86架構下的實現方案多為Hook技術,透過攔截核心中程序建立的關鍵API如nt!NtCreateProcess或nt!NtCreateProcessEx,透過堆疊來回溯到關鍵引數,如待建立程序的exe全路徑、父程序資訊,然後根據獲取到的全路徑檢測exe磁碟檔案,同時也可以分析程序鏈最終確定是否放行該動作。但這種技術方案存在一些缺陷,一方面其破壞了核心的完整性,導致系統的穩定性下降;另一方面,這些API很多都是未公開的,也就意味著需要透過逆向工程等技術手段來分析OS核心映象檔案,定位到關鍵的API。但如果系統升級了,該API可能就不存在了,這也導致安全軟體的相容性特別差;最重要的是各個安全廠家的實現方案不一樣,掛鉤的點也不同,很容易出現相互競爭的情況,極有可能會導致BSoD(Blue Screen of Death)。另一種傳統的基於特徵碼的攔截方式,也同樣存在類似的問題。需要為每個子版本的系統關鍵API做逆向分析,取出特徵碼,當系統更新或者打補丁,則需要再次逆向分析取出特徵碼。工作量巨大,效率低下,適配性很低,如果沒有及時更新特徵碼,很可能會使得監控失效,情況糟糕的時候會直接導致BSoD。為此,在x64架構下,核心一方面為了保護關鍵資料的完整性,另一方面也為了提高核心程式自身的穩定性,推出了諸如KDP(Kernel Data Protection)、PG等安全措施,使得傳統的 Hook技術失效;同時OS為了規範化安全相關資訊的獲取,使得安全軟體能夠在核心可控的情況下提供安全服務,Windows系統層面提供了一種基於回撥的方式來通知安全軟體註冊的核心回撥例程。這種方式優點是方便高效,可移植性好,穩定性高,且各個安全廠商之間也不會出現競爭的關係。

本文基於逆向工程及核心除錯技術,分析了該技術的具體實現及系統額外增加的資料檢測機制。藉助逆向工具IDA靜態逆向分析了系統關鍵API的內部動作及具體的實現,相關的資料結構,得到該技術實際觸發的呼叫源以及整個呼叫鏈。藉助VMWare搭建雙機除錯環境,利用Windbg動態除錯系統核心,檢視系統中所涉及到的關鍵資料,並與PCHunter給出的資料做對比分析,驗證了分析結論的正確性。此外還透過對呼叫鏈中的關鍵函式下斷點,透過棧回溯技術,動態觀察了整個呼叫鏈及觸發時間。分析得到的關鍵資料結構和系統對資料做的檢測校驗演算法可用於檢測病毒木馬等軟體惡意構造的表項,且還可以應用到安全廠商對抗惡意程式碼時,自動構造表項來檢測系統行為,完全脫離系統提供的註冊解除安裝API。

1 程序回撥原理分析1.1 安裝與解除安裝逆向分析

根據微軟官方技術文件MSDN上的說明,透過PsSetCreateProcessNotifyRoutine、PsSetCreateProcessNotifyRoutineEx和PsSetCreateProcessNotifyRoutineEx2這三API來安裝一個程序建立、退出通知回撥例程,當有程序建立或者退出時,系統會回撥引數中指定的函式。以PsSetCreateProcessNotifyRoutine為例子,基於IDA逆向分析該API的具體實現。如圖1所示,由圖可知,該API內部僅僅是簡單的呼叫另一個函式,其自身僅僅是一個stub,具體的實現在PspSetCreateProcessNotifyRoutine中,此函式的安裝回調例程的關鍵實現如圖所示。

呼叫ExAllocateCallBack,創建出了一個回撥物件,並將pNotifyRoutine和bRemovel作為引數傳入,以初始化該回調物件,程式碼如圖所示;其中pNotifyRoutine即是需要被回撥的函式例程,此處的bRemovel為false,表示當前是安裝回調例程。

緊接著呼叫ExCompareExchangeCallBack將初始化好的CallBack物件新增到PspCreateProcessNotifyRoutine所維護的全域性陣列中。值得注意的是,ExCompareExchangeCallBack中在安裝回調例程時,對回撥例程有一個特殊的操作如圖所示。

與0x0F做了或操作,等價於將低4位全部置1;若ExCompareExchangeCallBack執行失敗,則接著下一輪迴圈繼續執行。由圖2中第66行程式碼可知,迴圈的最大次數是0x40次。如果一直失敗,可呼叫ExFreePoolWithTag釋放掉pCallBack所佔用的記憶體,且返回0xC000000D錯誤碼。

然後根據v3的值判斷是透過上述三個API中的哪個安裝的回撥,來更新相應的全域性變數。其中PspCreateProcessNotifyRoutineExCount和PspCreateProcessNotifyRoutineCount分別記錄當前透過PsSetCreateProcessNotifyRoutineEx和PsSetCreateProcessNotifyRoutine安裝回調例程的個數。PspNotifyEnableMask用以表徵當前陣列中是否安裝了回撥例程,該值在系統遍歷回撥陣列執行回撥例程時,用以判斷陣列是否為空,加快程式的執行效率。

除了能夠安裝回調例程,這三個API也能解除安裝指定的回撥例程。以PsSetCreateProcessNotifyRoutine為例,分析其實現的關鍵部分,如圖所示。

透過一個while迴圈遍歷PspCreateProcessNotifyRoutine陣列,呼叫ExReferenceCallBackBlock取出陣列中的每一項,該API內部會做一些檢驗動作且對返回的資料也做了特殊處理,如圖所示。圖6中*pCallBackObj即是取出回撥物件中的回撥例程的函式地址,透過判斷其低4位是否為1來做一些資料的校驗,如17行所示。系統做這個處理也是起到保護作用,防止惡意構造資料填入表中,劫持正常的系統呼叫流程。此外,圖中第33行處的程式碼,在將回調例程返回給父呼叫時,也將回調例程的低4位全部清零,否則返回的地址是錯誤的,呼叫立馬觸發CPU異常。

ExReferenceCallBackBlock成功返回後,呼叫ExGetCallBackBlockRoutine從返回的回撥物件中取出回撥例程,並判斷取出的是否為當前指定需要解除安裝的項,如果是則呼叫ExDereferenceCallBackBlock遞減引用計數,接著呼叫ExFreePoolWithTag釋放掉Callback所佔用的記憶體。期間也會更新PspCreateProcessNotifyRoutineExCount或PspCreateProcessNotifyRoutineCount的值。根據原始碼還可以得知,該陣列總計64項,也即只能安裝64個回撥例程。如果遍歷完陣列的64項依舊沒有找到,則返回0xC000007A錯誤碼。

1.2 OS執行回撥例程分析

回撥例程安裝完之後,如果有新的程序建立或退出,核心則便會遍歷該陣列來執行其中安裝的每一項回撥例程。透過IDA的交叉引用功能,可分析出核心其他地方對PspCreateProcessNotifyRoutine的交叉引用,如圖所示,

共計5個地方涉及到此變數。其中PspCallProcessNotifyRoutines是直接呼叫回撥例程的函式,該函式的關鍵部分如圖所示。

透過while迴圈,遍歷PspCreateProcessNotifyRoutine陣列中安裝的所有回撥例程,依次執行。PspNotifyEnableMask & 2的操作即為判斷當前陣列中是否安裝有回撥例程,加快程式的執行效率,這個變數的值在PsSetCreateProcessNotifyRoutine中安裝回調例程時設定。bRemove & 2這個if分支,是用來判斷當前的回撥例程是透過PsSetCreateProcessNotifyRoutine還是PsSetCreateProcessNotifyRoutineEx安裝,因為這兩個API安裝的回撥例程的原型不同,在實際呼叫時傳入的引數也不同。兩者的回撥例程原分別為:void PcreateProcessNotifyRoutine(HANDLE ParentId,HANDLE ProcessId,BOOLEAN Create)和void PcreateProcessNotifyRoutineEx(PEPROCESS Process,HANDLE ProcessId,PPS_CREATE_NOTIFY_INFO CreateInfo)。此外,圖8中IDA給出的偽C程式碼RoutineFun((unsigned __int64)RoutineFun)明顯不對,因為回撥例程的引數個數是3個,而IDA分析出的引數只有1個,顯然有問題。直接看下反彙編程式碼即可得知,如圖所示,

根據x64下的呼叫約定可知,函式的前4個引數是透過rcx、rdx、r8和r9這四個暫存器傳遞,圖給出的正是回撥例程的前三個引數,_guard_dispatch_icall內部會直接取rax的值呼叫過去,而rax的值正是ExGetCallBackBlockRoutine呼叫返回的回撥例程函式地址。

上圖中的第二個涉及到PspCreateProcessNotifyRoutine陣列的是PspEnumerateCallback函式,該函式是系統內部函式,沒有匯出,其具體實現如圖所示。

該函式根據dwEnumType來判斷想要列舉的是哪個陣列,由程式碼分析可知,系統核心維護了三個回撥相關的陣列,分別為映象載入回撥陣列,程序建立退出回撥陣列,執行緒建立退出陣列。類似之前的函式校驗,這裡也檢測了索引是否超過0x40,超過了則返回0,以示失敗。

1.3 觸發呼叫的呼叫鏈分析

上節分析了回撥例程的直接呼叫上級函式,本節分析整個呼叫鏈,主要分析呼叫源及呼叫過程中涉及到的關鍵函式。根據IDA給出的交叉引用圖如圖所示。

涉及到的函式呼叫非常多,很多不相關的也被包含進來,不便於分析。經手動分析整理後的呼叫鏈,其鏈路中的關鍵API如圖所示。

虛線以上部分為使用者態程式,虛線以下為核心態程式,紅色標註的都是標準匯出的API。根據圖12可知,當用戶態程序呼叫RtlCreateUserProcess、RtlCreateUserProcesersEx或RtlExitUserProcess時,核心都會去遍歷PspCreateProcessNotifyRoutine陣列,依次執行回撥例程,通知給驅動程式做相應的處理。驅動接管之後,可以做安全校驗處理,分析程序的父程序或者進一步分析程序鏈,此外還可以對即將被拉起的子程序做特徵碼匹配,PE指紋識別,匯入表檢測等防禦手段。這種方式不需要去Hook任何API,也無需做特徵碼定位等重複繁瑣的工作,完全基於系統提供的回撥機制,且在Windows系統中都可以無縫銜接。且各個安全廠家之間也不存在相互競爭,大大縮小了系統藍色畫面的風險。圖12中NtCreateUserProcess呼叫PspInsertThread的原因是建立程序的API內部會建立該程序的主執行緒。將遍歷回撥例程陣列的工作統一到PspInsertThread中,由其去呼叫下層的PspCallProcessNotifyRoutines更為合理。

2 實驗2.1 觀察系統中已安裝的回撥例程

實驗環境如表1所示,藉助於VMWare進行雙機除錯。

Guest OS Build 10.0.16299.125Host OS Build 10.0.17134.885Windbg版本 10.0.17134.1VMWare 14.1.1 build-7528167PCHunter V1.56

在Windbg中觀察PspCreateProcessNotifyRoutine陣列,共計14項有效資料,如下所示;

1: kd> dd PspCreateProcessNotifyRoutineCount  l1fffff802`151f4e78  000000091: kd> dd PspCreateProcessNotifyRoutineExCount l1fffff802`151f4e7c  000000051: kd> dq PspCreateProcessNotifyRoutine l40fffff802`14da2a80  ffffcc8b`d884b9bf  ffffcc8b`d8d9c96ffffff802`14da2a90  ffffcc8b`d939975f  ffffcc8b`da00044ffffff802`14da2aa0  ffffcc8b`d9bd382f  ffffcc8b`da41e8dffffff802`14da2ab0  ffffcc8b`da53815f  ffffcc8b`da5ca8bffffff802`14da2ac0  ffffcc8b`dac5178f  ffffcc8b`dbef624ffffff802`14da2ad0  ffffcc8b`dce333af  ffffcc8b`dcec67dffffff802`14da2ae0  ffffcc8b`dc735def  ffffcc8b`dcabd32f拆解第一項,尋找其所對應的回撥例程,如下:1: kd> dq ffffcc8b`d884b9b0 l3ffffcc8b`d884b9b0  00000000`00000020 fffff802`13fd6268ffffcc8b`d884b9c0  00000000`00000000由此可知,安裝的回撥例程起始地址為fffff802`13fd6268,且還可知道Remove為0,即這個是已經安裝的。尋找該回調例程對應的驅動模組,如下:1: kd> u fffff802`13fd6268360qpesv64+0x26268:fffff802`13fd6268  mov  qword ptr [rsp+08h],rbxfffff802`13fd626d  mov  qword ptr [rsp+10h],rbpfffff802`13fd6272  mov  qword ptr [rsp+18h],rsifffff802`13fd6277  push rdi1: kd> lmvm 360qpesv64start              end                 module namefffff802`13fb0000 fffff802`14002000 360qpesv64Loaded symbol image file: 360qpesv64.sysImage path: 360qpesv64.sysImage name: 360qpesv64.sysTimestamp:  Wed May 27 20:13:22 2020 (5ECF2C52)CheckSum:   00054A2AImageSize:  00052000

可知該回調例程是360官方提供。藉助PCHunter來對比下,其給出的資料如圖所示,

2.2 動態除錯回撥例程

以表項的第14項為例,內容如下,

1: kd> dq ffffcc8b`dcabd320 l3ffffcc8b`dcabd320  00000000`00000020 fffff802`13d795b4ffffcc8b`dcabd330  00000000`000000061: kd> bp fffff802`13d795b41: kd> g斷點命中,檢視父程序相關資訊,如下,Breakpoint 0 hitfffff802`13d795b4 48895c2408      mov     qword ptr [rsp+8],rbx1: kd> dt _EPROCESS @$proc -yn ImageFileNament!_EPROCESS  +0x450 ImageFileName : [15]  "svchost.exe"由此可知,是svchost.exe這個父程序建立或者銷燬了一個子程序,更具體的資訊如下分析;檢視下當前的上下文環境;1: kd> rrax=fffff80213d795b4 rbx=ffffcb8050526c80 rcx=ffffcc8bdd67e080rdx=0000000000001f28 rsi=000000000000000d rdi=ffffcc8bdd67e080rip=fffff80213d795b4 rsp=ffffcb8050526c38 rbp=ffffcb8050526ca9r8=ffffcb8050526c80  r9=ffffcc8bdc735de0 r10=ffff9401cdcc2760r11=0000000000000000 r12=0000000000000001 r13=0000000000000000r14=ffffcc8bdcabd320 r15=fffff80214da2ae8iopl=0         nv up ei pl zr na po nccs=0010  ss=0018  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246根據x64的呼叫約定可知,rcx暫存器中儲存的是EPROCESS物件指標,該物件儲存的是即將被建立的子程序的相關資訊,可以獲取到的作為身份識別或者安全檢測的關鍵資訊如下:1: kd> dt _EPROCESS ffffcc8bdd67e080 -yn ImageFilentdll!_EPROCESS   +0x448 ImageFilePointer : 0xffffcc8b`dc97c5c0 _FILE_OBJECT   +0x450 ImageFileName : [15]  "UpdateAssistan"1: kd> dt 0xffffcc8b`dc97c5c0 _FILE_OBJECT -yn FileNamentdll!_FILE_OBJECT   +0x058 FileName : _UNICODE_STRING "\Windows\UpdateAssistant\UpdateAssistant.exe"1: kd> .process /p ffffcc8bdd67e080; !peb 186ef07000Implicit process is now ffffcc8b`dd67e080.cache forcedecodeuser donePEB at 000000186ef07000    CurrentDirectory:  'C:\Windows\system32\'    WindowTitle:  'C:\Windows\UpdateAssistant\UpdateAssistant.exe'    ImageFile:    'C:\Windows\UpdateAssistant\UpdateAssistant.exe'CommandLine:  'C:\Windows\UpdateAssistant\UpdateAssistant.exe /ClientID Win10Upgrade:VNL:NHV19:{} /CalendarRun'可以獲取到該程序的EXE路徑,建立時的命令列引數,父程序的PID等資訊,這些足以用於安全軟體的檢測。父程序的完整呼叫棧如下,1: kd> k # Child-SP          RetAddr           Call Site00 ffffcb80`50526c38 fffff802`14ef4ae5 0xfffff802`13d795b401 ffffcb80`50526c40 fffff802`14ef752c nt!PspCallProcessNotifyRoutines+0x24902 ffffcb80`50526d10 fffff802`14f2797b nt!PspInsertThread+0x5a403 ffffcb80`50526dd0 fffff802`14b79553 nt!NtCreateUserProcess+0x9c704 ffffcb80`50527a10 00007ffe`547d1654 nt!KiSystemServiceCopyEnd+0x1305 0000002f`4b67d258 00007ffe`50b406df ntdll!NtCreateUserProcess+0x1406 0000002f`4b67d260 00007ffe`50b3d013 KERNELBASE!CreateProcessInternalW+0x1b3f07 0000002f`4b67dec0 00007ffe`5216ee0f KERNELBASE!CreateProcessAsUserW+0x6308 0000002f`4b67df30 00007ffe`4ce0a136 KERNEL32!CreateProcessAsUserWStub+0x5f09 0000002f`4b67dfa0 00007ffe`4ce0bdd9 UBPM!UbpmpLaunchAction+0xb360a 0000002f`4b67e280 00007ffe`4ce08ee0 UBPM!UbpmLaunchTaskExe+0x2790b 0000002f`4b67e490 00007ffe`4ce10a86 UBPM!UbpmpLaunchOneTask+0x6c00c 0000002f`4b67e8f0 00007ffe`4ce0b8bc UBPM!UbpmpHandleGroupSid+0x2360d 0000002f`4b67ea10 00007ffe`4ce0b78b UBPM!UbpmpLaunchExeAction+0xec0e 0000002f`4b67eaf0 00007ffe`4ce0b5a3 UBPM!UbpmpTakeAction+0xeb0f 0000002f`4b67eb50 00007ffe`4ce0b193 UBPM!UbpmpPerformTriggerActions+0x29310 0000002f`4b67eca0 00007ffe`4ce1316c UBPM!UbpmpHandleTriggerArrived+0x56311 0000002f`4b67ef50 00007ffe`508c32d0 UBPM!UbpmpRepetitionArrived+0x1c12 0000002f`4b67ef90 00007ffe`508c3033 EventAggregation!EaiSignalAggregateEvent+0x16c13 0000002f`4b67f060 00007ffe`508c27aa EventAggregation!EaiSignalCallback+0xe714 0000002f`4b67f140 00007ffe`508c253e EventAggregation!EaiProcessNotification+0x1aa15 0000002f`4b67f270 00007ffe`508caef8 EventAggregation!WnfEventCallback+0x50616 0000002f`4b67f3a0 00007ffe`5476769f EventAggregation!AggregateEventWnfCallback+0x3817 0000002f`4b67f3f0 00007ffe`54767a51 ntdll!RtlpWnfWalkUserSubscriptionList+0x29b18 0000002f`4b67f4e0 00007ffe`5476b510 ntdll!RtlpWnfProcessCurrentDescriptor+0x10519 0000002f`4b67f560 00007ffe`54766b59 ntdll!RtlpWnfNotificationThread+0x801a 0000002f`4b67f5c0 00007ffe`54764b70 ntdll!TppExecuteWaitCallback+0xe11b 0000002f`4b67f600 00007ffe`52171fe4 ntdll!TppWorkerThread+0x8d01c 0000002f`4b67f990 00007ffe`5479ef91 KERNEL32!BaseThreadInitThunk+0x141d 0000002f`4b67f9c0 00000000`00000000 ntdll!RtlUserThreadStart+0x21由於前四個引數是透過的暫存器傳遞的,所以無法直接透過棧來回溯到引數,但可以透過手動方式分析得到。分析ntdll!NtCreateUserProcess的呼叫父函式,其返回地址處的彙編程式碼如下所示:1: kd> ub 00007ffe`50b406dfKERNELBASE!CreateProcessInternalW+0x1b11:00007ffe`50b406b1 488b842440040000 mov     rax,qword ptr [rsp+440h]00007ffe`50b406b9 4889442420      mov     qword ptr [rsp+20h],rax00007ffe`50b406be b800000002      mov     eax,2000000h00007ffe`50b406c3 448bc8          mov     r9d,eax00007ffe`50b406c6 448bc0          mov     r8d,eax00007ffe`50b406c9 488d942448010000 lea     rdx,[rsp+148h]00007ffe`50b406d1 488d8c24e0000000 lea     rcx,[rsp+0E0h]00007ffe`50b406d9 ff1521901600    call    qword ptr [KERNELBASE!_imp_NtCreateUserProcess (00007ffe`50ca9700)]可知,NtCreateUserProcess第一個引數和第二個引數再rsp+0xE0和rsp+0x148處;檢視該處的資料如下:1: kd> dpu 0000002f`4b67d260+E0 0000002f`4b67d260+148 0000002f`4b67d340  00000000`000000000000002f`4b67d348  00000000`000000040000002f`4b67d350  00000100`000000000000002f`4b67d358  00000000`000000200000002f`4b67d360  000001f2`d9b87cc0 "C:\Windows\UpdateAssistant\UpdateAssistant.exe"0000002f`4b67d368  00000000`000000000000002f`4b67d370  00000000`000000000000002f`4b67d378  0000002f`000000000000002f`4b67d380  000001f2`d8d43580 "C:\Windows\UpdateAssistant\UpdateAssistant.exe /ClientI"0000002f`4b67d388  00000000`000000000000002f`4b67d390  00000000`000086640000002f`4b67d398  000001f2`d9d73c40 "ALLUSERSPROFILE=C:\ProgramData"0000002f`4b67d3a0  00000000`000000000000002f`4b67d3a8  00000000`00000000

由此可知,svchost拉起的子程序為UpdateAssistant.exe,與之前分析得到的引數也相吻合。從呼叫棧可知,是在svchost建立子程序UpdateAssistant.exe時遍歷的回撥例程,通知給驅動軟體做相應的處理。

3 結束語

本文詳細地分析了系統實現程序回撥安全機制的內部原理,藉助IDA工具逆向系統映象檔案,分析了實現的關鍵程式碼部分,得到了關鍵資料結構及系統額外做的資料檢測校驗演算法。對關鍵全域性變數的作用也做了詳細解釋。此外,透過逆向分析,給出了整個機制的呼叫源與呼叫鏈。最後基於雙機除錯環境,動態檢視核心中維護的程序回撥例程表,並且下斷點實際動態除錯了整個過程。對於驅動開發,核心安全相關方面的研究工作者提供了該技術實現原理與機制。基於得到的關鍵資料結構和系統資料檢驗保護演算法,可以解密關鍵欄位後檢測表項中的惡意程式碼,也可以用於安全廠商在對抗過程中,完全脫離系統提供的API手工構建表項,達到監控系統行為的目的。

19
最新評論
  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • 面試官:Spring 建立Bean 時是怎樣判斷條件的?