这是本人初入看雪的第一篇帖子哦,请大家多多支持!
初入驱动,发篇基础文章,一是为了给同样初入驱动的朋友一个参考,同时也算是我的一篇学习笔记。如果有错的话还请大家多多指教。谢谢!
本文主要讲解如何实现应用-内核之间的通信。
作用:在内核驱动程序中,没有窗口也没有控制台,无法让使用计算机的用户知道程序究竟做了什么。那么我们可以通过编写一个普通的用户程序,但某些功能用内核驱动来实现。这就涉及到如何让应用与内核进行通信,以便让内核驱动程序实现用户想要实现的功能。
为了让驱动和应用通信,首先需要在内核中生成一个设备对象。用户以操作文件的方式打开设备对象,通过特定的API发送irp请求给设备对象,驱动程序通过分发函数对这个请求进行处理。
NTSTATUS IoCreateDevice(
_In_ PDRIVER_OBJECT DriverObject,
_In_ ULONG DeviceExtensionSize,
_In_opt_ PUNICODE_STRING DeviceName,
_In_ DEVICE_TYPE DeviceType,
_In_ ULONG DeviceCharacteristics,
_In_ BOOLEAN Exclusive,
_Out_ PDEVICE_OBJECT *DeviceObject
);
通过以上函数来创建一个设备对象,这里不具体解释此函数,在微软官方文档里可以找到它的详细介绍。
使用这个函数生成的设备对象只能被具有管理员权限的进程打开,但我们可以使用另一个函数来生成一个所有用户都能打开的设备对象:
NTSTATUS IoCreateDeviceSecure(
_In_ PDRIVER_OBJECT DriverObject,
_In_ ULONG DeviceExtensionSize,
_In_opt_ PUNICODE_STRING DeviceName,
_In_ DEVICE_TYPE DeviceType,
_In_ ULONG DeviceCharacteristics,
_In_ BOOLEAN Exclusive,
_In_ PCUNICODE_STRING DefaultSDDLString,
_In_opt_ LPCGUID DeviceClassGuid,
_Out_ PDEVICE_OBJECT *DeviceObject
);
可以看出,这个函数和上面那个函数没有太大的区别,只是增加了两个参数DefaultSDDLString和DeviceClassGuid。前者是这个对象的安全设置,对于这个参数,我个人搜集到的资料很少,只是看到一个所谓的“万能字符串”,可以让所有的用户都能访问此对象:"D:P(A;;GA;;;WD)",后者则是这个设备的GUID。
下面是我在DriverEntry里的部分代码
//设备名称
#define KS_DEVICE_NAME L"\\Device\\KS_user->win"
......
//GUID
const GUID KS_GUID_CLASS= { 0x69EB3219 , 0x1863 , 0x9672 , {0xA0,0x04 ,0x1F,0x0F,0xAF,0xA9,0xC6,0xA6 } };
//安全属性
UNICODE_STRING sa = RTL_CONSTANT_STRING(L"D:P(A;;GA;;;WD)");
//设备名称
UNICODE_STRING device_name =RTL_CONSTANT_STRING(KS_DEVICE_NAME);
......
//创建设备
status = IoCreateDeviceSecure(driver,0,&device_name,FILE_DEVICE_UNKNOWN,
FILE_DEVICE_SECURE_OPEN,FALSE,
&sa,(LPCGUID)&KS_GUID_CLASS,&KS_Device);
上面的GUID是我通过一个在线生成网站临时生成的。
在生成的设备对象中,应用程序是无法直接通过设备名称访问设备对象的,这时候就需要生成一个符号链接。符号链接就是记录一个字符串对应到另一个字符串的简单结构,如:以下创建了一个符号链接
//控制设备名
#define KS_SYB_LIC_NAME L"\\??\\KS_SYB_LIC_KUANGSAN_NAME"
//创建符号连接
status = IoCreateSymbolicLink(&device_syb, &device_name);
if (!NT_SUCCESS(status))
{
//失败了要删除设备
IoDeleteDevice(KS_Device);
DbgPrint("Create symbol lic link failed!\n");
return status;
}
至此,一个设备对象就创建好了,在驱动的卸载函数中,我们需要手动把生成的设备对象删除。如下:
//控制设备名
#define KS_SYB_LIC_NAME L"\\??\\KS_SYB_LIC_KUANGSAN_NAME"
//卸载函数;删除创建的设备对象,符号连接
VOID KS_MyUnload(PDRIVER_OBJECT driver)
{
UNICODE_STRING KS_syb = RTL_CONSTANT_STRING(KS_SYB_LIC_NAME);
if (KS_Device != NULL)
{
IoDeleteSymbolicLink(&KS_syb);
IoDeleteDevice(KS_Device);
}
DbgPrint("My driver is unloading...\n");
}
好了,下面进入重点,可以说整个通信机制的核心就是分发函数,首先我们在DriverEntry里设置分发函数:
for (i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++)
{
//分发函数初始化
driver->MajorFunction[i] = KS_Dispatch;
}
分发函数已经设置好了,现在来讨论下如何编写分发函数。用户程序给设备对象的每一个请求都有一个主功能号,来说明这个请求究竟要干什么,我们直接用当前栈空间指针来获取主功能号。
//功能函数,对应用程序的IRP请求的响应
NTSTATUS KS_Dispatch(IN PDEVICE_OBJECT dev, IN PIRP irp)
{
......
//得到当前堆栈位置
PIO_STACK_LOCATION KS_irp = IoGetCurrentIrpStackLocation(irp);
......
}
获取了当前栈空间指针后,可以直接使用KS_irp->Parameters.DeviceIoControl.IoControlCode
来获取主功能号,主功能号大致有以下几种:
.打开请求主功能号:IRP_MJ_CREATE
.关闭请求主功能号:IRP_MJ_CLOSE
.设备控制请求主功能号:IRP_MJ_CONTROL
然后就可以在分发函数里编写代码,针对不同的功能号来进行处理。
下面贴出我编写的应用端代码,很简单,大致功能就是打开设备对象,然后发送一串字符串给对象,驱动程序通过dbgprint打印出来
#include "stdafx.h"
#include <Windows.h>
#define KS_DEV_SYB_NAME L"\\\\.\\KS_SYB_LIC_KUANGSAN_NAME"
#define KS_DVC_RECV_STR (ULONG)CTL_CODE(FILE_DEVICE_UNKNOWN,0XA11,METHOD_BUFFERED,FILE_READ_DATA)
int main()
{
char *msg = { "I'M USER,HELLO DRIVER!\r\n" };
int ret = 0;
ULONG ret_len;
HANDLE device = NULL;
device = CreateFile(KS_DEV_SYB_NAME, GENERIC_READ | GENERIC_WRITE,
0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_SYSTEM, 0);
if (device == INVALID_HANDLE_VALUE)
{
printf("coworker demo:Open device failed.\r\n");
return -1;
}
else
printf("coworker demo:Open device successfully.\r\n");
if (!DeviceIoControl(device, KS_DVC_RECV_STR, msg, strlen(msg) + 1, NULL, 0, &ret_len, 0))
{
printf("coworker demo: Send message failed.\r\n");
ret = -2;
}
else
printf("coworker demo: Send message successfully.\r\n");
CloseHandle(device);
return ret;
}
然后是内核驱动程序的代码
#include <ntifs.h>
#include <wdmsec.h>
#define KS_DEVICE_NAME L"\\Device\\KS_user->win"
#define KS_SYB_LIC_NAME L"\\??\\KS_SYB_LIC_KUANGSAN_NAME"
#define KS_DVC_RECV_STR (ULONG)CTL_CODE(FILE_DEVICE_UNKNOWN,0XA11,METHOD_BUFFERED,FILE_READ_DATA)
#define KS_DVC_SEND_STR (ULONG)CTL_CODE(FILE_DEVICE_UNKNOWN,0XC11,METHOD_BUFFERED,FILE_READ_DATA)
PDEVICE_OBJECT KS_Device = NULL;
const GUID KS_GUID_CLASS= { 0x69EB3219 , 0x1863 , 0x9672 , {0xA0,0x04 ,0x1F,0x0F,0xAF,0xA9,0xC6,0xA6 } };
//卸载函数;删除创建的设备对象,符号连接
VOID KS_MyUnload(PDRIVER_OBJECT driver)
{
UNICODE_STRING KS_syb = RTL_CONSTANT_STRING(KS_SYB_LIC_NAME);
if (KS_Device != NULL)
{
IoDeleteSymbolicLink(&KS_syb);
IoDeleteDevice(KS_Device);
}
DbgPrint("My driver is unloading...\n");
}
//功能函数,对应用程序的IRP请求的响应
NTSTATUS KS_Dispatch(IN PDEVICE_OBJECT dev, IN PIRP irp)
{
NTSTATUS status = STATUS_SUCCESS;
ULONG ret_len = 0;
//得到当前堆栈位置
PIO_STACK_LOCATION KS_irp = IoGetCurrentIrpStackLocation(irp);
//如果请求的不是已经生成的设备
while (dev==KS_Device)
{
if (KS_irp->MajorFunction == IRP_MJ_CLOSE || KS_irp->MajorFunction == IRP_MJ_CREATE)
{
break;
}
//如果是控制请求
if (KS_irp->MajorFunction == IRP_MJ_DEVICE_CONTROL)
{
//获得缓冲区地址
PVOID buffer = irp->AssociatedIrp.SystemBuffer;
//获得输入缓冲区长度
ULONG inlen = KS_irp->Parameters.DeviceIoControl.InputBufferLength;
//获得输出缓冲区长度
ULONG outlen = KS_irp->Parameters.DeviceIoControl.OutputBufferLength;
switch (KS_irp->Parameters.DeviceIoControl.IoControlCode)
{
case KS_DVC_RECV_STR:
ASSERT(buffer != NULL);
ASSERT(inlen > 0);
ASSERT(outlen == 0);
DbgPrint((char *)buffer);
break;
case KS_DVC_SEND_STR:
default:
status = STATUS_INVALID_PARAMETER;
break;
}
}
break;
}
irp->IoStatus.Information = ret_len;
irp->IoStatus.Status = status;
IoCompleteRequest(irp, IO_NO_INCREMENT);
return status;
}
NTSTATUS DriverEntry(PDRIVER_OBJECT driver, PUNICODE_STRING reg_path)
{
//生成控制设备,生成符号链接
UNICODE_STRING sa = RTL_CONSTANT_STRING(L"D:P(A;;GA;;;WD)");
UNICODE_STRING device_name = RTL_CONSTANT_STRING(KS_DEVICE_NAME);
UNICODE_STRING device_syb = RTL_CONSTANT_STRING(KS_SYB_LIC_NAME);
NTSTATUS status;
//计数
ULONG i;
//创建设备
status = IoCreateDeviceSecure(driver,0, &device_name,FILE_DEVICE_UNKNOWN,
FILE_DEVICE_SECURE_OPEN,FALSE,
&sa,(LPCGUID)&KS_GUID_CLASS,&KS_Device);
if (!NT_SUCCESS(status))
{
DbgPrint("Create device failed!\n");
return status;
}
//创建符号连接
status = IoCreateSymbolicLink(&device_syb, &device_name);
if (!NT_SUCCESS(status))
{
//失败了要删除设备
IoDeleteDevice(KS_Device);
DbgPrint("Create symbol lic link failed!\n");
return status;
}
for (i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++)
{
//分发函数初始化
driver->MajorFunction[i] = KS_Dispatch;
}
//卸载函数地址
driver->DriverUnload = KS_MyUnload;
return STATUS_SUCCESS;
}
附上运行效果:
我一共启动了四次应用端,内核相应的打印出了四次应用端发出的字符串。最后卸载。
好了,本文至此就结束了,但我想写的内容并没有结束,我还会在接下来的几篇帖子里继续详写。文章中有错的地方还请大家多多指出。十分感谢大家,在这里献丑了。
[课程]FART 脱壳王!加量不加价!FART作者讲授!