一、内存申请、释放
在C开发R3层应用程序中,我们经常要用malloc申请内存,free释放内存;到了内核也有对应的函数申请内存和释放内存。
内核中微软比较推荐使用的是ExAllocatePoolWithTag和ExFreePoolWithTag,当然还有其他的函数。这对函数比较特殊的一点是有个Tag标签。这个标签的作用就是用来检测内存泄露的。系统根据这个Tag可以大概知道内存泄露的来源,这个标签可以是随意的32位数字,即使冲突也不会有问题。
下面给一个内核中使用这对函数动态申请内存的小demon:
#include <ntddk.h>
#define MEM_TAG 'MyTg'
VOID DriverUnload(PDRIVER_OBEJCT driver)
{
IoDeleteDevice(driver->DeviceObject);
DbgPrint("驱动卸载完成。。。\r\n");
}
NTSTATUS DriverEntry( PDRIVER_OBJECT driver, PUNICODE_STRING reg_path )
{
NTSTATUS status ;
UNICODE_STRING dst = {0};
UNICODE_STRING src = RLT_CONSTANT_STRING(L"Source String !");
src.Length = src.MaximumLength = wcslen(src.Buffer)*sizeof(WCHAR) ;
dst.Buffer = (PWCHAR)ExAllocatePoolWithTag(NonPagedPool,src.length, MEM_TAG ) ;
// 注意这里不需要再多申请一个1*sizeof(SIZE_T) 来存放结束标识符了,因为UNICODE_STRING最标注的判断就是长度
if(dst.Buffer == NULL)
{
status = STATUS_INSUFFICIENT_RESOURCES ;
<span style="white-space:pre"> </span>return status;
}
dst.Length = dst.MaximumLength = src.Length ;
RtlCopyUnicodeString(&dst,&src);
#if DBG
DbgPrint("%wZ",&dst); // 注意打印结构体 %wZ 对应结构体指针;打印字符串(确定是0结尾)%wS dst.buufer
#endif
ExFreePoolWithTag(dst.Buffer,MEM_TAG);
<span style="white-space:pre"> </span>return STATUS_SUCCESS;
}
上面这句话:
dst.Buffer = (PWCHAR)ExAllocatePoolWithTag(NonPagedPool,src.length, MEM_TAG ) ; 内核中动态申请内存;第一个参数用来标记申请的内存时候锁定在真实物理内存中。NonPagedPool 当发生缺页中断时候 这些内存不会被置换到硬盘中,永久驻留在物理内存中,直到ExFreePool或者ExFreePoolWitTag被调用,才从物理内存中释放掉。对应的一个是pagedPool 可以被置换到硬盘中。这样,大家应该就可猜出这个参数的意图。当我们申请的内存用来存放要执行的shellCode或者汇编代码的时候,必须放在NonPagePool内存中;当我们申请的空间想用来存放一些信息,比如 文件结构信息 可以用PagedPool。函数第二个参数:用来指定申请内存的长度,单位是字节。注意这个Length = wcslen(str)*sizeof(WCHAR) 这个是UNICODE_STRING 计算方法,也可以再多申请2个字节用来存放‘\0’'\0' 。第三参数就是Tag标签,一个32位的数字。
内存申请完毕后一定要释放掉。ExFreePoolWithTag(dst.Buffer,MEM_TAG);
二、 链表 LIST_ENTRY
windows内核中有已经定义好的链表,这样我们就不用自己设计链表,我们只要按照约定调用即可。要注意一点:LIST_ENTRY是一个双向链表。
三、 自旋锁 KSPIN_LOCK
在内核中必须考虑多线程,否则程序极易发生崩溃引发BSOD。内核引入自旋锁和队列自旋锁。这里我们先讨论自旋锁。
下面给个LIST_ENTRY 和使用自旋锁的例子。《Windows内核安全与驱动开发》这本书中的LIST_ENTRY代码是有错误的。具体错误(保密),等自己用到这节知识的时候自然会发现书中的错误。
下面是我写的双向列表和自旋锁的例子:
// list_entry 测试
// KSPIN_LOCK 多线程加锁
#include <ntddk.h>
#include <ntstrsafe.h>
#define MEM_TAG 'MyTg'
LIST_ENTRY my_list_entry_head ;
KSPIN_LOCK my_list_lock ;
typedef struct{
LIST_ENTRY list_entry ;
PFILE_OBJECT file_object;
PUNICODE_STRING file_name ;
PLARGE_INTEGER file_length ;
}MY_FILE_INFOR, *PMY_FILE_INFOR ;
// 初始化链表头 初始化自旋锁
VOID MyFileInforInit()
{
InitializeListHead(&my_list_entry_head);
KeInitializeSpinLock(&my_list_lock);
/*
note:链表和自旋锁初始化完成后,采用一系列的加锁操作代替普通的操作
*/
}
// 向链表中增加一个节点,注意file_name是外面分配的
NTSTATUS MyFileInforAppendNode(
PFILE_OBJECT file_object,
PUNICODE_STRING pfile_name,
PLARGE_INTEGER file_length)
{
// 给节点申请内存用来存放信息
PMY_FILE_INFOR my_file_infor = (PMY_FILE_INFOR)ExAllocatePoolWithTag(PagedPool,sizeof(MY_FILE_INFOR),MEM_TAG) ;
if( my_file_infor == NULL )
{
DbgPrint("申请内存失败!\r\n");
return STATUS_INSUFFICIENT_RESOURCES;
}
// 填写信息
my_file_infor->file_object = file_object ;
my_file_infor->file_name = pfile_name ;
my_file_infor->file_length = file_length ;
// 将数据插入链表,注意这里没有使用锁所以在多线程环境下是不安全的
//InsertHeadList(&my_list_entry_head,&(my_file_infor->list_entry)) ;
// 采用自选锁的方式插入 多线程安全
ExInterlockedInsertHeadList(&my_list_entry_head,&(my_file_infor->list_entry),&my_list_lock);
return STATUS_SUCCESS;
}
// 遍历整个链表
VOID MyFileInforPrint()
{
PLIST_ENTRY p ;
for( p=my_list_entry_head.Flink; p != &(my_list_entry_head);p = p->Flink)
{
PMY_FILE_INFOR elem = CONTAINING_RECORD(p,MY_FILE_INFOR,list_entry);
DbgPrint("FileName = %wZ \r\n",(elem->file_name));
}
}
// 清空链表 释放内存
VOID MyFileInforFree()
{
KdPrint(("Dump所有结点:\n"));
while(!IsListEmpty(&my_list_entry_head))
{
// 从头部移除 这种多线程不安全
// PLIST_ENTRY plist = RemoveHeadList(&my_list_entry_head) ;
// 采用多线程带锁操作
PLIST_ENTRY plist = ExInterlockedRemoveHeadList(&my_list_entry_head,&my_list_lock);
// 从尾部移除
// PLIST_ENTRY plist = RemoveTailList(&my_list_entry_head) ;
PMY_FILE_INFOR p = CONTAINING_RECORD(plist,MY_FILE_INFOR,list_entry);
// 打印数据
DbgPrint("FileName = %wZ \r\n",(p->file_name));
}
}
VOID DriverUnload( PDRIVER_OBJECT driver )
{
DbgPrint("List entry driver unload ...\r\n");
}
NTSTATUS DriverEntry( PDRIVER_OBJECT driver, PUNICODE_STRING reg_path )
{
INT i ;
UNICODE_STRING str[6] = {0};
PWSTR pbuffer = NULL ;
MyFileInforInit();
for(i=0; i<6 ;i++)
{
pbuffer = (LPWSTR)ExAllocatePoolWithTag(PagedPool,128,MEM_TAG);
RtlStringCbPrintfW(pbuffer,128,L"this is :%d string",i+1);
RtlInitUnicodeString(&str[i],pbuffer);
// 添加节点
MyFileInforAppendNode(NULL,&str[i],NULL);
}
// 遍历节点
MyFileInforPrint();
// 删除节点
MyFileInforFree();
for( i = 0 ;i <6;i++ )
{
ExFreePoolWithTag(str[i].Buffer,MEM_TAG);
}
driver->DriverUnload = DriverUnload ;
return STATUS_SUCCESS;
}
四、队列自旋锁
可以理解为自选锁的升级版。它和普通自选锁相比在多CPU平台上有更高的性能表现,并且遵循队列“First In,First Out”的原则。
简单用法:
// 初始化队列自旋锁
KSPIN_LOCK my_queue_spinlock = {0} ;
KeInitializeSpinLock( &my_queue_spinlock );
// 获取与释放
KLOCK_QUEUE_HANDLE my_lock_queue_handle ;
KeAcquireInStackQueuedSpinLock( &my_queue_spinlock, my_lock_queue_handle );
// do something ....
KeReleaseInStackQueuedSpinLock(&my_lock_queue_handle);
队列自旋锁使用增加了一个KLOCK_QUEUE_HANDLE的数据结构,这个结构也唯一表示了队列自旋锁。
note: 自旋锁和队列自旋锁本质上都是一样,但是两个锁不能交叉使用。