最近在公司给同学们分享windows驱动和一些系统内核知识,顺便把总结的文档发一下做备忘记录。
1. windows内核对象
每个对象都有对象头和对象体组成。所有类型的对象头结构都是相同的,而结构体部分却各不相同的。下面是内核对象的结构图:
进程句柄表
dt nt!_EPROCESS
+0x000 Pcb : _KPROCESS
+0x06c ProcessLock : _EX_PUSH_LOCK
+0x070 CreateTime : _LARGE_INTEGER
+0x078 ExitTime : _LARGE_INTEGER
+0x080 RundownProtect : _EX_RUNDOWN_REF
+0x084 UniqueProcessId : Ptr32 Void
+0x088 ActiveProcessLinks : _LIST_ENTRY
+0x090 QuotaUsage : [3] Uint4B
+0x09c QuotaPeak : [3] Uint4B
+0x0a8 CommitCharge : Uint4B
+0x0ac PeakVirtualSize : Uint4B
+0x0b0 VirtualSize : Uint4B
+0x0b4 SessionProcessLinks : _LIST_ENTRY
+0x0bc DebugPort : Ptr32 Void
+0x0c0 ExceptionPort : Ptr32 Void
+0x0c4 ObjectTable : Ptr32 _HANDLE_TABLE
不同操作系统的进程以HANDLE_TABLE索引句柄有不同的算法实现
内核句柄和进程句柄不相同,内核句柄表在未导出变量PspCidTable中
2. R3层加载驱动的另一种方式
- 初始化注册表项HKLM\System\CurrentControlSet\Services\[服务名]
- 未公开文档调用zwloaddriver(测试源码 R3DriverTest)
- 参考下面链接中的源码
3. 应用层驱动一般通信方式
- CreateFile打开symbollink指向的设备对象获得句柄
- 用DeviceIoControl进程驱动通信
- IOCTL自定义控制码,CTL_CODE定义如下
|
|
NT式驱动DeviceType为FILE_DEVICE_UNKNOWN
Function 一般取值范围为2048-4095
Method 按传递buffer传递方式如下:
- METHOD_BUFFERED R3层拷贝buffer到内核
- METHOD_IN_DIRECT R3层物理地址映射buffer到内核内存
- METHOD_OUT_DIRECT 同上
- METHOD_NEITHER 直接使用R3内存,需要try住
Access取值FILE_ANY_ACCESS, FILE_READ_ACCESS, FILE_WRITE_ACCESS
4. 驱动对象和设备对象及设备对象扩展
dt nt!_DRIVER_OBJECT
+0x000 Type : Int2B
+0x002 Size : Int2B
+0x004 DeviceObject : Ptr32 _DEVICE_OBJECT
+0x008 Flags : Uint4B
+0x00c DriverStart : Ptr32 Void
+0x010 DriverSize : Uint4B
+0x014 DriverSection : Ptr32 Void
+0x018 DriverExtension : Ptr32 _DRIVER_EXTENSION
+0x01c DriverName : _UNICODE_STRING
+0x024 HardwareDatabase : Ptr32 _UNICODE_STRING
+0x028 FastIoDispatch : Ptr32 _FAST_IO_DISPATCH
+0x02c DriverInit : Ptr32 long
+0x030 DriverStartIo : Ptr32 void
+0x034 DriverUnload : Ptr32 void
+0x038 MajorFunction : [28] Ptr32 long
- DeviceObject 驱动会创建一个或多个设备对象,同一个驱动对象的设备对象是用链表链起的,最后一个设备对象指向空
- DriverName 驱动名 \Driver\[驱动名]
- HardwareDataBase 注册表指向的硬件数据库名\REGISTRY\MACHINE\HARDWARE\DESCRIPTION\SYSTM
- DriverStartIo StartIo串行化处理例程
- DriverUnload 卸载驱动的回调
- MajorFunction 处理IRP的派遣函数
- FastIoDispatch 文件的快速Io处理,一般驱动不会涉及到
dt nt!_DEVICE_OBJECT
+0x000 Type : Int2B
+0x002 Size : Uint2B
+0x004 ReferenceCount : Int4B
+0x008 DriverObject : Ptr32 _DRIVER_OBJECT
+0x00c NextDevice : Ptr32 _DEVICE_OBJECT
+0x010 AttachedDevice : Ptr32 _DEVICE_OBJECT
+0x014 CurrentIrp : Ptr32 _IRP
+0x018 Timer : Ptr32 _IO_TIMER
+0x01c Flags : Uint4B
+0x020 Characteristics : Uint4B
+0x024 Vpb : Ptr32 _VPB
+0x028 DeviceExtension : Ptr32 Void
+0x02c DeviceType : Uint4B
+0x030 StackSize : Char
+0x034 Queue : __unnamed
+0x05c AlignmentRequirement : Uint4B
+0x060 DeviceQueue : _KDEVICE_QUEUE
+0x074 Dpc : _KDPC
+0x094 ActiveThreadCount : Uint4B
+0x098 SecurityDescriptor : Ptr32 Void
+0x09c DeviceLock : _KEVENT
+0x0ac SectorSize : Uint2B
+0x0ae Spare1 : Uint2B
+0x0b0 DeviceObjectExtension : Ptr32 _DEVOBJ_EXTENSION
+0x0b4 Reserved : Ptr32 Void
- DrivefrObject 指向创建自身的驱动对象
- NextDevice 指向下一个设备对象,同属一个驱动对象的设备
- AttachedDevice 指向更高的栈设备对象
- CurrentIrp 使用StartIo时指向的当前处理的IRP结构
- Flags 不同缓冲模式及其他一些标志
- DeviceExtension 自定义的设备扩展对象,一般用于保存或传递全局变量
- DeviceType FILE_DEVICE_UNKNOWN
- StackSize 多层驱动的栈层数,设备对象从高层传递到底层
winobj查看我们创建的对象
如果是打开文件,CreateFile -> NtCreateFile需要对文件名进行NT路径转换
5. 驱动下的对象操作及内存分配
- 分页与非分页内存
驱动中的中断优先级如下
|
|
当代码运行在DISPATCH_LEVEL级别以上,只能用非分页内存
代码节中的定义:
定义分页内存 #define CODEPAGE code_seg(“PAGE”)
定义非分页内存 #define LOCKEDPAGE code_seg()
定义初始化内存 #define INITPAGE code_seg(“INIT”)
数据类似,用 data_seg() 声明
- 分配内核内存,标记分页及非分页参数
|
|
POOL_TYPE参数为PagedPool, NonPagedPool
例子:PVOID p = ExAllocatePoolWithTag(PagedPool, 42, ‘TEST’);
- 对象操作调用
|
|
6. 常用驱动系统API使用
常用驱动操作
- 链表
|
|
InitializeListHead 初始化链表头
IsListEmpty 判断链表是否为空
InsertHeadList 从链表头部插入节点
InsertTailList 从链表尾部插入节点
RemoveHeadList 从链表头部删除节点
RemoveTailList 从链表尾部删除节点
CONTAINING_RECORD 指向链表中的特定成员
示例代码
|
|
可以看一下CONTAINING_RECORD的定义
|
|
- 字符串
|
|
注意参数AllocateDestinationString,如果为TRUE则需要调用RtlFree*String
- 文件和注册表
|
|
关键点在初始化ObjectAttribute的结构
参考源码
src2.zip
- 系统
SYSTEM_INFORMATION_CLASS 有大量非文档声明及特定的结构
undocument.txt
|
|
7. 内核线程及同步问题
- 等待对象
|
|
KeWaitForMultipleObjects
类似用户层WaitForSingleObjectEx
- 内核事件
|
|
互斥对象 KeInitizlizeMutex(略)
自旋锁
自旋锁相当于代码临界区
线程在空转等待锁资源,所以占用锁的时间尽可能的要短
|
|
单核cpu和多核cpu实现自旋锁机制完全不同,单核cpu只是简单的提升到DISPATCH_LEVEL优先级,而多核则需要原子判断并置位判断
- 内核线程
|
|