X86 處理器支持最近分支記錄(Last branch record),也就是記錄CPU的跳轉記錄(jmp,jcc,call,ret等指令,中斷和異常). 通過操作MSR寄存器(model specific register)來配置分支記錄功能.
1. 分支記錄格式(branch record)
有如下兩個函數:
int add_fun(int a,int b)
{
return (a+b);
}
int main(void)
{
return add_func(1,2);
}
分支記錄格式爲from -> to 形式,上面兩個函數調用,可以生成兩條branch record:
main -> add_fun /* call 指令*/
add_fun->main /* ret指令*/
2 分支記錄方式
X86提供LBR, BTM兩種方式來記錄分支信息, LBR把分支信息記錄到特定模型寄存器(MSR),而BTM把分支記錄通過消息的形式發送給系統總線或者寫到內存中(Branch Trace Store).
2.1 LBR(last branch record)
把分支信息保存在稱爲"LBR stack"的MSR寄存器組裏面,且用棧頂指針寄存器(top-of-stack (TOS) pointer
)來記錄當前index. 這種方式明顯缺點就是寄存器組數量有限,也就是保存的記錄有限.根據CPU型號不同,LBR stack大小也有差異,目前最多支持32組寄存器. 可以通過CPU的DisplayFamily和DisplayModel值來查詢LBR Stack的地址和大小.
2.2 BTM(Branch Trace Messages)
目前LBR stack方式記錄的分支記錄條數最多爲32組,通過BTM機制,CPU把分支信息通過消息的形式,發送到系統總線或者寫到內存,發送到系統總線時,需要在總線上外接調試設備來接受BTM消息. 而我們更多采用寫到內存的方式(BTS).BTS 把BTM消息保存在預先分配內存區(Debug Store)中,這樣能夠保存更多LBR條目.實際上DS區分爲三部分:內存管理區,BTS內存區,PEBS區(性能檢測)
內存管理區: 保存BTS內存和PEBS內存的信息
BTS buffer base :BTS內存首地址
BTS index:cpu當前寫入地址
BTS absolute maximum: BTS內存區結束地址
BTS interrupt threshold: 觸發中斷閾值,也就是BTS index=BTS interrupt threshold時,會產生中斷
用於通知從BTS內存區讀取分支信息,一條Branch record記錄,又包含三個部分:
3. MSR寄存器(model specific register)
特殊模型寄存器與CPU的體系結構強相關,必須通過CPUID指令查詢cpu的DispalyFamily和DisplayModel,從而得到CPU的MSR寄存器地址和功能(Feature Informatio)信息,這裏介紹與LBR和BTS相關的MSR寄存器.
3.1 CPUID.01指令
執行CPUID指令後,可以得到CPU的DF_DM和支持的LBR功能信息,返回信息存放在eax,ecx,edx中.
ECX返回的重要標誌位:
DTES64: 採用BTS內存方式時,cpu是否支持使用64bit存放分支記錄
DS-CP: cpu是否支持根據指令運行級別,對LBR進行過濾
PDCM: cpu是否支持調試功能
EDX返回的重要標誌位
MSR:CPU是否支持指令rdmsr/wrmsr來讀寫MSR寄存器.
DS: Debug Store,是否支持把BTM消息寫到內存,也可以理解爲是否支持BTS功能.
在使用LBR和BTS功能時,必須先用CPUID指令查詢CPU對LBR和BTS功能是否支持.
3.2 DEBUGCTL MSR(調試控制寄存器)
調試控制寄存器在不同CPU族中,有不同的名字,地址都是0x01D9,主要用來控制LBR,BTS,BTM,中斷等
IA32_DEBUGCTL: Nehalem, Core Solo, Core Duo
MSR_DEBUGCTLA: Pentium 4 and Intel Xeo
MSR_DEBUGCTLB: Pentium M
DEBUGCTLMSR: P6 Family
IA32_PERF_CAPABILITIES MSR
定義了LBR stack保存格式和系統進入SMM模式時是否自動關閉LBR功能,對於分支記錄功能,我們只用到底5位,
用來識別LBR stack存放格式.
IA32_MISC_ENABLE MSR
系統是否支持BTS功能,除了使用cpuid指令外,還需要查詢IA32_MISC_ENABLE MSR寄存器的BTS_UNAVAILABLE位
如果位0,表示支持BTS功能.
IA32_DS_AREA MSR
在採用BTS記錄分支信息時,需要預先分配內存(Debug Store),把DS內存地址寫到IA32_DS_AREA寄存器後,CPU就可以自動往內存中填充分支記錄信息.
MSR_LASTBRANCH_TOS MSR
在使用LBR stack記錄分支信息時,TOS寄存器指示stack當前位置. 從而可以從LBR stack中正確讀取分支記錄.
MSR_LASTBRANCH_x_FROM_IP/MSR_LASTBRANCH_x_TO_IP MSR
分支記錄寄存器,保存最近的分支記錄信息。
4 LBR功能實現
4.1 操作MSR寄存器
1. CPUID指令查詢DF_DM信息,獲取對相應MSR寄存器地址
cpuid(1,&eax,&ebx,&ecx,&edx);
2.查詢LBR stack存儲格式
rdmsrl(MSR_PERF_CAPABILITIES,lbr_fmt);
3.開啓LBR功能,DBUGCTL MSR的bit0寫1
wrmsrl(MSR_DEBUGCTL,ctl)
4.讀取TOS指針位置
rdmsrl(MSR_LBR_TOS_R,tos);
5. 讀取LBR stack寄存器
rdmsrl(MSR_LBR_FROM_IP+tos,from);
rdmsrl(MSR_LBR_TO_IP+tos,to);
其中注意點有:
1. 必須查詢LBR stack的存儲格式,才能解析分支記錄
2. 讀取LBR stack時,需要關閉LBR功能,避免讀取程序和CPU產生競爭.
3. 針對LBR還可以使用過濾功能(MSR_LBR_SELECT),根據branch type來過濾相關跳轉.
4.2 log輸出
可以看到能夠正常捕獲從LKM的init到關閉LBR的調用過程.
rootkits_lbr_init:from:rootkit_lbr_onoff [lbrs],to:native_write_msr
rootkits_lbr_init:from:rootkit_lbr_onoff [lbrs],to:rootkit_lbr_onoff [lbrs]
rootkits_lbr_init:from:native_read_msr,to:rootkit_lbr_onoff [lbrs]
rootkits_lbr_init:from:rootkit_lbr_onoff [lbrs],to:native_read_msr
rootkits_lbr_init:from:rootkits_lbr_init [lbrs],to:rootkit_lbr_onoff [lbrs]
5 BTS功能實現
5.1 操作MSR寄存器
1.BTS支持查詢
rdmsrl(MSR_MISC_ENABLE,val);/*查詢BTS_UNAVAILABLE */
edx&DS_MODE /*查詢DS功能是否支持 */
如果有一個不支持,則BTS功能無法實現.
2.內存分配和填充
把DS首地址寫到MSR_DS_AREA寄存器.
3. 開啓BTS和BTM功能
這裏採用poll的方式來獲取分支信息,所以禁止了中斷.
4.讀取BTS內存
在讀取之前,也要先關閉BTS和BTM功能
5.BTS與KPTI
在開啓頁表隔離時,BTS是無法使用的,因爲當程序運行在ring3時,CPU是無法訪問到驅動申請的DS內存.
解決辦法:
1.關閉KPTI
sudo vim /etc/default/grub
在command linux添加nopti或者"pti=off"
GRUB_CMDLINE_LINUX=" nopti"
然後sudo update-grub,sudo reboot
2. 對特定進程進行內存映射
把BTS相關的內存映射到特定進程空間.
6. Intel-PT
intel-pt是intel 在2015推出的實時指令採集,功能上更強大,性能影響也較小(5%)