内核编程刚刚入门,打算自己写点东西,正好书上提到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);
具体没有调试过,所以就放弃了。好吧,就写到这,去复习考试
[课程]Android-CTF解题方法汇总!