基于PMI實現對讀寫行為檢測
這幾天分析某驅動樣本發現幾種檢測讀寫的方法,就有意思的一種和大家分享。
PMI
使用CPU提供的性能監視(PM)功能,對內存讀寫時的關鍵掛靠函數(KeStackAttachProcess, KeAttachProcess)實現了一種類似hook的技術。
通過監視調用情況,來判斷進程內存是否被讀取。關于處理器的性能監視功能,不同的處理器架構都有差異,可以參考白皮書,或者這個翻譯的帖子:https://www.codenong.com/cs106475009/
大致原理:
設置ICACHE_MISS類型的監視事件開啟性能監視功能。當L1指令高速緩存未命中次數達到溢出值時,觸發PM中斷,執行樣本注冊的PMI回調函數。
在回調函數中判斷中斷的位置是否處于監視函數代碼的序言部分(這樣才能獲取到正確的調用參數),如果處于那么通過調用參數判斷掛靠的目標進程是否為被保護的進程,進而記錄下調用信息。不處于則使用clflush或clflushopt指令使監視函數代碼從緩存中失效,以提高下次被中斷到的概率。
下面是詳細的流程:
判斷處理器對性能監視功能的支持性
通過cpuid的0號功能來判斷處理器是不是intel(看代碼樣本尚未支持amd)。

Cupid的a號功能判斷處理器是否支持版本2以上的性能監視功能。

單個核心是否支持4個以上的計數器。

是否支持clflush指令,并且處理器是否支持ICACHE_MISS類型的監視事件。

獲取監視函數(KeStackAttachProcess, KeAttachProcess)
解析nt模塊的導出表得到兩個監視函數地址,并解析異常表得到兩個監視函數的序言部分的大小(SizeOfProlog),應該是為了兼容不同系統的做法。

配置并開啟性能監視
1、注冊PMI回調函數
獲取nt模塊導出的HalDispatchTable地址,這是hal提供的一張函數地址表。
調用其中的hal!HalpSetSystemInformation并指定HalProfileSourceInterruptHandler來注冊自己的PMI回調函數。
其中的foo就是注冊的回調函數地址。

樣本使用Windows提供的接口來注冊回調函數,當然也可以通過配置IA32_X2APIC_LVT_PMI寄存器來指定PMI發生時的中斷向量號。
Windows系統中配置的向量號是0xfe。

中斷歷程實際是_KINTERRUPT.ServiceRoutine字段指向的函數:hal!HalpPerfInterrupt
而這個函數很簡單就是逐個調用hal!HalpPerfInterruptHandler中注冊的PMI回調函數。

所以hal!HalpSetSystemInformation的作用很簡單就是指定的foo參數設置到HalpPerfInterruptHandler中。
2、開啟每個處理器核心中的計數器
開啟參數中CounterMode_0指明計數器的類型(固定用途、通用),當然樣本只用到了通用的性能監視計數器。
CtrxIndex_4是每個處理器核心中開啟的計數器的索引。
EVESEL_8是選擇的性能監視事件,也就是在一開始提到的ICACHE_MISS類型。
CtrCount_C是計數值,這個參數直接決定了PMI發生的頻率,計數值越低,頻率越高,當然中斷到監視函數的概率也越高。


開啟前保證計數器處于停止狀態。

重置指定計數器的計數值。

選擇監視事件。

開啟性能監視。

回調函數處理
樣本注冊的回調函數為PMICallBackMode0_140058d40,唯一的參數是_KTRAP_FRAME,記錄了發生中斷的寄存器環境。

通過_KTRAP_FRAME,可以拿到發生中斷的地址,用來判斷是否命中監視函數的序言部分。

如果命中,進而判斷是否掛靠指定進程。

如果未命中,將定時使用clflush或clflushopt指令讓監視函數代碼從緩存中失效。


最后,不管是否命中,都需要清除溢出標志,重置計數器以允許下一次PMI處理程序調用。



總結
我將樣本實現PMI的關鍵代碼拷貝出來編譯后在虛擬機中運行,并測試XT掃描進程鉤子時讀寫內存的情況,確實可以命中!
(測試時保證處理器支持PM并且虛擬機處理器開啟虛擬化CPU性能計數器)。

這種中斷實現的監視調用的方法應用場景有限,即使使用clflsuh指令提高了對監視函數的命中概率,實際測試效果也一般,考慮運行效率的情況下不能將計數值設置的足夠小,就不能保證每次調用監視函數時都能命中。
但在游戲安全方面,用來監視高頻率的讀寫行為確實很有效果,并且其不依賴系統函數的隱蔽性和中斷的隨機性意味著更難被作弊者發現。