x86驱动开发

WDK下载链接:https://learn.microsoft.com/zh-cn/windows-hardware/drivers/download-the-wdk,根据页面对应内容下载即可。

x86驱动打开只能使用<=wdk8.1版本,对应的可用vs版本为<=vs2017。因此X86篇章的驱动开发环境如下:

  • 系统:window7 x64 sp1
  • IDE:VS2013
  • WDK:8.1

一、DriverEntry

1、第一个驱动

安装WDK后,VS新建项目中会多出Windows Driver项。

image-20230524165705048

KMDF和WDM的区别不大,KMDF的驱动支持即插即用设备,例如U盘。在测试中发现x86下安装KMDF会失败,这里只写使用WDM进行编写驱动。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <ntifs.h> //内核头文件
//卸载驱动函数
VOID DrvUnload(PDRIVER_OBJECT pDrv)
{

}

NTSTATUS DriverEntry(PDRIVER_OBJECT pDrv, PUNICODE_STRING pReg)
{
NTSTATUS status = STATUS_SUCCESS; //返回值,内核中STATUS_SUCCESS表示操作成功,如果返回非值则代表操作失败。
do
{
pDrv->DriverUnload = DrvUnload; //用于驱动卸载时调用,如果不设置则无法进行卸载


} while (FALSE);

return status;
}

其中需要对VS设置如下:

1、关闭C++将警告视为错误

2、关闭WppTrapingRun Wpp Tracing

然后编译即可。在测试平台上使用KmdManager进行加载,首先点击Register,然后点击Run。使用PcHunter可以看到驱动已经加载。

image-20230524170812115

在加载驱动的时候首先需要将驱动的基本信息写入注册表中,写入的路径为DriverEntry的第二个参数。使用KdPrint进行输出。

KdPrint和DbgPrint区别是:KdPrint实际上是一个宏,会对Debug和Release进行判断,如果为Debug编译的驱动则会调用DbgPrint,否则不调用。

image-20230524171639600

1
KdPrint(("%wZ\r\n", pReg));

%wZ表示的是输出内核字符串。内核中一旦出现内存溢出或者空指针则会引发蓝屏,因此微软使用了更安全的UNICODE_STRING结构来表示字符串.

1
2
3
4
5
6
7
8
9
typedef struct _UNICODE_STRING {
USHORT Length; //字符串占用字节长度,而非字符串长度
USHORT MaximumLength; //字符串缓冲区的字节大小
#ifdef MIDL_PASS
[size_is(MaximumLength / 2), length_is((Length) / 2) ] USHORT * Buffer;
#else // MIDL_PASS
_Field_size_bytes_part_(MaximumLength, Length) PWCH Buffer; //!!!!!此处的指针指向的是字符串的指针
#endif // MIDL_PASS
} UNICODE_STRING;

加载驱动,使用DbgView捕获输出Capture->Cpature Kernel,Capture->Enable Verbose Kernel Output

image-20230524172139616

然后打开到对应的注册表查看。

image-20230524172346766

  • DisplayName:驱动对外显示的名字,比如PCHunter枚举驱动模块时显示的名字
  • ErrorControl:母鸡
  • ImagePath:驱动模块的路径,\??\为全路径磁盘就是挂载这个上面
  • Start:驱动加载的类型,<=2为开启自启,数字越低自启动时机与早。
  • Type:类型,1为驱动模块,0好像是服务。

驱动加载时,系统会将这些信息写入注册表,然后在拉起驱动模块。

驱动卸载时,系统首先会调用驱动的卸载函数(如果不设置则无法卸载),然后删除注册表。

当一个正常的内核模块加载完成时,注册表就保存着他的信息,通过枚举注册表即可获取系统加载的驱动模块,因此绕过这中枚举方法就是隐藏自己的注册表,例如驱动加载完毕后删除注册表。

二、蓝屏调试

当产生蓝屏时可以通过执行!analyze -v来获取详细信息。大概流程为:

1、执行!analyze -v获取详细信息。

image-20230524173256871

2、根据信息给出的蓝屏原因进行搜索。

image-20230524173602468

根据给出的原因到https://learn.microsoft.com/en-us/windows-hardware/drivers/debugger/搜索

image-20230524173355010

然后根据windbg给出的参数进行大致排查原因。

3、根据栈回溯查找原因

image-20230524173756293

可以大致看到最后一次自己模块的调用位置。还有异常所处的模块是什么。

  • k、kv:查看堆栈

三、驱动断链

PDRIVER_OBJECT中的DriverSection实际上是一个链表,结构为:KLDR_DATA_TABLE_ENTRY

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
typedef struct _NON_PAGED_DEBUG_INFO {
USHORT Signature;
USHORT Flags;
ULONG Size;
USHORT Machine;
USHORT Characteristics;
ULONG TimeDateStamp;
ULONG CheckSum;
ULONG SizeOfImage;
ULONGLONG ImageBase;
} NON_PAGED_DEBUG_INFO, *PNON_PAGED_DEBUG_INFO;

typedef struct _KLDR_DATA_TABLE_ENTRY {
LIST_ENTRY InLoadOrderLinks;
PVOID ExceptionTable;
ULONG ExceptionTableSize;
// ULONG padding on IA64
PVOID GpValue;
PNON_PAGED_DEBUG_INFO NonPagedDebugInfo;
PVOID DllBase;
PVOID EntryPoint;
ULONG SizeOfImage;
UNICODE_STRING FullDllName;
UNICODE_STRING BaseDllName;
ULONG Flags;
USHORT LoadCount;
USHORT __Unused5;
PVOID SectionPointer;
ULONG CheckSum;
// ULONG padding on IA64
PVOID LoadedImports;
PVOID PatchInformation;
} KLDR_DATA_TABLE_ENTRY, *PKLDR_DATA_TABLE_ENTRY;

如果遍历可以得到非处理的驱动模块(隐藏的不行)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
NTSTATUS DriverEntry(PDRIVER_OBJECT pDrv, PUNICODE_STRING pReg)
{
NTSTATUS status = STATUS_SUCCESS;
do
{
pDrv->DriverUnload = DrvUnload;

PKLDR_DATA_TABLE_ENTRY header = pDrv->DriverSection;
PKLDR_DATA_TABLE_ENTRY curt = header;


do
{
KdPrint(("DllBase:%p\n",curt->DllBase));
KdPrint(("EntryPoint:%p\n", curt->EntryPoint));
KdPrint(("SizeOfImage:%p\n", curt->SizeOfImage));
KdPrint(("FullDllName:%wZ\n", &curt->FullDllName));
KdPrint(("BaseDllName:%wZ\n", &curt->BaseDllName));
curt = curt->InLoadOrderLinks.Flink;
}while (header != curt);


} while (FALSE);
return status;
}

image-20230524174607757

其中可以看到第一个是自身,调用RemoveEntryList断链自身即可隐藏,但比较弱。