首页
社区
课程
招聘
[原创]windows内核编程之[应用-内核通信]
发表于: 2016-9-10 16:03 18119

[原创]windows内核编程之[应用-内核通信]

2016-9-10 16:03
18119
这是本人初入看雪的第一篇帖子哦,请大家多多支持!

    初入驱动,发篇基础文章,一是为了给同样初入驱动的朋友一个参考,同时也算是我的一篇学习笔记。如果有错的话还请大家多多指教。谢谢!

    本文主要讲解如何实现应用-内核之间的通信。
    作用:在内核驱动程序中,没有窗口也没有控制台,无法让使用计算机的用户知道程序究竟做了什么。那么我们可以通过编写一个普通的用户程序,但某些功能用内核驱动来实现。这就涉及到如何让应用与内核进行通信,以便让内核驱动程序实现用户想要实现的功能。

    为了让驱动和应用通信,首先需要在内核中生成一个设备对象。用户以操作文件的方式打开设备对象,通过特定的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作者讲授!

上传的附件:
收藏
免费 4
支持
分享
最新回复 (14)
雪    币: 522
活跃值: (10)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
2
处贴。。  必须支持

不过一般在IO过程中会先测试一下用户层传递过来的指针是否可读或可写    不会直接用非0来判断
2016-9-10 16:11
0
雪    币: 31
活跃值: (87)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
3
谢谢,细节上做的还不够好,这是我学习之后编写的测试程序。
2016-9-10 16:14
0
雪    币: 0
活跃值: (143)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
感谢lz能够发帖教导我们小白
2016-9-12 11:47
0
雪    币: 31
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
谢谢分享,我想知道你是看的书吗?能否告诉我一下。
2016-9-12 18:45
0
雪    币: 31
活跃值: (87)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
6
是的,我也是刚刚进入驱动大门,这里发帖的话主要还是为了写写自己看书的笔记,把自己的一些体会写出来,发表的话也是为了帮助更多的像我这样对内核不熟的小白,如果需要注明引用的话我下次会注意的,毕竟刚入看雪,虽然注册几个月了,也是这几天才发文
2016-9-12 22:57
0
雪    币: 8865
活跃值: (2379)
能力值: ( LV12,RANK:760 )
在线值:
发帖
回帖
粉丝
7
SDDL的资料很全的,参考MSDN
https://msdn.microsoft.com/en-us/library/ff563667(v=vs.85).aspx
PS:
为何总觉得你的文字很熟悉,貌似和 那本《Windows内核安全与驱动开发》里的某些章节内容神似。
2016-9-13 03:22
0
雪    币: 576
活跃值: (1163)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
8
学习驱动开发ing~
2016-9-13 17:20
0
雪    币: 76
活跃值: (28)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
9
很同意啊
2016-9-14 17:37
0
雪    币: 983
活跃值: (722)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
    很好,刚学驱动,都不理解符号链接等等这些基本概念。楼主的文章很好的帮我解决了一些问题。谢谢
2017-2-19 17:10
0
雪    币: 136
活跃值: (432)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
11
cvcvxk SDDL的资料很全的,参考MSDN https://msdn.microsoft.com/en-us/library/ff563667(v=vs.85).aspx PS: 为何总觉得你的文字很熟悉,貌 ...
我感觉就是...
2018-5-26 08:16
0
雪    币: 16
活跃值: (527)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
12
那个创建的设备对象其实不用那样处理,直接修改他的安全描述符,就可以实现普通用户访问。库里面提供了设置安全描述符的函数
2018-6-5 14:48
0
雪    币: 249
活跃值: (14)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
13
太感谢了,近两天一直没有找到合适的中文资料。 msdn的文档看得头大
2019-8-20 22:10
0
雪    币: 228
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
15
感谢分享~ 现在推荐上层App通过使用GUID来获取设备路径吧~
CM_Get_Device_Interface_List_Size/CM_Get_Device_Interface_List
2019-9-5 14:13
0
游客
登录 | 注册 方可回帖
返回
//