首页
社区
课程
招聘
[原创]自己动手实现dbgprint
发表于: 2016-6-25 11:28 6784

[原创]自己动手实现dbgprint

2016-6-25 11:28
6784
内核编程刚刚入门,打算自己写点东西,正好书上提到dbgprint,就自己写一个吧,哪错了希望各位大神指出来批评~~~,好直接进入正题。
首先必须要导出一个供其他内核模块用的函数
__declspec(dllexport) NTSTATUS myDbgPrint(CHAR *data);

由于这个函数可能会被任意线程在任意时刻被调用,所以应该考虑线程同步,于是应该定义一个队列将调试信息挂入双链表
[CODE]typedef struct _DBGDATA
{
	TIME_FIELDS time;
	CHAR data[MAX_DATA_LENGTH];
}DBGDATA,*PDBGDATA;
typedef struct _DBGDATALIST
{
	LIST_ENTRY ListEntry;
	DBGDATA listData;
}DBGDATALIST, *PDBGDATALIST;


LIST_ENTRY dbgPrintHead;[/CODE]

接着分析得出这样一个模型出来


说明一下这个模型
1.工作者线程,用于将调试信息队列填充到IRP中并唤醒IRP的完成,然后继续取下一个信息
2.对调试信息队列必须要互斥的访问,由于不同线程的访问
3.IRP的完成会被阻塞,由于工作线程的填充后被工作线程唤醒
下面思路就清晰了,首先定义这么几个全局变量
KEVENT WorkThreadEvent;//当调试信息队列中没有信息时,工作者线程需要睡眠,直到哪有线程调用被唤醒
LIST_ENTRY dbgPrintHead;//调试信息队列
HANDLE hThread;//工作者线程句柄
KMUTEX ListMutex;//用于互斥访问队列的互斥锁
BOOLEAN needWakeUp;//说明打印调试信息的函数是否需要唤醒工作线程

首先编写打印调试信息函数,这个函数比较简单,只用将调试信息挂入队列即可

__declspec(dllexport) NTSTATUS myDbgPrint(CHAR *data)
{
//获取互斥锁
	KeWaitForSingleObject(&ListMutex, Executive, KernelMode, FALSE, NULL);
//为队列分配内存
	PDBGDATALIST temp = ExAllocatePool(PagedPool, sizeof(DBGDATALIST));
	if (temp == NULL)
	{
		KeReleaseMutex(&ListMutex, FALSE);
		return STATUS_UNSUCCESSFUL;
	}
	RtlCopyMemory(&temp->listData.data, data, MAX_DATA_LENGTH);
	LARGE_INTEGER snow, now;

	KeQuerySystemTime(&snow);
	//转换为当地时间
	
	ExSystemTimeToLocalTime(&snow, &now);
	//转换为人们可以理解的时间格式
	RtlTimeToTimeFields(&now, &temp->listData.time);
//如果需要唤醒工作者线程则唤醒
	if (needWakeUp);
	{
		KeSetEvent(&WorkThreadEvent, 0, FALSE);
	}
//将调试信息挂入队列
	InsertTailList(&dbgPrintHead, &temp->ListEntry);
//释放互斥锁
	KeReleaseMutex(&ListMutex, FALSE);
}


接下来是重要的工作者线程

VOID TransDataThread(
	_In_ PVOID StartContext
	)
{
//这里得到设备扩展,用于唤醒IRP完成的事件定义在这里面
	PDEVICE_OBJECT_EXTENSION ext = StartContext;
//无限循环,不断的取队列与分发
	while (TRUE)
	{
//获取队列互斥锁
		KeWaitForSingleObject(&ListMutex, Executive, KernelMode, FALSE, NULL);
//如果队列是空的,则交出互斥锁并说明自己需要被唤醒
		if (IsListEmpty(&dbgPrintHead))
		{
			KeResetEvent(&WorkThreadEvent);
			needWakeUp = TRUE;
			KeReleaseMutex(&ListMutex, FALSE);
	
			KeWaitForSingleObject(&WorkThreadEvent, Executive, KernelMode, FALSE, NULL);
	
			continue;
		}
//队列不空,所以不需要被唤醒
		needWakeUp = FALSE;
//队列中取下队列头
		PLIST_ENTRY entry = RemoveHeadList(&dbgPrintHead);
//队列操作完毕,释放互斥锁
		KeReleaseMutex(&ListMutex, FALSE);
//拿到调试信息
		PDBGDATALIST pdata = CONTAINING_RECORD(entry, DBGDATALIST, ListEntry);
//这里要等待IRP的到来,如果没有R3应用程序发出请求,则一直阻塞
		KeWaitForSingleObject(&ext->irpEvent, Executive, KernelMode, FALSE, NULL);
//填充IRP
		PIRP irp = ext->currentIRP;
		PVOID buf = irp->AssociatedIrp.SystemBuffer;
		RtlCopyMemory(buf, &pdata->listData, sizeof(DBGDATA));
		irp->IoStatus.Status = STATUS_SUCCESS;
		irp->IoStatus.Information = sizeof(DBGDATA);
		KeResetEvent(&ext->irpEvent);
//告诉IRP_MJ_READ函数,可以调用iocompleterequest了
		KeSetEvent(&ext->completeEvent, 0, FALSE);
	}
	
}


然后编写设备驱动的read函数
NTSTATUS myRead(PDEVICE_OBJECT Deviceobj, PIRP irp)
{
	NTSTATUS status;
	PDEVICE_OBJECT_EXTENSION ext = Deviceobj->DeviceExtension;
//将设备扩展中的当前IRP设置,让工作者线程知道填充哪个IRP
	ext->currentIRP = irp;
//告诉工作者线程,可以开始填充IRP了
	KeSetEvent(&ext->irpEvent, 0, FALSE);
	//__asm int 3
//等待工作者线程填充完毕
	KeWaitForSingleObject(&ext->completeEvent, Executive, KernelMode, FALSE, NULL);
	KeResetEvent(&ext->completeEvent);
//完成IRP并返回R3
	IoCompleteRequest(irp, IO_NO_INCREMENT);
	//__asm int 3
	return STATUS_SUCCESS;
}


总体思路就是这样,最后要对这些东西进行初始化,这个写在driverentry的addDevice函数里面,这是个NT式驱动

NTSTATUS myAddDevice(PDRIVER_OBJECT driverObj, PUNICODE_STRING Registry)
{
//创建设备
	NTSTATUS status = STATUS_SUCCESS;
	UNICODE_STRING DeviceName;
	UNICODE_STRING SymbolicName;
	PDEVICE_OBJECT dev;
	PDEVICE_OBJECT_EXTENSION ext;
	RtlInitUnicodeString(&DeviceName, L"\\Device\\MyDbgPrint");
	RtlInitUnicodeString(&SymbolicName, L"\\??\\MyDbgPrint");
	status = IoCreateDevice(driverObj, sizeof(DEVICE_OBJECT_EXTENSION), &DeviceName, FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN, TRUE, &dev);
	if (status != STATUS_SUCCESS)
	{
		DbgPrint("can not create dbgprint dev");
		return status;
	}
	dev->Flags |= DO_BUFFERED_IO;
	ext = dev->DeviceExtension;
	RtlCopyUnicodeString(&ext->SymbolicName, &SymbolicName);
	IoCreateSymbolicLink(&SymbolicName, &DeviceName);
	//初始化设备扩展,将两个事件初始化
	KeInitializeEvent(&ext->irpEvent, NotificationEvent, FALSE);
	KeInitializeEvent(&ext->completeEvent, NotificationEvent, FALSE);
	ext->currentIRP = NULL;
	//初始化链表头,工作者线程事件,互斥锁
	KeInitializeEvent(&WorkThreadEvent, NotificationEvent, FALSE);
	KeInitializeMutex(&ListMutex, 0);
	InitializeListHead(&dbgPrintHead);
	needWakeUp = TRUE;

//创建工作者线程
	status = PsCreateSystemThread(&hThread, GENERIC_ALL, 0, NULL, NULL, TransDataThread, ext);
	if (status != STATUS_SUCCESS)
	{
		DbgPrint("can not create thread");
		return status;
	}
	ZwClose(hThread);
	return status;
}


最后,我对于事件的类型也是纠结了不少,即创建为通知事件还是同步事件,主要考虑到这样一种情况,即

kesetevnet(&v1);
kewaitforsingleobject(&v2);
当执行行第一句的时候,立马线程切换,在另外一个线程里将V2激活了,而这时候这个线程还没开始等待,所以一直得不到唤醒,所以用通知事件可以避免这个事,虽然机率不大,但是,写程序还是应该严谨。
PS.在MSDN上面我看到kesetevent第三个参数指明这函数过后是否跟有一个wait函数,但是可以指的是同一事件的情况即

kesetevnet(&v1);
kewaitforsingleobject(&v1);
具体没有调试过,所以就放弃了。好吧,就写到这,去复习考试

[课程]Linux pwn 探索篇!

收藏
免费 3
支持
分享
最新回复 (1)
雪    币: 207
活跃值: (26)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
2
不好意思不好意思,我以为直接传到看雪后台就能直接看到图片,于是就没管了,现在图片放出来了。好关键的东西~~~~
2016-6-28 23:41
0
游客
登录 | 注册 方可回帖
返回
//