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%)