Windows驱动开发入门 开始 到微软官网上下载并安装SDK和WDK,这俩内部版本号必须得对上,且安装时必须保持默认安装路径不变,新建Empty WDM Driver项目。当编译时出现Spectre相关错误,在项目设置中把这东西关掉。如果还是不能编译通过,把项目目录下的.inf文件从工程中移除。当某些警告被当作错误处理时,降低警告等级并关闭警告视为错误。
如果你的项目设置中没有链接器等选项(像我这样),恭喜你需要重装系统,或搞个虚拟机装吧!
WinDBG调试 Windows 7 别真机搞,弄个虚拟机,先在虚拟机设置中把打印机删除,再添加串行端口,使用名字“\\.\pipe\com_1”,并把轮询时中断CPU勾选上。当虚拟机为Windows 7时运行msconfig,引导中选择高级选项并勾选调试。Windows 10中要在设置的安全和更新选项卡的针对开发人员选项中开启开发人员模式。最后运行命令:
1 bcdedit /set testsigning on
打开WinDBG Preview并Attach to Kernel,选项选COM,勾选Pipe和Reconnect,Port输入“\\.\pipe\com_1”。
Windows 10 关机然后删掉打印机,新建串口,方法同上。Windows 10中要在设置的安全和更新选项卡的针对开发人员选项中开启开发人员模式。运行命令:
1 2 3 4 5 6 bcdedit /set testsigning on bcdedit /set “{current}” bootmenupolicy Legacy bcdedit /dbgsettings SERIAL DEBUGPORT:1 BAUDRATE:115200 bcdedit /copy “{current}” /d “Debug” bcdedit /debug “{<新建的启动配置的标识符>}” on bcdedit /enum ;看看Windows启动加载器的debug选项是不是Yes
重启自动进入Windows启动管理器,进入Debug,即可被WinDBG附加。
加载驱动 Windows 7 x64 兄弟,别用。这个系统的驱动加载与签名校验都有大病。
Windows 10 写个驱动并编译试试:
1 2 3 4 5 6 7 8 9 10 11 12 #include <ntddk.h> VOID DriverUnload (PDRIVER_OBJECT pDriver) { DbgPrintEx (DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "[%d] Unloading...\r\n" , __LINE__); return ; }; NTSTATUS DriverEntry (PDRIVER_OBJECT pDriverObject, PUNICODE_STRING RegistryPath) { DbgPrintEx (DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "RegistryPath = %S\r\n" , RegistryPath->Buffer); DbgPrintEx (DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "[%d] Loading...\r\n" , __LINE__); pDriverObject->DriverUnload = DriverUnload; return STATUS_SUCCESS; };
驱动想装载需要签名校验,有两种方法,一是尝试签个能用的证书,二是直接关掉Win10的签名校验机制。
第一种方法用软件Windows 64Signer,勾选上开启Test Signing启动选项然后重启,用它签名.sys即可。
第二种方法尝试在管理员下执行:
1 2 bcdedit /set testsigning on bcdedit -set loadoptions DDISABLE_INTEGRITY_CHECKS
然后按着Shift点击开始菜单的重启,选择疑难解答->高级启动选项,重启,启动时选择倒数第二个选项(虚拟机可能汉字显示框框)。重启后桌面壁纸右下角应该显示测试模式即可。
然后以后每次都得在启动时选择带“DEBUG”的启动选项。
打开DebugView,菜单勾选上“Capture”的“Capture Win32”、“Capture Global Win32”、“Capture Kernel”等,再利用DriverMonitor进行加载即可,可以看到DebugView中的调试信息。
WinDBG初探 Attach to Kernel后,无论任何情况按一或两次F5或命令g
即可继续被调试机的运行。例如调试驱动程序MyDriver1.sys,用DriverMonitor进行加载后,Alt+Delete进行中断,并命令bp MyDriver1+0x5000
,其中0x5000是该.sys的入口点地址,随便找个PE分析工具都能看。重新运行几次驱动后可断在入口点。F11为步进,F10为步过,Shift+F11为运行到返回。
基础速成 字符串 双字节字符串定义:
1 2 3 4 5 typedef struct _UNICODE_STRING { USHORT Length; USHORT MaximumLength; PWSTR Buffer; }UNICODE_STRING *PUNICODE_STRING;
打印方法:
1 2 UNICODE_STRING str=RTL_CONSTANT_STRING (TEXT ("xxx" )); DbgPrint ("%wZ" ,&str);
也有ANSI版的结构但极少使用。
UNICODE_STRING
类型不能随便初始化,有两种方法,注意清零时Buffer
一定要赋值为缓冲区指针,否则空指针访问异常:
1 2 3 4 5 6 7 8 9 10 11 12 13 UNICODE_STRING str = { 0 }; WCHAR strBuf[128 ] = { 0 }; str.Buffer = strBuf; wcscpy (str.Buffer, TEXT ("xxx" ));str.Length = str.MaximumLength = wcslen (TEXT ("xxx" )) * sizeof (WCHAR); UNICODE_STRING str; str.Buffer = TEXT ("xxx" ); str.Length = str.MaximumLength = wcslen (TEXT ("xxx" )) * sizeof (WCHAR); UNICODE_STRING str = { 0 }; RtlInitUnicodeString (&str, TEXT ("xxx" ));
字符串src
拷贝到dst
中,dst
的缓冲区为dst_buf
:
1 2 3 4 5 UNICODE_STRING dst; WCHAR dst_buf[256 ] = { 0 }; UNICODE_STRING src = RTL_CONSTANT_STRING (TEXT ("xxx" )); RtlInitEmptyUnicodeString (&dst, dst_buf, 256 * sizeof (WCHAR));RtlCopyUnicodeString (&dst, &src);
字符串连接,有两个API能用,这俩具体啥区别我也不知道:
1 2 3 NTSTATUS status = STATUS_SUCCESS; status = RtlAppendUnicodeToString (&dst, &src); status = RtlAppendUnicodeStringToString (&dst, &src);
字符串打印,缓冲区不够直接截断并返回STATUS_BUFFER_OVERFLOW,最好是缓冲区多次扩展长度直到STATUS_SUCCESS为止:
1 2 3 4 5 6 7 8 NTSTATUS status=STATUS_SUCCESS; UNICODE_STRING dst = { 0 }; WCHAR dst_buf[256 ] = { 0 }; UNICODE_STRING file_path = RTL_CONSTANT_STRING (TEXT ("\\??\\C:\\xxx.xxx" )); RtlInitEmptyUnicodeString (&dst, dst_buf, 256 * sizeof (WCHAR));USHORT file_size = 1024 ; status = RtlStringCbPrintfW (dst.Buffer, 512 * sizeof (WCHAR), TEXT ("%wZ -> %d\r\n" ), &file_path, file_size); dst.Length = wcslen (dst.Buffer) * sizeof (WCHAR);
多线程安全性 一个函数的多线程安全性指的是一个函数在被调用过程中,还未返回时,又再次被调用情况下执行结果可靠性。
可能运行于多线程环境函数必须多线程安全,只运行于单线程环境的函数不需要。函数所有调用源只运行于同一单线程环境下则该函数也只运行在单线程环境下。函数其中一个调用源可能运行在多线程环境下或多个调用源可能运行于不同可并发多个线程环境下,且调用路径上没有采取多线程序列化强制措施时该函数可能运行在多线程环境下。若函数所有可能运行于多线程环境下调用路径上都有序列化强制措施,则运行在单线程环境下。只使用函数内部资源,完全不使用全局变量、静态变量或其他全局性资源时多线程安全的。对某个全局/静态变量所有访问被强制同步手段限制为同一时刻只有一个线程访问,即使使用全局/静态变量则不影响多线程安全性。
例如DriverEntry
和DriverUnload
需要单线程,各种分发/完成/NDIS回调函数运行环境为多线程。
代码中断级(IRQL) 有Dispatch和Passive两种中断级,Dispatch比Passive级高。很多复杂功能API需要Passive级执行。
调用路径上没有导致中断级改变的操作时,则与调用源中断级相同。调用路径上有获取自旋锁时中断级升高,有释放自旋锁时中断级下降。
例如DriverEntry
和DriverUnload
还有各种分发函数需要Passive级,完成函数、各种NDIS回调函数需要Dispatch级。
这玩意儿没法强制升降,需要些编程技巧。
参数说明宏 与_In_
与_Out_
一样:
1 2 3 4 5 6 VOID NdisProtStatus ( IN NDIS_HANDLE ProtocolBindingContext, IN NDIS_STATUS GeneralStatus, __in_bcount(StatusBufferSize) IN PVOID StatusBuffer, IN UINT StatusBufferSize )
预编译指令 默认代码在text段的PAGELK节,但一般可以把DriverEntry
放到INIT节,INIT节初始化完毕后就被释放,不占内存。NDIS相关也可放到PAGE节,这个节在内存紧张时可被交换到磁盘上。但PAGE节函数不可在Dispatch级调用,否则可诱发缺页中断,且缺页中断处理不能在Dispatch级完成,可用PAGED_CODE
宏测试,如果发现在Dispatch级程序直接报异常:
1 2 3 4 5 6 7 8 9 10 #pragma alloc_text(INIT,DriverEntry) #pragma alloc_text(PAGE,SfAttachToMountedDevice) NTSTATUS SfAttachToMountedDevice (IN PDEVICE_OBJECT DeviceObject, IN PDEVICE_OBJECT SFilterDeviceObject) { PSFILTER_DEVICE_EXTENSION newDevExt = SFilterDeviceObject->DeviceExtension; NTSTATUS status = STATUS_SUCCESS; ULONG i = 0 ; PAGED_CODE (); };
内存 内存分配与释放,非分页内存永久在物理地址上而不会被交换到磁盘上,ExFreePool
只能释放用ExAllocatePoolWithTag
申请的内存,如果不释放则重启计算机前该内存将永久泄露。
1 2 3 4 5 6 7 8 9 10 11 NTSTATUS status=STATUS_SUCCESS; UNICODE_STRING dst = { 0 }; UNICODE_STRING src = RTL_CONSTANT_STRING (TEXT ("xxx" )); dst.Buffer = (PWCHAR)ExAllocatePoolWithTag (NonPagedPool, src.Length, 'MyTt' ); if (dst.Buffer == NULL ) status = STATUS_INSUFFICIENT_RESOURCES; dst.Length = dst.MaximumLength = src.Length; RtlCopyUnicodeString (&dst, &src);ExFreePool (dst.Buffer);dst.Buffer = NULL ; dst.Length = dst.MaximumLength = 0 ;
LIST_ENTRY结构 定义如下:
1 2 3 4 typedef struct _LIST_ENTRY { struct _LIST_ENTRY *Flink; struct _LIST_ENTRY *Blink; }LIST_ENTRY,*PLIST_ENTRY;
初始化与增加一个链表节点:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 LIST_ENTRY my_list_head; VOID MyFileInforInit (VOID) { InitializeListHead (&my_list_head); return ; }; typedef struct { LIST_ENTRY list_entry; PFILE_OBJECT file_object; PUNICODE_STRING file_name; LARGE_INTEGER file_length; }MY_FILE_INFOR,*PMY_FILE_INFOR; NTSTATUS MyFileInforAppendNode (PFILE_OBJECT file_object, PUNICODE_STRING file_name, PLARGE_INTEGER file_length) { PMY_FILE_INFOR my_file_infor = (PMY_FILE_INFOR)ExAllocatePoolWithTag (PagedPool, sizeof (MY_FILE_INFOR), 'MyTt' ); if (my_file_infor == NULL ) return STATUS_INSUFFICIENT_RESOURCES; my_file_infor->file_object = file_object; my_file_infor->file_name = file_name; my_file_infor->file_length = file_length; InsertHeadList (&my_list_head, (PLIST_ENTRY)&my_file_infor); return STATUS_SUCCESS; };
遍历链表:
1 2 3 4 for (PLIST_ENTRY p = my_list_head.Flink; p != &my_list_head.Flink; p = p->Flink) { PMY_FILE_INFOR elem = CONTAINING_RECORD (p, MY_FILE_INFOR, list_entry); };
长长整型 LARGE_INTEGER
类似于__int64
。
高32位为HighPart
,低32位为LowPart
,整个64位为QuadPart
。
1 2 3 4 5 6 LARGE_INTEGER a = { 0 }, b = { 0 }; a.QuadPart = 100 ; a.QuadPart *= 100 ; b.QuadPart = a.QuadPart; if (b.QuadPart>1000 )
自旋锁 基本操作:
1 2 3 4 5 6 7 KSPIN_LOCK my_spin_lock; KeInitializeSpinLock (&my_spin_lock);KIRQL irql = 0 ; KeAcquireSpinLock (&my_spin_lock,&irql); KeReleaseSpinLock (&my_spin_lock,&irql);
给LIST_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 LIST_ENTRY my_list_head; KSPIN_LOCK my_list_lock; VOID MyFileInforInit (VOID) { InitializeListHead (&my_list_head); KeInitializeSpinLock (&my_list_head); return ; }; typedef struct { LIST_ENTRY list_entry; PFILE_OBJECT file_object; PUNICODE_STRING file_name; LARGE_INTEGER file_length; }MY_FILE_INFOR,*PMY_FILE_INFOR; NTSTATUS MyFileInforAppendNode (PFILE_OBJECT file_object, PUNICODE_STRING file_name, PLARGE_INTEGER file_length) { PMY_FILE_INFOR my_file_infor = (PMY_FILE_INFOR)ExAllocatePoolWithTag (PagedPool, sizeof (MY_FILE_INFOR), 'MyTt' ); if (my_file_infor == NULL ) return STATUS_INSUFFICIENT_RESOURCES; my_file_infor->file_object = file_object; my_file_infor->file_name = file_name; my_file_infor->file_length = file_length; ExInterlockedInsertHeadList (&my_list_head, (PLIST_ENTRY)&my_file_infor, &my_list_lock); my_file_infor=ExInterlockedRemoveHeadList (&my_list_head, &my_list_lock); return STATUS_SUCCESS; };
队列自旋锁使用KLOCK_QUEUE_HANDLE结构定义,除了获取和释放外,其他操作跟原子自旋锁一模一样,这是队列自旋锁的区别点:
1 2 3 4 5 6 KSPIN_LOCK my_Queue_SpinLock = { 0 }; KeinitializeSpinLock (&my_Queue_SpinLock);KLOCK_QUEUE_HANDLE my_lock_queue_handle = { 0 }; KeAcquireInStackQueuedSpinLock (&my_Queue_SpinLock, &my_lock_queue_handle); KeReleaseInStackQueuedSpinLock (&my_lock_queue_handle);