病毒木马查杀实战第024篇:MBR病毒之编程解析引导区
前言
通过之前的学习,相信大家已经对磁盘的引导区有了充分的认识。但是我们之前的学习都是利用现成的工具来对引导区进行解析的,而对于一名反病毒工程师而言,不单单需要有扎实的逆向分析功底,同时也需要有很强的编程能力来解决实际问题。对于我们本次的课程来说,就需要大家亲自动手,利用程序来实现引导区的解析。这样做的目的,一方面是为了提高大家的编程能力,而另一方面则有助于我们更好地理解引导区的内容。
通过程序解析MBR
对于学习过PE文件格式解析的朋友来说,解析MBR可能不会有太大的问题,毕竟二者的原理还是非常相似的。但是虽然解析过PE文件,还是有一些微小的差别。首先造成解析困难的一点是MBR没有给出具体的结构体。如果大家分析过PE文件结构,那就知道,各个结构体在WinNt.h头文件中都有给出现成的定义,而MBR的定义是没有给出的。因此我们上次课在解析MBR时,并没有对照着结构体给大家介绍。再一个问题是,解析PE文件时,我们会打开具体的可执行文件去按照PE文件结构的定义进行解析,而硬盘的引导区并不属于某一个文件。用WinHex打开的是物理硬盘,那么我们如何通过程序来打开物理硬盘呢?这就是比较困惑的地方。不过,这些都不是太大的问题。本次的课程只要解决了这两个问题,那么,我们的进行编程解析时就容易多了。
自定义MBR的各种结构体
下面介绍如何将MBR的信息定义成一个个的结构体。通过前面用WinHex对MBR的手动分析,我们了解到MBR分为五部分,并且知道每部分占用的字节数。因此,可以将MBR定义为如下:
typedef struct _MBR
{
unsigned char BootRecord[440]; // 引导程序
unsigned char ulSigned[4]; // Windows磁盘签名
unsigned char sReserve[2]; // 保留位
unsigned char Dpt[64]; // 分区表
unsigned char EndSign[2]; // 结束标志
}MBR, *PMBR;
这就是定义的MBR了,引导程序共440个字节,Windows签名共4个字节,保留字节共2个字节,分区表共64个字节,再加上2个结束标志,一共512个字节。不过这样定义并不好,因为里面的常量比较多,下面修改一下,定义如下:
#define BOOTRECORDSIZE 440
#define DPTSIZE 64
typedef struct _MBR
{
unsigned char BootRecord[BOOTRECORDSIZE]; // 引导程序
unsigned char ulSigned[4]; // Windows磁盘签名
unsigned char sReserve[2]; // 保留位
unsigned char Dpt[DPTSIZE]; // 分区表
unsigned char EndSign[2]; // 结束标志
} MBR, *PMBR;
这样定义后,可以很方便地获得引导程序的大小和分区表的大小。虽然这样定义直观一些,但是还不能算太直观,因为定义的都是unsigned char类型,无法真正反映出每个成员变量的具体含义。下面再次进行修改,定义如下:
#define BOOTRECORDSIZE 440
typedef struct _BOOTRECORD
{
unsigned char BootRecord[BOOTRECORDSIZE];
}BOOTRECORD, *PBOOTRECORD;
#define DPTSIZE 64
typedef struct _DPT
{
unsigned char Dpt[DPTSIZE];
}DPT, *PDPT;
typedef struct _MBR
{
BOOTRECORD BootRecord;
unsigned char ulSigned[4];
unsigned char sReserve[2];
DPT Dpt;
unsigned char EndSign[2];
}MBR, *PMBR;
这次修改后,可以很容易地从MBR这个结构体中看出主要两个成员变量的含义了。虽然直观了,但还是有问题。Dpt其实是一个有4条记录的表,也就是说它其实是一个数组,这样的定义当解析它的时候并不方便。这样的定义方便我们一次性将DPT读出,只要再定义一个DP的结构体来对DPT进行转换,就可以方便地对DPT进行解析了。下面再次定义一个结构体,定义如下:
#define DPTNUMBER 4
typedef struct _DP
{
unsigned char BootSign; // 引导标志
unsigned char StartHsc[3]; // 分区的起始磁头号、扇区号、柱面号
unsigned char PartitionType; // 分区类型
unsigned char EndHsc[3]; // 分区的结束磁头号、扇区号、柱面号
ULONG SectorsPreceding; // 本分区之前使用的扇区数
ULONG SectorsInPartition; // 分区的总扇区数
}DP, *PDP;
有了这个结构体,就可以方便地对DPT进行解析了。最后两个定义就是对MBR各结构体的完整定义。之所以如此反复介绍如何进行MBR结构体的定义,是想告诉大家一个在没有相关数据结构定义的情况下如何通过自己的分析来定义数据结构的思路和方法。
大家可以思考一下,如果不定义这些结构体是不是就无法对MBR进行解析,定义了这些结构体后对于解析MBR有哪些影响。对于MBR的解析,可以完全不定义这些结构体,定义这些结构体的目的是方便对程序的后期维护,并使程序在整体上有一个良好的格式。定义数据结构可以清晰地表达各个数据结构之间的关系,让我们在写程序的过程中有一个清晰的思路,让看程序的人也可以一目了然。
硬盘设备的符号链接
有了上面的结构体,解析MBR已经不是太大的问题了。不过还有一个问题,那就是如何打开硬盘读取MBR。其实很简单,只要打开硬盘设备提供的设备符号链接就可以了。如何找到硬盘的设备符号链接呢?有一款工具WinObj可以帮助查找到。打开WinObj,再依次打开左边的树形控件,如下图所示:
通过上图可以找到硬盘设备的设备名称,例如可以通过\Device\Harddisk0\DR0这个设备名称再去查找相应的设备符号链接。我们再依次打开WinObj左边的树形控件,如下图所示:
由上图可知,硬盘的设备符号链接为PhysicalDrive0,那么在使用时就应该书写为\\.\PhysicalDrive0。
这里给大家简单地介绍一下设备名和设备符号链接。每个设备在Windows的内核中都有对应的驱动模块,在驱动模块中会为设备提供一个名字来对设备进行操作,驱动模块中提供的名字即为“设备名”。设备名只能在内核模块中使用。如果想要在应用程序下对设备进行操作,不能直接使用设备名称,应该使用设备符号链接。设备符号链接就是驱动模块为应用程序提供的操作设备的一个符号,通过这个符号可与设备进行对应。
解析MBR的程序实现
到了这里大家可能会觉得通过程序解析MBR已经不是问题了,下面直接提供程序的代码吧。如果代码中有不理解的地方,可以参考一下我们是如何通过WinHex对MBR进行解析的。代码如下:
#include "windows.h"
#include "stdio.h"
// 显示MBR数据
VOID ShowMbr (HANDLE hDevice, PMBR pMbr)
{
DWORD dwRead = 0;
ReadFile(hDevice, (LPVOID)pMbr, sizeof(MBR), &dwRead, NULL);
int i;
for(i= 0; i < 512; i++)
{
printf("%02X", ((BYTE*)pMbr)[i]);
if ((i+1)%16 == 0)
{
printf("\r\n");
}
}
}
// 解析MBR
VOID ParseMbr(MBR Mbr)
{
printf("引导记录: \r\n");
for( int i = 0; i < BOOTRECORDSIZE; i++ )
{
printf("%02X ", Mbr.BootRecord.BootRecord[i]);
if((i+1)%16 == 0)
{
printf("\r\n");
}
}
printf("\r\n");
printf("磁盘签名: \r\n");
for(i = 0; i < 4; i ++)
{
printf("%02X ", Mbr.ulSigned[i]);
}
printf("\r\n");
printf("解析分区表: \r\n");
for(i = 0; i < DPTSIZE; i++)
{
printf("%02X ", Mbr.Dpt.Dpt[i]);
if((i+1)%16 == 0)
{
printf("\r\n");
}
}
printf("\r\n");
// 获取分区表的地址,并将指向分区表的指针转换为PDP的类型,最后赋给pDp
PDP pDp = (PDP)&(Mbr.Dpt.Dpt);
for(i = 0; i < DPTNUMBER; i++)
{
printf("引导标志:%02X ",pDp[i].BootSign);
printf("分区类型:%02X ",pDp[i].PartitionType);
printf("\r\n");
printf("本分区之前扇区数:%d ",pDp[i].SectorsPreceding);
printf("本分区的总扇区数:%d ",pDp[i].SectorsInPartition);
printf("\r\n");
printf("该分区的大小:%f \r\n", (double)pDp[i].SectorsInPartition/1024*512/1024/1024);
printf("\r\n \r\n");
}
printf("结束标志:\r\n");
for (i = 0; i < 2; i ++)
{
printf("%02X ", Mbr.EndSign[i]);
}
printf("\r\n");
}
int main(int argc, char* argv[])
{
// 打开物理硬盘设备
HANDLE hDevice = CreateFile("\\\\.\\PhysicalDrive0",
GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
0,
NULL);
if (hDevice == INVALID_HANDLE_VALUE)
{
printf("CreateFile Error %d \r\n", GetLastError());
return -1;
}
MBR Mbr = { 0 };
ShowMbr(hDevice, &Mbr);
ParseMbr(Mbr);
CloseHandle(hDevice);
getchar();
return 0;
}
代码非常短,也不复杂,看起来跟读写文件没什么太大的差别,其实就是在读写文件。前面介绍过,Windows将各种设备都当作文件来看待,因此打开硬盘设备的时候直接使用CreateFile()函数就可以了。
小结
我们这次的实验,为了实现编程解析MBR的目的,首先是通过之前的知识创建了相应的结构体,并给大家演示了我们的结构体一步一步不断完善的过程。接下来给大家讲解了如何利用工具查找硬盘设备的符号链接,通过这个符号链接,我们就可以通过程序来打开MBR了。最后是整个程序的编写,主要实现了显示MBR的数据以及MBR的解析这两大功能,希望大家能够举一反三,有所收获。
病毒木马查杀实战第024篇:MBR病毒之编程解析引导区的更多相关文章
- 病毒木马查杀实战第025篇:JS下载者脚本木马的分析与防御
前言 这次我与大家分享的是我所总结的关于JS下载者脚本木马的分析与防御技术.之所以要选择这样的一个题目,是因为在日常的病毒分析工作中,每天都会遇到这类病毒样本,少则几个,多则几十个(当然了,更多的样本 ...
- 病毒木马查杀实战第011篇:QQ盗号木马之专杀工具的编写
前言 由于我已经在<病毒木马查杀第004篇:熊猫烧香之专杀工具的编写>中编写了一个比较通用的专杀工具的框架,而这个框架对于本病毒来说,经过简单修改也是基本适用的,所以本文就不讨论那些重叠的 ...
- 病毒木马查杀实战第010篇:QQ盗号木马之十六进制代码分析
前言 按照我的个人习惯,在运用诸如IDA Pro与OllyDBG对病毒进行逆向分析之前,我都会利用一些自动化的工具,通过静态或动态的分析方法(参见<病毒木马查杀第008篇:熊猫烧香之病毒查杀总结 ...
- 病毒木马查杀实战第009篇:QQ盗号木马之手动查杀
前言 之前在<病毒木马查杀第002篇:熊猫烧香之手动查杀>中,我在不借助任何工具的情况下,基本实现了对于"熊猫烧香"病毒的查杀.但是毕竟"熊猫烧香" ...
- 病毒木马查杀实战第023篇:MBR病毒之引导区的解析
前言 引导型病毒指寄生在磁盘引导区或主引导区的计算机病毒.这种病毒利用系统引导时,不对主引导区的内容正确与否进行判别的缺点,在引导系统的过程中入侵系统,驻留内存,监视系统运行,伺机传染和破坏.按照引导 ...
- 病毒木马查杀实战第020篇:Ring3层主动防御之基本原理
前言 假设说我们的计算机中安装有杀毒软件,那么当我们有意或无意地下载了一个恶意程序后.杀软一般都会弹出一个对话框提示我们,下载的程序非常可能是恶意程序,建议删除之类的.或者杀软就不提示.直接删除了:或 ...
- 病毒木马查杀实战第015篇:U盘病毒之脱壳研究
前言 因为我们的终于目标是编写出针对于这次的U盘病毒的专杀工具.而通过上次的分析我们知道,病毒有可能在不同的计算机中会以不同的名称进行显示.假设真是如此,那么就有必要在此分析出病毒的命名规律等特征,然 ...
- 病毒木马查杀实战第017篇:U盘病毒之专杀工具的编写
前言 经过前几次的讨论,我们对于这次的U盘病毒已经有了一定的了解,那么这次我们就依据病毒的行为特征,来编写针对于这次U盘病毒的专杀工具. 专杀工具功能说明 因为这次是一个U盘病毒,所以我打算把这次的专 ...
- 病毒木马查杀实战第022篇:txt病毒研究
前言 反病毒爱好者们非常喜欢讨论的一个问题就是,现在什么样的病毒才算得上是主流,或者说什么样的病毒才是厉害的病毒呢?我们之前的课程所解说的都是Ring3层的病毒.所以有些朋友可能会觉得.那么Ring0 ...
随机推荐
- sqlyog如何增删改查?
转: sqlyog如何增删改查? 下面是一道完整的 sqlyog 增删改查的练习, 顺着做下去,可以迅速掌握. 1. 创建部门表dept,并插入数据: 2. 创建emp员工表,并插入数据: sql 代 ...
- Git使用的常用场景
场景一 小张作为一个开发人员,刚进团队,发现团队是使用git进行代码管理的,现在需要去初始化团队的代码仓库以及新增提交自己修改的一部分代码 1.克隆远程仓库 git clone <ssh> ...
- 有钱人买钻石+dfs中使用贪心
有钱人买钻石 ECNU-3306 题解:这个题目,乍一看以为是dp背包,可是数据量却那么大,只有1,5,10,25四种面额的硬币,每种数量若干,要使得能够刚好兑换成功总金额,在此前提下,还要使得硬币数 ...
- 链表算法题之中等级别,debug调试更简单
文章简述 大家好,本篇是个人的第 5 篇文章 从本篇文章开始,分享关于链表的题目为中等难度,本次共有 3 道题目. 一,两数相加 1.1 题目分析 题中写到数字是按照逆序的方式存储,从进位的角度看,两 ...
- MySQL基础知识:Character Set和Collation
A character set is a set of symbols and encodings. A collation is a set of rules for comparing chara ...
- android分析之mutex
Android的锁是对Linux锁的一种包装: // ------------------------------------------------------------------------- ...
- arcgis for js 4.6加载本地发布好的2维地图
我本地发布好的地图服务信息如下图所示: 我们在代码中使用到的url是图中所示的REST URL 加载代码如下: <!DOCTYPE html> <html> <head& ...
- get和post的区别主要有以下几方面
1.url可见性: get,参数url可见: post,url参数不可见 2.数据传输上: get,通过拼接url进行传递参数: post,通过body体传输参数 3.缓存性: get请求是可以缓存的 ...
- 在Windows10搭建WebAssembly开发环境
最近研究WebAssembly技术,准备用WebAssembly编译C/C++代码供前端调用.网上看了很多文章,收获很大,现在就遇到的问题做一个记录. 官网关于windows开发环境搭建基本上几句话, ...
- 使用docker搭建sonarqube
sonarqube是一款代码质量检查工具,使用sonar扫描我们写过的代码,可以有助于检查出代码的bug.规范性和健壮性,有助于提高我们的代码质量. 一.安装docker 安装完成之后,命令行输入 d ...