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定义如下
#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查看我们创建的对象
如果是打开文件,CreateFile -> NtCreateFile需要对文件名进行NT路径转换
5. 驱动下的对象操作及内存分配
- 分页与非分页内存
驱动中的中断优先级如下
#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() 声明
- 分配内核内存,标记分页及非分页参数
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’);
- 对象操作调用
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使用
常用驱动操作
- 链表
typedef struct _LIST_ENTRY {
struct _LIST_ENTRY *Flink; // 指向下一个节点
struct _LIST_ENTRY *Blink; // 指向前一个节点
} LIST_ENTRY, *PLIST_ENTRY;
InitializeListHead 初始化链表头 IsListEmpty 判断链表是否为空 InsertHeadList 从链表头部插入节点 InsertTailList 从链表尾部插入节点 RemoveHeadList 从链表头部删除节点 RemoveTailList 从链表尾部删除节点 CONTAINING_RECORD 指向链表中的特定成员
示例代码
#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的定义
#define CONTAINING_RECORD(addr,type,field) ((type*)((unsigned char*)addr - (unsigned long)&((type*)0)->field))
- 字符串
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
- 文件和注册表
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
NTSTATUS WINAPI ZwQuerySystemInformation(
_In_ SYSTEM_INFORMATION_CLASS SystemInformationClass,
_Inout_ PVOID SystemInformation,
_In_ ULONG SystemInformationLength,
_Out_opt_ PULONG ReturnLength
);
7. 内核线程及同步问题
- 等待对象
NTKERNELAPI NTSTATUS KeWaitForSingleObject(
PVOID Object,
KWAIT_REASON WaitReason,
KPROCESSOR_MODE WaitMode,
BOOLEAN Alertable,
PLARGE_INTEGER Timeout
);
KeWaitForMultipleObjects 类似用户层WaitForSingleObjectEx
- 内核事件
//初始化内核事件
KeInitializeEvent(&kEvent,NotificationEvent,FALSE);
//第二个参数是事件的类型,“通知事件”参数是NotificationEvent。“同步事件”对应是SynchronizationEvent
//同步会在KeWaitForSingleObject后自动设置未激发态,通知需要调用KeSetEvent设置
互斥对象 KeInitizlizeMutex(略)
自旋锁 自旋锁相当于代码临界区 线程在空转等待锁资源,所以占用锁的时间尽可能的要短
//初始化自旋锁
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优先级,而多核则需要原子判断并置位判断
- 内核线程
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);
- 原文作者:Binean
- 原文链接:https://bzhou830.github.io/post/20191223WindowsKernelProgramingSummer01/
- 版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可,非商业转载请注明出处(作者,原文链接),商业转载请联系作者获得授权。