This page looks best with JavaScript enabled

Windows驱动开发总结(一)

 ·   ·  ☕ 6 min read  ·  ✍️ oser

最近在公司给同学们分享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定义如下
1
2
3
4
5
#define CTL_CODE(DeviceType, Function, Method, Access) (

  ((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method)

)

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查看我们创建的对象

TestDriver设备对象

如果是打开文件,CreateFile -> NtCreateFile需要对文件名进行NT路径转换

5. 驱动下的对象操作及内存分配

  • 分页与非分页内存

驱动中的中断优先级如下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#define PASSIVE_LEVEL                             0
#define LOW_LEVEL                                 0
#define APC_LEVEL                                 1
#define DISPATCH_LEVEL                            2
...
#define PROFILE_LEVEL                             27
#define CLOCK1_LEVEL                              28
#define CLOCK2_LEVEL                              28
#define IPI_LEVEL                                 29
#define POWER_LEVEL                               30
#define HIGH_LEVEL                                31

当代码运行在DISPATCH_LEVEL级别以上,只能用非分页内存
代码节中的定义:
定义分页内存 #define CODEPAGE code_seg(“PAGE”)
定义非分页内存 #define LOCKEDPAGE code_seg()
定义初始化内存 #define INITPAGE code_seg(“INIT”)
数据类似,用 data_seg() 声明

  • 分配内核内存,标记分页及非分页参数
1
2
  NTKERNELAPI PVOID ExAllocatePool(POOL_TYPE PoolType,SIZE_T NumberOfBytes);
  NTKERNELAPI PVOID ExAllocatePoolWithTag(POOL_TYPE PoolType,SIZE_T NumberOfBytes, ULONG Tag)

POOL_TYPE参数为PagedPool, NonPagedPool
例子:PVOID p = ExAllocatePoolWithTag(PagedPool, 42, ‘TEST’);

  • 对象操作调用
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
NTKERNELAPI NTSTATUS ObReferenceObjectByHandle(
  HANDLE                     Handle,
  ACCESS_MASK                DesiredAccess,
  POBJECT_TYPE               ObjectType,
  KPROCESSOR_MODE            AccessMode,
  PVOID                      *Object,
  POBJECT_HANDLE_INFORMATION HandleInformation
);

NTKERNELAPI LONG_PTR ObfReferenceObject(
  PVOID Object
);

NTKERNELAPI NTSTATUS IoGetDeviceObjectPointer(
  PUNICODE_STRING ObjectName,
  ACCESS_MASK     DesiredAccess,
  PFILE_OBJECT    *FileObject,
  PDEVICE_OBJECT  *DeviceObject
);

6. 常用驱动系统API使用

常用驱动操作

  • 链表
1
2
3
4
  typedef struct _LIST_ENTRY {
    struct _LIST_ENTRY  *Flink;    // 指向下一个节点
    struct _LIST_ENTRY  *Blink;    // 指向前一个节点
} LIST_ENTRY, *PLIST_ENTRY;

InitializeListHead 初始化链表头
IsListEmpty 判断链表是否为空
InsertHeadList 从链表头部插入节点
InsertTailList 从链表尾部插入节点
RemoveHeadList 从链表头部删除节点
RemoveTailList 从链表尾部删除节点
CONTAINING_RECORD 指向链表中的特定成员

示例代码

 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
#include <ntddk.h>
typedef struct _MYDATASTRUCT 
{
    ULONG number;
    LIST_ENTRY ListEntry;
}MYDATASTRUCT, *PMYDATASTRUCT;

VOID LinkListTest()
{
    LIST_ENTRY linkListHead;
    //初始化链表
    InitializeListHead(&linkListHead);
    PMYDATASTRUCT pData;
    ULONG i = 0;
    //在链表中插入10个元素
    for(i=0; i<10; i++)
    {
        //分配页内存
        pData = (PMYDATASTRUCT)ExAllocatePool(PagedPool, sizeof(MYDATASTRUCT));
        pData->number = i;
        //从头部插入链表
        InsertHeadList(&linkListHead, &pData->ListEntry);
    }
    //从链表中取出,并显示
    while(!IsListEmpty(&linkListHead))
    {
        //从尾部删除一个元素
        PLIST_ENTRY pEntry = RemoveTailList(&linkListHead); //返回删除结构中ListEntry的位置
        pData = CONTAINING_RECORD(pEntry,MYDATASTRUCT,ListEntry);
        ExFreePool(pData);
    }
}

可以看一下CONTAINING_RECORD的定义

1
#define CONTAINING_RECORD(addr,type,field) ((type*)((unsigned char*)addr - (unsigned long)&((type*)0)->field))
  • 字符串
 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
typedef struct  _UNICODE_STRING {

    USHORT Length;
    USHORT MaximumLength;
    PWCH   Buffer;
} UNICODE_STRING;

NTSYSAPI VOID RtlInitUnicodeString(
  PUNICODE_STRING         DestinationString,
  PCWSTR                  SourceString
);
NTSYSAPI LONG RtlCompareUnicodeString(
  PCUNICODE_STRING String1,
  PCUNICODE_STRING String2,
  BOOLEAN          CaseInSensitive
);
NTSYSAPI NTSTATUS RtlUpcaseUnicodeString(
  PUNICODE_STRING  DestinationString,
  PCUNICODE_STRING SourceString,
  BOOLEAN          AllocateDestinationString
);
NTSYSAPI NTSTATUS RtlUnicodeStringToInteger(
  PCUNICODE_STRING String,
  ULONG            Base,
  PULONG           Value
);
NTSTATUS RtlUnicodeStringToAnsiString(
  PANSI_STRING     DestinationString,
  PCUNICODE_STRING SourceString,
  BOOLEAN          AllocateDestinationString
);

注意参数AllocateDestinationString,如果为TRUE则需要调用RtlFree*String

  • 文件和注册表
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
NTSTATUS ZwCreateFile(
  _Out_     PHANDLE FileHandle,
  _In_      ACCESS_MASK DesiredAccess,
  _In_      POBJECT_ATTRIBUTES ObjectAttributes,
  _Out_     PIO_STATUS_BLOCK IoStatusBlock,
  _In_opt_  PLARGE_INTEGER AllocationSize,
  _In_      ULONG FileAttributes,
  _In_      ULONG ShareAccess,
  _In_      ULONG CreateDisposition,
  _In_      ULONG CreateOptions,
  _In_opt_  PVOID EaBuffer,
  _In_      ULONG EaLength
);

NTSYSAPI NTSTATUS ZwCreateKey(
  PHANDLE            KeyHandle,
  ACCESS_MASK        DesiredAccess,
  POBJECT_ATTRIBUTES ObjectAttributes,
  ULONG              TitleIndex,
  PUNICODE_STRING    Class,
  ULONG              CreateOptions,
  PULONG             Disposition
);

关键点在初始化ObjectAttribute的结构

参考源码
src2.zip

  • 系统

SYSTEM_INFORMATION_CLASS 有大量非文档声明及特定的结构
undocument.txt

1
2
3
4
5
6
NTSTATUS WINAPI ZwQuerySystemInformation(
  _In_       SYSTEM_INFORMATION_CLASS SystemInformationClass,
  _Inout_    PVOID SystemInformation,
  _In_       ULONG SystemInformationLength,
  _Out_opt_  PULONG ReturnLength
);

7. 内核线程及同步问题

  • 等待对象
1
2
3
4
5
6
7
NTKERNELAPI NTSTATUS KeWaitForSingleObject(
  PVOID  Object,
  KWAIT_REASON WaitReason,
  KPROCESSOR_MODE WaitMode,
  BOOLEAN  Alertable,
  PLARGE_INTEGER  Timeout
);

KeWaitForMultipleObjects
类似用户层WaitForSingleObjectEx

  • 内核事件
1
2
3
4
    //初始化内核事件
    KeInitializeEvent(&kEvent,NotificationEvent,FALSE);
    //第二个参数是事件的类型,“通知事件”参数是NotificationEvent。“同步事件”对应是SynchronizationEvent
    //同步会在KeWaitForSingleObject后自动设置未激发态,通知需要调用KeSetEvent设置

互斥对象 KeInitizlizeMutex(略)

自旋锁
自旋锁相当于代码临界区
线程在空转等待锁资源,所以占用锁的时间尽可能的要短

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
//初始化自旋锁
VOID KeInitializeSpinLock(
_Out_ PKSPIN_LOCK SpinLock
);
//获取自旋锁
VOID KeAcquireSpinLock(
  _In_  PKSPIN_LOCK SpinLock,
  _Out_ PKIRQL      OldIrql
);
//释放自旋锁
VOID KeReleaseSpinLock(
  _Inout_ PKSPIN_LOCK SpinLock,
  _In_    KIRQL       NewIrql
);

单核cpu和多核cpu实现自旋锁机制完全不同,单核cpu只是简单的提升到DISPATCH_LEVEL优先级,而多核则需要原子判断并置位判断

  • 内核线程
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
NTSTATUS PsCreateSystemThread(
  _Out_      PHANDLE ThreadHandle,
  _In_       ULONG DesiredAccess,
  _In_opt_   POBJECT_ATTRIBUTES ObjectAttributes,
  _In_opt_   HANDLE ProcessHandle,
  _Out_opt_  PCLIENT_ID ClientId,
  _In_       PKSTART_ROUTINE StartRoutine,
  _In_opt_   PVOID StartContext
);

//线程需要调用强制退出
PsTerminateSystemThread(STATUS_SUCCESS);  

Share on

oser
WRITTEN BY
oser
愿我的狗子在天堂也能每天开心咧嘴笑