看雪-课程-Windows内核安全编程实践之路-笔记
July 16, 2020 10:30 PM
环境搭建
安于此生@ExpLife,https://www.github.com/explife0011
开发环境:WDK+VS
调试环境:VM+VirtualKD+WindbugX
VirtualKD:
免去配置Windbg双机调试琐碎的步骤
VS -> VisualC++ -> Empty WDM Driver
驱动编程书籍:
- Windows驱动开发技术详解
- 天书夜读:从汇编语言到Windows内核编程
- 寒江独钓:Windows内核安全编程
- Windows内核安全与驱动开发
- Rootkits-Windows内核的安全防护
- Rootkit 系统灰色地带的潜伏者
- Windows PE权威指南
- 深入解析Windows操作系统
- windows内核情景分析
- windows内核原理与实现
- Windows内核设计思想
- Reversing Modern Malware and Next Generation Threats
编写一个最简单的驱动:
驱动的入口函数DriverEntry: (可以与C语言控制台程序的入口函数main进行类比)
NTSTATUS
DriverEntry(
IN PDRIVER_OBJECT DriverObject,
IN PUNICODE_STRING RegistryPath
);
入口函数的第一个参数PDRIVER_OBJECT DriverObject
入口函数的第二个参数PUNICODE_STRING RegistryPath
指向代表该驱动程序注册表键路径的UNICODE_STRING结构
typedef struct _DRIVER_OBJECT {
CSHORT Type; //驱动程序的类型
CSHORT Size; //驱动对象结构体的大小
PDEVICE_OBJECT DeviceObject; //指向驱动程序创建的设备对象,这些设备对象构成一个链表
ULONG Flags; //驱动程序标志
PVOID DriverStart; //驱动程序映像的起始地址
ULONG DriverSize; //驱动程序映像的大小
PVOID DriverSection; //指向驱动程序映像的内存区对象,可以通过该成员遍历系统中所有的驱动模块
PDRIVER_EXTENSION DriverExtension;//指向驱动程序对象的扩展结构
UNICODE_STRING DriverName; //驱动的名称
PUNICODE_STRING HardwareDatabase; //设备的硬件数据库名,一般为HKEY_LOCAL_MACHINEHardwareDESCRIPTIONSystem
PFAST_IO_DISPATCH FastIoDispatch; //指向文件系统以及网络传输驱动会用到的派遣函数的指针表
PDRIVER_INITIALIZE DriverInit; //指向DriverEntry函数,这是IO管理器设置的
PDRIVER_STARTIO DriverStartIo; //记录StartIO例程的函数地址,用于串行化操作.
PDRIVER_UNLOAD DriverUnload; //指向驱动卸载时所用的回调函数地址
PDRIVER_DISPATCH MajorFunction[IRP_MJ_MAXIMUM_FUNCTION + 1]; //指向驱动程序的派遣函数地址表
} DRIVER_OBJECT,*PDRIVER_OBJECT;
UNREFERENCED_PARAMETER宏的运用:
在编写驱动的时候,如果有函数的参数在函数体内未引用,并且将警告视为错误的编译选项启用了的话,这时驱动程序编译会报错,并提示程序中存在未引用的变量.这个时候我们只需要使用UNREFERENCED_PARAMETER来描述未引用到的参数就可以消除该警告。
UNICODE_STRING结构:
typedef struct _UNICODE_STRING {
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
} UNICODE_STRING, *PUNICODE_STRING;
Length:存储在缓冲区中的字符串所占的字节数
MaxinumLength:缓冲区能够容纳的字节数
Buffer:指向了用于包含一个宽字符字符串的缓冲区
分页与非分页内存
windows使用虚拟内存的方式来管理内存.虚拟内存系统可以让软件有一个比物理内存大得多的虚拟内存空间.为了做到这一点,内存管理器需要在物理内存与磁盘文件之间交换页帧. 但操作系统的某些部分是不能被分页的,比如处理缺页中断的代码与数据结构就必须常驻内存.
windows把内核模式地址空间分成分页内存池与非分页内存池.
用户模式地址空间总是分页的.必须常驻内存的代码与数据放在非分页池;不必常驻内存的代码与数据放在分页池.
编写驱动程序的时候决定代码与数据是否需要常驻非分页池有一个简单规则:执行在>=DISPATCH_LEVEL级的代码不可以引发缺页中断
PAGED_CODE()
windows为每个硬件中断和少数软件事件赋予了一个优先级,即中断请求级别(interrupt request level - IRQL):
申请内存 ExAllocatePool/ExAllocatePoolWithTag 类比 C语言中的malloc
释放内存 ExFreePool/ExFreePoolWithTag 类比 C语言中的free
常见的函数返回值类型NTSTATUS
WDK头文件中的定义
常用的状态码:
STATUS_SUCCESS - 成功
STATUS_UNSUCCESSFUL - 失败
判断是否状态码是否
成功的宏NT_SUCCESS(ntStatus)
内核下的字符串操作
RtlInitUnicodeString : 初始化一个UNICODE_STRING结构 RtlInitUnicodeStringEx: 初始化一个UNICODE_STRING结构 RtlAnsiCharToUnicodeChar:将第一个多字节字符串转换为一个宽字节字符串
RtlAnsiStringToUnicodeString:将一个ANSI_STRING转换为一个UNICODE_STRING
RtlAnsiStringToUnicodeSize:如果将一个ANSI_STRING转换为一个UNICODE_STRING,那么这个UNICODE_STRING的缓冲区所需要占用的字节数
RtlAppendUnicodeStringToString:将一个UNICODE_STRING连接到一个UNICODE_STRING中
RtlAppendUnicodeToString:将一个宽字节字符串连接到一个UNICODE_STRING中
RtlCompareUnicodeString:比较两个UINICODE_STRING的大小RtlCompareUnicodeStrings:比较两个宽字节字符串RtlConvertSidToUnicodeString:生成一个可打印的代表安全标识符的UNICODE_STRING
RtlCopyUnicodeString:将一个UNICODE_STRING拷贝到另一个UNICODE_STRING中
RtlCreateUnicodeString:用一个宽字节字符串创建一个新的UNICOCE_STRING
RtlDowncaseUnicodeChar:将指定的宽字节字符串转换为小写
RtlDowncaseUnicodeString:将指定的UNICODE_STRING转换为小写
RtlDuplicateUnicodeString:将一个UNICODE_STRING拷贝至另一个UNICODE_STRING
RtlEqualUnicodeString:判断两个UNICODE_STRING是否相等
RtlFindUnicodePrefix:查找UNICODE_STRING的前缀
RtlFreeUnicodeString:释放UNICOCE_STRING的存储空间
RtlHashUnicodeString:为UNICODE_STRING创建一个哈希值
RtlInitializeUnicodePrefix:初始化一个前缀表
RtlInsertUnicodePrefix:插入一个新元素到Unicode前缀表中
RtlInt64ToUnicodeString:将一个无符号64位整数值转换为UNICODE_STRING
RtlIntegerToUnicode:将一个整数值转换为宽字节字符串
RtlIntegerToUnicodeString:将一个整数值转换为UNICODE_STRING
RtlMultiByteToUnicodeN:将多字节字符串转换为宽字节字符串
RtlMultiByteToUnicodeSize:将多字节字符串转换为的宽字节字符串所需的字节数
RtlUpcaseUnicodeChar:将指定的宽字节字符串转换为大写
RtlUpcaseUnicodeString:将指定的UNICODE_STRING转换为大写
RtlValidateUnicodeString:验证UNICODE_STRING是否有效
Code Demo
#include <Ntifs.h>
#include <ntddk.h> //头文件如果顺序想法,会提示重定义。
VOID StringExample() // Demo for string
{
NTSTATUS ntStatus = STATUS_UNSUCCESSFUL;
UNICODE_STRING ustr1 = {0};
UNICODE_STRING ustr2 = {0};
UNICODE_STRING ustr3 = {0};
ANSI_STRING str1 = {0};
LONG lRet = 0;
RtlInitUnicodeString(&ustr1, L"This is a unicode string!");
RtlInitAnsiString(&str1, "This is a ansi string!");
RtlAnsiStringToUnicodeString(&ustr2, &str1, TRUE);
lRet = RtlCompareUnicodeString(&ustr1, &ustr2, FALSE);
if (lRet > 0)
{
//ustr1 > ustr2
DbgPrint("ustr1 > ustr2
");// Function Print
}
else if (lRet == 0)
{
//ustr1 == ustr2
DbgPrint("ustr1 == ustr2
"); // Function Print
}
else
{
//ustr1 < ustr2
DbgPrint("ustr1 < ustr2
");// Function Print
}
if (ustr2.Buffer)
{
RtlFreeUnicodeString(&ustr2);
}
ntStatus = RtlDowncaseUnicodeString(&ustr3, &ustr1, TRUE);
if (NT_SUCCESS(ntStatus))
{
if (ustr3.Buffer)
{
RtlFreeUnicodeString(&ustr3);
}
}
}
VOID
Unload(
IN PDRIVER_OBJECT DriverObject
) //Driver Unload
{
UNREFERENCED_PARAMETER(DriverObject);
}
NTSTATUS
DriverEntry(
IN PDRIVER_OBJECT DriverObject,
IN PUNICODE_STRING RegistryPath
) // Driver Main Entrance
{
UNREFERENCED_PARAMETER(DriverObject); //消除警告
UNREFERENCED_PARAMETER(RegistryPath); //消除警告
KdBreakPoint(); // BreakPoint
DriverObject->DriverUnload = Unload;
StringExample();
return STATUS_SUCCESS; // Return Code
}