内核API参考文档
https://learn.microsoft.com/zh-cn/windows-hardware/drivers/ddi/_kernel/
基本数据类型
在内核编程时遵守WDK编码习惯
1 2 3 4 ULONG(unsigned long ) PULONG(unsigned long *) UCHAR(unsigned char ) PUCHAR(unsigned char *) UINT(unsigned int ) PUINT(unsigned int *) VOID(void ) PVOID(void *)
返回值
大部分内核函数的返回值都是
NTSTATUS类型,它本质是一个宏,里面包含的类型有很多,如下三个就是常见的返回值:
宏名称
实际值
含义
STATUS_SUCCESS
0x00000000
成功
STATUS_INVALID_PARAMETER
0xC000000D
参数无效
STATUS_BUFFER_OVERFLOW
0x80000005
缓冲区长度不够
当调用内核函数失败时 即返回结构不是STATUS_SUCCESS
那么可以根据返回值在 ntstatus.h 头文件中去查找
也可在微软的SDK文档搜索相关宏名称
img
内核中的异常处理
在内核中一个小小的错误就可能导致蓝屏,例如我们去读写一个无效的内存地址。为了让自己的内核程序更加
健壮,在编写内核程序时要使用到异常处理。
在Windows下提供了结构化异常处理机制,编译器普遍都支持,如下
1 2 3 4 5 6 __try { __except (filter_value) {
如上示例中的filter_value,就是当内核程序出现异常时决定程序如何执行的,一般有这三种情况:
宏名称
实际值
含义
EXCEPTION_EXECUTE_HANDLER
1
进入except代码块执行
EXCEPTION_CONTINUE_SEARCH
0
不处理异常,由上一层调用函数处理
EXCEPTION_CONTINUE_EXECUTION
-1
继续执行错误处的代码
常用的内核内存函数
对内存的使用主要是 申请、设置、拷贝、释放
常用函数如下集合应用层与内核层对比
应用层
内核
malloc
ExAllocatePool
memset
RtlFillMemory
memcpy
RtlMoveMemory
free
ExFreePool
使用内存
1 2 3 4 5 6 7 8 9 10 11 12 13 14 PVOID ExAllocatePoolWithTag ( POOL_TYPE PoolType, SIZE_T NumberOfBytes, ULONG Tag ) ;VOID ExFreePoolWithTag ( PVOID P, ULONG Tag ) ;
内核中的字符串
内核字符串种类
CHAR(char)、WCHAR(wchar_t)、ANSI_STRING、UNICODE_STRING
CHAR是char在内核中的写法,WCHAR是wchar_t是在内核中的写法,但是一般不建议使用这两种
而使用后两者
ANSI_STRING 和UNICODE_STRING(Ascii/Unicode)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 typedef struct _STRING { USHORT Length; USHORT MaximumLength; #ifdef MIDL_PASS [size_is(MaximumLength), length_is(Length) ] #endif Field_size_bytes_part_opt_(MaximumLength, Length) PCHAR Buffer; } STRING; typedef STRING ANSI_STRINGtypedef 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_opt_(MaximumLength, Length) PWCH Buffer; #endif } UNICODE_STRING;
内核字符串函数
字符串的操作就是创建、复制、比较、转换,但由于编码问题在内核中也有不同的函数表达:
ANSI_STRING字符串
UNICODE_STRING字符串
含义
RtlInitAnsiString
RtlInitUnicodeString
创建字符串
RtlCopyString
RtlCopyUnicodeString
字符串复制
RtlCompareString
RtlCompareUnicoodeString
字符串比较
RtlAnsiStringToUnicodeString
RtlUnicodeStringToAnsiString
编码转换
内核字符串操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 UNICODE_STRING Demostring = { 0 }; RtlInitUnicodeString(&Demostring, L"This is a ....\n" ); DbgPrint("%wZ" , &Demostring); UNICODE_STRING naizistring = { 0 }; WCHAR str[] = L"hacker...\n" ; RtlInitUnicodeString(&naizistring, str); str[0 ] = 0 ; DbgPrint("%wZ" , &naizistring); WCHAR str[128 ] = { 0 }; UNICODE_STRING naizistring = { 0 }; RtlInitEmptyUnicodeString(&naizistring, str, sizeof (str)); RtlUnicodeStringCopyString(&naizistring, L"naizi hacker" ); DbgPrint("%wZ" , &naizistring);
内核空间与内存模块
进程中有一个4GB大小的内存空间 低2G是程序自己的 高2G是共享的
img
按照规定格式编写的驱动
每一个都可以当做一个模块,也可以称之为内核模块,都遵循PE结构,并可以加载到内核中
在编写时 一般都需要一个入口函数和一个卸载函数
在入口函数中需要两个参数 PDRIVER_OBJECT 和
PUNICODE_STRING
1 NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
img
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 typedef struct _DRIVER_OBJECT { CSHORT Type; CSHORT Size; PDEVICE_OBJECT DeviceObject; ULONG Flags; PVOID DriverStart; ULONG DriverSize; PVOID DriverSection; UNICODE_STRING DriverName; PFAST_IO_DISPATCH FastIoDispatch; PDRIVER_INITIALIZE DriverInit; PDRIVER_STARTIO DriverStartIo; PDRIVER_UNLOAD DriverUnload; } DRIVER_OBJECT; typedef struct _DRIVER_OBJECT *PDRIVER_OBJECT ;
通过之前的demoDriver 可以找到驱动对象地址
img
通过windbg进入到其中查看
1 dt _DRIVER_OBJECT 驱动对象地址
img
DriverSection; // 指针 指向_LDR_DATA_TABLE_ENTRY结构体
,在该结构体内有一个成员InLoadOrderLinks是双链表,它记录着前一个和后一个内核模块的_LDR_DATA_TABLE_ENTRY结构体地址:
1 dt _LDR_DATA_TABLE_ENTRY 0xffffc90e4c1f98d0
img
1 dt _LDR_DATA_TABLE_ENTRY 0xffffc90e4cc04a10
img
应用与内核间通信
创建设备对象
正常情况下,一个设备对象是对应一个设备的,如:鼠标、键盘。但是设备对象也可以是一个抽象的概念,不
对应到具体某个硬件,也就是我们可以使用如下代码去创建一个设备对象。
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 34 35 36 37 38 39 40 41 42 NTSTATUS Status; UNICODE_STRING DeviceName; UNICODE_STRING SymbolName; PDEVICE_OBJECT dev = NULL ; RtlInitUnicodeString(&DeviceName, L"\\Device\\MyDriverDemo" ); Status = IoCreateDevice( DriverObject, 0 , &DeviceName, FILE_DEVICE_UNKNOWN, 0 , TRUE, &dev ); do { if (!NT_SUCCESS(Status)) { if (Status == STATUS_OBJECT_NAME_COLLISION) { DbgPrint("[*]设备名称冲突......\n" ); } DbgPrint("[*]创建失败" ); break ; } RtlInitUnicodeString(&SymbolName, SYMBOLLINK); Status = IoCreateSymbolicLink(&SymbolName, &DeviceName); if (!NT_SUCCESS(Status)) { IoDeleteDevice(dev); DbgPrint("[*]删除设备成功" ); break ; } else { DbgPrint("[*]创建符号链接成功" ); } } while (FALSE); return Status;
数据交换配置
1 2 pDeviceObj->Flags |= DO_BUFFERED_IO;
创建好设备对象之后,就需要设置0环和3环交换数据的方式,有以下三种方式:
\1.
缓冲区读写(DO_BUFFERED_IO ),操作系统将应用程序提供缓冲区的数据直接复制到内核模式下的地址中;
\2.
直接读写(DO_DIRECT_IO ),操作系统会将用户模式下的缓冲区锁住,然后操作系统将这段缓冲区在
内核模式地址再次映射一遍,这样用户模式的缓冲区和内核模式的缓冲区指向的是同一区域的物理内
存,缺点就是要单独占用物理页面;
\3.
其他读写(0环直接读取3环线性地址),在调用IoCreateDevice函数创建设备后不设置交换数据模式即默认为其他方式读写,在使用
其他方式读写设备时,派遣函数直接读写应用程序提供的缓冲区地址。在驱动程序中,直接操作应用程
序的缓冲区地址是很危险的(不建议使用 ),只有驱动程序与应用程序运行在相同线程上下文的情况下,才能使用这种方式。
设备符号连接
设备名称是给0环使用的,如果3环想要访问到设备就需要通过符号连接,可以理解成它是一个设备的别名,如果不这样设置
3环无法访问到设备。
1 2 3 4 UNICODE_STRING DeviceName; UNICODE_STRING SymbolName; RtlInitUnicodeString(&DeviceName, L"\\Device\\MyDriverDemo" );
注意:
设备名称的作用是给内核对象使用的,如果要在Ring3访问
必须有符号链接(可以理解为是设备的别名),没有这个在RIng3下不可见
内核模式下符号链接名是以\??\
开头 如 C盘就是
\??\C:
用户模式下符号链接名则是以\\.\
开头 如C盘就是
\\.\C:
在实际代码中需要对 \
转义为 \\
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 34 35 36 37 38 39 40 41 42 43 44 45 46 47 L"\\??\\MyDriverDemo" \\??\MyDriverDemoL"\\\\.\\MyDriverDemo" \\.\MyDriverDemoPDEVICE_OBJECT dev = NULL ; NTSTATUS CreateDevice (PDRIVER_OBJECT DriverObject) { NTSTATUS Status; UNICODE_STRING DeviceName; UNICODE_STRING SymbolName; RtlInitUnicodeString(&DeviceName, L"\\Device\\MyDriverDemo" ); Status = IoCreateDevice( DriverObject, 0 , &DeviceName, FILE_DEVICE_UNKNOWN, 0 , TRUE, &dev ); do { if (!NT_SUCCESS(Status)) { if (Status == STATUS_OBJECT_NAME_COLLISION) { DbgPrint("[*]设备名称冲突......\n" ); } DbgPrint("[*]创建失败" ); break ; } RtlInitUnicodeString(&SymbolName, SYMBOLLINK); Status = IoCreateSymbolicLink(&SymbolName, &DeviceName); if (!NT_SUCCESS(Status)) { IoDeleteDevice(dev); DbgPrint("[*]删除设备成功" ); break ; } else { DbgPrint("[+]创建符号链接成功" ); } } while (FALSE); return Status; }
派遣函数与IRP类型
当单击鼠标时会产生MSG消息,该消息发送给窗口对象,在窗口对象内会根据消息序号找到对应的处理函数进行处理。同理,在驱动程序中,当在3环使用了某个函数就会产生IRP消息,该消息发送给设备对象,在设备对象内会根据消息类型选择对应的派遣函数处理。
img
常见的IRP类型与其对应的3环下的函数及作用如下所示:
IRP类型
来源函数
函数作用
IRP_MJ_CREATE
CreateFile
打开设备
IRP_MJ_READ
ReadFile
从设备中读取数据
IRP_MJ_WRITE
WriteFile
从设备中写入数据
IRP_MJ_CLOSE
CloseHandle
关闭设备
IRP_MJ_DEVICE_CONTROL
DeviceIoControl
设备控制,比读取、写入操作更加灵活
IRP_MJ_POWER
X
在操作系统处理电源消息时产生该类型
IRP_MJ_SHUTDOWN
X
关闭系统前会产生该类型
想注册某个IRP类型对应的派遣函数时候可以使用如下格式 :
1 DriverObject->MajorFunction[IRP类型] = 派遣函数名
//MajorFunction是一个具有28个成员的数组 对应着28种IRP类型
img
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 #define IRP_MJ_CREATE 0x00 #define IRP_MJ_CREATE_NAMED_PIPE 0x01 #define IRP_MJ_CLOSE 0x02 #define IRP_MJ_READ 0x03 #define IRP_MJ_WRITE 0x04 #define IRP_MJ_QUERY_INFORMATION 0x05 #define IRP_MJ_SET_INFORMATION 0x06 #define IRP_MJ_QUERY_EA 0x07 #define IRP_MJ_SET_EA 0x08 #define IRP_MJ_FLUSH_BUFFERS 0x09 #define IRP_MJ_QUERY_VOLUME_INFORMATION 0x0a #define IRP_MJ_SET_VOLUME_INFORMATION 0x0b #define IRP_MJ_DIRECTORY_CONTROL 0x0c #define IRP_MJ_FILE_SYSTEM_CONTROL 0x0d #define IRP_MJ_DEVICE_CONTROL 0x0e #define IRP_MJ_INTERNAL_DEVICE_CONTROL 0x0f #define IRP_MJ_SHUTDOWN 0x10 #define IRP_MJ_LOCK_CONTROL 0x11 #define IRP_MJ_CLEANUP 0x12 #define IRP_MJ_CREATE_MAILSLOT 0x13 #define IRP_MJ_QUERY_SECURITY 0x14 #define IRP_MJ_SET_SECURITY 0x15 #define IRP_MJ_POWER 0x16 #define IRP_MJ_SYSTEM_CONTROL 0x17 #define IRP_MJ_DEVICE_CHANGE 0x18 #define IRP_MJ_QUERY_QUOTA 0x19 #define IRP_MJ_SET_QUOTA 0x1a #define IRP_MJ_PNP 0x1b #define IRP_MJ_PNP_POWER IRP_MJ_PNP #define IRP_MJ_MAXIMUM_FUNCTION 0x1b
派遣函数一般也是有固定格式
1 2 3 4 5 6 7 8 9 NTSTATUS MyDispatchFunction (PDEVICE_OBJECT pDevObj, PIRP pIrp) { pIrp->IoStatus.Status = STATUS_SUCCESS; pIrp->IoStatus.Information = 0 ; IoCompleteRequest(pIrp, IO_NO_INCREMENT); return STATUS_SUCCESS; }
1 2 3 4 5 for (ULONG i = 0 ; i < IRP_MJ_MAXIMUM_FUNCTION; i++){ DriverObject->MajorFunction[i] = MyDispatch; }
灵活通信
应用层
在应用层使用DeviceIoControl函数向驱动发送请求
DeviceIoControl会使内核中的设备对象收到一个设备控制请求
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 34 35 36 37 38 39 40 41 42 43 #include <stdio.h> #include <Windows.h> #define SENDSTR CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_WRITE_DATA) #define RECVSTR CTL_CODE(FILE_DEVICE_UNKNOWN,0x801,METHOD_BUFFERED,FILE_READ_DATA) #define SEND_AND_RECV_STR CTL_CODE(FILE_DEVICE_UNKNOWN,0x802,METHOD_BUFFERED,FILE_ANY_ACCESS) #define STR_MAX_LEN 512 void initoutbuf (UCHAR buf[STR_MAX_LEN]) { memset (buf, 0 , sizeof (char ) * STR_MAX_LEN); return ; } void main () { HANDLE device = CreateFileW(L"\\\\.\\MyDriverDemo" , GENERIC_READ | GENERIC_WRITE, 0 , NULL , OPEN_EXISTING, FILE_ATTRIBUTE_SYSTEM, NULL ); if (device == INVALID_HANDLE_VALUE) { printf ("[*]获取驱动句柄失败!错误:%d\n" , GetLastError()); getchar(); return ; } UCHAR buffer[STR_MAX_LEN] ; DWORD size = 0 ; BOOL bRet = DeviceIoControl(device, SEND_AND_RECV_STR, "Ring3->Ring0" , strlen ("Ring3->Ring0" ) + 1 , (LPVOID)buffer, STR_MAX_LEN, &size, 0 ); if (bRet) { printf ("[+]接收到R0发送的消息%s\r\n" , buffer); } CloseHandle(device); getchar(); return 0 ; }
内核
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 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 #include "ntddk.h" #define SENDSTR CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_WRITE_DATA) #define RECVSTR CTL_CODE(FILE_DEVICE_UNKNOWN,0x801,METHOD_BUFFERED,FILE_READ_DATA) #define SEND_AND_RECV_STR CTL_CODE(FILE_DEVICE_UNKNOWN,0x802,METHOD_BUFFERED,FILE_ANY_ACCESS) #define SYMBOLLINK L"\\??\\MyDriverDemo" #define STR_MAX_LEN 512 PDEVICE_OBJECT dev = NULL ; NTSTATUS CreateDevice (PDRIVER_OBJECT DriverObject) { NTSTATUS Status; UNICODE_STRING DeviceName; UNICODE_STRING SymbolName; RtlInitUnicodeString(&DeviceName, L"\\Device\\MyDriverDemo" ); Status = IoCreateDevice( DriverObject, 0 , &DeviceName, FILE_DEVICE_UNKNOWN, 0 , TRUE, &dev ); do { if (!NT_SUCCESS(Status)) { if (Status == STATUS_OBJECT_NAME_COLLISION) { DbgPrint("[*]设备名称冲突......\n" ); } DbgPrint("[*]创建失败" ); break ; } RtlInitUnicodeString(&SymbolName, SYMBOLLINK); Status = IoCreateSymbolicLink(&SymbolName, &DeviceName); if (!NT_SUCCESS(Status)) { IoDeleteDevice(dev); DbgPrint("[+]删除设备成功" ); break ; } else { DbgPrint("[+]创建符号链接成功" ); } } while (FALSE); return Status; } NTSTATUS MyDispatch (PDEVICE_OBJECT dev, PIRP pIrp) { NTSTATUS Status = STATUS_SUCCESS; ULONG retLen = 0 ; PIO_STACK_LOCATION IrpStack = IoGetCurrentIrpStackLocation(pIrp); ULONG uIoControlCode = IrpStack->Parameters.DeviceIoControl.IoControlCode; PVOID Outputbuffer = pIrp->AssociatedIrp.SystemBuffer; PVOID Inputbuffer = pIrp->AssociatedIrp.SystemBuffer; ULONG uDataInlen = IrpStack->Parameters.DeviceIoControl.InputBufferLength; ULONG uDataOutlen = IrpStack->Parameters.DeviceIoControl.InputBufferLength; if (IrpStack->MajorFunction == IRP_MJ_DEVICE_CONTROL){ switch (uIoControlCode) { case SEND_AND_RECV_STR: if (Inputbuffer != NULL && uDataInlen > 0 ) { DbgPrint("[+]接收到R3发送的消息:%s\r\n" , Inputbuffer); } if (Outputbuffer != NULL && uDataInlen >= strlen ("Ring0->Ring3" ) + 1 ) { memcpy (Outputbuffer, "Ring0->Ring3" , strlen ("Ring0->Ring3" ) + 1 ); Status = STATUS_SUCCESS; retLen = strlen ("Ring0->Ring3" ) + 1 ; } break ; default : Status = STATUS_INVALID_PARAMETER; break ; } } pIrp->IoStatus.Information = retLen; pIrp->IoStatus.Status = Status; IoCompleteRequest(pIrp, IO_NO_INCREMENT); return Status; } VOID DriverUnload (PDRIVER_OBJECT DriverObject) { if (DriverObject != NULL ) { UNICODE_STRING SymbolName; RtlInitUnicodeString(&SymbolName, SYMBOLLINK); IoDeleteSymbolicLink(&SymbolName); if (dev != NULL ) { IoDeleteDevice(dev); } DbgPrint("[+]删除设备和符号链接成功...\n" ); } return STATUS_SUCCESS; } NTSTATUS DriverEntry (PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) { DbgPrint("[%ws]Hello Kernel World\n" , __FUNCTIONW__); if (RegistryPath != NULL ) { DbgPrint("[%ws]所在注册表位置:%wZ\n" , __FUNCTIONW__, RegistryPath); } if (DriverObject != NULL ) { DbgPrint("[%ws]驱动对象地址:%p\n" , __FUNCTIONW__, DriverObject); CreateDevice(DriverObject); for (ULONG i = 0 ; i < IRP_MJ_MAXIMUM_FUNCTION; i++) { DriverObject->MajorFunction[i] = MyDispatch; } DriverObject->DriverUnload = DriverUnload; DbgPrint("[+]驱动加载成功" ); } return STATUS_SUCCESS; }
img