很久以前分析的时候写的,最近开始学习分析bootkit类的病毒,记录下
bootcode代码
以WIN7为例(bootcode代码,win7到win10都一样),MBR大小为1个扇区,大小为512(0x200),
第一块选区0x000-0x162为MBR的可执行代码
第二块选区0x163-0x1b2为错误信息显示,查找分区表失败时会在系统显示这些字符
第三块选区0x1be-0x1fd为分区表。
0x1b5处值为错误信息字符串的偏移,这里值为0x63,再加上0x100就是0x163,即字符串偏移,刚好为第二块选区。
不同版本该值不一样,如xp的为0x2c ,Vista为0x62,win 7到win10系统为0x63。
要检测mbr有没有被修改,我们可以对正常mbr代码区域算个sha256作为白名单,
计算代码开始到错误提示字符串的代码部分hash256,在这个例子是win7,错误提示字符串为0x163,也就是计算从0到0x162部分的sha256
与白名单sha256比较是否一样;不一样,则mbr被修改了。
系统版本 | 字符串偏移 | 白名单Sha256 |
---|---|---|
xp | 0x2c | b5ed343494f0326a08aa6abf7cc9aa4d96207532cf0d2b39453c6eb7bede19e3 |
vista | 0x62 | 4799e8c92d32bca8e5103110a322523adb7a3909324132bd9abab8f3345e094a |
Win7 以上 | 0x63 | 088995559ab317af9b3291408da689651e8353f62e0a478d92eb0b5a947063fd |
1 | typedef struct _MBR |
分区表
从0x1be~0x1fd是分区表。最大只能有四个主分区,每个分区表为16bit大小,分区表采用chs寻址方式,具体结构如下:
1 |
|
活动分区的首个扇区为VBR,同样只有一个扇区。开头eb 52 90是直接跳过bpb结构到bootStrapCode执行代码。
第一块选区 0xb-0x53为BIOS Parameter Block,即NTFS_BPB结构。
第二块选区 0x54 -0x18b为bootStrapCode代码区域
第三块选取 0x18c-0x1f6为错误显示字符串,引导系统失败时显示该字符串
1 | typedef struct _NTFS_VOLUME_BOOT_RECORD |
BIOS Parameter Block
该结构体提供了NTFS文件系统的一些信息
1 | struct NTFS_BPB |
VBR和MBR一样,也可以根据错误显示字符串的偏移来推测出操作系统的版本,0x1f8处值为第一个错误信息字符串的偏移,此处值为0x8c,加上0x100即0x18c,该偏移就是错误信息字符串区域。
对于vbr也可以通过计算正常vbr代码区域的sha256作为白名单,vbr代码sha256计算,应该计算 “eb 50 90”(也是代码)+ vbr代码区域 即0x54到错误信息字符串前。
版本 | 字符串偏移 | 白名单Sha256 |
---|---|---|
xp | 0x83 | 5cb5aa385e0ada266690a2821e3a36ad372720d2ff47c0b1cd9d6ebcab25bf4e |
vista | 0x80 | a1932aaba7d6d3adb1637e2ee0c8355706842ba825ea811728165420c518c0b1 |
Win7 | 0x8c | 96d38c1be37b9124fb71d1d0f5c52969f0074687fe17aef0e1bafc54428674f6 |
Win8 | 0x18a(如果前面三个值都没命中,则看0x1f6到0x1f8的值) | 51643dcce7e93d795b08e1f19e38374ae4deaf3b1217527561a44aa9913ded23 |
VBR加载IPL的代码,在IPL中解析NTFS格式定位bootmgr位置
IPL的位置由VBR 的NTFS_BPB的hiddenSectors字段(相对于扇区起始位置0x1c)指定,即IPL的初始扇区。
IPL同样可以使用白名单来验证IPL是否被修改。IPL共15个扇区,但因为最后一个扇区有时会保存于引导启动无关的数据,因此只计算前面14个扇区的IPL。
左边为win7,右边为win10 ,从win 8以后,IPL区域增加了 引导程序启动失败时显示的字符。该字符串偏移 由0x177-0x178处指定。
“An operating system wasn’t found. Try disconnecting any drives that don’t contain an operating system.”
Win7以下则计算14个扇区的sha256,win8 以上则需要排除掉两个地方:错误信息字符串和保存该字符串索引的位置0x117-0x118。
右图win10的sha256就是计算 0x200~0x299 + 0x319-第十四扇区结束。我们可以根据 0x256处的跳转指来来判断对应的操作系统:
如果是e9,则是win8及以上。
版本 | Sha256 |
---|---|
xp | 525788a688cfbe9e416122f0bc3cfb32ce9699fd12356b6ccaa173444c7d8f3f |
vista | ff1aae04bac3e29f062a7fa17320d7d26363256a69f96840718d45301da71291 |
Win7 | 462afe2322bad3d1c2747d7437d5f6c157e00ca37e5d38ebedd25346b3b488ce |
Win 8以上 | c09d496a1f24086c333468d58256d5db9c73fee945fca74603bdab05f19a6d57 |
上列结构都是针对代码部分做的检测,如果bootkit改动的不是代码,而是改分区表或者BPB的hiddenSectors 字段,则不能检测到是否被感染
正常系统boot引导代码
设置好调试环境后,附加到gdb调试,这时会在bios启动后的第一条指令处断下BIOS会 把MBR加载到0x7c00;这时候所有的段都是0x0,
MBR运行在实模式下,地址都是16bit。在0x7c00设置断点,调整段为16bit,
MBR
1 | MEMORY:7C00 loc_7C00: |
找到分区表的活动分区
1 | MEMORY:061C FB sti |
调用int 13,功能号41. 检测是否支持int 13扩展功能。执行int 13中断功能号42后,会将返回信息存放在ah,bx和cx寄存器。如果支持int 13扩展,则cf为0,bx为0xaa55,cx最低位为1.
1 | MEMORY:0634 mov [bp+0], dl |
扩展int 13读硬盘,在堆栈上构建调用int 13扩展读所需要的参数块,在这里将活动分区的VBR拷贝到0x7c00偏移处。如果从硬盘读取VBR数据成功,则cf=0,ah=0
Offset | Size | Description |
---|---|---|
0 | 1 | 结构体大小 |
1 | 1 | 总是0 |
2 | 2 | 要拷贝的扇区大小,最大是127个扇区 |
4 | 4 | 要拷贝到的位置 |
8 | 4 | 要拷贝的数据的位置 |
1 | MEMORY:0659 loc_659: ; CODE XREF: MEMORY:0648 |
标准int 13读VBR
1 | MEMORY:0687 mov ax, 201h ;ah=02,al=01,即调用int 13中断 功能号2。每次读取一个扇区 |
VBR代码分析
vbr第一条指令为jmp,跳过bios参数块
1 | MEMORY:7C00 jmp short near ptr unk_7C54 |
设置ss、sp和ds的值
1 | MEMORY:7C54 loc_7C54: |
根据VBR结构oemID来检测是否是 ntfs文件系统,如果不是,则在屏幕上显示 “Disk read error”
接着再检测是否支持int 13扩展读。
因为后面需要调用Int 13中断的一些功能号42h-44h,47h,48h,而这些需要支持int 13扩展才能使用。而且标准的int13中断只能访问8gb以内大小的硬盘空间。如果不支持就失败,显示错误信息。
执行int 13中断功能号42后,会将返回信息存放在ah,bx和cx寄存器。如果支持int 13扩展,则cf为0,bx为0xaa55,cx为1.
1 | MEMORY:7C66 loc_7C66: ; CODE XREF: MEMORY:7C65 |
如果支持扩展int13中断调用,接下来调用int13中断获取硬盘信息
1 | MEMORY:7C8D loc_7C8D: ; CODE XREF: MEMORY:7C88 |
返回内容存放地址ds:si; 具体结构如下:
判断返回的数据是不是有效数据。不是就屏幕显示错误提示字符串
1 | MEMORY:7CA0 lahf |
循环调用int 13拷贝VBR之后的IPL 15个扇区,拷贝到内存0x7e00~0x9bff,代码结束地址为0x8c27,剩余部分都被以0填充。
1 | MEMORY:7CC5 |
进入unk_7D1D函数,可以看到调用了int 13扩展读(功能号42) 来从硬盘拷贝一个扇区到内存
1 | MEMORY:7D1D pushad |
把IPL的数据全部都从硬盘读到内存后,调用 Int 1a中断, ah为0xbb 检测是否支持TCG
1 | MEMORY:7CD6 mov ax, 0BB00h |
将IPL非代码部分用0填充。
1 | MEMORY:7D0D xor ax, ax |
IPL代码
IPL的15个扇区复制完毕后,接着便是解析NTFS文件系统 找到bootmgr文件。计算每簇所占的字节数
1 | MEMORY:7E7A movzx eax, word_B ;ds为0x7c00,隐藏word_b为0x7c0b,即 |
计算文件记录(File Record)的大小(文件记录 NTFS文件系统的名词),当簇的大小大于文件记录的大小时,clustersPerMFTRecord的值为负数,但是大小是不可能为负的。举个栗子,这里值是0xf6,实际上是计算2^|-10|=1024
1 | MEMORY:7E8D mov ecx, dword_40; clustersPerMFTRecord每个文件记录所占的簇数 ,这里值为0xf6(-10) |
最后更新: 2023年10月17日 15:59:50
本文链接: http://shxi.cc/post/efe148e3.html
版权声明: 本作品采用 CC BY-NC-SA 4.0 许可协议进行许可,转载请注明出处!