4.1内核方面的编程

coworker.c

#include 
#include  //链接器添加 wdmsec.lib

/**
 * 应用层与内核层通信
 * 与用户层应用加载DLL模块调用导出函数不同
 * 想要与内核驱动通信,需要以设备对象为介质
 * 设备对象可以在内核中暴露给应用层,让应用层像操作文件一样操作它
 */

 /**
  * IRQL_GT_ZERO_AT_SYSTEM_SERVICE (4a)
  * Returning to usermode from a system call at an IRQL > PASSIVE_LEVEL.
  *
  * 例子在用户层应用调用DeviceIoControl发请求时蓝屏,实际测试书中配套例子同样有这个问题,先往后看
  * 
  * 验证了下,单纯输出获取到的信息是不会蓝屏的
  * 原因应该是使用了自旋锁,IRQL提升到了DISPATCH_LEVEL,所以蓝屏dump信息如上
  */

#define DEV_NAME L"DeviceCO_WORK_DRIVER"
#define SYM_NAME L"DosDevicesMY_CO_WORK_DRIVER" //DosDevices的符号链接名就是??, 所以"DosDevicesXXXX"其实就是??XXXX
#define SDDL_SYM L"D:P(A;;GA;;;WD)" //安全描述符,D:P开头接多个类似括号中的符号,本例为允许任何用户访问该设备的安全字符串,方便测试

const GUID  CWK_GUID_CLASS_MYCDO =
{ 0x17a0d1e0L, 0x3249, 0x12e1, {0x92,0x16, 0x45, 0x1a, 0x21, 0x30, 0x29, 0x06} };

// 从应用层给驱动发送一个字符串。
#define  CWK_DVC_SEND_STR 
	(ULONG)CTL_CODE( 
	FILE_DEVICE_UNKNOWN, 
	0x911,METHOD_BUFFERED, 
	FILE_WRITE_DATA)

// 从驱动读取一个字符串
#define  CWK_DVC_RECV_STR 
	(ULONG)CTL_CODE( 
	FILE_DEVICE_UNKNOWN, 
	0x912,METHOD_BUFFERED, 
	FILE_READ_DATA)

// 定义一个链表用来保存字符串
#define CWK_STR_LEN_MAX 512
typedef struct {
	LIST_ENTRY list_entry;
	char buf[CWK_STR_LEN_MAX];
} CWK_STR_NODE, * PCWK_STR_NODE;

// 还必须有一把自旋锁来保证链表操作的安全性
KSPIN_LOCK g_cwk_lock;
// 一个事件来标识是否有字符串可以取
KEVENT  g_cwk_event;
// 必须有个链表头
LIST_ENTRY g_cwk_str_list;

#define MEM_TAG 'cwkr'

// 分配内存并初始化一个链表节点
PCWK_STR_NODE cwkMallocStrNode()
{
	PCWK_STR_NODE ret = ExAllocatePoolWithTag(
		NonPagedPool, sizeof(CWK_STR_NODE), MEM_TAG);
	if (ret == NULL)
		return NULL;
	return ret;
}

PDEVICE_OBJECT g_cdo = NULL;

NTSTATUS CoWorkDispatch(PDEVICE_OBJECT pDevObj, PIRP pIrp)
{
	DbgPrint("Enter CoWorkDispatch
");
	NTSTATUS status = STATUS_SUCCESS;
	//获取irp所在栈
	PIO_STACK_LOCATION  irpsp = IoGetCurrentIrpStackLocation(pIrp);
	ULONG ret_len = 0;

	while (pDevObj == g_cdo)
	{
		if (irpsp->MajorFunction == IRP_MJ_CREATE || irpsp->MajorFunction == IRP_MJ_CLOSE)
		{
			// 生成和关闭请求,这个一律简单地返回成功就可以
			// 了。就是无论何时打开和关闭都可以成功。
			break;
		}

		if (irpsp->MajorFunction == IRP_MJ_DEVICE_CONTROL)
		{
			// 处理DeviceIoControl。
			PVOID buffer = pIrp->AssociatedIrp.SystemBuffer;//获取缓冲区
			ULONG inlen = irpsp->Parameters.DeviceIoControl.InputBufferLength;//输入缓冲区长度
			ULONG outlen = irpsp->Parameters.DeviceIoControl.OutputBufferLength;//输出缓冲区长度
			ULONG len;
			PCWK_STR_NODE str_node;
			switch (irpsp->Parameters.DeviceIoControl.IoControlCode)
			{
			case CWK_DVC_SEND_STR:
				ASSERT(buffer != NULL);
				ASSERT(outlen == 0);

				// 安全的编程态度之一:检查输入缓冲的长度对于长度超出预期的,果
				// 断返回错误。
				if (inlen > CWK_STR_LEN_MAX)
				{
					status = STATUS_INVALID_PARAMETER;
					break;
				}

				// 安全的编程态度之二:检查字符串的长度,不要使用strlen!如果使
				// 用strlen,一旦攻击者故意输入没有结束符的字符串,会导致内核驱
				// 动访问非法内存空间而崩溃。
				DbgPrint("strnlen = %llur
", strnlen((char*)buffer, inlen));
				if (strnlen((char*)buffer, inlen) == inlen)
				{
					// 字符串占满了缓冲区,且中间没有结束符。立刻返回错误。
					status = STATUS_INVALID_PARAMETER;
					break;
				}

				// 现在可以认为输入缓冲是安全而且不含恶意的。分配节点。
				str_node = cwkMallocStrNode();
				if (str_node == NULL)
				{
					// 如果分配不到空间了,返回资源不足的错误
					status = STATUS_INSUFFICIENT_RESOURCES;
					break;
				}

				// 前面已经检查了缓冲区中的字符串的确长度合适而且含有结束符
				// ,所以这里用什么函数来拷贝字符串对安全性而言并不非常重要。
				strncpy(str_node->buf, (char*)buffer, CWK_STR_LEN_MAX);
				// 插入到链表末尾。用锁来保证安全性。
				ExInterlockedInsertTailList(&g_cwk_str_list, (PLIST_ENTRY)str_node, &g_cwk_lock);
				// InsertTailList(&g_cwk_str_list, (PLIST_ENTRY)str_node);
				// 打印
				DbgPrint((char*)buffer);
				// 那么现在就可以认为这个请求已经成功。因为刚刚已经插入了一
				// 个,那么可以设置事件来表明队列中已经有元素了。
				KeSetEvent(&g_cwk_event, 0, TRUE);
				break;
			case CWK_DVC_RECV_STR:
				ASSERT(buffer != NULL);
				ASSERT(inlen == 0);
				// 应用要求接收字符串。对此,安全上要求是输出缓冲要足够长。
				if (outlen < CWK_STR_LEN_MAX)
				{
					status = STATUS_INVALID_PARAMETER;
					break;
				}
				while (1)
				{
					// 插入到链表末尾。用锁来保证安全性。
					str_node = (CWK_STR_NODE*)ExInterlockedRemoveHeadList(&g_cwk_str_list, &g_cwk_lock);
					// str_node = RemoveHeadList(&g_cwk_str_list);
					if (str_node != NULL)
					{
						// 这种情况下,取得了字符串。那就拷贝到输出缓冲中。然后
						// 整个请求就返回了成功。
						strncpy((char*)buffer, str_node->buf, CWK_STR_LEN_MAX);
						ret_len = strnlen(str_node->buf, CWK_STR_LEN_MAX) + 1;
						ExFreePool(str_node);
						break;
					}
					else
					{
						// 对于合法的要求,在缓冲链表为空的情况下,等待事件进行
						// 阻塞。也就是说,如果缓冲区中没有字符串,就停下来等待
						// 。这样应用程序也会被阻塞住,DeviceIoControl是不会返回
						// 的。但是一旦有就会返回。等于驱动“主动”通知了应用。
						KeWaitForSingleObject(&g_cwk_event, Executive, KernelMode, 0, 0);
					}
				}
				break;
			default:
				// 到这里的请求都是不接受的请求。未知的请求一律返回非法参数错误。
				status = STATUS_INVALID_PARAMETER;
				break;
			}
		}
		break;
	}

	//分发中返回请求
	pIrp->IoStatus.Status = status;
	pIrp->IoStatus.Information = 0;
	IoCompleteRequest(pIrp, IO_NO_INCREMENT);

	DbgPrint("Leave CoWorkDispatch
");
}

VOID DriverUnload(PDRIVER_OBJECT pDriverObject)
{
	DbgPrint("Enter DriverUnload
");
	//删除设备和符号
	UNICODE_STRING ustrSymName;
	RtlInitUnicodeString(&ustrSymName, SYM_NAME);
	IoDeleteSymbolicLink(&ustrSymName);
	if (g_cdo)
	{
		IoDeleteDevice(g_cdo);
	}
	// 释放分配过的所有内核内存。
	PCWK_STR_NODE str_node;
	while (TRUE)
	{
		str_node = ExInterlockedRemoveHeadList(
			&g_cwk_str_list, &g_cwk_lock);
		// str_node = RemoveHeadList(&g_cwk_str_list);
		if (str_node != NULL)
			ExFreePool(str_node);
		else
			break;
	};

	DbgPrint("Leave DriverUnload
");
}

NTSTATUS CreateDevice(PDRIVER_OBJECT pDriverObject)
{
	DbgPrint("Enter CreateDevice
");
	NTSTATUS status = STATUS_SUCCESS;
	UNICODE_STRING ustrDevName, ustrSymName, ustrSDDL;

	RtlInitUnicodeString(&ustrDevName, DEV_NAME);
	RtlInitUnicodeString(&ustrSymName, SYM_NAME);
	RtlInitUnicodeString(&ustrSDDL, SDDL_SYM);
	//status = IoCreateDevice(pDriverObject, 0, &ustrDevName, FILE_DEVICE_UNKNOWN, 0, FALSE, &pDevObj);
	status = IoCreateDeviceSecure( //创建一个所有用户都能访问的设备,方便测试,正式环境最好不用
		pDriverObject,
		0, &ustrDevName,
		FILE_DEVICE_UNKNOWN,
		FILE_DEVICE_SECURE_OPEN,
		FALSE, &ustrSDDL,
		(LPCGUID)&CWK_GUID_CLASS_MYCDO,
		&g_cdo);
	if (!NT_SUCCESS(status))
	{
		DbgPrint("IoCreateDevice Failed
");
		return status;
	}
	status = IoCreateSymbolicLink(&ustrSymName, &ustrDevName);
	if (!NT_SUCCESS(status))
	{
		DbgPrint("IoCreateSymbolicLink Failed
");
		IoDeleteDevice(g_cdo);
		return status;
	}
	DbgPrint("Leave CreateDevice
");
	return status;
}

VOID Init()
{
	// 初始化事件、锁、链表头。
	KeInitializeEvent(&g_cwk_event, SynchronizationEvent, TRUE);
	KeInitializeSpinLock(&g_cwk_lock);
	InitializeListHead(&g_cwk_str_list);
}

NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING RegistryPath)
{
	DbgPrint("Enter DriverEntry
");
	NTSTATUS status = STATUS_SUCCESS;

	pDriverObject->DriverUnload = DriverUnload;
	for (ULONG i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++)
	{
		//注册分发函数
		pDriverObject->MajorFunction[i] = CoWorkDispatch;
	}

	//初始化链表、锁等
	Init();
	//创建设备对象
	status = CreateDevice(pDriverObject);

	DbgPrint("Leave DriverEntry
");
	return status;
}
展开阅读全文

页面更新:2024-02-07

标签:内核   蓝屏   缓冲区   初始化   字符串   长度   分配   错误   事件   设备

1 2 3 4 5

上滑加载更多 ↓
推荐阅读:
友情链接:
更多:

本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828  

© CopyRight 2020-2024 All Rights Reserved. Powered By 71396.com 闽ICP备11008920号-4
闽公网安备35020302034903号

Top