登录  | 立即注册

游客您好!登录后享受更多精彩

查看: 48|回复: 0

WindowsX86内核03.控制,通讯方式,分页

[复制链接]

71

主题

1

回帖

146

积分

管理员

积分
146
发表于 2024-12-5 15:12:54 | 显示全部楼层 |阅读模式
控制
昨天我已经实现了 打开,关闭 ,读写功能,或许会有疑问,已经有了读写功能,为什么还要控制功能呢,那是因为读写传递的参数较少,有时候并不能满足需要,例如当设备或者功能较多时,我们无法知道应该操作哪个设备或者哪个功能
例如昨天的代码 ,我们再结构体里面再添加2个成员,那个我们进行读写时,无法确定操作哪个成员,因为缺少参数
struct MyDeviceExt {
int nVirtualReg;
int nVirtualReg2;
int nVirtualReg3;
};
//读操作
DevExt.nVirtualReg = -1;
​ if (!ReadFile(hFile, &DevExt, sizeof(DevExt), &dwBytes, NULL)) //从寄存器里面读值
​ {
​ DisplayErrorText();
​ }
​ else
​ {
​ printf("ReadFile dwBytes = %d VirtualReg:%d\n", dwBytes, DevExt.nVirtualReg);
​ }
如果我们把 DevExt 当做序号传递,可以解决无法知道访问哪个成员的问题,但是如果进程访问,例如A进程写
B进程读 就会出问题
因此我们希望 ReadFile 能够多传一个参数,这样问题就简单了,但是如果结构体是数组没那么一个参数将不够用,因此五门无法确定要用到定义的参数个数
解决办法 是 我们可以传一个 int cod , void* buff , int len ,void*可以转换成任何结构体指针,code 表示要进行的操作,这个就叫做控制
控制和读写没什么特别大的区别,就多了3个参数
1662645632787-f244d100-0b97-40f7-af65-6b9ff5f26f44.png
跟读写相比 就多了 第 2,3,4个参数, 操作码 缓冲区 缓冲区大小
完成控制功能定义控制码
控制码是有规范的
1662646004783-5548c9c3-cef0-40c1-9382-b57248ac2545.png
一共32位 0-1 是类型 2 - 12才是真正的控制吗 14-15 是设备访问权限 16-30 是设备类型
可以看到定义控制码很麻烦,因此微软给出了一个宏来辅助用户定义
#define IOCTL_Device_Function CTL_CODE(DeviceType*,** Function*,** Method**,** *Access*)
我么只需要把四个成员填进去就行了
  • 设备类型
  • 控制码 (不能低于800,前面的被微软用了)
  • 通讯方式 METHOD_BUFFERED
  • 访问权限 (读 或 写 或 打开.....)
一般我们自己会在定义一个红包裹这个宏
这样我们就可以定义宏了
#define IOCTL_GET_REG1 CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS)#define IOCTL_GET_REG2 CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS)

但是定义吗我么后面可能会越来越多,后面如果改起来就很麻烦,例如 要修改设备类型,所以一般我们会再套一个宏
//定义控制码#define MY_CODE_BASE 0x800#define MY_CTL_CODE(code) CTL_CODE(FILE_DEVICE_UNKNOWN, MY_CODE_BASE + code, METHOD_BUFFERED, FILE_ANY_ACCESS)#define IOCTL_GET_REG1 MY_CTL_CODE(0)#define IOCTL_GET_REG2 MY_CTL_CODE(1)#define IOCTL_GET_REG3 MY_CTL_CODE(2)#define IOCTL_SET_REG1 MY_CTL_CODE(3)#define IOCTL_SET_REG2 MY_CTL_CODE(4)#define IOCTL_SET_REG3 MY_CTL_CODE(5)这样如果要改信息只需要修改 宏 MY_CTL_CODE  就可以了

实现控制函数
驱动代码
  1. --------------Hello.h-----------------
  2. #pragma once

  3. extern "C" {

  4. #include <Ntddk.h>

  5.     struct MyDeviceExt {
  6.         int nVirtualReg;
  7.         int nVirtualReg2;
  8.         int nVirtualReg3;
  9.     };


  10.     #define DEVICE_NAME  L"\\Device\\CR42"
  11.     #define SYMBOL_NAME L"\\DosDevices\\CR42VirtualRegister"

  12.         //定义控制码
  13.     #define MY_CODE_BASE 0x800
  14.     #define MY_CTL_CODE(code) CTL_CODE(FILE_DEVICE_UNKNOWN, MY_CODE_BASE + code, METHOD_BUFFERED, FILE_ANY_ACCESS)
  15.     #define IOCTL_GET_REG1 MY_CTL_CODE(0)
  16.     #define IOCTL_GET_REG2 MY_CTL_CODE(1)
  17.     #define IOCTL_GET_REG3 MY_CTL_CODE(2)
  18.     #define IOCTL_SET_REG1 MY_CTL_CODE(3)
  19.     #define IOCTL_SET_REG2 MY_CTL_CODE(4)
  20.     #define IOCTL_SET_REG3 MY_CTL_CODE(5)


  21.     /*驱动入口函数*/
  22.     NTSTATUS DriverEntry(__in struct _DRIVER_OBJECT* DriverObject,
  23.         __in PUNICODE_STRING  RegistryPath);


  24.     VOID Unload(__in struct _DRIVER_OBJECT* DriverObject);

  25.     NTSTATUS DispatchCreate(
  26.         _In_ struct _DEVICE_OBJECT* DeviceObject,
  27.         _Inout_ struct _IRP* Irp
  28.     );

  29.     NTSTATUS DispatchClose(
  30.         _In_ struct _DEVICE_OBJECT* DeviceObject,
  31.         _Inout_ struct _IRP* Irp
  32.     );

  33.     NTSTATUS DispatchRead(
  34.         _In_ struct _DEVICE_OBJECT* DeviceObject,
  35.         _Inout_ struct _IRP* Irp
  36.     );

  37.     NTSTATUS DispatchWrite(
  38.         _In_ struct _DEVICE_OBJECT* DeviceObject,
  39.         _Inout_ struct _IRP* Irp
  40.     );

  41.     NTSTATUS DispatchControl(
  42.         _In_ struct _DEVICE_OBJECT* DeviceObject,
  43.         _Inout_ struct _IRP* Irp
  44.     );

  45. }


  46. --------------Hello.cpp-----------------
  47. #include "Hello.h"


  48. MyDeviceExt g_Registers;


  49. /*驱动卸载函数 clean_up*/
  50. VOID Unload(__in struct _DRIVER_OBJECT* DriverObject)
  51. {
  52.     DbgPrint("[51asm] Unload! DriverObject:%p\n", DriverObject);

  53.     //删除符号链接
  54.     UNICODE_STRING ustrSymbolName;
  55.     RtlInitUnicodeString(&ustrSymbolName, SYMBOL_NAME);
  56.     IoDeleteSymbolicLink(&ustrSymbolName);

  57.     //删除设备
  58.     if (DriverObject->DeviceObject != NULL)
  59.         IoDeleteDevice(DriverObject->DeviceObject);
  60. }

  61. /*1.驱动入口函数*/
  62. NTSTATUS DriverEntry(
  63.     __in struct _DRIVER_OBJECT* DriverObject,
  64.     __in PUNICODE_STRING  RegistryPath)
  65. {
  66.     DbgPrint("[51asm] Hello WDK! DriverObject:%p RegistryPath:%wZ\n",
  67.         DriverObject, RegistryPath);

  68.     //2.创建设备
  69.     UNICODE_STRING ustrDevName;
  70.     RtlInitUnicodeString(&ustrDevName, DEVICE_NAME);  //等于上面的3行代码



  71.     PDEVICE_OBJECT pDevObj = NULL;
  72.     NTSTATUS Status = IoCreateDevice(DriverObject,
  73.         0,
  74.         &ustrDevName,
  75.         FILE_DEVICE_UNKNOWN,  //不知道的设备类型
  76.         FILE_DEVICE_SECURE_OPEN,
  77.         FALSE, //独占
  78.         &pDevObj);
  79.     if (!NT_SUCCESS(Status)) {
  80.         DbgPrint("[51asm] IoCreateDevice Status:%p\n", Status);
  81.         return Status;
  82.     }


  83.     //MS-DOS Device Names  创建符号链接
  84.     UNICODE_STRING ustrSymbolName;
  85.     RtlInitUnicodeString(&ustrSymbolName, SYMBOL_NAME);
  86.     Status = IoCreateSymbolicLink(&ustrSymbolName, &ustrDevName);
  87.     if (!NT_SUCCESS(Status)) {
  88.         DbgPrint("[51asm] IoCreateSymbolicLink Status:%p\n", Status);

  89.         if (pDevObj != NULL)
  90.             IoDeleteDevice(pDevObj);

  91.         return Status;
  92.     }
  93.     DbgPrint("[51asm] IoCreateSymbolicLink %wZ OK\n", &ustrSymbolName);

  94.     //3.注册派遣函数
  95.     DriverObject->MajorFunction[IRP_MJ_CREATE] = &DispatchCreate;
  96.     DriverObject->MajorFunction[IRP_MJ_CLOSE] = &DispatchClose;
  97.     DriverObject->MajorFunction[IRP_MJ_READ] = &DispatchRead;
  98.     DriverObject->MajorFunction[IRP_MJ_WRITE] = &DispatchWrite;
  99.     DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = &DispatchControl;


  100.     //4.注册卸载函数
  101.     DriverObject->DriverUnload = Unload;

  102.     return STATUS_SUCCESS;
  103. }


  104. NTSTATUS DispatchCreate(_DEVICE_OBJECT* DeviceObject, _IRP* Irp)
  105. {
  106.     UNREFERENCED_PARAMETER(DeviceObject);
  107.     UNREFERENCED_PARAMETER(Irp);
  108.     DbgPrint("[51asm] %s\n", __FUNCTION__);

  109.     //完成请求  如果没完成 3环的程序就会挂起
  110.     Irp->IoStatus.Status = STATUS_SUCCESS;   //状态,成功
  111.     Irp->IoStatus.Information = 0; //成功操作的字节数,非读写一般是0 就算是读写文件 dwBytes的返回值

  112.     //第二个参数线程的优先级,内核一般时 IO_NO_INCREMENT 不提升优先级
  113.     IoCompleteRequest(Irp, IO_NO_INCREMENT);  

  114.     return STATUS_SUCCESS;
  115. }

  116. NTSTATUS DispatchClose(_DEVICE_OBJECT* DeviceObject, _IRP* Irp)
  117. {
  118.     UNREFERENCED_PARAMETER(DeviceObject);
  119.     UNREFERENCED_PARAMETER(Irp);
  120.     DbgPrint("[51asm] %s\n", __FUNCTION__);


  121.     //完成请求  如果没完成 3环的程序就会挂起
  122.     Irp->IoStatus.Status = STATUS_SUCCESS;   //状态,成功
  123.     Irp->IoStatus.Information = 0; //成功操作的字节数,非读写一般是0 就算是读写文件 dwBytes的返回值

  124.     //第二个参数线程的优先级,内核一般时 IO_NO_INCREMENT 不提升优先级
  125.     IoCompleteRequest(Irp, IO_NO_INCREMENT);

  126.     return STATUS_SUCCESS;
  127. }

  128. NTSTATUS DispatchRead(_DEVICE_OBJECT* DeviceObject, _IRP* Irp)
  129. {
  130.     UNREFERENCED_PARAMETER(DeviceObject);
  131.     UNREFERENCED_PARAMETER(Irp);
  132.     DbgPrint("[51asm] %s\n", __FUNCTION__);

  133.     //获取用户的缓冲区
  134.     PVOID pBuffer = Irp->UserBuffer;   //就是用户参数的 缓冲地址

  135.      //获取当前IRP堆栈
  136.     PIO_STACK_LOCATION pIrpStack = IoGetCurrentIrpStackLocation(Irp);
  137.     ULONG nLength = pIrpStack->Parameters.Read.Length;    //就是用户参数的 缓冲大小  read表示读操作
  138.     DbgPrint("[51asm] %s pBuffer:%p nLength:%d\n", __FUNCTION__, pBuffer, nLength);


  139.     if (nLength > sizeof(g_Registers.nVirtualReg)) {  //判断用户缓冲区大小是否足够
  140.         nLength = sizeof(g_Registers.nVirtualReg);
  141.     }
  142.         
  143.     //拷贝信息  就等于  memcoy
  144.     RtlCopyMemory(pBuffer,&g_Registers.nVirtualReg, nLength);  //把寄存器数据数据传入用缓冲区

  145.     //完成请求  如果没完成 3环的程序就会挂起
  146.     Irp->IoStatus.Status = STATUS_SUCCESS;   //状态,成功
  147.     Irp->IoStatus.Information = nLength; //成功操作的字节数,非读写一般是0 就算是读写文件 dwBytes的返回值

  148.     //第二个参数线程的优先级,内核一般时 IO_NO_INCREMENT 不提升优先级
  149.     IoCompleteRequest(Irp, IO_NO_INCREMENT);

  150.     return STATUS_SUCCESS;
  151. }


  152. NTSTATUS DispatchWrite(_DEVICE_OBJECT* DeviceObject, _IRP* Irp)
  153. {
  154.     UNREFERENCED_PARAMETER(DeviceObject);
  155.     UNREFERENCED_PARAMETER(Irp);
  156.     DbgPrint("[51asm] %s\n", __FUNCTION__);

  157.     //获取用户的缓冲区
  158.     PVOID pBuffer = Irp->UserBuffer;   //就是用户参数的 缓冲地址

  159.     //获取当前IRP堆栈
  160.     PIO_STACK_LOCATION pIrpStack = IoGetCurrentIrpStackLocation(Irp);
  161.     ULONG nLength = pIrpStack->Parameters.Write.Length;    //就是用户参数的 缓冲大小  Write表示写操作
  162.    
  163.     DbgPrint("[51asm] %s pBuffer:%p nLength:%d\n", __FUNCTION__, pBuffer, nLength);

  164.     if (nLength > sizeof(g_Registers.nVirtualReg)) {
  165.         nLength = sizeof(g_Registers.nVirtualReg);
  166.     }
  167.       
  168.     RtlCopyMemory(&g_Registers.nVirtualReg, pBuffer, nLength);  //将用户数据拷贝进内核的缓冲区


  169.     //完成请求  如果没完成 3环的程序就会挂起
  170.     Irp->IoStatus.Status = STATUS_SUCCESS;   //状态,成功
  171.     Irp->IoStatus.Information = nLength; //成功操作的字节数,非读写一般是0 就算是读写文件 dwBytes的返回值

  172.     //第二个参数线程的优先级,内核一般时 IO_NO_INCREMENT 不提升优先级
  173.     IoCompleteRequest(Irp, IO_NO_INCREMENT);

  174.     return STATUS_SUCCESS;
  175. }

  176. NTSTATUS DispatchControl(_DEVICE_OBJECT* DeviceObject, _IRP* Irp)
  177. {
  178.     UNREFERENCED_PARAMETER(DeviceObject);
  179.     UNREFERENCED_PARAMETER(Irp);
  180.     DbgPrint("[51asm] %s\n", __FUNCTION__);


  181.     //获取当前IRP堆栈
  182.     PIO_STACK_LOCATION pIrpStack = IoGetCurrentIrpStackLocation(Irp);

  183.     //获取用户的缓冲区
  184.     PVOID pOutBuffer = Irp->UserBuffer;  //获取用户输出缓冲区
  185.     ULONG nOutLength = pIrpStack->Parameters.DeviceIoControl.OutputBufferLength;   //获取用户输出缓冲区大小

  186.     PVOID pInputBuffer = pIrpStack->Parameters.DeviceIoControl.Type3InputBuffer;  //获取用户输入缓冲区
  187.     ULONG nInputLength = pIrpStack->Parameters.DeviceIoControl.InputBufferLength;  //获取用户输入缓冲区大小

  188.     ULONG nIoControlCode = pIrpStack->Parameters.DeviceIoControl.IoControlCode;    //获取操作码

  189.     //DbgPrint("[51asm] %s nIoControlCode:%p pInputBuffer:%p nInputLength:%d pOutBuffer:%p nOutLength:%d\n", __FUNCTION__,  nIoControlCode, pBuffer, nLength);

  190.     DbgPrint("[51asm] %s nIoControlCode:%p pInputBuffer:%p nInputLength:%d pOutBuffer:%p nOutLength:%d\n"
  191.         , __FUNCTION__, nIoControlCode, pOutBuffer, nOutLength, pInputBuffer, nInputLength);


  192.     ULONG nSize = 0;
  193.     if (nOutLength!=4)
  194.     {
  195.         nOutLength = 4;
  196.     }
  197.     if (nInputLength != 4)
  198.     {
  199.         nInputLength = 4;
  200.     }

  201.     KeEnterCriticalRegion();
  202.     switch (nIoControlCode) {
  203.     case IOCTL_GET_REG1:
  204.         RtlCopyMemory(pOutBuffer, &g_Registers.nVirtualReg, nOutLength);
  205.         nSize = nOutLength;

  206.         DbgPrint("[51asm] %s IOCTL_GET_REG1 value:%d\n", __FUNCTION__, g_Registers.nVirtualReg);
  207.         break;
  208.     case IOCTL_GET_REG2:
  209.         RtlCopyMemory(pOutBuffer, &g_Registers.nVirtualReg2, nOutLength);
  210.         nSize = nOutLength;

  211.         DbgPrint("[51asm] %s IOCTL_GET_REG2 value:%d\n", __FUNCTION__, g_Registers.nVirtualReg2);
  212.         break;
  213.     case IOCTL_GET_REG3:
  214.         RtlCopyMemory(pOutBuffer, &g_Registers.nVirtualReg3, nOutLength);
  215.         nSize = nOutLength;

  216.         DbgPrint("[51asm] %s IOCTL_GET_REG2 value:%d\n", __FUNCTION__, g_Registers.nVirtualReg3);
  217.         break;
  218.     case IOCTL_SET_REG1:
  219.         RtlCopyMemory(&g_Registers.nVirtualReg, pInputBuffer, nInputLength);
  220.         nSize = nInputLength;

  221.         DbgPrint("[51asm] %s IOCTL_SET_REG1 value:%d\n", __FUNCTION__, g_Registers.nVirtualReg);
  222.         break;
  223.     case IOCTL_SET_REG2:
  224.         RtlCopyMemory(&g_Registers.nVirtualReg2, pInputBuffer, nInputLength);
  225.         nSize = nInputLength;

  226.         DbgPrint("[51asm] %s IOCTL_SET_REG2 value:%d\n", __FUNCTION__, g_Registers.nVirtualReg2);
  227.         break;
  228.     case IOCTL_SET_REG3:
  229.         RtlCopyMemory(&g_Registers.nVirtualReg3, pInputBuffer, nInputLength);
  230.         nSize = nInputLength;

  231.         DbgPrint("[51asm] %s IOCTL_SET_REG3 value:%d\n", __FUNCTION__, g_Registers.nVirtualReg3);
  232.         break;
  233.     }
  234.     KeLeaveCriticalRegion();

  235.     Irp->IoStatus.Status = STATUS_SUCCESS;
  236.     Irp->IoStatus.Information = nSize;
  237.     IoCompleteRequest(Irp, IO_NO_INCREMENT);

  238.     return STATUS_SUCCESS;
  239. }
复制代码


测试代码
  1. #include <stdio.h>
  2. #include <Windows.h>
  3. #include <stdlib.h>
  4. #include <winioctl.h>

  5. struct MyDeviceExt {
  6.     int nVirtualReg;
  7.     int nVirtualReg2;
  8.     int nVirtualReg3;
  9. };

  10. //定义控制码
  11. #define MY_CODE_BASE 0x800
  12. #define MY_CTL_CODE(code) CTL_CODE(FILE_DEVICE_UNKNOWN, MY_CODE_BASE + code, METHOD_BUFFERED, FILE_ANY_ACCESS)
  13. #define IOCTL_GET_REG1 MY_CTL_CODE(0)
  14. #define IOCTL_GET_REG2 MY_CTL_CODE(1)
  15. #define IOCTL_GET_REG3 MY_CTL_CODE(2)
  16. #define IOCTL_SET_REG1 MY_CTL_CODE(3)
  17. #define IOCTL_SET_REG2 MY_CTL_CODE(4)
  18. #define IOCTL_SET_REG3 MY_CTL_CODE(5)


  19. void DisplayErrorText()
  20. {
  21.     LPVOID lpMsgBuf;
  22.     FormatMessage(
  23.         FORMAT_MESSAGE_ALLOCATE_BUFFER |
  24.         FORMAT_MESSAGE_FROM_SYSTEM |
  25.         FORMAT_MESSAGE_IGNORE_INSERTS,
  26.         NULL,
  27.         GetLastError(),
  28.         MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
  29.         (LPTSTR)&lpMsgBuf,
  30.         0,
  31.         NULL
  32.     );

  33.     printf((LPCTSTR)lpMsgBuf);

  34.     // Free the buffer.
  35.     LocalFree(lpMsgBuf);
  36. }

  37. int main()
  38. {
  39.     HANDLE hFile = CreateFile("\\\\?\\CR42VirtualRegister",
  40.         GENERIC_ALL,
  41.         0,
  42.         NULL,
  43.         OPEN_EXISTING,
  44.         FILE_ATTRIBUTE_NORMAL,
  45.         NULL);
  46.     if (hFile == INVALID_HANDLE_VALUE)
  47.     {
  48.         DisplayErrorText();
  49.     }
  50.     else {
  51.         printf("hFile = %p\n", hFile);
  52.     }




  53.     MyDeviceExt DevExt = { 0 };
  54.     DWORD dwBytes = 0;
  55.     DevExt.nVirtualReg = 100;
  56.     if (!WriteFile(hFile, &DevExt, 10000, &dwBytes, NULL))
  57.     {
  58.         DisplayErrorText();
  59.     }
  60.     else
  61.     {
  62.         printf("WriteFile dwBytes = %d  VirtualReg:%d\n", dwBytes, DevExt.nVirtualReg);
  63.     }


  64.     DevExt.nVirtualReg = 2;
  65.     if (!ReadFile(hFile, &DevExt, sizeof(DevExt), &dwBytes, NULL))
  66.     {
  67.         DisplayErrorText();
  68.     }
  69.     else
  70.     {
  71.         printf("ReadFile dwBytes = %d  VirtualReg:%d\n", dwBytes, DevExt.nVirtualReg);
  72.     }


  73.     //向内核传递数据,不需要接收数据,所以给空
  74.     int nReg = 123;
  75.     DeviceIoControl(hFile, IOCTL_SET_REG1, &nReg, sizeof(nReg), NULL, 0, &dwBytes, NULL);
  76.     printf("DeviceIoControl dwBytes = %d  nReg:%d\n", dwBytes, nReg);

  77.     //从内核获取数据,不需要传递数据,所以给空
  78.     DeviceIoControl(hFile, IOCTL_GET_REG1, NULL, 0, &nReg, sizeof(nReg), &dwBytes, NULL);
  79.     printf("DeviceIoControl dwBytes = %d  nReg:%d\n", dwBytes, nReg);

  80.     CloseHandle(hFile);

  81.     system("pause");
  82.     return 0;
  83. }
复制代码


测试
1662648797545-567a5ace-81ec-4973-9cd0-2f21b3f949a6.png
一般在做内核驱动的时候给个控制和打开,关闭就可以,读和写可以实现也可以不实现
解决同步问题
可以通过临界区解决
KeEnterCriticalRegion();   //进去临界区  //需要同步的代码 KeLeaveCriticalRegion();  //退出临界区

  KeEnterCriticalRegion();  


  1. KeEnterCriticalRegion();
  2.   switch(nIoControlCode) {
  3.   case IOCTL_GET_REG1:
  4.     RtlCopyMemory(pBuffer, &g_Registers.nVirtualReg, nLength);
  5.     nSize = nLength;
  6.     DbgPrint("[51asm] %s IOCTL_GET_REG1 value:%d\n", __FUNCTION__, g_Registers.nVirtualReg);
  7.     break;
  8.   case IOCTL_GET_REG2:
  9.     RtlCopyMemory(pBuffer, &g_Registers.nVirtualReg2, nLength);
  10.     nSize = nLength;
  11.     DbgPrint("[51asm] %s IOCTL_GET_REG2 value:%d\n", __FUNCTION__, g_Registers.nVirtualReg2);
  12.     break;
  13.   case IOCTL_GET_REG3:
  14.     RtlCopyMemory(pBuffer, &g_Registers.nVirtualReg3, nLength);
  15.     nSize = nLength;
  16.     DbgPrint("[51asm] %s IOCTL_GET_REG2 value:%d\n", __FUNCTION__, g_Registers.nVirtualReg3);
  17.     break;
  18.   case IOCTL_SET_REG1:
  19.     RtlCopyMemory(&g_Registers.nVirtualReg, pBuffer, nLength);
  20.     DbgPrint("[51asm] %s IOCTL_SET_REG1 value:%d\n", __FUNCTION__, g_Registers.nVirtualReg);
  21.     nSize = nLength;
  22.     break;
  23.   case IOCTL_SET_REG2:
  24.     RtlCopyMemory(&g_Registers.nVirtualReg2, pBuffer, nLength);
  25.     DbgPrint("[51asm] %s IOCTL_SET_REG2 value:%d\n", __FUNCTION__, g_Registers.nVirtualReg2);
  26.     nSize = nLength;
  27.     break;
  28.   case IOCTL_SET_REG3:
  29.     RtlCopyMemory(&g_Registers.nVirtualReg3, pBuffer, nLength);
  30.     DbgPrint("[51asm] %s IOCTL_SET_REG3 value:%d\n", __FUNCTION__, g_Registers.nVirtualReg3);
  31.     nSize = nLength;
  32.     break;
  33.   }
  34.   KeLeaveCriticalRegion();
复制代码



注意: 上面的代码存在问题 ,set功能会导致蓝屏
安全问题
我们的回调函数可能在任何一个进程,线程中运行
0~2G 不共享
2G~4G 共享
内核中不推荐直接访问用户空间地址(线程上下文相关)
3环的地址和大小是不可信任的
问题
  • 线程上下文问题: 简单来说就是 回调函数可能被操作系统的线程执行了,这个时候访问用户空间地址是,进程和线程已经切了,切了之后这个3环的地址可能在对方进程是一个无效的地址,于是就会蓝屏
  • 安全问题 :内核驱动去3环缓冲区读数据 , 如果有以下代码
void *p = malloc();
free(p);
ReadFile(p);
驱动访问 *p 地址就会蓝屏,因此很容易被人攻击
通讯方式
1662654080003-2e38ba99-f2c1-42a9-bc97-f33e3c031b25.png
  • 其它方式(直接访问用户地址) : 不推荐,很容易蓝屏
  • 缓冲区方式 (常用)
在创建设备时设置
//设置设备的缓冲区通讯方式 控制是在操作码里面设置的
pDevObj->Flags |= DO_BUFFERED_IO; //缓冲区通讯方式
1662654204214-ffcacbf0-d497-495e-bb10-217c22c34f69.png
1662657062092-2d989cee-464e-49b6-a9f5-12d37ce679f2.png
  • 直接方式
//设置设备的缓冲区通讯方式 控制是在操作码里面设置的
pDevObj->Flags |= DO_DIRECT_IO; //直接方式
1662654224039-7181e3dc-5195-45f7-b4f7-704dd005d8e3.png
1662657155615-b4282844-24ed-4716-afe6-5f13d51a375c.png
直接或者缓冲只能设置一个,不能同时设两个
控制输入和输出共用一个缓冲区,是因为 系统先把输入缓冲区的数据读进来,此时输入缓冲区就没用了,刚好可以用来作输出缓冲区.
  1. --------------------Hello.h------------------

  2. #pragma once
  3. extern "C" {
  4. #include <Ntddk.h>

  5.     struct MyDeviceExt {
  6.         int nVirtualReg;
  7.         int nVirtualReg2;
  8.         int nVirtualReg3;
  9.     };

  10.     #define DEVICE_NAME  L"\\Device\\CR42"
  11.     #define SYMBOL_NAME L"\\DosDevices\\CR42VirtualRegister"

  12.     //定义控制码
  13.     #define MY_CODE_BASE 0x800
  14.     #define MY_CTL_CODE(code) CTL_CODE(FILE_DEVICE_UNKNOWN, MY_CODE_BASE + code, METHOD_BUFFERED, FILE_ANY_ACCESS)
  15.     #define IOCTL_GET_REG1 MY_CTL_CODE(0)
  16.     #define IOCTL_GET_REG2 MY_CTL_CODE(1)
  17.     #define IOCTL_GET_REG3 MY_CTL_CODE(2)
  18.     #define IOCTL_SET_REG1 MY_CTL_CODE(3)
  19.     #define IOCTL_SET_REG2 MY_CTL_CODE(4)
  20.     #define IOCTL_SET_REG3 MY_CTL_CODE(5)


  21.     /*驱动入口函数*/
  22.     NTSTATUS DriverEntry(__in struct _DRIVER_OBJECT* DriverObject,
  23.         __in PUNICODE_STRING  RegistryPath);


  24.     VOID Unload(__in struct _DRIVER_OBJECT* DriverObject);

  25.     NTSTATUS DispatchCreate(
  26.         _In_ struct _DEVICE_OBJECT* DeviceObject,
  27.         _Inout_ struct _IRP* Irp
  28.     );

  29.     NTSTATUS DispatchClose(
  30.         _In_ struct _DEVICE_OBJECT* DeviceObject,
  31.         _Inout_ struct _IRP* Irp
  32.     );

  33.     NTSTATUS DispatchRead(
  34.         _In_ struct _DEVICE_OBJECT* DeviceObject,
  35.         _Inout_ struct _IRP* Irp
  36.     );

  37.     NTSTATUS DispatchWrite(
  38.         _In_ struct _DEVICE_OBJECT* DeviceObject,
  39.         _Inout_ struct _IRP* Irp
  40.     );

  41.     NTSTATUS DispatchControl(
  42.         _In_ struct _DEVICE_OBJECT* DeviceObject,
  43.         _Inout_ struct _IRP* Irp
  44.     );

  45. }

  46. --------------------Hello.cpp------------------

  47. #include "Hello.h"

  48. MyDeviceExt g_Registers;

  49. /*驱动卸载函数 clean_up*/
  50. VOID Unload(__in struct _DRIVER_OBJECT* DriverObject)
  51. {
  52.     DbgPrint("[51asm] Unload! DriverObject:%p\n", DriverObject);

  53.     //删除符号链接
  54.     UNICODE_STRING ustrSymbolName;
  55.     RtlInitUnicodeString(&ustrSymbolName, SYMBOL_NAME);
  56.     IoDeleteSymbolicLink(&ustrSymbolName);

  57.     //删除设备
  58.     if (DriverObject->DeviceObject != NULL)
  59.         IoDeleteDevice(DriverObject->DeviceObject);
  60. }

  61. /*1.驱动入口函数*/
  62. NTSTATUS DriverEntry(
  63.     __in struct _DRIVER_OBJECT* DriverObject,
  64.     __in PUNICODE_STRING  RegistryPath)
  65. {
  66.     DbgPrint("[51asm] Hello WDK! DriverObject:%p RegistryPath:%wZ\n",
  67.         DriverObject, RegistryPath);

  68.     //2.创建设备
  69.     UNICODE_STRING ustrDevName;
  70.     RtlInitUnicodeString(&ustrDevName, DEVICE_NAME);  //等于上面的3行代码

  71.     PDEVICE_OBJECT pDevObj = NULL;
  72.     NTSTATUS Status = IoCreateDevice(DriverObject,
  73.         0,
  74.         &ustrDevName,
  75.         FILE_DEVICE_UNKNOWN,  //不知道的设备类型
  76.         FILE_DEVICE_SECURE_OPEN,
  77.         FALSE, //独占
  78.         &pDevObj);
  79.     if (!NT_SUCCESS(Status)) {
  80.         DbgPrint("[51asm] IoCreateDevice Status:%p\n", Status);
  81.         return Status;
  82.     }
  83.     //设置设备的缓冲区通讯方式
  84.     pDevObj->Flags |= DO_BUFFERED_IO; //缓冲区通讯方式 (这里只能设置读和写的)
  85.     //pDevObj->Flags |= DO_DIRECT_IO;   //直接方式


  86.     //MS-DOS Device Names  创建符号链接
  87.     UNICODE_STRING ustrSymbolName;
  88.     RtlInitUnicodeString(&ustrSymbolName, SYMBOL_NAME);
  89.     Status = IoCreateSymbolicLink(&ustrSymbolName, &ustrDevName);
  90.     if (!NT_SUCCESS(Status)) {
  91.         DbgPrint("[51asm] IoCreateSymbolicLink Status:%p\n", Status);

  92.         if (pDevObj != NULL)
  93.             IoDeleteDevice(pDevObj);

  94.         return Status;
  95.     }
  96.     DbgPrint("[51asm] IoCreateSymbolicLink %wZ OK\n", &ustrSymbolName);

  97.     //3.注册派遣函数
  98.     DriverObject->MajorFunction[IRP_MJ_CREATE] = &DispatchCreate;
  99.     DriverObject->MajorFunction[IRP_MJ_CLOSE] = &DispatchClose;
  100.     DriverObject->MajorFunction[IRP_MJ_READ] = &DispatchRead;
  101.     DriverObject->MajorFunction[IRP_MJ_WRITE] = &DispatchWrite;
  102.     DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = &DispatchControl;


  103.     //4.注册卸载函数
  104.     DriverObject->DriverUnload = Unload;

  105.     return STATUS_SUCCESS;
  106. }


  107. NTSTATUS DispatchCreate(_DEVICE_OBJECT* DeviceObject, _IRP* Irp)
  108. {
  109.     UNREFERENCED_PARAMETER(DeviceObject);
  110.     UNREFERENCED_PARAMETER(Irp);
  111.     DbgPrint("[51asm] %s\n", __FUNCTION__);

  112.     //完成请求  如果没完成 3环的程序就会挂起
  113.     Irp->IoStatus.Status = STATUS_SUCCESS;   //状态,成功
  114.     Irp->IoStatus.Information = 0; //成功操作的字节数,非读写一般是0 就算是读写文件 dwBytes的返回值

  115.     //第二个参数线程的优先级,内核一般时 IO_NO_INCREMENT 不提升优先级
  116.     IoCompleteRequest(Irp, IO_NO_INCREMENT);  

  117.     return STATUS_SUCCESS;
  118. }

  119. NTSTATUS DispatchClose(_DEVICE_OBJECT* DeviceObject, _IRP* Irp)
  120. {
  121.     UNREFERENCED_PARAMETER(DeviceObject);
  122.     UNREFERENCED_PARAMETER(Irp);
  123.     DbgPrint("[51asm] %s\n", __FUNCTION__);

  124.     //完成请求  如果没完成 3环的程序就会挂起
  125.     Irp->IoStatus.Status = STATUS_SUCCESS;   //状态,成功
  126.     Irp->IoStatus.Information = 0; //成功操作的字节数,非读写一般是0 就算是读写文件 dwBytes的返回值

  127.     //第二个参数线程的优先级,内核一般时 IO_NO_INCREMENT 不提升优先级
  128.     IoCompleteRequest(Irp, IO_NO_INCREMENT);

  129.     return STATUS_SUCCESS;
  130. }

  131. NTSTATUS DispatchRead(_DEVICE_OBJECT* DeviceObject, _IRP* Irp)
  132. {
  133.     UNREFERENCED_PARAMETER(DeviceObject);
  134.     DbgPrint("[51asm] %s\n", __FUNCTION__);

  135.     //获取用户的缓冲区
  136.     //PVOID pBuffer = Irp->UserBuffer;   //就是用户参数的 缓冲地址
  137.     PVOID pBuffer = Irp->AssociatedIrp.SystemBuffer;  //因为设置了缓冲区通讯方式,用户缓冲区数据从系统申请的缓冲区读

  138.      //获取当前IRP堆栈
  139.     PIO_STACK_LOCATION pIrpStack = IoGetCurrentIrpStackLocation(Irp);
  140.     ULONG nLength = pIrpStack->Parameters.Read.Length;    //就是用户参数的 缓冲大小  read表示读操作
  141.    
  142.     DbgPrint("[51asm] %s pBuffer:%p nLength:%d\n", __FUNCTION__, pBuffer, nLength);
  143.     DbgPrint("[51asm] %s UserBuffer:%p pBuffer:%p nLength:%d\n", __FUNCTION__, Irp->UserBuffer, pBuffer, nLength);

  144.     if (nLength > sizeof(g_Registers.nVirtualReg)) {  //判断用户缓冲区大小是否足够
  145.         nLength = sizeof(g_Registers.nVirtualReg);
  146.     }
  147.         
  148.     //拷贝信息  就等于  memcoy
  149.     RtlCopyMemory(pBuffer,&g_Registers.nVirtualReg, nLength);  //把寄存器数据数据传入用缓冲区

  150.     //完成请求  如果没完成 3环的程序就会挂起
  151.     Irp->IoStatus.Status = STATUS_SUCCESS;   //状态,成功
  152.     Irp->IoStatus.Information = nLength; //成功操作的字节数,非读写一般是0 就算是读写文件 dwBytes的返回值

  153.     //第二个参数线程的优先级,内核一般时 IO_NO_INCREMENT 不提升优先级
  154.     IoCompleteRequest(Irp, IO_NO_INCREMENT);

  155.     return STATUS_SUCCESS;
  156. }


  157. NTSTATUS DispatchWrite(_DEVICE_OBJECT* DeviceObject, _IRP* Irp)
  158. {
  159.     UNREFERENCED_PARAMETER(DeviceObject);
  160.     DbgPrint("[51asm] %s\n", __FUNCTION__);

  161.     //获取用户的缓冲区
  162.     //PVOID pBuffer = Irp->UserBuffer;   //就是用户参数的 缓冲地址
  163.     PVOID pBuffer = Irp->AssociatedIrp.SystemBuffer;  //因为设置了缓冲区通讯方式,用户缓冲区数据从系统申请的缓冲区读

  164.     //获取当前IRP堆栈
  165.     PIO_STACK_LOCATION pIrpStack = IoGetCurrentIrpStackLocation(Irp);
  166.     ULONG nLength = pIrpStack->Parameters.Write.Length;    //就是用户参数的 缓冲大小  Write表示写操作
  167.    
  168.    
  169.     DbgPrint("[51asm] %s pBuffer:%p nLength:%d\n", __FUNCTION__, pBuffer, nLength);
  170.     DbgPrint("[51asm] %s UserBuffer:%p pBuffer:%p nLength:%d\n",__FUNCTION__, Irp->UserBuffer, pBuffer, nLength);

  171.     if (nLength > sizeof(g_Registers.nVirtualReg)) {
  172.         nLength = sizeof(g_Registers.nVirtualReg);
  173.     }
  174.       
  175.     RtlCopyMemory(&g_Registers.nVirtualReg, pBuffer, nLength);  //将用户数据拷贝进内核的缓冲区


  176.     //完成请求  如果没完成 3环的程序就会挂起
  177.     Irp->IoStatus.Status = STATUS_SUCCESS;   //状态,成功
  178.     Irp->IoStatus.Information = nLength; //成功操作的字节数,非读写一般是0 就算是读写文件 dwBytes的返回值

  179.     //第二个参数线程的优先级,内核一般时 IO_NO_INCREMENT 不提升优先级
  180.     IoCompleteRequest(Irp, IO_NO_INCREMENT);

  181.     return STATUS_SUCCESS;
  182. }

  183. NTSTATUS DispatchControl(_DEVICE_OBJECT* DeviceObject, _IRP* Irp)
  184. {
  185.     UNREFERENCED_PARAMETER(DeviceObject);
  186.     DbgPrint("[51asm] %s\n", __FUNCTION__);

  187.     //获取当前IRP堆栈
  188.     PIO_STACK_LOCATION pIrpStack = IoGetCurrentIrpStackLocation(Irp);

  189. #if 0
  190.     //获取用户的缓冲区
  191.     PVOID pOutBuffer = Irp->UserBuffer;  //获取用户输出缓冲区
  192.     ULONG nOutLength = pIrpStack->Parameters.DeviceIoControl.OutputBufferLength;   //获取用户输出缓冲区大小

  193.     PVOID pInputBuffer = pIrpStack->Parameters.DeviceIoControl.Type3InputBuffer;  //获取用户输入缓冲区
  194.     ULONG nInputLength = pIrpStack->Parameters.DeviceIoControl.InputBufferLength;  //获取用户输入缓冲区大小
  195. #endif // 0


  196.     //获取用户的缓冲区
  197.     PVOID pBuffer = Irp->AssociatedIrp.SystemBuffer;  //因为设置了缓冲区通讯方式,用户缓冲区数据从系统申请的缓冲区读
  198.     ULONG nLength = pIrpStack->Parameters.DeviceIoControl.InputBufferLength;
  199.     ULONG nIoControlCode = pIrpStack->Parameters.DeviceIoControl.IoControlCode;    //获取操作码

  200.     DbgPrint("[51asm] %s nIoControlCode:%p pBuffer:%p nLength:%d\n", __FUNCTION__,
  201.         nIoControlCode, pBuffer, nLength);

  202.     ULONG nSize = 0;
  203.     if (nLength != 4)
  204.     {
  205.         nLength = 4;
  206.     }
  207.    
  208.     KeEnterCriticalRegion();
  209.     switch (nIoControlCode) {
  210.     case IOCTL_GET_REG1:
  211.         RtlCopyMemory(pBuffer, &g_Registers.nVirtualReg, nLength);
  212.         nSize = nLength;

  213.         DbgPrint("[51asm] %s IOCTL_GET_REG1 value:%d\n", __FUNCTION__, g_Registers.nVirtualReg);
  214.         break;
  215.     case IOCTL_GET_REG2:
  216.         RtlCopyMemory(pBuffer, &g_Registers.nVirtualReg2, nLength);
  217.         nSize = nLength;

  218.         DbgPrint("[51asm] %s IOCTL_GET_REG2 value:%d\n", __FUNCTION__, g_Registers.nVirtualReg2);
  219.         break;
  220.     case IOCTL_GET_REG3:
  221.         RtlCopyMemory(pBuffer, &g_Registers.nVirtualReg3, nLength);
  222.         nSize = nLength;

  223.         DbgPrint("[51asm] %s IOCTL_GET_REG2 value:%d\n", __FUNCTION__, g_Registers.nVirtualReg3);
  224.         break;
  225.     case IOCTL_SET_REG1:
  226.         RtlCopyMemory(&g_Registers.nVirtualReg, pBuffer, nLength);
  227.         nSize = nLength;

  228.         DbgPrint("[51asm] %s IOCTL_SET_REG1 value:%d\n", __FUNCTION__, g_Registers.nVirtualReg);
  229.         break;
  230.     case IOCTL_SET_REG2:
  231.         RtlCopyMemory(&g_Registers.nVirtualReg2, pBuffer, nLength);
  232.         nSize = nLength;

  233.         DbgPrint("[51asm] %s IOCTL_SET_REG2 value:%d\n", __FUNCTION__, g_Registers.nVirtualReg2);
  234.         break;
  235.     case IOCTL_SET_REG3:
  236.         RtlCopyMemory(&g_Registers.nVirtualReg3, pBuffer, nLength);
  237.         nSize = nLength;

  238.         DbgPrint("[51asm] %s IOCTL_SET_REG3 value:%d\n", __FUNCTION__, g_Registers.nVirtualReg3);
  239.         break;
  240.     }
  241.     KeLeaveCriticalRegion();

  242.     Irp->IoStatus.Status = STATUS_SUCCESS;
  243.     Irp->IoStatus.Information = nSize;
  244.     IoCompleteRequest(Irp, IO_NO_INCREMENT);

  245.     return STATUS_SUCCESS;
  246. }
复制代码

  1. #include <stdio.h>
  2. #include <Windows.h>
  3. #include <stdlib.h>
  4. #include <winioctl.h>

  5. struct MyDeviceExt {
  6.     int nVirtualReg;
  7.     int nVirtualReg2;
  8.     int nVirtualReg3;
  9. };

  10. //定义控制码
  11. #define MY_CODE_BASE 0x800
  12. #define MY_CTL_CODE(code) CTL_CODE(FILE_DEVICE_UNKNOWN, MY_CODE_BASE + code, METHOD_BUFFERED, FILE_ANY_ACCESS)
  13. #define IOCTL_GET_REG1 MY_CTL_CODE(0)
  14. #define IOCTL_GET_REG2 MY_CTL_CODE(1)
  15. #define IOCTL_GET_REG3 MY_CTL_CODE(2)
  16. #define IOCTL_SET_REG1 MY_CTL_CODE(3)
  17. #define IOCTL_SET_REG2 MY_CTL_CODE(4)
  18. #define IOCTL_SET_REG3 MY_CTL_CODE(5)


  19. void DisplayErrorText()
  20. {
  21.     LPVOID lpMsgBuf;
  22.     FormatMessage(
  23.         FORMAT_MESSAGE_ALLOCATE_BUFFER |
  24.         FORMAT_MESSAGE_FROM_SYSTEM |
  25.         FORMAT_MESSAGE_IGNORE_INSERTS,
  26.         NULL,
  27.         GetLastError(),
  28.         MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
  29.         (LPTSTR)&lpMsgBuf,
  30.         0,
  31.         NULL
  32.     );

  33.     printf((LPCTSTR)lpMsgBuf);

  34.     // Free the buffer.
  35.     LocalFree(lpMsgBuf);
  36. }

  37. int main()
  38. {
  39.     HANDLE hFile = CreateFile("\\\\?\\CR42VirtualRegister",
  40.         GENERIC_ALL,
  41.         0,
  42.         NULL,
  43.         OPEN_EXISTING,
  44.         FILE_ATTRIBUTE_NORMAL,
  45.         NULL);
  46.     if (hFile == INVALID_HANDLE_VALUE)
  47.     {
  48.         DisplayErrorText();
  49.     }
  50.     else {
  51.         printf("hFile = %p\n", hFile);
  52.     }

  53.     MyDeviceExt DevExt = { 0 };
  54.     DWORD dwBytes = 0;
  55.     DevExt.nVirtualReg = 100;
  56.     if (!WriteFile(hFile, &DevExt, 10000, &dwBytes, NULL))
  57.     {
  58.         DisplayErrorText();
  59.     }
  60.     else
  61.     {
  62.         printf("WriteFile dwBytes = %d  VirtualReg:%d\n", dwBytes, DevExt.nVirtualReg);
  63.     }

  64.     DevExt.nVirtualReg = 2;
  65.     if (!ReadFile(hFile, &DevExt, sizeof(DevExt), &dwBytes, NULL))
  66.     {
  67.         DisplayErrorText();
  68.     }
  69.     else
  70.     {
  71.         printf("ReadFile dwBytes = %d  VirtualReg:%d\n", dwBytes, DevExt.nVirtualReg);
  72.     }
  73.     int nReg = 123;
  74.     DeviceIoControl(hFile, IOCTL_SET_REG1, &nReg, sizeof(nReg), NULL, 0, &dwBytes, NULL);
  75.     printf("DeviceIoControl dwBytes = %d  nReg:%d\n", dwBytes, nReg);

  76.     nReg = 0;
  77.     DeviceIoControl(hFile, IOCTL_GET_REG1, NULL, 0, &nReg, sizeof(nReg), &dwBytes, NULL);
  78.     printf("DeviceIoControl dwBytes = %d  nReg:%d\n", dwBytes, nReg);

  79.     CloseHandle(hFile);

  80.     system("pause");
  81.     return 0;
  82. }
复制代码




但这种方式他有缺点,就是效率比较低,特别对于一个硬件设备来说,他在大量传输数据的时候他会有卡顿,因为数据量很大,每次返回数据有2次拷贝
内存共享可以极大的提交效率,但同时也会带来安全问题(0环和3环同时操作一个缓冲区)
因此可以通过直接方式通讯,应发跟通讯方式没啥区别,只不过获取缓冲区的方式不同,但注意要进行地址判断,防止地址为空,一般直接方式只会在硬件上看到,应为硬件比较简单
控制码里第3个参数它还有别的取值,对于直接方式,它有两个取值,两个挑一个用
METHOD_IN_DIRECT 1 ;直接方式 输入缓冲区
METHOD_OUT_DIRECT 2 ;直接方式 输出缓冲区
如果选择输出缓冲区直接,那么输出缓冲区就用Mdl,输入缓冲区就用SystemBuffer了;
输入缓冲区直接的话,输入缓冲区就用Mdl,输出就用SystemBuffer;反正只能选择一个
输入从Mdl拿
PVOID pInputBuffer =
​ MmGetSystemAddressForMdlSafe(Irp->MdlAddress,NormalPagePriority);
输出从SystemBuffer拿
​ PVOID pOutBuffer = Irp->AssociatedIrp.SystemBuffer;
内核的堆空间
内核的堆空间和3环的堆空间是分开的,不允许用户堆空间去操作内核的堆空间
内核申请堆空间
ExAllocatePool (不让用了)
1662620804708-596eb12b-22b3-45cb-b10d-218a428f2dc8.png
参数:
  • 内存分页池类型
  • 大小
或者
ExAllocatePoolWithTag
1662690868920-32e751ab-efd8-4020-af97-80cb66b4c12f.png
参数:
  • 内存分页池类型
  • 大小
  • 标记 (四个字符)
跟上一个相比多一个参数: 标记
防止内存泄露,因为我们驱动卸载以后可以通过这个标记去找内存,如果找到了就存在内存泄漏,如果没找到就不存在
标记大小是四个字符, 例如: '1234' (注意单引号),意思是这里的最低一个字节用 sacii 码4取代.,倒数第二个用ascii码 3 取代......... 即在内存中 是 0x34 0x33 0x32 0x31 因为内存中的值小尾方式保存,因此通常会倒过来写 '4321',这样在内存中就是顺序排列
内存分页池
  • NonPagedPool 非分页内存池
  • PagedPool 分页内存池
我们申请内存时必须告诉系统从那哪个内存池里面申请内存,不管哪个池分配都是一个分页
NonPagedPool 相当于买房子 PagedPool 相当于租房子
当申请分页内存时,如果操作系统没有内存,就会把内存里面的数丢到磁盘中,把控件腾出来给系统用,当系统用完之后,再从磁盘把你数据还回来,这个过程需要时间.在window里面一般叫做虚拟内存(把硬盘当内存用),默认是开的
1662693144216-60529533-8990-4688-a2f6-dcce2c3929c6.png
非分页内存是系统没内存用就没内存用,除非内存自己没用了,否则系统没法使用
因为我们申请内存时一般都是申请分页内存,如果代码很重要(实时性要求很高),需要一直再用,就可以考虑用非分页内存池.如果我们把非分页内存申请完,系统将会死机
系统的代码都是在非分页内存池中,一般正常的驱动代码都是在分页内存池
3环只能申请分页内存,不能申请非分页内存
自己写的函数(就那些Read,Write....)代码,默认都是非分页内存池中
申请分页内存
通过3环的一个宏来解决
#param alloc_text("PAGENAME",FUN1)
其中PAGENAME是节的名称,PAGE就是把代码放到分页内存池,FUN1是函数名称
#pragma alloc_text("PAGE", DispatchRead);这意思就是把DispatchRead函数放到分页内存里
它可以用逗号分隔,写多个函数,但是不推荐,一般还是一个函数写一行这样的,方便修改
#pragma alloc_text( "PAGE", DispatchCreate)
#pragma alloc_text( "PAGE", DispatchClose)
#pragma alloc_text( "PAGE", DispatchWrite)
#pragma alloc_text( "PAGE", DispatchRead)
#pragma alloc_text( "PAGE", DispatchControl)
#pragma alloc_text( "PAGE", Unload)
#pragma alloc_text( "INIT", DriverEntry)
  • 操作系统会定义节来把函数放到对应的节里
  • 1662657201533-e87c0c41-9ad2-4bf9-a73c-6c707f90daee.png

    • 放到.text就非分页内存
    • 放到PAGE就是分页内存
    • .INIT节是放一些只执行一次的代码 ,用完之后内存就会被释放

因为内核的内存是很宝贵的,而DriverEntry这个函数执行完之后就没用了,它就入口执行一次,所以就把他放到.INIT节里,就是一次性的简单来说就是驱动加载完后这个节的内存全部会被释放掉,也就是DriverEntry执行完后INIT整个节内存就被释放了.Unload是不能放到这个节里的,因为可能执行卸载函数的时候这个内存已经被释放了,所以就释放的时候就崩了

  • 到此这个框架就完成了,要实现功能的话就写控制代码就好了
完整源码内核驱动
  1. #pragma once

  2. extern "C" {

  3. #include <Ntddk.h>

  4.     struct MyDeviceExt {
  5.         int nVirtualReg;
  6.         int nVirtualReg2;
  7.         int nVirtualReg3;
  8.     };

  9.     #define DEVICE_NAME  L"\\Device\\CR42"
  10.     #define SYMBOL_NAME L"\\DosDevices\\CR42VirtualRegister"

  11.         //定义控制码
  12.     #define MY_CODE_BASE 0x800
  13.     #define MY_CTL_CODE(code) CTL_CODE(FILE_DEVICE_UNKNOWN, MY_CODE_BASE + code, METHOD_BUFFERED, FILE_ANY_ACCESS)
  14.     #define IOCTL_GET_REG1 MY_CTL_CODE(0)
  15.     #define IOCTL_GET_REG2 MY_CTL_CODE(1)
  16.     #define IOCTL_GET_REG3 MY_CTL_CODE(2)
  17.     #define IOCTL_SET_REG1 MY_CTL_CODE(3)
  18.     #define IOCTL_SET_REG2 MY_CTL_CODE(4)
  19.     #define IOCTL_SET_REG3 MY_CTL_CODE(5)


  20.     /*驱动入口函数*/
  21.     NTSTATUS DriverEntry(__in struct _DRIVER_OBJECT* DriverObject,
  22.         __in PUNICODE_STRING  RegistryPath);


  23.     VOID Unload(__in struct _DRIVER_OBJECT* DriverObject);

  24.     NTSTATUS DispatchCreate(
  25.         _In_ struct _DEVICE_OBJECT* DeviceObject,
  26.         _Inout_ struct _IRP* Irp
  27.     );

  28.     NTSTATUS DispatchClose(
  29.         _In_ struct _DEVICE_OBJECT* DeviceObject,
  30.         _Inout_ struct _IRP* Irp
  31.     );

  32.     NTSTATUS DispatchRead(
  33.         _In_ struct _DEVICE_OBJECT* DeviceObject,
  34.         _Inout_ struct _IRP* Irp
  35.     );

  36.     NTSTATUS DispatchWrite(
  37.         _In_ struct _DEVICE_OBJECT* DeviceObject,
  38.         _Inout_ struct _IRP* Irp
  39.     );

  40.     NTSTATUS DispatchControl(
  41.         _In_ struct _DEVICE_OBJECT* DeviceObject,
  42.         _Inout_ struct _IRP* Irp
  43.     );

  44. #pragma alloc_text( "PAGE", DispatchCreate)
  45. #pragma alloc_text( "PAGE", DispatchClose)
  46. #pragma alloc_text( "PAGE", DispatchWrite)
  47. #pragma alloc_text( "PAGE", DispatchRead)
  48. #pragma alloc_text( "PAGE", DispatchControl)
  49. #pragma alloc_text( "PAGE", Unload)
  50. #pragma alloc_text( "INIT", DriverEntry)

  51. }
复制代码

  1. #include "Hello.h"


  2. MyDeviceExt g_Registers;


  3. /*驱动卸载函数 clean_up*/
  4. VOID Unload(__in struct _DRIVER_OBJECT* DriverObject)
  5. {
  6.     DbgPrint("[51asm] Unload! DriverObject:%p\n", DriverObject);

  7.     //删除符号链接
  8.     UNICODE_STRING ustrSymbolName;
  9.     RtlInitUnicodeString(&ustrSymbolName, SYMBOL_NAME);
  10.     IoDeleteSymbolicLink(&ustrSymbolName);

  11.     //删除设备
  12.     if (DriverObject->DeviceObject != NULL)
  13.         IoDeleteDevice(DriverObject->DeviceObject);
  14. }

  15. /*1.驱动入口函数*/
  16. NTSTATUS DriverEntry(
  17.     __in struct _DRIVER_OBJECT* DriverObject,
  18.     __in PUNICODE_STRING  RegistryPath)
  19. {
  20.     DbgPrint("[51asm] Hello WDK! DriverObject:%p RegistryPath:%wZ\n",
  21.         DriverObject, RegistryPath);

  22.     //2.创建设备
  23.     UNICODE_STRING ustrDevName;
  24.     RtlInitUnicodeString(&ustrDevName, DEVICE_NAME);  //等于上面的3行代码

  25.     PDEVICE_OBJECT pDevObj = NULL;
  26.     NTSTATUS Status = IoCreateDevice(DriverObject,
  27.         0,
  28.         &ustrDevName,
  29.         FILE_DEVICE_UNKNOWN,  //不知道的设备类型
  30.         FILE_DEVICE_SECURE_OPEN,
  31.         FALSE, //独占
  32.         &pDevObj);
  33.     if (!NT_SUCCESS(Status)) {
  34.         DbgPrint("[51asm] IoCreateDevice Status:%p\n", Status);
  35.         return Status;
  36.     }
  37.     //设置设备的缓冲区通讯方式
  38.     pDevObj->Flags |= DO_BUFFERED_IO; //缓冲区通讯方式
  39.     //pDevObj->Flags |= DO_DIRECT_IO;   //直接方式


  40.     //MS-DOS Device Names  创建符号链接
  41.     UNICODE_STRING ustrSymbolName;
  42.     RtlInitUnicodeString(&ustrSymbolName, SYMBOL_NAME);
  43.     Status = IoCreateSymbolicLink(&ustrSymbolName, &ustrDevName);
  44.     if (!NT_SUCCESS(Status)) {
  45.         DbgPrint("[51asm] IoCreateSymbolicLink Status:%p\n", Status);

  46.         if (pDevObj != NULL)
  47.             IoDeleteDevice(pDevObj);

  48.         return Status;
  49.     }
  50.     DbgPrint("[51asm] IoCreateSymbolicLink %wZ OK\n", &ustrSymbolName);



  51.     //3.注册派遣函数
  52.     DriverObject->MajorFunction[IRP_MJ_CREATE] = &DispatchCreate;
  53.     DriverObject->MajorFunction[IRP_MJ_CLOSE] = &DispatchClose;
  54.     DriverObject->MajorFunction[IRP_MJ_READ] = &DispatchRead;
  55.     DriverObject->MajorFunction[IRP_MJ_WRITE] = &DispatchWrite;
  56.     DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = &DispatchControl;


  57.     //4.注册卸载函数
  58.     DriverObject->DriverUnload = Unload;

  59.     return STATUS_SUCCESS;
  60. }


  61. NTSTATUS DispatchCreate(_DEVICE_OBJECT* DeviceObject, _IRP* Irp)
  62. {
  63.     UNREFERENCED_PARAMETER(DeviceObject);
  64.     UNREFERENCED_PARAMETER(Irp);
  65.     DbgPrint("[51asm] %s\n", __FUNCTION__);

  66.     //完成请求  如果没完成 3环的程序就会挂起
  67.     Irp->IoStatus.Status = STATUS_SUCCESS;   //状态,成功
  68.     Irp->IoStatus.Information = 0; //成功操作的字节数,非读写一般是0 就算是读写文件 dwBytes的返回值

  69.     //第二个参数线程的优先级,内核一般时 IO_NO_INCREMENT 不提升优先级
  70.     IoCompleteRequest(Irp, IO_NO_INCREMENT);  

  71.     return STATUS_SUCCESS;
  72. }

  73. NTSTATUS DispatchClose(_DEVICE_OBJECT* DeviceObject, _IRP* Irp)
  74. {
  75.     UNREFERENCED_PARAMETER(DeviceObject);
  76.     UNREFERENCED_PARAMETER(Irp);
  77.     DbgPrint("[51asm] %s\n", __FUNCTION__);


  78.     //完成请求  如果没完成 3环的程序就会挂起
  79.     Irp->IoStatus.Status = STATUS_SUCCESS;   //状态,成功
  80.     Irp->IoStatus.Information = 0; //成功操作的字节数,非读写一般是0 就算是读写文件 dwBytes的返回值

  81.     //第二个参数线程的优先级,内核一般时 IO_NO_INCREMENT 不提升优先级
  82.     IoCompleteRequest(Irp, IO_NO_INCREMENT);

  83.     return STATUS_SUCCESS;
  84. }

  85. NTSTATUS DispatchRead(_DEVICE_OBJECT* DeviceObject, _IRP* Irp)
  86. {
  87.     UNREFERENCED_PARAMETER(DeviceObject);
  88.     UNREFERENCED_PARAMETER(Irp);
  89.     DbgPrint("[51asm] %s\n", __FUNCTION__);

  90.     //获取用户的缓冲区
  91.     //PVOID pBuffer = Irp->UserBuffer;   //就是用户参数的 缓冲地址
  92.     PVOID pBuffer = Irp->AssociatedIrp.SystemBuffer;  //因为设置了缓冲区通讯方式,用户缓冲区数据从系统申请的缓冲区读
  93.     //PVOID pBuffer = MmGetSystemAddressForMdlSafe(Irp->MdlAddress, NormalPagePriority);  //设置了直接通讯方式获取用户缓冲区

  94.      //获取当前IRP堆栈
  95.     PIO_STACK_LOCATION pIrpStack = IoGetCurrentIrpStackLocation(Irp);
  96.     ULONG nLength = pIrpStack->Parameters.Read.Length;    //就是用户参数的 缓冲大小  read表示读操作
  97.    
  98.     DbgPrint("[51asm] %s pBuffer:%p nLength:%d\n", __FUNCTION__, pBuffer, nLength);
  99.     DbgPrint("[51asm] %s UserBuffer:%p pBuffer:%p nLength:%d\n", __FUNCTION__, Irp->UserBuffer, pBuffer, nLength);

  100.     if (nLength > sizeof(g_Registers.nVirtualReg)) {  //判断用户缓冲区大小是否足够
  101.         nLength = sizeof(g_Registers.nVirtualReg);
  102.     }
  103.         
  104.     //拷贝信息  就等于  memcoy
  105.     RtlCopyMemory(pBuffer,&g_Registers.nVirtualReg, nLength);  //把寄存器数据数据传入用缓冲区

  106.     //完成请求  如果没完成 3环的程序就会挂起
  107.     Irp->IoStatus.Status = STATUS_SUCCESS;   //状态,成功
  108.     Irp->IoStatus.Information = nLength; //成功操作的字节数,非读写一般是0 就算是读写文件 dwBytes的返回值

  109.     //第二个参数线程的优先级,内核一般时 IO_NO_INCREMENT 不提升优先级
  110.     IoCompleteRequest(Irp, IO_NO_INCREMENT);

  111.     return STATUS_SUCCESS;
  112. }


  113. NTSTATUS DispatchWrite(_DEVICE_OBJECT* DeviceObject, _IRP* Irp)
  114. {
  115.     UNREFERENCED_PARAMETER(DeviceObject);
  116.     UNREFERENCED_PARAMETER(Irp);
  117.     DbgPrint("[51asm] %s\n", __FUNCTION__);

  118.     //获取用户的缓冲区
  119.     //PVOID pBuffer = Irp->UserBuffer;   //就是用户参数的 缓冲地址
  120.     PVOID pBuffer = Irp->AssociatedIrp.SystemBuffer;  //因为设置了缓冲区通讯方式,用户缓冲区数据从系统申请的缓冲区读
  121.     //PVOID pBuffer = MmGetSystemAddressForMdlSafe(Irp->MdlAddress, NormalPagePriority);  //设置了直接通讯方式获取用户缓冲区
  122.    
  123.      //获取当前IRP堆栈
  124.     PIO_STACK_LOCATION pIrpStack = IoGetCurrentIrpStackLocation(Irp);
  125.     ULONG nLength = pIrpStack->Parameters.Write.Length;    //就是用户参数的 缓冲大小  Write表示写操作
  126.    
  127.    
  128.     DbgPrint("[51asm] %s pBuffer:%p nLength:%d\n", __FUNCTION__, pBuffer, nLength);
  129.     DbgPrint("[51asm] %s UserBuffer:%p pBuffer:%p nLength:%d\n",__FUNCTION__, Irp->UserBuffer, pBuffer, nLength);

  130.     if (nLength > sizeof(g_Registers.nVirtualReg)) {
  131.         nLength = sizeof(g_Registers.nVirtualReg);
  132.     }
  133.       
  134.     RtlCopyMemory(&g_Registers.nVirtualReg, pBuffer, nLength);  //将用户数据拷贝进内核的缓冲区


  135.     //完成请求  如果没完成 3环的程序就会挂起
  136.     Irp->IoStatus.Status = STATUS_SUCCESS;   //状态,成功
  137.     Irp->IoStatus.Information = nLength; //成功操作的字节数,非读写一般是0 就算是读写文件 dwBytes的返回值

  138.     //第二个参数线程的优先级,内核一般时 IO_NO_INCREMENT 不提升优先级
  139.     IoCompleteRequest(Irp, IO_NO_INCREMENT);

  140.     return STATUS_SUCCESS;
  141. }

  142. NTSTATUS DispatchControl(_DEVICE_OBJECT* DeviceObject, _IRP* Irp)
  143. {
  144.     UNREFERENCED_PARAMETER(DeviceObject);
  145.     UNREFERENCED_PARAMETER(Irp);
  146.     DbgPrint("[51asm] %s\n", __FUNCTION__);


  147.     //获取当前IRP堆栈
  148.     PIO_STACK_LOCATION pIrpStack = IoGetCurrentIrpStackLocation(Irp);

  149. #if 0
  150.     //获取用户的缓冲区
  151.     PVOID pOutBuffer = Irp->UserBuffer;  //获取用户输出缓冲区
  152.     ULONG nOutLength = pIrpStack->Parameters.DeviceIoControl.OutputBufferLength;   //获取用户输出缓冲区大小

  153.     PVOID pInputBuffer = pIrpStack->Parameters.DeviceIoControl.Type3InputBuffer;  //获取用户输入缓冲区
  154.     ULONG nInputLength = pIrpStack->Parameters.DeviceIoControl.InputBufferLength;  //获取用户输入缓冲区大小
  155. #endif // 0


  156.     //获取用户的缓冲区
  157.     PVOID pBuffer = Irp->AssociatedIrp.SystemBuffer;  //因为设置了缓冲区通讯方式,用户缓冲区数据从系统申请的缓冲区读
  158.     ULONG nLength = pIrpStack->Parameters.DeviceIoControl.InputBufferLength;
  159.     ULONG nIoControlCode = pIrpStack->Parameters.DeviceIoControl.IoControlCode;    //获取操作码


  160.     DbgPrint("[51asm] %s nIoControlCode:%p pBuffer:%p nLength:%d\n", __FUNCTION__,
  161.         nIoControlCode, pBuffer, nLength);


  162.     ULONG nSize = 0;
  163.     if (nLength != 4)
  164.     {
  165.         nLength = 4;
  166.     }
  167.    
  168.     KeEnterCriticalRegion();
  169.     switch (nIoControlCode) {
  170.     case IOCTL_GET_REG1:
  171.         RtlCopyMemory(pBuffer, &g_Registers.nVirtualReg, nLength);
  172.         nSize = nLength;

  173.         DbgPrint("[51asm] %s IOCTL_GET_REG1 value:%d\n", __FUNCTION__, g_Registers.nVirtualReg);
  174.         break;
  175.     case IOCTL_GET_REG2:
  176.         RtlCopyMemory(pBuffer, &g_Registers.nVirtualReg2, nLength);
  177.         nSize = nLength;

  178.         DbgPrint("[51asm] %s IOCTL_GET_REG2 value:%d\n", __FUNCTION__, g_Registers.nVirtualReg2);
  179.         break;
  180.     case IOCTL_GET_REG3:
  181.         RtlCopyMemory(pBuffer, &g_Registers.nVirtualReg3, nLength);
  182.         nSize = nLength;

  183.         DbgPrint("[51asm] %s IOCTL_GET_REG2 value:%d\n", __FUNCTION__, g_Registers.nVirtualReg3);
  184.         break;
  185.     case IOCTL_SET_REG1:
  186.         RtlCopyMemory(&g_Registers.nVirtualReg, pBuffer, nLength);
  187.         nSize = nLength;

  188.         DbgPrint("[51asm] %s IOCTL_SET_REG1 value:%d\n", __FUNCTION__, g_Registers.nVirtualReg);
  189.         break;
  190.     case IOCTL_SET_REG2:
  191.         RtlCopyMemory(&g_Registers.nVirtualReg2, pBuffer, nLength);
  192.         nSize = nLength;

  193.         DbgPrint("[51asm] %s IOCTL_SET_REG2 value:%d\n", __FUNCTION__, g_Registers.nVirtualReg2);
  194.         break;
  195.     case IOCTL_SET_REG3:
  196.         RtlCopyMemory(&g_Registers.nVirtualReg3, pBuffer, nLength);
  197.         nSize = nLength;

  198.         DbgPrint("[51asm] %s IOCTL_SET_REG3 value:%d\n", __FUNCTION__, g_Registers.nVirtualReg3);
  199.         break;
  200.     }
  201.     KeLeaveCriticalRegion();

  202.     Irp->IoStatus.Status = STATUS_SUCCESS;
  203.     Irp->IoStatus.Information = nSize;
  204.     IoCompleteRequest(Irp, IO_NO_INCREMENT);

  205.     return STATUS_SUCCESS;
  206. }
复制代码


3环程序
  1. #include <stdio.h>
  2. #include <Windows.h>
  3. #include <stdlib.h>
  4. #include <winioctl.h>

  5. struct MyDeviceExt {
  6.     int nVirtualReg;
  7.     int nVirtualReg2;
  8.     int nVirtualReg3;
  9. };

  10. //定义控制码
  11. #define MY_CODE_BASE 0x800
  12. #define MY_CTL_CODE(code) CTL_CODE(FILE_DEVICE_UNKNOWN, MY_CODE_BASE + code, METHOD_BUFFERED, FILE_ANY_ACCESS)
  13. #define IOCTL_GET_REG1 MY_CTL_CODE(0)
  14. #define IOCTL_GET_REG2 MY_CTL_CODE(1)
  15. #define IOCTL_GET_REG3 MY_CTL_CODE(2)
  16. #define IOCTL_SET_REG1 MY_CTL_CODE(3)
  17. #define IOCTL_SET_REG2 MY_CTL_CODE(4)
  18. #define IOCTL_SET_REG3 MY_CTL_CODE(5)


  19. void DisplayErrorText()
  20. {
  21.     LPVOID lpMsgBuf;
  22.     FormatMessage(
  23.         FORMAT_MESSAGE_ALLOCATE_BUFFER |
  24.         FORMAT_MESSAGE_FROM_SYSTEM |
  25.         FORMAT_MESSAGE_IGNORE_INSERTS,
  26.         NULL,
  27.         GetLastError(),
  28.         MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
  29.         (LPTSTR)&lpMsgBuf,
  30.         0,
  31.         NULL
  32.     );

  33.     printf((LPCTSTR)lpMsgBuf);

  34.     // Free the buffer.
  35.     LocalFree(lpMsgBuf);
  36. }

  37. int main()
  38. {
  39.     HANDLE hFile = CreateFile("\\\\?\\CR42VirtualRegister",
  40.         GENERIC_ALL,
  41.         0,
  42.         NULL,
  43.         OPEN_EXISTING,
  44.         FILE_ATTRIBUTE_NORMAL,
  45.         NULL);
  46.     if (hFile == INVALID_HANDLE_VALUE)
  47.     {
  48.         DisplayErrorText();
  49.     }
  50.     else {
  51.         printf("hFile = %p\n", hFile);
  52.     }

  53.     MyDeviceExt DevExt = { 0 };
  54.     DWORD dwBytes = 0;
  55.     DevExt.nVirtualReg = 100;
  56.     if (!WriteFile(hFile, &DevExt, 10000, &dwBytes, NULL))
  57.     {
  58.         DisplayErrorText();
  59.     }
  60.     else
  61.     {
  62.         printf("WriteFile dwBytes = %d  VirtualReg:%d\n", dwBytes, DevExt.nVirtualReg);
  63.     }

  64.     DevExt.nVirtualReg = 2;
  65.     if (!ReadFile(hFile, &DevExt, sizeof(DevExt), &dwBytes, NULL))
  66.     {
  67.         DisplayErrorText();
  68.     }
  69.     else
  70.     {
  71.         printf("ReadFile dwBytes = %d  VirtualReg:%d\n", dwBytes, DevExt.nVirtualReg);
  72.     }
  73.     int nReg = 123;
  74.     DeviceIoControl(hFile, IOCTL_SET_REG1, &nReg, sizeof(nReg), NULL, 0, &dwBytes, NULL);
  75.     printf("DeviceIoControl dwBytes = %d  nReg:%d\n", dwBytes, nReg);

  76.     nReg = 0;
  77.     DeviceIoControl(hFile, IOCTL_GET_REG1, NULL, 0, &nReg, sizeof(nReg), &dwBytes, NULL);
  78.     printf("DeviceIoControl dwBytes = %d  nReg:%d\n", dwBytes, nReg);

  79.     CloseHandle(hFile);

  80.     system("pause");
  81.     return 0;
  82. }
复制代码



您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|小黑屋|断点社区 |网站地图

GMT+8, 2024-12-24 00:32 , Processed in 0.070159 second(s), 32 queries .

Powered by XiunoBBS

Copyright © 2001-2025, 断点社区.

快速回复 返回顶部 返回列表