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项。
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; do { pDrv->DriverUnload = DrvUnload;
} while (FALSE);
return status; }
|
其中需要对VS设置如下:
1、关闭C++将警告视为错误。
2、关闭WppTrapingRun Wpp Tracing。
然后编译即可。在测试平台上使用KmdManager进行加载,首先点击Register,然后点击Run。使用PcHunter可以看到驱动已经加载。
在加载驱动的时候首先需要将驱动的基本信息写入注册表中,写入的路径为DriverEntry的第二个参数。使用KdPrint进行输出。
KdPrint和DbgPrint区别是:KdPrint实际上是一个宏,会对Debug和Release进行判断,如果为Debug编译的驱动则会调用DbgPrint,否则不调用。
Text1
| 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 _Field_size_bytes_part_(MaximumLength, Length) PWCH Buffer; #endif } UNICODE_STRING;
|
加载驱动,使用DbgView捕获输出Capture->Cpature Kernel,Capture->Enable Verbose Kernel Output
然后打开到对应的注册表查看。
DisplayName:驱动对外显示的名字,比如PCHunter枚举驱动模块时显示的名字
ErrorControl:母鸡
ImagePath:驱动模块的路径,\??\为全路径磁盘就是挂载这个上面
Start:驱动加载的类型,<=2为开启自启,数字越低自启动时机与早。
Type:类型,1为驱动模块,0好像是服务。
驱动加载时,系统会将这些信息写入注册表,然后在拉起驱动模块。
驱动卸载时,系统首先会调用驱动的卸载函数(如果不设置则无法卸载),然后删除注册表。
当一个正常的内核模块加载完成时,注册表就保存着他的信息,通过枚举注册表即可获取系统加载的驱动模块,因此绕过这中枚举方法就是隐藏自己的注册表,例如驱动加载完毕后删除注册表。
二、蓝屏调试
当产生蓝屏时可以通过执行!analyze -v来获取详细信息。大概流程为:
1、执行!analyze -v获取详细信息。
2、根据信息给出的蓝屏原因进行搜索。
根据给出的原因到https://learn.microsoft.com/en-us/windows-hardware/drivers/debugger/搜索
然后根据windbg给出的参数进行大致排查原因。
3、根据栈回溯查找原因
可以大致看到最后一次自己模块的调用位置。还有异常所处的模块是什么。
三、驱动断链
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; 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; 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; }
|
其中可以看到第一个是自身,调用RemoveEntryList断链自身即可隐藏,但比较弱。