内核编程基础和练习
前言:因为保护模式的知识点学完了,然后第二章就是学习相关的驱动的知识,这篇作为了解基础的笔记
内核API的使用
在应用层编程我们可以使用WINDOWS提供的各种API函数,只要导入头文件<windows.h>就可以了,但是在内核编程的时候,我们不能像在Ring3那样直接使用。微软为内核程序提供了专用的API,只要在程序中包含相应的头文件就可以使用了,如:#include <ntddk.h> (假设你已经正确安装了WDK)
在应用层编程的时候,我们通过MSDN来了解函数的详细信息,在内核编程的时候,要使用WDK自己的帮助文档。
未导出函数的使用
WDK说明文档中只包含了内核模块导出的函数,对于未导出的函数,则不能直接使用,这里的话就比如PspTerminateProcess内核函数,在文档中就查询不到
但是这里我们有相关的PDB文件,所以在windbg中可以直接进行查看u PspTerminateProcess
,如下图所示
如果要使用未导出的函数,只要自己定义一个函数指针,并且为函数指针提供正确的函数地址就可以使用了。
有两种办法都可以获取为导出的函数地址:
1、特征码搜索
2、解析内核PDB文件
后面老师还讲了一种方法:如果一个导出函数中用到了这个未导出函数(该未导出函数是未文档化函数),那么可以获取该导出函数中获取该未导出函数
基本数据类型
1、在内核编程的时候,强烈建议大家遵守WDK的编码习惯,不要这样写:
unsigned long length;
2、习惯使用WDK自己的类型:
ULONG(unsigned long) PULONG(unsigned long *) UCHAR(unsigned char) PUCHAR(unsigned char *) UINT(unsigned int) PUNIT(unsigned int*) VOID(void) PVOID(void*)
返回值
大部分内核函数的返回值都是NTSTATUS类型,如:
NTSTATUS PsCreateSystemThread(); NTSTATUS ZwOpenProcess(); NTSTATUS ZwOpenEvent();
这个值能说明函数执行的结果,比如:
STATUS_SUCCESS 0x00000000 成功 STATUS_INVALID_PARAMETER 0xC000000D 参数无效 STATUS_BUFFER_OVERFLOW 0x80000005 缓冲区长度不够
当你调用的内核函数,如果返回的结果不是STATUS_SUCCESS,就说明函数执行中遇到了问题,具体是什么问题,可以在ntstatus.h文件中查看。
内核中的异常处理
在内核中,一个小小的错误就可能导致蓝屏,比如:读写一个无效的内存地址。
为了让自己的内核程序更加健壮,强烈建议大家在编写内核程序时,使用异常处。
Windows提供了结构化异常处理机制,一般的编译器都是支持的,如下:
__try{ //可能出错的代码 } __except(filter_value) { //出错时要执行的代码 }
出现异常时,可根据filter_value的值来决定程序该如果执行,当filter_value的值为:
EXCEPTION_EXECUTE_HANDLER(1),代码进入except块 EXCEPTION_CONTINUE_SEARCH(0),不处理异常,由上一层调用函数处理 EXCEPTION_CONTINUE_EXECUTION(-1),回去继续执行错误处的代码
常用的内核内存函数
对内存的使用,主要就是:申请、设置、拷贝以及释放。
内核字符串种类
CHAR(char)/WCHAR(wchar_t)/ANSI_STRING/UNICODE_STRING
ANSI_STRING字符串:
typedef struct _STRING { USHORT Length; USHORT MaximumLength; PCHAR Buffer; }STRING;
UNICODE_STRING字符串:
typedef struct _UNICODE_STRING { USHORT Length; USHORT MaxmumLength; PWSTR Buffer; } UNICODE_STRING;
内核字符串常用函数
相关字符串的创建、复制、比较以及转换等等
文档内核函数查询
这里用到的文档是WDK文档,如下图所示,通过这个文档可以类似msdn一样进行查询内核函数
什么是中断请求级别IQRL(INTERRUPT REQUEST LEVEL)
这里举个例子,就比如ExAllocatePoolWithTag这个函数,这个函数的作用是在内核中进行申请一段内存,下面是它的参数,如下所示
PVOID ExAllocatePoolWithTag( IN POOL_TYPE PoolType, IN SIZE_T NumberOfBytes, IN ULONG Tag );
其中第一个参数就是要申请内存的类型,它是一个POOL_TYPE的枚举类,如下所示
typedef enum _POOL_TYPE { NonPagedPool, PagedPool, NonPagedPoolMustSucceed, DontUseThisType, NonPagedPoolCacheAligned, PagedPoolCacheAligned, NonPagedPoolCacheAlignedMustS } POOL_TYPE;
其中NonPagedPool参数为非分页类型,PagedPool是分页类型
NonPagedPool:表示你申请的内存在不使用的时候不会将你的内存存放到虚拟内存文件中pagefile.sys
PagedPool:表示你申请的内存在不使用的时候会将你的内存存放到虚拟内存文件中pagefile.sys
知识点:
1、如果你申请的内存中要存储你的代码的话,那么你申请的内存的类型就需要是NonPagedPool
2、如果你申请的内存中要存储你的数据的话,那么你申请的内存的类型就需要是PagedPool
这里就需要谈及到中断请求级别IQRL(INTERRUPT REQUEST LEVEL),那么分页/非分页跟这个中断请求级别又有什么关系呢?如下图所示
Windows系统上内存都是带有对应中断请求级别IQRL的!
举一个例子,如果内存是处于中断请求级别IQRL=2,那么也就是系统调度和DPC,然后你进行进行了HOOK,你申请的内存是分页PagedPool类型(IQRL=0),那么此时这段系统调度在运行,而刚刚好这段申请的分页类型的内存被存储到了虚拟内存文件中,那么在这段分页内存就会抛出异常,那么此时的IQRL=0,但是此时的内存中断请求级别IQRL=2,那么操作系统就不会理会,最终就是导致缺页,最后导致蓝屏
练习
1、申请一块内存,并在内存中存储GDT、IDT所有数据,并在debugview中进行打印,最后释放内存
kd> r gdtr gdtr=8003f000 kd> r gdtl gdtl=000003ff kd> r idtr idtr=8003f400 kd> r idtl idtl=000007ff
测试代码如下:
#include <ntddk.h> VOID UnDriver(PDRIVER_OBJECT driver) { DbgPrint(("uninstall driver is OK \n")); } VOID getGDT() { __try{ ULONG i; PLONG pGDT = (PLONG)ExAllocatePoolWithTag(PagedPool, 0x3fff, (ULONG)"Tag1"); if (pGDT == 0) { DbgPrint("pIDT ExAllocatePoolWithTag Error..."); return; } RtlMoveMemory((PVOID)pGDT, 0x8003f000, 0xfff); for (i = 0; i<(0xfff/sizeof(ULONG)); i++) { DbgPrint("%ud %x\n", i, *(pGDT+i)); } ExFreePoolWithTag((PVOID)pGDT, (ULONG)"Tag1"); } __except (EXCEPTION_EXECUTE_HANDLER) { DbgPrint("Exception Error..."); } } VOID getIDT() { __try{ ULONG i; PLONG pGDT = ExAllocatePoolWithTag(PagedPool, 0x7ff, (ULONG)"Tag2"); if (pGDT == 0) { DbgPrint("pIDT ExAllocatePoolWithTag Error..."); return; } RtlMoveMemory((PVOID)pGDT, 0x8003f400, 0x7ff); for (i = 0; i<(0x7ff/sizeof(ULONG)); i++) { DbgPrint("%ud %x\n", i, *(pGDT+i)); } ExFreePoolWithTag((PVOID)pGDT, (ULONG)"Tag2"); } __except (EXCEPTION_EXECUTE_HANDLER) { DbgPrint("Exception Error..."); } } NTSTATUS DriverEntry(PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath) { DbgPrint(("hello zpchcbd \n")); getGDT(); // getIDT(); Driver->DriverUnload = UnDriver; return STATUS_SUCCESS; }
2、初始化一段字符串,拷贝一段字符串、比较两段字符串是否相等、ANSI_STRING<=>UNICODE_STRING字符串相互转换
void test01() { __try{ UNICODE_STRING unicodeString1; UNICODE_STRING unicodeString2; RtlInitUnicodeString(&unicodeString1, L"123456"); RtlCopyUnicodeString(&unicodeString2, &unicodeString1); LONG lRes = RtlCompareUnicodeString(&unicodeString1, &unicodeString2, 0); DbgPrint("lRes: %d\n", lRes); } __except (EXCEPTION_EXECUTE_HANDLER) { DbgPrint("Exception Error...\n"); } } NTSTATUS DriverEntry(PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath) { DbgPrint(("hello zpchcbd \n")); //getGDT(); //getIDT(); test01(); Driver->DriverUnload = UnDriver; return STATUS_SUCCESS; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY