基于TDI实现的网络通信

Resurgam

发布日期: 2021-08-30 11:08:21 浏览量: 163
评分:
star star star star star star star star star star_border
*转载请注明来自write-bug.com

1.概述

在Windows操作系统下提供了两种网络编程模式,分别为用户模式和内核模式。顾名思义,用户模式是主要是通过调用用户层的API接口函数实现的用户程序,内核模式主要是通过调用内核层的内核API函数接口或是自定义构造通信协议实现的内核驱动程序。用户模式虽然易于开发,但是容易被监控;内核模式实现较为复杂,但是实现更为底层,通信更为隐蔽,较难监控。

内核模式下的网络编程,主要是通过传输数据接口TDI(Transport Data Interface)和网络驱动接口规范NDIS(Network Driver Interface Specification)实现的。TDI是直接使用现有的TCP/IP协议来通信的,无需自己重新编写新的协议,所以,基于TDI开发的网络通信是能够被网络防火墙检测到的。而NDIS可以直接在网络上读写原始报文,需要自定义通信协议,能够绕过网络防火墙的检测。

数据回传对于病毒木马来说是极为关键的一步,稍有差池,则会原形毕露。所以,内核下的网络通信,将会使病毒木马的通信方式更为底层隐蔽,难以检测。

接下来,本文将介绍基于TDI实现的TCP网络通信,实现一个驱动客户端程序,能够与用户层的服务端程序建立TCP连接并使用TCP通信。

2.实现过程

前面用户篇的时候介绍过Socket编程之TCP通信的小程序,Socket编程中调用的API函数接口比较容易理解,然而,内核下的TDI并没有现成封装好的函数接口供程序调用。为了方便读者类比Socket编程来理解TDI编程,所以,接下来将基于TDI实现TCP客户端的实现原理分成5个部分来介绍,分别为:TDI初始化、TDI TCP连接、TDI TCP数据发送、TDI TCP数据接收以及TDI关闭。

2.1 TDI初始化

在调用TDI实现TCP数据通信之前,需要先初始化TDI操作。初始化操作主要包括创建本地地址对象、创建端点对象以及将端点对象与本地地址对象进行关联。那么,具体的TDI初始化实现步骤如下所示。

首先,在创建本地地址对象之前,先构建本地地址拓展属性结构PFILE_FULL_EA_INFORMATION。主要设置该拓展属性结构的名称为TdiTransportAddress,拓展属性结构的内容是TA_IP_ADDRESS,里面存储着通信协议类型、本地IP地址及端口等信息。TA_IP_ADDRESS中的AddressType表示通信协议类型,TDI_ADDRESS_TYPE_IP则支持UDP和TCP等IP协议。将IP地址以及端口都置为0,表示本机本地IP地址和随机端口。

在构建本地地址拓展属性结构完成之后,就可以调用ZwCreateFile函数来根据本地地址拓展属性结构创建本地地址对象。该函数中,打开的设备名称为“\\Device\\Tcp”,即打开TCP设备驱动服务。ZwCreateFile函数中的重要参数是拓展属性(Extended Attributes),通过拓展属性可以向其他的驱动程序传递信息。所以,驱动程序会将本地地址拓展属性结构的数据传递TCP设备驱动,创建本地地址对象,并获取对象句柄。在获取本地地址对象句柄后,通过调用ObReferenceObjectByHandle函数来获取本地地址对象的文件对象,并根据得到的文件对象调用IoGetRelatedDeviceObject获取对应的本地地址对象的驱动设备指针,以方便后续的操作。

然后,便可以开始创建端点对象。同样的,在创建端点对象之前,先构建上下文拓展属性结构PFILE_FULL_EA_INFORMATION。主要设置该拓展属性结构的名称为TdiConnectionContext,拓展属性结构的内容为CONNECTION_CONTEXT。本文并没有用到CONNECTION_CONTEXT结构里的数据,所以都置为0。

构建上下文拓展属性结构完成后,同样是调用ZwCreateFile函数根据上下文拓展属性结构来创建端点对象。仍是打开TCP设备驱动服务,向TCP设备驱动传递上下文结构数据,创建端点对象,并获取端点对象句柄。在获取端点对象句柄之后,直接调用ObReferenceObjectByHandle函数来获取端点对象的文件对象,以方便后续的操作。

最后,创建了本地地址对象和端点对象后,需要将两者关联起来,没有关联地址的端点没有任何用处。其中,本地地址对象存储的信息向系统表明驱动程序使用的本地IP地址和端口。直接调用TdiBuildInternalDeviceControlIrp函数创建TDI的I/O请求包IRP,消息类型为TDI_ASSOCIATE_ADDRESS,表示端点对象关联本地地址对象;需要用到上述获取的本地地址驱动设备对象指针以及端点文件对象指针作为参数。TdiBuildInternalDeviceControlIrp实际是一个宏,它内部调用了IoBuildDeviceIoControlRequest,给这个宏的一些参数实际被忽略了。所以,再调用TdiBuildAssociateAddress宏,将上述获取的本地地址驱动设备对象指针以及端点文件对象指针添加到IRP的I/O堆栈空间中。

完成上述3个操作之后,就可以调用IoCallDriver函数向驱动设备发送TDI的I/O请求包IRP。其中,驱动程序需要等待系统执行IRP,所以,需要调用IoSetCompletionRoutine设置完成回调函数,通知程序IRP执行完成。这样,TDI的初始化操作到此结束了。

那么,TDI初始化的具体实现代码如下所示。

  1. // TDI初始化设置
  2. NTSTATUS TdiOpen(PDEVICE_OBJECT *ppTdiAddressDevObj, PFILE_OBJECT *ppTdiEndPointFileObject, HANDLE *phTdiAddress, HANDLE *phTdiEndPoint)
  3. {
  4. DbgPrint("Enter OpenTdi\n");
  5. NTSTATUS status = STATUS_UNSUCCESSFUL;
  6. PFILE_FULL_EA_INFORMATION pAddressEaBuffer = NULL;
  7. ULONG ulAddressEaBufferLength = 0;
  8. PTA_IP_ADDRESS pTaIpAddr = NULL;
  9. UNICODE_STRING ustrTDIDevName;
  10. OBJECT_ATTRIBUTES ObjectAttributes = { 0 };
  11. IO_STATUS_BLOCK iosb = { 0 };
  12. HANDLE hTdiAddress = NULL;
  13. PFILE_OBJECT pTdiAddressFileObject = NULL;
  14. PDEVICE_OBJECT pTdiAddressDevObj = NULL;
  15. PFILE_FULL_EA_INFORMATION pContextEaBuffer = NULL;
  16. ULONG ulContextEaBufferLength = 0;
  17. HANDLE hTdiEndPoint = NULL;
  18. PFILE_OBJECT pTdiEndPointFileObject = NULL;
  19. KEVENT irpCompleteEvent = { 0 };
  20. PIRP pIrp = NULL;
  21. do
  22. {
  23. // 为本地地址拓展属性结构申请内存及初始化
  24. ulAddressEaBufferLength = sizeof(FILE_FULL_EA_INFORMATION) + TDI_TRANSPORT_ADDRESS_LENGTH + sizeof(TA_IP_ADDRESS);
  25. pAddressEaBuffer = (PFILE_FULL_EA_INFORMATION)ExAllocatePool(NonPagedPool, ulAddressEaBufferLength);
  26. if (NULL == pAddressEaBuffer)
  27. {
  28. ShowError("ExAllocatePool[Address]", 0);
  29. break;
  30. }
  31. RtlZeroMemory(pAddressEaBuffer, ulAddressEaBufferLength);
  32. RtlCopyMemory(pAddressEaBuffer->EaName, TdiTransportAddress, (1 + TDI_TRANSPORT_ADDRESS_LENGTH));
  33. pAddressEaBuffer->EaNameLength = TDI_TRANSPORT_ADDRESS_LENGTH;
  34. pAddressEaBuffer->EaValueLength = sizeof(TA_IP_ADDRESS);
  35. // 初始化本机IP地址与端口
  36. pTaIpAddr = (PTA_IP_ADDRESS)((PUCHAR)pAddressEaBuffer->EaName + pAddressEaBuffer->EaNameLength + 1);
  37. pTaIpAddr->TAAddressCount = 1;
  38. pTaIpAddr->Address[0].AddressLength = TDI_ADDRESS_LENGTH_IP;
  39. pTaIpAddr->Address[0].AddressType = TDI_ADDRESS_TYPE_IP;
  40. pTaIpAddr->Address[0].Address[0].sin_port = 0; // 0表示本机任意随机端口
  41. pTaIpAddr->Address[0].Address[0].in_addr = 0; // 0表示本机本地IP地址
  42. RtlZeroMemory(pTaIpAddr->Address[0].Address[0].sin_zero, sizeof(pTaIpAddr->Address[0].Address[0].sin_zero));
  43. // 创建TDI驱动设备字符串与初始化设备对象
  44. RtlInitUnicodeString(&ustrTDIDevName, COMM_TCP_DEV_NAME);
  45. InitializeObjectAttributes(&ObjectAttributes, &ustrTDIDevName, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL);
  46. // 根据本地地址拓展属性结构创建本地地址对象
  47. status = ZwCreateFile(&hTdiAddress, GENERIC_READ | GENERIC_WRITE | SYNCHRONIZE,
  48. &ObjectAttributes, &iosb, NULL, FILE_ATTRIBUTE_NORMAL,
  49. FILE_SHARE_READ, FILE_OPEN, 0, pAddressEaBuffer, ulAddressEaBufferLength);
  50. if (!NT_SUCCESS(status))
  51. {
  52. ShowError("ZwCreateFile[Address]", status);
  53. break;
  54. }
  55. // 根据本地地址对象句柄获取对应的本地地址文件对象
  56. status = ObReferenceObjectByHandle(hTdiAddress,
  57. FILE_ANY_ACCESS, 0, KernelMode, &pTdiAddressFileObject, NULL);
  58. if (!NT_SUCCESS(status))
  59. {
  60. ShowError("ObReferenceObjectHandle[EndPoint]", status);
  61. break;
  62. }
  63. // 获取本地地址文件对象对应的驱动设备
  64. pTdiAddressDevObj = IoGetRelatedDeviceObject(pTdiAddressFileObject);
  65. if (NULL == pTdiAddressDevObj)
  66. {
  67. ShowError("IoGetRelatedDeviceObject", 0);
  68. }
  69. // 为上下文拓展属性申请内存并初始化
  70. ulContextEaBufferLength = FIELD_OFFSET(FILE_FULL_EA_INFORMATION, EaName) + TDI_CONNECTION_CONTEXT_LENGTH + 1 + sizeof(CONNECTION_CONTEXT);
  71. pContextEaBuffer = (PFILE_FULL_EA_INFORMATION)ExAllocatePool(NonPagedPool, ulContextEaBufferLength);
  72. if (NULL == pContextEaBuffer)
  73. {
  74. ShowError("ExAllocatePool[EndPoint]", 0);
  75. break;
  76. }
  77. RtlZeroMemory(pContextEaBuffer, ulContextEaBufferLength);
  78. RtlCopyMemory(pContextEaBuffer->EaName, TdiConnectionContext, (1 + TDI_CONNECTION_CONTEXT_LENGTH));
  79. pContextEaBuffer->EaNameLength = TDI_CONNECTION_CONTEXT_LENGTH;
  80. pContextEaBuffer->EaValueLength = sizeof(CONNECTION_CONTEXT);
  81. // 根据上下文创建TDI端点对象
  82. status = ZwCreateFile(&hTdiEndPoint, GENERIC_READ | GENERIC_WRITE | SYNCHRONIZE,
  83. &ObjectAttributes, &iosb, NULL, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ,
  84. FILE_OPEN, 0, pContextEaBuffer, ulContextEaBufferLength);
  85. if (!NT_SUCCESS(status))
  86. {
  87. ShowError("ZwCreateFile[EndPoint]", status);
  88. break;
  89. }
  90. // 根据TDI端点对象句柄获取对应的端点文件对象
  91. status = ObReferenceObjectByHandle(hTdiEndPoint,
  92. FILE_ANY_ACCESS, 0, KernelMode, &pTdiEndPointFileObject, NULL);
  93. if (!NT_SUCCESS(status))
  94. {
  95. ShowError("ObReferenceObjectHandle[EndPoint]", status);
  96. break;
  97. }
  98. // 设置事件
  99. KeInitializeEvent(&irpCompleteEvent, NotificationEvent, FALSE);
  100. // 将TDI端点与本地地址对象关联, 创建TDI的I/O请求包:TDI_ASSOCIATE_ADDRESS
  101. pIrp = TdiBuildInternalDeviceControlIrp(TDI_ASSOCIATE_ADDRESS,
  102. pTdiAddressDevObj, pTdiEndPointFileObject, &irpCompleteEvent, &iosb);
  103. if (NULL == pIrp)
  104. {
  105. ShowError("TdiBuildInternalDeviceControlIrp", 0);
  106. return STATUS_INSUFFICIENT_RESOURCES;
  107. }
  108. // 拓展I/O请求包
  109. TdiBuildAssociateAddress(pIrp, pTdiAddressDevObj, pTdiEndPointFileObject, NULL, NULL, hTdiAddress);
  110. // 设置完成实例的回调函数
  111. IoSetCompletionRoutine(pIrp, TdiCompletionRoutine, &irpCompleteEvent, TRUE, TRUE, TRUE);
  112. // 发送I/O请求包并等待执行
  113. status = IoCallDriver(pTdiAddressDevObj, pIrp);
  114. if (STATUS_PENDING == status)
  115. {
  116. KeWaitForSingleObject(&irpCompleteEvent, Executive, KernelMode, FALSE, NULL);
  117. }
  118. // 返回数据
  119. *ppTdiAddressDevObj = pTdiAddressDevObj;
  120. *ppTdiEndPointFileObject = pTdiEndPointFileObject;
  121. *phTdiAddress = hTdiAddress;
  122. *phTdiEndPoint = hTdiEndPoint;
  123. }while (FALSE);
  124. // 释放内存
  125. if (pTdiAddressFileObject)
  126. {
  127. ObDereferenceObject(pTdiAddressFileObject); // 测试
  128. }
  129. if (pContextEaBuffer)
  130. {
  131. ExFreePool(pContextEaBuffer);
  132. }
  133. if (pAddressEaBuffer)
  134. {
  135. ExFreePool(pAddressEaBuffer);
  136. }
  137. DbgPrint("Leave OpenTdi\n");
  138. return status;
  139. }

2.2 TDI TCP连接

在TDI初始化完成之后,驱动程序便可以向TCP服务端程序发送连接请求,并建立TCP连接。主要操作就是构造一个包含服务器IP地址以及监听端口的IRP,再发送给驱动程序执行。那么,基于TDI的TCP连接的具体实现流程如下所示。

首先,直接调用TdiBuildInternalDeviceControlIrp宏创建IRP,设置IRP的消息类型为TDI_CONNECT,表示建立TCP连接。

然后,构建TDI连接信息结构TDI_CONNECTION_INFORMATION,主要设置IP地址的相关信息TA_IP_ADDRESS,包括通信协议类型、服务器的IP地址以及服务器监听端口号等。并调用TdiBuildConnect宏将TDI连接信息结构的数据添加到IRP的I/O堆栈空间中。

最后,调用IoCallDriver函数向驱动程序发送上述构建好的IRP,并创建完成回调函数等待系统处理IRP。系统成功处理完毕后,驱动程序便与服务端程序成功建立TCP连接。

那么,TCP连接的具体实现代码如下所示。

  1. // TDI TCP连接服务器
  2. NTSTATUS TdiConnection(PDEVICE_OBJECT pTdiAddressDevObj, PFILE_OBJECT pTdiEndPointFileObject, LONG *pServerIp, LONG lServerPort)
  3. {
  4. DbgPrint("Enter TdiConnection\n");
  5. NTSTATUS status = STATUS_SUCCESS;
  6. IO_STATUS_BLOCK iosb = { 0 };
  7. PIRP pIrp = NULL;
  8. KEVENT connEvent = { 0 };
  9. TA_IP_ADDRESS serverTaIpAddr = { 0 };
  10. ULONG serverIpAddr = 0;
  11. USHORT serverPort = 0;
  12. TDI_CONNECTION_INFORMATION serverConnection = { 0 };
  13. // 创建连接事件
  14. KeInitializeEvent(&connEvent, NotificationEvent, FALSE);
  15. // 创建TDI连接I/O请求包:TDI_CONNECT
  16. pIrp = TdiBuildInternalDeviceControlIrp(TDI_CONNECT, pTdiAddressDevObj, pTdiEndPointFileObject, &connEvent, &iosb);
  17. if (NULL == pIrp)
  18. {
  19. ShowError("TdiBuildInternalDeviceControlIrp_TDI_CONNECT", 0);
  20. return STATUS_INSUFFICIENT_RESOURCES;
  21. }
  22. // 初始化服务器IP地址与端口
  23. serverIpAddr = INETADDR(pServerIp[0], pServerIp[1], pServerIp[2], pServerIp[3]);
  24. serverPort = HTONS(lServerPort);
  25. serverTaIpAddr.TAAddressCount = 1;
  26. serverTaIpAddr.Address[0].AddressLength = TDI_ADDRESS_LENGTH_IP;
  27. serverTaIpAddr.Address[0].AddressType = TDI_ADDRESS_TYPE_IP;
  28. serverTaIpAddr.Address[0].Address[0].sin_port = serverPort;
  29. serverTaIpAddr.Address[0].Address[0].in_addr = serverIpAddr;
  30. serverConnection.UserDataLength = 0;
  31. serverConnection.UserData = 0;
  32. serverConnection.OptionsLength = 0;
  33. serverConnection.Options = 0;
  34. serverConnection.RemoteAddressLength = sizeof(TA_IP_ADDRESS);
  35. serverConnection.RemoteAddress = &serverTaIpAddr;
  36. // 把上述的地址与端口信息增加到I/O请求包中,增加连接信息
  37. TdiBuildConnect(pIrp, pTdiAddressDevObj, pTdiEndPointFileObject, NULL, NULL, NULL, &serverConnection, 0);
  38. // 设置完成实例回调函数
  39. IoSetCompletionRoutine(pIrp, TdiCompletionRoutine, &connEvent, TRUE, TRUE, TRUE);
  40. // 发送I/O请求包并等待执行
  41. status = IoCallDriver(pTdiAddressDevObj, pIrp);
  42. if (STATUS_PENDING == status)
  43. {
  44. KeWaitForSingleObject(&connEvent, Executive, KernelMode, FALSE, NULL);
  45. }
  46. DbgPrint("Leave TdiConnection\n");
  47. return status;
  48. }

2.3 TDI TCP数据发送

在成功建立TCP连接之后,客户端程序与服务端程序便可以相互通信,进行数据的交互了。基于TDI实现的TCP数据发送主要的操作便是构造一个发送数据的IRP,并向IRP添加发送的数据,然后发送给驱动程序处理即可。具体的实现流程如下所示。

首先,直接调用TdiBuildInternalDeviceControlIrp宏创建IRP,设置IRP的消息类型为TDI_SEND,表示发送数据。

然后,需要将发送的数据调用IoAllocateMdl函数来将数据创建一份新的映射并获取分配到的MDL结构,因为驱动程序接下来需要调用TdiBuildSend宏将TDI发送数据的MDL结构数据添加到IRP的I/O堆栈空间中,以此传递发送的数据信息。

最后,调用IoCallDriver函数向驱动程序发送上述构建好的IRP,并创建完成回调函数等待系统处理IRP。处理完毕后,要记得调用IoFreeMdl函数来释放MDL。

那么,TCP数据发送的具体实现代码如下所示。

  1. // TDI TCP发送信息
  2. NTSTATUS TdiSend(PDEVICE_OBJECT pTdiAddressDevObj, PFILE_OBJECT pTdiEndPointFileObject, PUCHAR pSendData, ULONG ulSendDataLength)
  3. {
  4. DbgPrint("Enter TdiSend\n");
  5. NTSTATUS status = STATUS_SUCCESS;
  6. KEVENT sendEvent;
  7. PIRP pIrp = NULL;
  8. IO_STATUS_BLOCK iosb = {0};
  9. PMDL pSendMdl = NULL;
  10. // 初始化事件
  11. KeInitializeEvent(&sendEvent, NotificationEvent, FALSE);
  12. // 创建I/O请求包:TDI_SEND
  13. pIrp = TdiBuildInternalDeviceControlIrp(TDI_SEND, pTdiAddressDevObj, pTdiEndPointFileObject, &sendEvent, &iosb);
  14. if (NULL == pIrp)
  15. {
  16. ShowError("TdiBuildInternalDeviceControlIrp", 0);
  17. return STATUS_INSUFFICIENT_RESOURCES;
  18. }
  19. // 创建MDL
  20. pSendMdl = IoAllocateMdl(pSendData, ulSendDataLength, FALSE, FALSE, pIrp);
  21. if (NULL == pSendMdl)
  22. {
  23. ShowError("IoAllocateMdl", 0);
  24. return STATUS_INSUFFICIENT_RESOURCES;
  25. }
  26. MmProbeAndLockPages(pSendMdl, KernelMode, IoModifyAccess);
  27. // 拓展I/O请求包,添加发送信息
  28. TdiBuildSend(pIrp, pTdiAddressDevObj, pTdiEndPointFileObject, NULL, NULL, pSendMdl, 0, ulSendDataLength);
  29. // 设置完成实例回调函数
  30. IoSetCompletionRoutine(pIrp, TdiCompletionRoutine, &sendEvent, TRUE, TRUE, TRUE);
  31. // 发送I/O请求包并等待执行
  32. status = IoCallDriver(pTdiAddressDevObj, pIrp);
  33. if (STATUS_PENDING == status)
  34. {
  35. KeWaitForSingleObject(&sendEvent, Executive, KernelMode, FALSE, NULL);
  36. }
  37. // 释放MDL
  38. if (pSendMdl)
  39. {
  40. IoFreeMdl(pSendMdl);
  41. }
  42. DbgPrint("Leave TdiSend\n");
  43. return status;
  44. }

2.4 TDI TCP数据接收

基于TDI实现的TCP数据接收具体实现流程和数据发送类似,同样是构造数据接收的IRP,设置接收数据缓冲区,将IRP发送给驱动程序处理即可。具体的数据接收实现流程如下所示。

首先,直接调用TdiBuildInternalDeviceControlIrp宏创建IRP,设置IRP的消息类型为TDI_RECV,表示发送接收。

然后,需要将数据接收缓冲区调用IoAllocateMdl函数来将缓冲区创建一份新的映射并获取分配到的MDL结构,因为驱动程序接下来需要调用TdiBuildReceive宏将TDI接收数据缓冲区的MDL结构数据添加到IRP的I/O堆栈空间中,以此传递接收数据缓冲区的信息。

最后,调用IoCallDriver函数向驱动程序发送上述构建好的IRP,并创建完成回调函数等待系统处理IRP。处理完毕后,要记得调用IoFreeMdl函数来释放MDL。

那么,TCP数据接收的具体实现代码如下所示。

  1. // TDI TCP接收信息
  2. ULONG_PTR TdiRecv(PDEVICE_OBJECT pTdiAddressDevObj, PFILE_OBJECT pTdiEndPointFileObject, PUCHAR pRecvData, ULONG ulRecvDataLength)
  3. {
  4. DbgPrint("Enter TdiRecv\n");
  5. NTSTATUS status = STATUS_SUCCESS;
  6. KEVENT recvEvent;
  7. PIRP pIrp = NULL;
  8. IO_STATUS_BLOCK iosb = { 0 };
  9. PMDL pRecvMdl = NULL;
  10. ULONG_PTR ulRecvSize = 0;
  11. // 初始化事件
  12. KeInitializeEvent(&recvEvent, NotificationEvent, FALSE);
  13. // 创建I/O请求包:TDI_SEND
  14. pIrp = TdiBuildInternalDeviceControlIrp(TDI_RECV, pTdiAddressDevObj, pTdiEndPointFileObject, &recvEvent, &iosb);
  15. if (NULL == pIrp)
  16. {
  17. ShowError("TdiBuildInternalDeviceControlIrp", 0);
  18. return STATUS_INSUFFICIENT_RESOURCES;
  19. }
  20. // 创建MDL
  21. pRecvMdl = IoAllocateMdl(pRecvData, ulRecvDataLength, FALSE, FALSE, pIrp);
  22. if (NULL == pRecvMdl)
  23. {
  24. ShowError("IoAllocateMdl", 0);
  25. return STATUS_INSUFFICIENT_RESOURCES;
  26. }
  27. MmProbeAndLockPages(pRecvMdl, KernelMode, IoModifyAccess);
  28. // 拓展I/O请求包,添加发送信息
  29. TdiBuildReceive(pIrp, pTdiAddressDevObj, pTdiEndPointFileObject, NULL, NULL, pRecvMdl, TDI_RECEIVE_NORMAL, ulRecvDataLength);
  30. // 设置完成实例回调函数
  31. IoSetCompletionRoutine(pIrp, TdiCompletionRoutine, &recvEvent, TRUE, TRUE, TRUE);
  32. // 发送I/O请求包并等待执行
  33. status = IoCallDriver(pTdiAddressDevObj, pIrp);
  34. if (STATUS_PENDING == status)
  35. {
  36. KeWaitForSingleObject(&recvEvent, Executive, KernelMode, FALSE, NULL);
  37. }
  38. // 获取实际接收的数据大小
  39. ulRecvSize = pIrp->IoStatus.Information;
  40. // 释放MDL
  41. if (pRecvMdl)
  42. {
  43. IoFreeMdl(pRecvMdl);
  44. }
  45. DbgPrint("Leave TdiRecv\n");
  46. return status;
  47. }

2.5 TDI关闭

所谓的关闭TDI,主要是负责资源数据的释放和清理工作。通过调用ObDereferenceObject函数释放端点文件对象资源,调用ZwClose函数关闭端点对象句柄以及本地地址对象句柄。

那么,TDI关闭的具体实现代码如下所示。

  1. // TDI关闭释放
  2. VOID TdiClose(PFILE_OBJECT pTdiEndPointFileObject, HANDLE hTdiAddress, HANDLE hTdiEndPoint)
  3. {
  4. DbgPrint("Enter TdiClose\n");
  5. if (pTdiEndPointFileObject)
  6. {
  7. ObDereferenceObject(pTdiEndPointFileObject);
  8. }
  9. if (hTdiEndPoint)
  10. {
  11. ZwClose(hTdiEndPoint);
  12. }
  13. if (hTdiAddress)
  14. {
  15. ZwClose(hTdiAddress);
  16. }
  17. DbgPrint("Leave TdiClose\n");
  18. }

3.测试

在64位Windows 10操作系统上,先运行TCP服务端程序ChatServer.exe,设置服务端程序的IP地址以及监听端口分别为127.0.0.1和12345,并开始进行监听。然后,直接加载并运行上述驱动程序,连接监听状态的服务端程序,连接成功,并成功向服务端程序发送数据“I am Demon`Gan—->From TDI”。服务端程序成功与驱动程序建立TCP连接,并成功接收来自驱动程序发送的数据。处于用户层的服务端程序向驱动程序发送数据“nice to meet you, Demon”,驱动程序也能成功接收。所以,基于TDI通信的驱动程序测试成功,如图7-3所示。

打开cmd.exe命令行窗口后,输入命令netstat -ano来查看网络连接情况以及对应的进程PID,执行命令的结果如图7-4所示,从中可以知道,与服务端程序建立通信连接的进程PID为4,即system.exe进程,因为是驱动程序与用户程序建立的TCP连接,所以,进程PID显示为4。

4.小结

基于TDI的TCP客户端的实现原理实际上是通过构造不同信息的TDI的I/O请求包IRP,携带不同的参数数据,发送给驱动函数进行处理实现的。实现该程序的关键,则是在于TDI的I/O请求包IRP的构建上。

其中,在通信的过程中,要注意及时调用IoFreeMdl函数来释放创建的MDL。同时,驱动程序可以通过调用PsCreateSystemThread函数创建一个多线程,循环接收来自服务端程序的数据。

上传的附件 cloud_download TdiCommunicate_Test.zip ( 424.25kb, 4次下载 )

发送私信

每个人最终和自己越长越像

25
文章数
16
评论数
最近文章
eject