在笔者上一篇文章《内核层InlineHook挂钩函数》中介绍了通过替换函数头部代码的方式实现Hook挂钩,对于ARK工具来说实现扫描与摘除InlineHook钩子也是最基本的功能,此类功能的实现一般可在应用层进行,而驱动层只需要保留一个读写字节的函数即可,将复杂的流程放在应用层实现是一个非常明智的选择,与《内核实现进程反汇编》中所使用的读写驱动基本一致,本篇文章中的驱动只保留两个功能,控制信号IOCTL_GET_CUR_CODE用于读取函数的前16个字节的内存,信号IOCTL_SET_ORI_CODE则用于设置前16个字节的内存。

之所以是前16个字节是因为一般的内联Hook只需要使用两条指令就可实现劫持,如下是通用ARK工具扫描到的被挂钩函数的样子。

首先将内核驱动程序代码放到如下,内核驱动程序没有任何特别的,仅仅只是一个通用驱动模板,在其基础上使用CR3读写,如果不理解CR3读写的原理您可以去看《内核CR3切换读写内存》这一篇中的详细介绍。

  1. #include <ntifs.h>
  2. #include <intrin.h>
  3. #include <windef.h>
  4. #define DEVICE_NAME L"\\Device\\WinDDK"
  5. #define LINK_NAME L"\\DosDevices\\WinDDK"
  6. #define LINK_GLOBAL_NAME L"\\DosDevices\\Global\\WinDDK"
  7. // 控制信号 IOCTL_GET_CUR_CODE 用于读 | IOCTL_SET_ORI_CODE 用于写
  8. #define IOCTL_GET_CUR_CODE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS)
  9. #define IOCTL_SET_ORI_CODE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x801, METHOD_BUFFERED, FILE_ANY_ACCESS)
  10. // 引用__readcr0等函数必须增加
  11. #pragma intrinsic(_disable)
  12. #pragma intrinsic(_enable)
  13. // 定义读写结构体
  14. typedef struct
  15. {
  16. PVOID Address;
  17. ULONG64 Length;
  18. UCHAR data[256];
  19. } KF_DATA, *PKF_DATA;
  20. KIRQL g_irql;
  21. // 关闭写保护
  22. void WPOFFx64()
  23. {
  24. ULONG64 cr0;
  25. g_irql = KeRaiseIrqlToDpcLevel();
  26. cr0 = __readcr0();
  27. cr0 &= 0xfffffffffffeffff;
  28. __writecr0(cr0);
  29. _disable();
  30. }
  31. // 开启写保护
  32. void WPONx64()
  33. {
  34. ULONG64 cr0;
  35. cr0 = __readcr0();
  36. cr0 |= 0x10000;
  37. _enable();
  38. __writecr0(cr0);
  39. KeLowerIrql(g_irql);
  40. }
  41. // 设备创建时触发
  42. NTSTATUS DispatchCreate(PDEVICE_OBJECT pDevObj, PIRP pIrp)
  43. {
  44. pIrp->IoStatus.Status = STATUS_SUCCESS;
  45. pIrp->IoStatus.Information = 0;
  46. DbgPrint("[LyShark] 设备已创建 \n");
  47. IoCompleteRequest(pIrp, IO_NO_INCREMENT);
  48. return STATUS_SUCCESS;
  49. }
  50. // 设备关闭时触发
  51. NTSTATUS DispatchClose(PDEVICE_OBJECT pDevObj, PIRP pIrp)
  52. {
  53. pIrp->IoStatus.Status = STATUS_SUCCESS;
  54. pIrp->IoStatus.Information = 0;
  55. DbgPrint("[LyShark] 设备已关闭 \n");
  56. IoCompleteRequest(pIrp, IO_NO_INCREMENT);
  57. return STATUS_SUCCESS;
  58. }
  59. // 主派遣函数
  60. NTSTATUS DispatchIoctl(PDEVICE_OBJECT pDevObj, PIRP pIrp)
  61. {
  62. NTSTATUS status = STATUS_INVALID_DEVICE_REQUEST;
  63. PIO_STACK_LOCATION pIrpStack;
  64. ULONG uIoControlCode;
  65. PVOID pIoBuffer;
  66. ULONG uInSize;
  67. ULONG uOutSize;
  68. // 获取当前设备栈
  69. pIrpStack = IoGetCurrentIrpStackLocation(pIrp);
  70. uIoControlCode = pIrpStack->Parameters.DeviceIoControl.IoControlCode;
  71. // 获取缓冲区
  72. pIoBuffer = pIrp->AssociatedIrp.SystemBuffer;
  73. // 获取缓冲区长度
  74. uInSize = pIrpStack->Parameters.DeviceIoControl.InputBufferLength;
  75. // 输出缓冲区长度
  76. uOutSize = pIrpStack->Parameters.DeviceIoControl.OutputBufferLength;
  77. switch (uIoControlCode)
  78. {
  79. // 读内存
  80. case IOCTL_GET_CUR_CODE:
  81. {
  82. KF_DATA dat = { 0 };
  83. // 将缓冲区格式化为KF_DATA结构体
  84. RtlCopyMemory(&dat, pIoBuffer, 16);
  85. WPOFFx64();
  86. // 将数据写回到缓冲区
  87. RtlCopyMemory(pIoBuffer, dat.Address, dat.Length);
  88. WPONx64();
  89. status = STATUS_SUCCESS;
  90. break;
  91. }
  92. // 写内存
  93. case IOCTL_SET_ORI_CODE:
  94. {
  95. KF_DATA dat = { 0 };
  96. // 将缓冲区格式化为KF_DATA结构体
  97. RtlCopyMemory(&dat, pIoBuffer, sizeof(KF_DATA));
  98. WPOFFx64();
  99. // 将数据写回到缓冲区
  100. RtlCopyMemory(dat.Address, dat.data, dat.Length);
  101. WPONx64();
  102. status = STATUS_SUCCESS;
  103. break;
  104. }
  105. }
  106. if (status == STATUS_SUCCESS)
  107. pIrp->IoStatus.Information = uOutSize;
  108. else
  109. pIrp->IoStatus.Information = 0;
  110. pIrp->IoStatus.Status = status;
  111. IoCompleteRequest(pIrp, IO_NO_INCREMENT);
  112. return status;
  113. }
  114. // 驱动卸载
  115. VOID DriverUnload(PDRIVER_OBJECT pDriverObj)
  116. {
  117. UNICODE_STRING strLink;
  118. // 删除符号链接卸载设备
  119. RtlInitUnicodeString(&strLink, LINK_NAME);
  120. IoDeleteSymbolicLink(&strLink);
  121. IoDeleteDevice(pDriverObj->DeviceObject);
  122. }
  123. // 驱动程序入口
  124. NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObj, PUNICODE_STRING pRegistryString)
  125. {
  126. NTSTATUS status = STATUS_SUCCESS;
  127. UNICODE_STRING ustrLinkName;
  128. UNICODE_STRING ustrDevName;
  129. PDEVICE_OBJECT pDevObj;
  130. // 初始化派遣函数
  131. pDriverObj->MajorFunction[IRP_MJ_CREATE] = DispatchCreate;
  132. pDriverObj->MajorFunction[IRP_MJ_CLOSE] = DispatchClose;
  133. pDriverObj->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DispatchIoctl;
  134. DbgPrint("hello lysahrk.com \n");
  135. // 初始化设备名
  136. RtlInitUnicodeString(&ustrDevName, DEVICE_NAME);
  137. // 创建设备
  138. status = IoCreateDevice(pDriverObj, 0, &ustrDevName, FILE_DEVICE_UNKNOWN, 0, FALSE, &pDevObj);
  139. if (!NT_SUCCESS(status))
  140. {
  141. return status;
  142. }
  143. // 创建符号链接
  144. RtlInitUnicodeString(&ustrLinkName, LINK_NAME);
  145. status = IoCreateSymbolicLink(&ustrLinkName, &ustrDevName);
  146. if (!NT_SUCCESS(status))
  147. {
  148. IoDeleteDevice(pDevObj);
  149. return status;
  150. }
  151. pDriverObj->DriverUnload = DriverUnload;
  152. return STATUS_SUCCESS;
  153. }

接着来分析下应用层做了什么,首先GetKernelBase64函数的作用,该函数内部通过GetProcAddress()函数动态寻找到ZwQuerySystemInformation()函数的内存地址(此函数未被到处所以只能动态找到),找到后调用ZwQuerySystemInformation()直接拿到系统中的所有模块信息,通过pSystemModuleInformation->Module[0].Base得到系统中第一个模块的基地址,此模块就是ntoskrnl.exe,该模块也是系统运行后的第一个启动的,此时我们即可拿到KernelBase也就是系统内存中的基地址。

此时通过LoadLibraryExA()函数动态加载,此时加载的是磁盘中的被Hook函数的所属模块,获得映射地址后将此地址装入hKernel变量内,此时我们拥有了内存中的KernelBase以及磁盘中加载的hKernel,接着调用RepairRelocationTable()让两者的重定位表保持一致。

此时当用户调用GetSystemRoutineAddress()则执行如下流程,想要获取当前内存地址,则需要使用当前内存中的KernelBase模块基址加上通过GetProcAddress()动态获取到的磁盘基址中的函数地址减去磁盘中的基地址,将内存中的KernelBase加上磁盘中的相对偏移就得到了当前内存中加载函数的实际地址。

  • address1 = KernelBase + (ULONG64)GetProcAddress(hKernel, "NtWriteFile") - (ULONG64)hKernel
  • address2 = KernelBase - (ULONG64)hKernel + (ULONG64)GetProcAddress(hKernel, "NtWriteFile")

调用GetOriginalMachineCode()则用于获取相对偏移地址,该地址的获取方式如下,用户传入一个Address当前地址,该地址减去KernelBase内存中的基址,然后再加上hKernel磁盘加载的基址来获取到相对偏移。

  • OffsetAddress = Address - KernelBase + hKernel

有了这两条信息那么功能也就实现了,通过GetOriginalMachineCode()得到指定内存地址处原始机器码,通过GetCurrentMachineCode()得到当前内存机器码,两者通过memcmp()函数比对即可知道是否被挂钩了,如果被挂钩则可以通过CR3切换将原始机器码覆盖到特定位置替换即可,这段程序的完整代码如下;

  1. #include <stdio.h>
  2. #include <Windows.h>
  3. #pragma comment(lib,"user32.lib")
  4. #pragma comment(lib,"Advapi32.lib")
  5. #ifndef NT_SUCCESS
  6. #define NT_SUCCESS(Status) ((NTSTATUS)(Status) >= 0)
  7. #endif
  8. #define BYTE_ARRAY_LENGTH 16
  9. #define SystemModuleInformation 11
  10. #define STATUS_INFO_LENGTH_MISMATCH ((NTSTATUS)0xC0000004L)
  11. typedef long(__stdcall *ZWQUERYSYSTEMINFORMATION)
  12. (
  13. IN ULONG SystemInformationClass,
  14. IN PVOID SystemInformation,
  15. IN ULONG SystemInformationLength,
  16. IN PULONG ReturnLength OPTIONAL
  17. );
  18. typedef struct
  19. {
  20. ULONG Unknow1;
  21. ULONG Unknow2;
  22. ULONG Unknow3;
  23. ULONG Unknow4;
  24. PVOID Base;
  25. ULONG Size;
  26. ULONG Flags;
  27. USHORT Index;
  28. USHORT NameLength;
  29. USHORT LoadCount;
  30. USHORT ModuleNameOffset;
  31. char ImageName[256];
  32. } SYSTEM_MODULE_INFORMATION_ENTRY, *PSYSTEM_MODULE_INFORMATION_ENTRY;
  33. typedef struct
  34. {
  35. ULONG Count;
  36. SYSTEM_MODULE_INFORMATION_ENTRY Module[1];
  37. } SYSTEM_MODULE_INFORMATION, *PSYSTEM_MODULE_INFORMATION;
  38. typedef struct
  39. {
  40. PVOID Address;
  41. ULONG64 Length;
  42. UCHAR data[256];
  43. } KF_DATA, *PKF_DATA;
  44. HANDLE hDriver = 0;
  45. HMODULE hKernel = 0;
  46. ULONG64 KernelBase = 0;
  47. CHAR NtosFullName[260] = { 0 };
  48. // 生成控制信号
  49. DWORD CTL_CODE_GEN(DWORD lngFunction)
  50. {
  51. return (FILE_DEVICE_UNKNOWN * 65536) | (FILE_ANY_ACCESS * 16384) | (lngFunction * 4) | METHOD_BUFFERED;
  52. }
  53. // 发送控制信号的函数
  54. BOOL IoControl(HANDLE hDrvHandle, DWORD dwIoControlCode, PVOID lpInBuffer, DWORD nInBufferSize, PVOID lpOutBuffer, DWORD nOutBufferSize)
  55. {
  56. DWORD lDrvRetSize;
  57. return DeviceIoControl(hDrvHandle, dwIoControlCode, lpInBuffer, nInBufferSize, lpOutBuffer, nOutBufferSize, &lDrvRetSize, 0);
  58. }
  59. // 动态获取ntdll.dll模块的基地址
  60. ULONG64 GetKernelBase64(PCHAR NtosName)
  61. {
  62. ZWQUERYSYSTEMINFORMATION ZwQuerySystemInformation;
  63. PSYSTEM_MODULE_INFORMATION pSystemModuleInformation;
  64. ULONG NeedSize, BufferSize = 0x5000;
  65. PVOID pBuffer = NULL;
  66. NTSTATUS Result;
  67. // 该函数只能通过动态方式得到地址
  68. ZwQuerySystemInformation = (ZWQUERYSYSTEMINFORMATION)GetProcAddress(GetModuleHandleA("ntdll.dll"), "ZwQuerySystemInformation");
  69. do
  70. {
  71. pBuffer = malloc(BufferSize);
  72. if (pBuffer == NULL) return 0;
  73. // 查询系统中的所有模块信息
  74. Result = ZwQuerySystemInformation(SystemModuleInformation, pBuffer, BufferSize, &NeedSize);
  75. if (Result == STATUS_INFO_LENGTH_MISMATCH)
  76. {
  77. free(pBuffer);
  78. BufferSize *= 2;
  79. }
  80. else if (!NT_SUCCESS(Result))
  81. {
  82. free(pBuffer);
  83. return 0;
  84. }
  85. } while (Result == STATUS_INFO_LENGTH_MISMATCH);
  86. // 取模块信息结构
  87. pSystemModuleInformation = (PSYSTEM_MODULE_INFORMATION)pBuffer;
  88. // 得到模块基地址
  89. ULONG64 ret = (ULONG64)(pSystemModuleInformation->Module[0].Base);
  90. // 拷贝模块名
  91. if (NtosName != NULL)
  92. {
  93. strcpy(NtosName, pSystemModuleInformation->Module[0].ImageName + pSystemModuleInformation->Module[0].ModuleNameOffset);
  94. }
  95. free(pBuffer);
  96. return ret;
  97. }
  98. // 判断并修复重定位表
  99. BOOL RepairRelocationTable(ULONG64 HandleInFile, ULONG64 BaseInKernel)
  100. {
  101. PIMAGE_DOS_HEADER pDosHeader;
  102. PIMAGE_NT_HEADERS64 pNtHeader;
  103. PIMAGE_BASE_RELOCATION pRelocTable;
  104. ULONG i, dwOldProtect;
  105. // 得到DOS头并判断是否符合DOS规范
  106. pDosHeader = (PIMAGE_DOS_HEADER)HandleInFile;
  107. if (pDosHeader->e_magic != IMAGE_DOS_SIGNATURE)
  108. {
  109. return FALSE;
  110. }
  111. // 得到Nt头
  112. pNtHeader = (PIMAGE_NT_HEADERS64)((ULONG64)HandleInFile + pDosHeader->e_lfanew);
  113. // 是否存在重定位表
  114. if (pNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size)
  115. {
  116. // 获取到重定位表基地址
  117. pRelocTable = (PIMAGE_BASE_RELOCATION)((ULONG64)HandleInFile + pNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress);
  118. do
  119. {
  120. // 得到重定位号
  121. ULONG numofReloc = (pRelocTable->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / 2;
  122. SHORT minioffset = 0;
  123. // 得到重定位数据
  124. PUSHORT pRelocData = (PUSHORT)((ULONG64)pRelocTable + sizeof(IMAGE_BASE_RELOCATION));
  125. // 循环或直接判断*pRelocData是否为0也可以作为结束标记
  126. for (i = 0; i<numofReloc; i++)
  127. {
  128. // 需要重定位的地址
  129. PULONG64 RelocAddress;
  130. // 重定位的高4位是重定位类型,判断重定位类型
  131. if (((*pRelocData) >> 12) == IMAGE_REL_BASED_DIR64)
  132. {
  133. // 计算需要进行重定位的地址
  134. // 重定位数据的低12位再加上本重定位块头的RVA即真正需要重定位的数据的RVA
  135. minioffset = (*pRelocData) & 0xFFF; // 小偏移
  136. // 模块基址+重定位基址+每个数据表示的小偏移量
  137. RelocAddress = (PULONG64)(HandleInFile + pRelocTable->VirtualAddress + minioffset);
  138. // 直接在RING3修改: 原始数据+基址-IMAGE_OPTINAL_HEADER中的基址
  139. VirtualProtect((PVOID)RelocAddress, 4, PAGE_EXECUTE_READWRITE, &dwOldProtect);
  140. // 因为是R3直接LOAD的所以要修改一下内存权限
  141. *RelocAddress = *RelocAddress + BaseInKernel - pNtHeader->OptionalHeader.ImageBase;
  142. VirtualProtect((PVOID)RelocAddress, 4, dwOldProtect, NULL);
  143. }
  144. // 下一个重定位数据
  145. pRelocData++;
  146. }
  147. // 下一个重定位块
  148. pRelocTable = (PIMAGE_BASE_RELOCATION)((ULONG64)pRelocTable + pRelocTable->SizeOfBlock);
  149. } while (pRelocTable->VirtualAddress);
  150. return TRUE;
  151. }
  152. return FALSE;
  153. }
  154. // 初始化
  155. BOOL InitEngine(BOOL IsClear)
  156. {
  157. if (IsClear == TRUE)
  158. {
  159. // 动态获取ntdll.dll模块的基地址
  160. KernelBase = GetKernelBase64(NtosFullName);
  161. printf("模块基址: %llx | 模块名: %s \n", KernelBase, NtosFullName);
  162. if (!KernelBase)
  163. {
  164. return FALSE;
  165. }
  166. // 动态加载模块到内存,并获取到模块句柄
  167. hKernel = LoadLibraryExA(NtosFullName, 0, DONT_RESOLVE_DLL_REFERENCES);
  168. if (!hKernel)
  169. {
  170. return FALSE;
  171. }
  172. // 判断并修复重定位表
  173. if (!RepairRelocationTable((ULONG64)hKernel, KernelBase))
  174. {
  175. return FALSE;
  176. }
  177. return TRUE;
  178. }
  179. else
  180. {
  181. FreeLibrary(hKernel);
  182. return TRUE;
  183. }
  184. }
  185. // 获取原始函数机器码
  186. VOID GetOriginalMachineCode(ULONG64 Address, PUCHAR ba, SIZE_T Length)
  187. {
  188. ULONG64 OffsetAddress = Address - KernelBase + (ULONG64)hKernel;
  189. RtlCopyMemory(ba, (PVOID)OffsetAddress, Length);
  190. }
  191. // 获取传入函数的内存地址
  192. ULONG64 GetSystemRoutineAddress(PCHAR FuncName)
  193. {
  194. return KernelBase + (ULONG64)GetProcAddress(hKernel, FuncName) - (ULONG64)hKernel;
  195. }
  196. // 获取当前函数机器码
  197. VOID GetCurrentMachineCode(ULONG64 Address, PUCHAR ba, SIZE_T Length)
  198. {
  199. ULONG64 dat[2] = { 0 };
  200. dat[0] = Address;
  201. dat[1] = Length;
  202. IoControl(hDriver, CTL_CODE_GEN(0x800), dat, 16, ba, Length);
  203. }
  204. // 清除特定位置的机器码
  205. VOID ClearInlineHook(ULONG64 Address, PUCHAR ba, SIZE_T Length)
  206. {
  207. KF_DATA dat = { 0 };
  208. dat.Address = (PVOID)Address;
  209. dat.Length = Length;
  210. // 直接调用写出控制码
  211. RtlCopyMemory(dat.data, ba, Length);
  212. IoControl(hDriver, CTL_CODE_GEN(0x801), &dat, sizeof(KF_DATA), 0, 0);
  213. }
  214. // 打印数据
  215. VOID PrintBytes(PCHAR DescriptionString, PUCHAR ba, UINT Length)
  216. {
  217. printf("%s", DescriptionString);
  218. for (UINT i = 0; i<Length; i++)
  219. {
  220. printf("%02x ", ba[i]);
  221. }
  222. printf("\n");
  223. }
  224. int main(int argc, char *argv[])
  225. {
  226. UCHAR OriginalMachineCode[BYTE_ARRAY_LENGTH];
  227. UCHAR CurrentMachineCode[BYTE_ARRAY_LENGTH];
  228. ULONG64 Address = 0;
  229. hDriver = CreateFileA("\\\\.\\WinDDK", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
  230. // 初始化
  231. if (!InitEngine(TRUE) || hDriver == 0)
  232. {
  233. return 0;
  234. }
  235. // 需要获取的函数列表
  236. CHAR *FunctionList[128] = { "PsLookupProcessByProcessId", "NtCommitEnlistment", "NtCommitComplete", "NtCommitTransaction" };
  237. for (size_t i = 0; i < 4; i++)
  238. {
  239. // 清空缓存
  240. RtlZeroMemory(OriginalMachineCode, 0, BYTE_ARRAY_LENGTH);
  241. RtlZeroMemory(CurrentMachineCode, 0, BYTE_ARRAY_LENGTH);
  242. // 获取到当前函数地址
  243. Address = GetSystemRoutineAddress(FunctionList[i]);
  244. printf("\n函数地址: %p | 函数名: %s\n", Address, FunctionList[i]);
  245. if (Address == 0 || Address < KernelBase)
  246. {
  247. return 0;
  248. }
  249. GetOriginalMachineCode(Address, OriginalMachineCode, BYTE_ARRAY_LENGTH);
  250. PrintBytes("原始机器码: ", OriginalMachineCode, BYTE_ARRAY_LENGTH);
  251. GetCurrentMachineCode(Address, CurrentMachineCode, BYTE_ARRAY_LENGTH);
  252. PrintBytes("当前机器码: ", CurrentMachineCode, BYTE_ARRAY_LENGTH);
  253. /*
  254. // 不相同则询问是否恢复
  255. if (memcmp(OriginalMachineCode, CurrentMachineCode, BYTE_ARRAY_LENGTH))
  256. {
  257. printf("按下[ENTER]恢复钩子");
  258. getchar();
  259. ClearInlineHook(Address, OriginalMachineCode, BYTE_ARRAY_LENGTH);
  260. }
  261. */
  262. }
  263. // 注销
  264. InitEngine(FALSE);
  265. system("pause");
  266. return 0;
  267. }

首先编译驱动程序WinDDK.sys并通过KmdManager将驱动程序拉起来,运行客户端lyshark.exe程序会输出当前FunctionList列表中,指定的4个函数的挂钩情况。

5.10 Windows驱动开发:摘除InlineHook内核钩子的更多相关文章

  1. Windows驱动开发(中间层)

    Windows驱动开发 一.前言 依据<Windows内核安全与驱动开发>及MSDN等网络质料进行学习开发. 二.初步环境 1.下载安装WDK7.1.0(WinDDK\7600.16385 ...

  2. [Windows驱动开发](一)序言

    笔者学习驱动编程是从两本书入门的.它们分别是<寒江独钓——内核安全编程>和<Windows驱动开发技术详解>.两本书分别从不同的角度介绍了驱动程序的制作方法. 在我理解,驱动程 ...

  3. windows驱动开发推荐书籍

    [作者] 猪头三 个人网站 :http://www.x86asm.com/ [序言] 很多人都对驱动开发有兴趣,但往往找不到正确的学习方式.当然这跟驱动开发的本土化资料少有关系.大多学的驱动开发资料都 ...

  4. windows 驱动开发入门——驱动中的数据结构

    最近在学习驱动编程方面的内容,在这将自己的一些心得分享出来,供大家参考,与大家共同进步,本人学习驱动主要是通过两本书--<独钓寒江 windows安全编程> 和 <windows驱动 ...

  5. Windows驱动——读书笔记《Windows驱动开发技术详解》

    =================================版权声明================================= 版权声明:原创文章 谢绝转载  请通过右侧公告中的“联系邮 ...

  6. Windows驱动开发-IRP的完成例程

    <Windows驱动开发技术详解 >331页, 在将IRP发送给底层驱动或其他驱动之前,可以对IRP设置一个完成例程,一旦底层驱动将IRP完成后,IRP完成例程立刻被处罚,通过设置完成例程 ...

  7. C++第三十八篇 -- 研究一下Windows驱动开发(二)--WDM式驱动的加载

    基于Windows驱动开发技术详解这本书 一.简单的INF文件剖析 INF文件是一个文本文件,由若干个节(Section)组成.每个节的名称用一个方括号指示,紧接着方括号后面的就是节内容.每一行就是一 ...

  8. C++第三十三篇 -- 研究一下Windows驱动开发(一)内部构造介绍

    因为工作原因,需要做一些与网卡有关的测试,其中涉及到了驱动这一块的知识,虽然程序可以运行,但是不搞清楚,心里总是不安,觉得没理解清楚.因此想看一下驱动开发.查了很多资料,看到有人推荐Windows驱动 ...

  9. Windows 驱动开发 - 5

    上篇<Windows 驱动开发 - 4>我们已经完毕了硬件准备. 可是我们还没有详细的数据操作,比如接收读写操作. 在WDF中进行此类操作前须要进行设备的IO控制,已保持数据的完整性. 我 ...

  10. Windows 驱动开发 - 7

    在<Windows 驱动开发 - 5>我们所说的读写操作在本篇实现. 在WDF中实现此功能主要为:EvtIoRead和EvtIoWrite. 首先,在EvtDeviceAdd设置以上两个回 ...

随机推荐

  1. Codeforces Round #704 (Div. 2) A~E

    比赛链接:https://codeforces.com/contest/1492 1492A.Three swimmers 题意: 有三名游泳的人,他们分别需要 \(a,b,c\) 分钟才能在一个游泳 ...

  2. Codeforces Round #665 (Div. 2) A - D题题解

    成功拼手速提前过了AC两题,估计因为这个原因排名挺高的,B题晚上做的时候没绕出来,wa4发... 1401A - Distance and Axis 如果 \(n\) 小 于 \(k\) ,则必须将\ ...

  3. C#设计模式13——建造者模式的写法

    1. 什么是建造者模式? 建造者模式是一种创建型设计模式,它通过将一个复杂的对象分解为多个简单的对象并按照一定的顺序进行组装而创建出一个复杂的对象.这样可以使得构造过程更加灵活,同时也可以隐藏创建过程 ...

  4. C#设计模式06——适配器的写法

    什么是适配器模式? 适配器模式是一种结构型设计模式,用于将现有接口转换为符合客户端期望的接口.适配器模式允许不兼容的类可以相互协作. 为什么需要适配器模式? 在实际开发中,经常会遇到需要复用一些已有的 ...

  5. spring-transaction源码分析(2)EnableTransactionManagement注解

    概述(Java doc) 该注解开启spring的注解驱动事务管理功能,通常标注在@Configuration类上面用于开启命令式事务管理或响应式事务管理. @Configuration @Enabl ...

  6. python3之lambda表达式

    技术背景 lambda表达式本身是一个非常基础的python函数语法,其基本功能跟使用def所定义的python函数是一样的,只是lambda表达式基本在一行以内就完整的表达了整个函数的运算逻辑.这里 ...

  7. 【FreeRTOS】内核查找最高优先级就绪任务

    查找最高优先级就绪任务 FreeRTOS\Source\tasks.c #if ( configUSE_PORT_OPTIMISED_TASK_SELECTION == 0 ) /* If confi ...

  8. mysql-字符函数-拼接-长度-切片-替换

  9. [转帖]Linux下非oracle用户如何使用系统认证方式登陆数据

    https://www.cnblogs.com/kerrycode/p/17772866.html Linux系统中,DBA一般使用oracle用户登陆/访问Linux操作系统,然后使用sqlplus ...

  10. [转帖]网站开启 IPv6 的三种方式

    https://zhuanlan.zhihu.com/p/443835798 从传统二进制部署的 Nginx ,到云原生部署的 K8S.Istio,分别介绍网站开启 IPv6 的三种方式. 1.Ngi ...