使用英特尔处理器跟踪来跟踪系统管理模式代码


本文致力于测试使用英特尔处理器跟踪(Intel PT)技术以系统管理模式(SMM)模式记录跟踪的可能性。 这项工作是在Summer Of Hack 2019的一部分中完成的。 @sysenter_eip发表。


使用的大多数工具都是由其他人(特别是@d_olex@aionescu编写的 。 结果只是可用工具的组合,以便获得一个特定主板在SMM模式下的代码执行路径。 但是,对于那些希望在其平台上重复此操作或仅对SMM工作感兴趣的人,这些材料可能会很有趣。


系统管理模式


SMM是x86体系结构处理器的特殊特权模式,在操作系统运行时可用,但对其完全不可见。 它设计用于与硬件的低级交互,电源管理,设备遗留的仿真,过渡到睡眠模式(S3),访问TPM等。 它与操作系统完全隔离。 在执行SMM的过程中,操作系统将完全停止。 在此模式下运行的程序代码存储在主板的SPI闪存中,并且是UEFI BIOS固件的一部分。


使用特殊的SMI中断(系统管理中断)可以切换到SMM模式。 此中断的选项之一可用于零环(即来自OS内核)-应用程序级别SMI中断(软件SMI)。 此外,我们将重点关注这些干扰。


由于具有很高的特权,SMM对于安全性研究特别感兴趣。 SMM的破坏会严重违反整个系统的完整性和机密性,并且在大多数情况下,它使您可以将无法删除且无法被操作系统检测到的恶意代码注入UEFI BIOS固件。


英特尔处理器跟踪


各种高负载应用程序的调试过程的陷阱之一是开销-调试工具的成本。 可以使用支持硬件的解决方案来减少它们。


英特尔(Broadwell)的第五代处理器向世界展示了诸如Intel Processor Trace之类的技术。 有什么用? 英特尔PT允许您以最小的开销(<5%)获得调试应用程序的完整执行流(控制流)。 同时,它支持多线程,并且可以帮助修复由于记录应用程序跟踪时的时间戳引起的“竞赛条件”之类的错误。 毫无疑问,英特尔PT技术为在应用程序中编写漏洞搜索工具提供了巨大的机会。


如今,在用户和内核级应用程序中,该技术已用于各种工具中以跟踪,调试和评估代码覆盖率。 可以在英特尔网站上找到工具示例。 PTfuzzer存储库中提供了一个利用Intel PT的AFL模糊器选项。 在最近的项目中,请注意iptanalyzer


但是,我们还没有看到在SMM模式下使用Intel PT的任何工作。 由于在这种情况下无法阻止我们使用Intel PT,因此我们决定确定是否可以使用它跟踪系统管理模式代码。


准备工作


根据《 英特尔开发人员手册》 ,不可能使用常规方法从外部激活SMM中的英特尔PT跟踪。 如果在触发SMI时处于活动状态,则处理器将禁用它,然后再将控制权转移到SMI处理程序入口点。 唯一的激活方法是代码本身自愿包含SMI处理程序。


即使处理器最初没有提供这种机会,我们也可以拦截它并手动激活Intel PT。 但是,您需要以某种方式确定系统已准备好记录跟踪(设置了输出缓冲区的地址),并且还需要在处理器执行结束时(执行RSM指令)关闭跟踪。 否则,处理器将关闭整个系统。


首先,您需要访问SMRAM(在SMM模式下执行的代码所在的RAM区域)。 由于此RAM区域受保护,因此我们无法从操作系统访问它(即使使用DMA也无法做到)。 有几种方案:


  1. 利用SMM中的已知漏洞并从中获取R / W原语。 这可能是软件错误(SMI处理器本身存在漏洞;通常,在SMM中,OEM添加了足够的代码,因此漏洞并不少见),也可能是易受攻击的平台配置(解锁/移动SMRAM);
  2. 以某种方式修补UEFI映像,以使我们具有用于读取和写入任意地址的接口-后门。 要实施此选项,您需要找到禁用了Intel Boot Guard的主板,或者存在可以规避它的漏洞。

将您的代码嵌入固件


尽管不时发现各种制造商代码中的SMM漏洞,但如果我们不依赖它们,那就更好了。 对于我们而言,在新固件上跟踪代码,并因此尝试查找其中的漏洞更为有趣。 我们已经有禁用了英特尔Boot Guard的技嘉GA-Q270M-D3H主板,因此我们要做的就是为SMM添加后门。



图1.测试台


已经有一个“感染” SMM并使用后门框架 。 它由三个组件组成:C中的UEFI驱动程序,“感染器”和Python中的客户端脚本。 对于其操作,您需要提取一个任意的DXE驱动程序(可以使用UEFITool进行此操作),并使用感染程序对其进行处理。 原始模块被“改良”所代替,并且固件被上载到SPI存储器(为方便刷新,从板上移除了SPI闪存驱动器)。



图2. SPI闪存芯片


系统成功启动,现在我们可以从Python完全访问SMRAM(后门提供了使用示例)。 由于后门的客户端脚本基于CHIPSEC ,因此您需要授予其访问内核模式的权限(我们使用了RWEverything驱动程序;对于某些人来说,使用其自己的CHIPSEC驱动程序会很方便,并且系统中的签名验证已关闭)。


您可以通过请求SMRAM转储来检查后门。


$ python SmmBackdoor.py -d


执行此命令后,将创建包含当前SMRAM状态的SMRAM_dump_cb000000_cb7fffff.bin文件。 值cb000000和cb7fffff分别是SMRAM开头和结尾的物理地址。


使用转储SMRAM


可以将SMRAM转储加载到反汇编程序中,或传递给smram_parse.py脚本进行分析,该脚本将为我们提取许多有用的信息。 对我们来说最重要的是SMI入口点的地址。 这些是触发SMI时将控制权转移到的功能的地址。 每个CPU都有自己的入口点。



图3. smram_parse脚本的输出


让我们看看他们的代码。 由于SMM在16位实模式下开始执行(前4 GB RAM反映在虚拟空间中),因此代码要做的第一件事就是切换到64位模式。 同时,由于仅创建了一个段,因此整个SMRAM都具有写入和执行权限(是否有其他供应商以不同的方式来做?)。


我们不想自己编写16位代码或准备切换到64位模式所需的一切,因此我们将在调用SMI管理器功能之前将拦截器放置在该位置(此功能根据执行情况决定应转移哪个SMM模块)调用了什么服务或发生了什么事件)。



图4.挂钩位置


控制的最简单方法是用我们的地址替换调度程序地址。 所有入口点都具有相同的代码,因此每个补丁都需要重复。


注意:关于拦截器代码的位置。 由于我们并不完全了解SMRAM的结构,因此我们在其中一个放置拦截器代码的入口点附近选择了一个随机的零内存块。 最好的选择是将您的SMM模块添加到固件中,UEFI会将其合法地放置在SMRAM中,以免担心重要的事情会被我们的代码覆盖。


实施SMI Manager拦截器


让我们指定拦截器内部要执行的操作。 首先,我们需要确定在移动到SMM之前是否已打开Intel PT。 从英特尔文档中可以知道,每个处理器都有其自己的SMBASE基数(MSR 0x9E)和它自己的空间,用于在过渡到SMM时存储处理器状态(SMM保存状态区域)。



图5. SMBASE布局


我们确定英特尔PT的状态


在保存状态SMM中,必须保存负责管理Intel PT跟踪的MSR寄存器IA32_RTIT_CTL的值。 不幸的是,英特尔手册没有指出处理器在过渡到SMM时将IA32_RTIT_CTL.TraceEn位的状态保存在何处(是否启用了跟踪,为零)。 但是,我们可以通过两次转储“保存状态” SMM来自己确定:启用和不启用跟踪。


我们使用WinIPT工具在Python解释器进程(pid 1337 )上激活跟踪,为跟踪缓冲区分配2 ^ 12 (4096)字节,然后在解释器内部执行SmmBackdoor.py脚本(参数0是一个标志,对我们来说不是重要,因为在SMM中您仍然必须强制执行跟踪设置)。


$ ipttool.exe --start 1337 12 0


通过比较SMRAM快照,我们确定了IA32_RTIT_CTL寄存器在SMM保存状态结构中的位置。 它存储在偏移SMBASE + 0xFE3C中。 IA32_RTIT_CTL.TraceEn位的状态是SMM内部Intel PT重新激活的主要条件。 在此偏移量处的字段在英特尔开发人员手册中标记为保留。



图6.标记保留字段


编写shellcode


我们不想自己在SMM中配置Intel PT,因为这会使我们的Shellcode复杂化(例如,在SMM中,很难分配大量的RAM,以便操作系统本身不使用它)。 因此,我们决定使用已经配置的跟踪器,并简单地在SMM中“跳过”它,特别是因为它已经具有将跟踪保存到文件中的功能。


因为我们为此目的使用了WinIPT,当时它不支持跟踪内核代码(CPL == 0),所以很明显,即使将跟踪包含在SMM中,日志中也不会出现任何内容,因为SMM代码是在CPL = 0时执行的。 我们需要修改一些过滤器,以便跟踪器可以在SMM中花费的整个时间中正常工作。 我们列出了所有需要检查和安装的内容:


  1. 必须启用CPL = 0的跟踪。
  2. 必须启用CPL> 0的跟踪(可选)。
  3. 必须禁用用于记录事件的有效IP范围。
  4. IA32_RTIT_STATUS.PacketByteCnt必须重置。
  5. 必须禁用CR3过滤。

关于PacketByteCnt应该说几句话。 该计数器确定您需要在什么时候将同步数据包(几个PSB命令的序列)插入到跟踪中。 我们需要重置此计数器,否则,在跟踪处理期间,将错过进入SMM的时刻,并且当PSB自然生成时,跟踪将从随机位置开始。


下面是我们使用的shellcode:


  sub rsp, 0x18 ; this will align stack at 16 byte boundary (in case SMM ; code uses align dependent instructions) mov qword ptr ss:[rsp+0x10], rcx ; need to save rcx for SMI_Dispatcher mov ecx, 0x9E ; MSR_IA32_SMBASE rdmsr test byte ptr ds:[rax+0xFE3C], 0x1 ; Save State area contains saved ; IA32_RTIT_CTL.TraceEn je short @NoTrace call @Trace_Enable mov rcx, qword ptr ss:[rsp+0x10] ; SMI_Dispatcher is __fastcall ; (first argument in rcx) mov eax, 0xCB7DDAA4 ; original SMI_Dispatcher !!!!!!!!!!!!!!!!!!!!! call rax call @Trace_Disable add rsp, 0x18 ret @NoTrace: mov rcx, qword ptr ss:[rsp+0x10] ; SMI_Dispatcher is __fastcall mov eax, 0xCB7DDAA4 ; original SMI_Dispatcher !!!!!!!!!!!!!!!!!!!!! call rax add rsp, 0x18 ret @Trace_Disable: mov ecx, 0x570 ; IA32_RTIT_CTL rdmsr mov rax, qword ptr ss:[rsp+0x10] ; restore IA32_RTIT_STATUS wrmsr mov ecx, 0x571 ; IA32_RTIT_STATUS rdmsr mov rax, qword ptr ss:[rsp+0x8] ; restore IA32_RTIT_CTL wrmsr ret @Trace_Enable: mov ecx, 0x571 ; IA32_RTIT_STATUS rdmsr mov qword ptr ss:[rsp+0x8], rax ; save IA32_RTIT_STATUS and edx, 0xFFFF0000 ; IA32_RTIT_STATUS.PacketByteCnt = 0 wrmsr mov ecx, 0x570 ; IA32_RTIT_CTL rdmsr mov qword ptr ss:[rsp+0x10], rax ; save IA32_RTIT_CTL and eax, 0xFFFFFFBF ; IA32_RTIT_CTL.CR3Filter = 0 or eax, 0x5 ; IA32_RTIT_CTL.OS = 1; IA32_RTIT_CTL.User = 1; and edx, 0xFFFF0000 ; IA32_RTIT_CTL.ADDRx_CFG = 0 wrmsr ret 

该代码必须放在SMRAM中,并且必须修补向SMI管理器的过渡才能转到我们的代码。 所有这些都是使用SmmBackdoor完成的。


处理轨道


SMI管理器拦截器允许我们从SMM编写第一个代码跟踪。 以下命令可以要求WinIPT跟踪保存到文件中:


$ ipttool.exe --trace 1337 trace_file_name


禁用进程跟踪:


$ ipttool.exe --stop 1337


您可以尝试使用libipt中的dumppt实用工具反汇编跟踪


$ ptdump.exe --no-pad ./examples/trace_smm_handler_33 > ./examples/trace_smm_handler_33_pt_dump.txt


输出示例:



图7.第一个SMM指令路径


我们可以看到一些地址,但是使用此信息非常困难,因为它非常底层。


为了使外观更具可读性,有一个ptxed实用程序(来自libipt)将跟踪转换为已执行的汇编程序指令的日志。 当然,由于IPT日志不包含有关存储单元的值或执行了哪些指令的信息,因此我们必须为实用程序提供SMRAM内存转储。 它仅包含有关控制流中发生了什么更改的信息。


$ ptxed.exe --pt tracesmm_12 --raw SMRAM_dump_cb000000_cb7fffff.bin:0xcb000000 > tracesmm_12_ptasm



图8.对应于IPT日志的汇编程序列表


它看起来已经好多了,但是如果代码中包含一个循环,则输出将被相同的指令所阻塞。


使用跟踪定义代码覆盖率


为了获得覆盖可视化,我们选择了IDA Pro的Lighthouse插件,该插件使用drcov格式。


找不到现成的工具,因此我们修改了ptxed,以便在此过程中还生成了coverage文件。 修补程序ptxed存储库中可用。 查看提交历史以确定确切添加了什么。


ptxed完成后,将显示SMRAM_dump_cb000000_cb7fffff.bin.log文件,其中将包含drcov格式的覆盖率信息。


注意:第一个PSB上的反汇编程序同步存在一个小问题。 由于不完全清楚的原因,如果PSB在PGE之前生成(在再次激活跟踪之前将计数器重置为零),则无法在其上同步ptxed 。 要变通解决此问题,我们做了一个小补丁。 目前尚不清楚这是ptxed本身的问题,还是我们通过重置IA32_RTIT_STATUS.PacketByteCnt做错了什么。



图9.一个修补程序,使您可以使用位于PGE前面的PSB


可以将生成的覆盖率文件下载到IDA Pro中并突出显示,以及每个功能的覆盖率统计信息。



图10.具有代码覆盖率信息的IDA Pro Lighthouse插件


注意: Lighthouse插件在未完全分析的数据库上工作时有些奇怪(未标记可执行代码,未创建函数)。 我们将此“问题”追溯到\ lighthouse \ metadata.py文件中的get_instructions_slice函数,该函数即使对于手动创建函数的地址也返回0条指令。 该插件似乎使用了缓存,而忽略了新的特定代码。 可以通过在程序上调用Reanalyze并重新打开IDB来避免这种情况。 只有在那之后,插件才能看到新代码并开始考虑它。 由于在SMRAM转储的情况下此问题非常不便(第一次启动时,它几乎完全由未定义的代码组成),因此我们对Lighthouse代码进行了一些小的更改,以便我们可以更快地手动定义新代码。



图11.添加了日志消息以帮助识别新代码


Linux支援


由于我们所有的测试都是在Windows 10 x64上进行的(我们需要ipt.sys,该文件出现在Windows October Creators Update 2018中),因此让我们来谈谈在Linux中实现此功能的可能性。


  • 有一个Linux内核性能模块,可以执行相同的WinIPT(ipt.sys)操作,包括以内核模式跟踪代码的功能。
  • 由于后门SMM接口基于跨平台的CHIPSEC框架,因此我们的补丁程序无需修改即可在Linux系统上运行。

结论


我们成功地完成了使用英特尔处理器跟踪技术获得在SMM中执行的代码跟踪的任务。 借助昂贵的设备和软件(并非向所有人出售),可以获得类似的结果。 我们手头上有一块主板和SPI编程器就足够了。 移走轨道的速度确实令人印象深刻,并且对结果的准确性没有任何抱怨。


我们希望本文能帮助其他人利用英特尔PT技术来调查和搜索SMM代码中的漏洞。 使我们的工作适应其他主板应该不会造成困难(不要忘了Intel Boot Guard)。 最主要的是要完全了解它是如何工作的。 最困难的部分是确定如何拦截SMI调度程序并为拦截器编写shellcode。 在我们的版本中,使用了“有线”地址,因此您应该小心地将shellcode转移到另一个系统。


GitHub上存储库中提供了所有使用过的工具和脚本。

Source: https://habr.com/ru/post/zh-CN481692/


All Articles