上一篇讲述Intel CPUID指令介绍后,这篇来介绍Intel LBR & BTS 功能
Intel CPUID指令介绍
最近因为工作需要,需要学习intel x86_64 & ADM64 处理器的LBR(Last branch record)功能(记录分支跳转指令),简单来说就是记录CPU产出的跳转指令:包括call、ret、jmp、jxx(间接跳转)、中断、异常。利用这个功能,就可以保存每个分支跳转指令的起始地址和目的地址。这样就可以了解到当前CPU执行代码的流程情况。
举一个简单的例子jmp指令:
4004d3: eb 03 jmp 4004d8
当执行下面这条jmp指令时,那么将产生一个 from 4004d3 to 4004d8的跳转,那么产生的LBR记录项就是
{ From:4004d3 ,To:4004d8 } /* jmp指令 */
再举一个简单的例子关于call ,ret 指令:
int add(int a, int b)
{
return (a+b);
}
int main(void)
{
return add(1,2);
}
上述两个函数将会产生两条LBR记录项:
{From : main , to : add} /* call 指令*/
{From : add , to : main} /* ret 指令*/
在二进制安全中,有很多攻击都是hook控制流,比如隐藏进程,隐藏文件,隐藏tcp,提权等等,使正在运行的系统按照攻击者的流程运行下去,这样就和系统正常的控制转移过程将会不一样,比如在Linux系统上,由于攻击者hook掉一些函数,当用户使用ls、top、ps、netstat等命令时,那么将会过滤掉一些进程,文件,tcp连接等等,那么攻击者就会利用到这些进程、文件、tcp连接等,对系统危害极大。
CPU当前正在产生的分支、中断和异常等跳转指令不但对于调试,跟踪代码有用,同时也提供一种方法来确定系统当前运行中的控制转移过程。我们根据这些跳转指令便能够分析出系统是否被攻击了。
MSR(Model Specific Register)是x86架构中的概念,指的是在x86架构处理器中,一系列用于控制CPU运行、功能开关、调试、跟踪程序执行、监测CPU性能等方面的寄存器。
不同的CPU型号或不同的CPU厂商(Intel&AMD),它的MSR寄存器可能是不一样的,它会根据具体的CPU型号的变化而变化,每款新的CPU都有可能引入新的MSR寄存器。
Intel提供了RDMSR,WRMSR这两条指令,通过调用这两条指令来读取,写入相应的 MSR寄存器的值。
这两条指令必须在 privilege level 0(linux中的内核态)或实模式下执行:
linux 内核提供了两个接口,位于arch/x86/include/asm/msr.h文件中:
rdmsrl(msr, val)
wrmsrl(msr, val)
有关MSR寄存器的介绍可以参考intel vol4 chapter2,Intel vol4都是在描述MSR寄存器。
IA32_DEBUGCTL 寄存器其地址为 01D9H(不同的CPU family名字可能会不一样,比如MSR_DEBUGCTLA, MSR_DEBUGCTLB),可以用来调试,跟踪中断、LBR、BTS等等。
下面介绍几个较重要的位:
(1)bit0: LBR (last branch/interrupt/exception) flag 。该位被设置后,处理器开始记录跟踪
处理器产生的最近的分支、中断和/或异常,并储存在 LBR stack MSRs中。
(2)bit1: BTF (single-step on branches) flag 。该位被设置后,处理器将EFLAGS寄存器中的TF标志视为:TF as single-step on branches instead of single-step on instructions。(x86_64的gdb单步调试功能就是用EFLAGS寄存器中的TF位—single-step on instructions 来实现的,后面会写一篇x86_64的gdb单步调试跟踪原理的文章)
(3)bit6: TR (trace message enable) flag。该位被设置后,当处理器检测到一个已经产生的分支,中断,或异常;它将这个分支记录作为 branch trace message(BTM)发送到系统总线上。
(4)bit7: BTS (branch trace store) flag。该位被设置后,BTS能够将BTMS保存到DS save area的内存驻留BTS buffer中。
(5)bit8: BTINT (branch trace interrupt) flag。该位被设置后,当BTS缓冲区满时,BTS会产生一个中断。
(6)bit9: BTS_OFF_OS (branch trace off in privileged code) flag。该位被设置后,如果CPL为0,则跳过BTS或BTM,即:在内核态不启用bts功能。
(7)bit10:BTS_OFF_USR (branch trace off in user code) flag。该位被设置后,如果CPL大于0,则跳过BTS或BTM。即:在用户态不启用bts功能。
因此可以通过这两bit BTS_OFF_OS/BTS_OFF_USR 来设置是获取用户态的分支跳转指令还是内核态的分支跳转指令。
CPL:Current Privilege Level,该值为0代表最高优先级,该值为3代表最低优先级,linux 中,0为内核态,3为用户态。
前面已经说过, IA32_DEBUGCTL MSR 寄存器的bit0被设置后,处理器开始自动记录 产生的分支,中断,异常等branch records,并存储在 LBR stack MSRs中。下面来介绍下 LBR stack与 TOS Pointer
(1)Last Branch Record (LBR) Stack :LBR由N对msr寄存器组成(N是LBR堆栈大小,如下表所示),msr存储了最近分支的源地址和目的地址。
(2)Last Branch Record Top-of-Stack (TOS) Pointer:TOS Pointer MSR中最低有效的M位包含了一个指向LBR堆栈中MSR的M位指针,该指针包含了记录的最近的分支、中断或异常。
在使用LBR stack记录分支信息时,TOS寄存器指示stack当前位置. 从而可以从LBR stack中正确读取分支记录.
从下表可以看出,LBR堆栈中msr的个数和TOS指针值的有效范围对于不同的处理器家族中会有所不同。
LBR msr是64位的寄存器。在64位模式下, last branch records存储完整地址。32位模式下,高32位置0,低32为存储最近分支记录。
MSR_LASTBRANCH_0_FROM_IP — (N-1) MSR address 存储分支记录源地址
MSR_LASTBRANCH_0_TO_IP — (N-1) MSR address 存储分支记录目的地址
LBR的简单使用:
(1)查询LBR stack存储格式 :IA32_PERF_CAPABILITIES MSR (调用rdmsrl)
(2)开启LBR功能,设置 IA32_DEBUGCTL MSR寄存的 bit0 = 1 (调用wrmsrl)
(3) 读取TOS指针位置 (调用rdmsrl)
读取 MSR_LASTBRANCH_TOS 寄存器 ,请参考 Intel vol4
(4) 读取LBR stack寄存器 (调用rdmsrl)
读取 MSR_LASTBRANCH_x_FROM_IP / MSR_LASTBRANCH_x_TO_IP 寄存器 ,请参考 Intel vol4
LBR的优点:分支记录存储在寄存器中,几乎没有性能开销。
LBR的缺点:寄存器组数量有限,这样我们保存的分支记录也有限。
上面提到LBR获取的分支、中断和异常的记录,会保存在LBR Stack MSR中。通过设置TR标志将这些记录作为BTMS发送到系统总线上。BTS机制提供了一个额外功能可以将分支记录保存在 memory-resident BTS 缓冲区中,该缓冲区是DS save area的一部分。(BTMS:当处理器检测到一个分支、异常或中断时,它在系统总线上发送一个分支记录作为BTM。监视系统总线的调试设备可以读取这些消息。)
BTS的优点:用系统DRAM来存储更多的指令和事件,仅受目标系统上的内存量限制。
LBR的缺点:由于用内存存储消息,性能开销较大。
PEBS,全称Processor Event Based Sampling(也可以叫Precise Event-Based Sampling),详细介绍可参考Intel vol3 19.6.2.4 小节。主要用来性能监控(Performance monitoring),使用调试存储机制和性能监视中断来存储处理器的一组体系结构状态信息(8个通用寄存器、EIP寄存器和EFLAGS寄存器的状态),该信息提供了在导致 event 的指令之后执行的指令的体系结构状态。
这里简单介绍下perf ,perf是一款Linux性能分析工具。通过perf,应用程序可以利用PMU、tracepoint和内核中的计数器来进行性能统计。它不但可以分析制定应用程序的性能问题(per thread),也可以用来分析内核的性能问题。
event是perf工作的基础,perf提供两种工作模式:采样模式(sampling)和计数模式(count)。
skid问题:当perf工作在采样模式(sampling)时,由于采样事件发生时和实际处理采样事件之间有时间上的delay,以及CPU流水线和乱序执行等因素,所以得到的指令地址IP(Instruction Pointer)并不是当时产生采样事件的IP。为了改善这种状况,使IP值更加准确,Intel使用PEBS(Processor Event-Based Sampling),而AMD则使用IBS(Instruction-Based Sampling)。
the skids的问题可以通过处理器本身将指令指针(以及其他信息)存储在内存中指定的缓冲区中来缓解,这需要硬件的支持。以PEBS为例:每次采样事件发生时,会先把采样数据存到一个缓冲区中(PEBS buffer),当缓冲区内容达到某一值时,再一次性处理,这样可以很好地解决skid问题。
使用PEBS的好处:
解决The skid 问题。
减少开销,因为Linux内核只在PEBS缓冲区被填满时才会涉及到,也就是说,直到有大量的样本可用时才会有中断。
总结:
PEBS is a feature of the PMU. PEBS is an extension of “sampling”.
在采集样本时,PMU将会收集更多的信息。例如,精确的指令计数器、寄存器或标志将被记录下来。
调用CPUID函数后,如果寄存器edx的第21位是1,表示处理器提供调试存储(DS)机制。
关于CPUID指令请参考我的这篇博客:Intel cpuid指令介绍
代码如下:
//the processor provides the debug store (DS) mechanism
#define DS_IS_SUPPORT (1<<21)
unsigned int eax = 0;
unsigned int ebx = 0;
unsigned int ecx = 0;
unsigned int edx = 0;
cpuid(1, &eax, &ebx, &ecx, &edx);
if(!(edx & DS_IS_SUPPORT)) {
printk("Don't support DS buffer feature\n");
return 0;
}
The DS mechanism allows:
(1)BTMs将被保存在 memory-resident BTS buffer中。
(2)Processor event-based sampling (PEBS)也使用了DS save area提供的调试存储机制。PEBS的性能因微架构的不同而不同(PEBS主要用于处理器的性能监控)。
When CPUID.1:EDX[21] is set:
(3)IA32_MISC_ENABLE MSR 的 BTS_UNAVAILABLE标志位 和 PEBS_UNAVAILABLE 标志位 表示是否支持BTS和PEBS。
IA32_MISC_ENABLE 是一个 MSR 寄存器 ,其地址是1A0H (参考intel 手册 vol4 chapter2)
可以在内核模块用 rdmsrl 指令查看处理器是否支持BTS和PEBS。
#define MSR_MISC_ENABLE (0x1A0)
#define MISC_BTS_UNAVAILABLE (1<<11)
#define MISC_PEBS_UNAVAILABLE (1<<12)
unsigned long long val_bts = 0 ;
rdmsrl(MSR_MISC_ENABLE, val_bts);
if(val_bts & MISC_BTS_UNAVAILABLE) {
printk("Don't support BTS\n");
return 0;
}
unsigned long long val_pebs = 0 ;
rdmsrl(MSR_MISC_ENABLE, val_pebs);
if(val_pebs & MISC_PEBS_UNAVAILABLE) {
printk("Don't support PEBS\n");
return 0;
}
(4)IA32_DS_AREA MSR存在并指向 DS save area。
The debug store (DS) save area 是一个软件指定的内存区域,用于收集以下两种类型的信息:
(1)Branch records:当IA32_DEBUGCTL MSR寄存器设置了BTS标志后,每当检测到发生分支、中断或异常时,分支记录被存储在 DS save area的BTS缓冲区中。
(2)PEBS records:当为PEBS配置性能计数器时,在计数器发生溢出后,PEBS记录存储在DS保存区中的PEBS缓冲区中。该记录包含下一次导致计数器溢出的PEBS事件发生时处理器的体系结构状态(8个通用寄存器、EIP寄存器和EFLAGS寄存器的状态)。当记录状态信息时,计数器将自动重置为指定值,事件计数将再次开始。
The DS save area分为三个部分:buffer management area, BTS(branch trace store ) buffer, and PEBS(Processor event-based sampling) buffer。如下图所示:
用于定义BTS和PEBS的位置和大小缓冲区。其线性地址由IA32_DS_AREA MSR寄存器指定。
BTS buffer base:BTS缓冲区起始的线性地址(intel手册中很少看见虚拟地址,一般都是用线性地址表示)。
BTS index:CPU当前要写入的BTS记录的线性地址。刚开始时,BTS index就是指向BTS buffer base。
BTS absolute maximum:BTS缓冲区结束的线性地址。
BTS interrupt threshold:产生一个中断的BTS记录的线性地址,该地址必须指向BTS缓冲区基的偏移量,该偏移量是BTS记录 大小的倍数。此外,它必须有几个记录短于BTS绝对最大地址,以允许在处理器写入BTS绝对最大记录之前处理一个挂起中断。
简单来说:当BTS index=BTS interrupt threshold时,会产生中断,用于通知从BTS内存区读取分支信息。
(备注:CPU需要知道这块内存的起始地址、结束地址、以及当记录到第几个记录时候需要触发一个中断程序来负责将现有的BTS记录保存下来防止内存溢出)
PEBS buffer base:PEBS 缓冲区起始的线性地址。
PEBS index:CPU当前要写入的PEBS记录的线性地址。刚开始时, PEBS index就是指向 PEBS buffer base。
PEBS absolute maximum:PEBS 缓冲区结束的线性地址。
PEBS interrupt threshold:类似于BTS interrupt threshold。
PEBS counter reset value:当一个PEBS记录要写时要设置一个64位的计数器。此值允许在每次发生指定数量的事件时定期收集状态信息。
Branch records:当IA32_DEBUGCTL MSR寄存器设置了BTS标志后,每当检测到发生分支、中断或异常时,分支记录被存储在 DS save area的BTS缓冲区中。
每条 Branch record记录如下图所示:
Last branch from:产生的分支、中断或异常指令的线性地址(分支指令的from地址)。
Last branch to :分支目标的线性地址中断或异常服务程序中的第一条指令地址分支指令的to地址)。
下图为每条分支记录的格式:
PEBS records:当为PEBS配置性能计数器后,在计数器发生溢出后(计数器从最大计数溢出到零时),PEBS硬件被激活。PEBS记录存储在DS保存区中的PEBS缓冲区中。该记录包含下一次导致计数器溢出的PEBS事件发生时处理器的体系结构状态(8个通用寄存器、EIP寄存器和EFLAGS寄存器的状态)。当记录状态信息时,计数器将自动重置为指定值,事件计数将再次开始。
这些寄存器的值是那些引起event的指令开始的值。
下图为每条PEBS记录的格式:
要用BTS缓冲区保存分支记录,必须首先在内存中设置DS保存区,如下面的步骤所述
(1) 在内存中创建DS缓冲区管理信息区域。
(2)将DS缓冲区管理区域的基本线性地址写入IA32_DS_AREA MSR中。
(3)在xAPIC(Advanced Programmable Interrupt Controller) LVT( local vector table)中设置性能 计数器条目。(有关APIC 和 LVT请参考 Intel vol3 chapter10)
(4)在IDT(Interrupt Descriptor Table)中为步骤3 设置的 性能计数器条目对应的向量建立一个中断处理程序。
(5)编写一个用来处理BTS溢出操作的中断服务程序。参考2.8节。
简单来说就是创建一个DS缓冲区,并将其地址写入一个MSR寄存器中,当BTS记录项接近溢出时,就会通过在 APCI LVT设置的性能计数器条目中存储的中断向量值去触发对应的中断程序,进而来处理内存溢出。
IA32_DEBUGCTL中的三个标志位用来控制分支记录的生成,并将它们存储在BTS缓冲区中。分别是TR、BTS和BTINT。
(1)TR标志用来生成BTMS。
(2)BTS标志确定btm是在系统总线上发送还是存储在BTS缓冲区。BTS不能同时发送到系统总线,又记录在BTS缓冲区中。
(3)BTINT标志允许在BTS缓冲区满时产生中断。
如果处理器支持 CPL-qualified 上次分支记录机制,则分支记录的生成和存储分支记录在BTS缓冲区中由TR、BTS、BTS_OFF_OS、BTS_OFF_USR和BTINT决定。
(PS:这一节完全按照intel 手册来翻译的,感觉翻译得很烂,将就看吧)
BTS 和 PEBS 共享相同的中断向量和中断服务程序(称为调试存储中断服务程序或DS ISR)。要处理BTS和PEBS中断:DS ISR中必须包含独立的处理程序例程。在编写DS ISR来处理BTS、PEBS中断时,请使用以下指导原则:
(1)DS中断服务程序(ISR)必须是内核驱动程序的一部分,并在当前的特权级别0上操作,以确保缓冲区存储区域的安全。
(2)由于BTS和PEBS共享相同的中断向量,DS ISR必须检查来自这些工具的所有可能的中断原因,并将控制权传递给适当的处理程序。
如果缓冲区索引匹配/超过指定的中断阈值,BTS和PEBS缓冲区溢出将是中断的来源。
(3)MP(Multiprocessor)系统中的每个处理器都必须有独立的保存区域、缓冲区和状态。
(4)在进入ISR后,应该禁用 branch trace messages and PEBS,以防止在访问DS保存区域期间出现竞争情况。这可以通过清除IA32_DEBUGCTL中的TR标志和MSR_PEBS_ENABLE MSR中的 precise event启用标志来实现。当退出ISR时,这些设置应该恢复到它们的原始值。
(5)当缓冲区已满且未选择循环模式时,处理器不会禁用 DS save area。在退出时,ISR必须保留并恢复当前DS设置。
(6)在读取到适当缓冲区中的数据后,直到当前索引进入缓冲区,但不包括当前索引,ISR必须将缓冲区索引重置到缓冲区的开头。否则,索引之前的所有内容在下一次ISR调用时看起来都像是新的条目。
(7)ISR必须清除性能计数器 LVT entry中的掩码位。
(8)如果ISR正在处理由于PEBS而溢出的PMI(performance monitoring interrupt),那么ISR必须重新启用计数器通过IA32_PERF_GLOBAL_CTRL 、IA32_PERF_GLOBAL_OVF_CTRL计数。
这篇文章主要介绍了intel 处理器的LBR和BTS的功能和使用。可以利用它们来进行安全领域的对抗,比如:当启用cpu分支跳转记录功能后,将产生的跳转指令收集保存起来,然后与操作系统的text段中的跳转指令进行对比,如果当前CPU产生的跳转指令不在操作系统的text段的跳转指令中,则有可能系统被黑客攻击了(系统被hook后,系统的控制转移将会发生改变,与正常情况的跳转过程将会不一样)。
我以ls命令,ext2文件系统为例子,展示隐藏文件的过程(ext4文件系统函数名会稍有变化,大家有兴趣的可以自己去翻阅源码)。
https://blog.csdn.net/bin_linux96/article/details/104236808
https://nanxiao.me/perf-note-2/
https://stackoverflow.com/questions/14670586/what-is-the-overhead-of-using-intel-last-branch-record
https://bbs.csdn.net/topics/390084569
https://www.asset-intertech.com/resources/blog/2013/11/what-are-intel-lbr-bts-and-aet/
linux内核源码 3.10.0
Intel官方手册 vol2
Intel官方手册 vol3
Intel官方手册 vol4