病毒木马查杀实战第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左边的树形控件,如下图所示:

病毒木马查杀实战第024篇:MBR病毒之编程解析引导区_第1张图片

       由上图可知,硬盘的设备符号链接为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的解析这两大功能,希望大家能够举一反三,有所收获。

你可能感兴趣的:(安全,病毒,木马,引导型病毒,姜晔)