在 Windows 操作系统内核中,PspCidTable 通常是与进程(Process)管理相关的数据结构之一。它与进程的标识和管理有关,每个进程都有一个唯一的标识符,称为进程 ID(PID)。与之相关的是客户端 ID,它是一个结构,其中包含唯一标识进程的信息。这样的标识符在进程管理、线程管理和内核对象的创建等方面都起到关键作用。

PspCidTable 可用于快速查找和管理进程信息,以便在系统中追踪、查询和操作进程。该表格可能包含有关每个进程的关键信息,例如 PID、客户端 ID、进程状态等。

在上一篇文章《内核枚举DpcTimer定时器》中我们通过枚举特征码的方式找到了DPC定时器基址并输出了内核中存在的定时器列表,本章将学习如何通过特征码定位的方式寻找Windows 10系统下面的PspCidTable内核句柄表地址。

首先引入一段基础概念;

  • 1.在windows下所有的资源都是用对象的方式进行管理的(文件、进程、设备等都是对象),当要访问一个对象时,如打开一个文件,系统就会创建一个对象句柄,通过这个句柄可以对这个文件进行各种操作。
  • 2.句柄和对象的联系是通过句柄表来进行的,准确来说一个句柄就是它所对应的对象在句柄表中的索引。
  • 3.通过句柄可以在句柄表中找到对象的指针,通过指针就可以对,对象进行操作。

PspCidTable 就是这样的一种表(内核句柄表),表的内部存放的是进程EPROCESS线程ETHREAD的内核对象,并通过进程PID线程TID进行索引,ID号以4递增,内核句柄表不属于任何进程,也不连接在系统的句柄表上,通过它可以返回系统的任何对象。

内核句柄表与普通句柄表完全一样,但它与每个进程私有的句柄表有以下不同;

  • 1.PspCidTable 中存放的对象是系统中所有的进程线程对象,其索引就是PIDTID
  • 2.PspCidTable 中存放的直接是对象体EPROCESS和ETHREAD,而每个进程私有的句柄表则存放的是对象头OBJECT_HEADER
  • 3.PspCidTable 是一个独立的句柄表,而每个进程私有的句柄表以一个双链连接起来。
  • 4.PspCidTable 访问对象时要掩掉低三位,每个进程私有的句柄表是双链连接起来的。

那么在Windows10系统中该如何枚举句柄表;

  • 1.首先找到PsLookupProcessByProcessId函数地址,该函数是被导出的可以动态拿到。
  • 2.其次在PsLookupProcessByProcessId地址中搜索PspReferenceCidTableEntry函数。
  • 3.最后在PspReferenceCidTableEntry地址中找到PspCidTable函数。

首先第一步先要得到PspCidTable函数内存地址,输入dp PspCidTable即可得到,如果在程序中则是调用MmGetSystemRoutineAddress取到。

PspCidTable是一个HANDLE_TALBE结构,当新建一个进程时,对应的会在PspCidTable存在一个该进程和线程对应的HANDLE_TABLE_ENTRY项。在windows10中依然采用动态扩展的方法,当句柄数少的时候就采用下层表,多的时候才启用中层表或上层表。

接着我们解析ffffdc88-79605dc0这个内存地址,执行dt _HANDLE_TABLE 0xffffdc8879605dc0得到规范化结构体。

内核句柄表分为三层如下;

  • 下层表:是一个HANDLE_TABLE_ENTRY项的索引,整个表共有256个元素,每个元素是一个8个字节长的HANDLE_TABLE_ENTRY项及索引,HANDLE_TABLE_ENTRY项中保存着指向对象的指针,下层表可以看成是进程和线程的稠密索引。
  • 中层表:共有256个元素,每个元素是4个字节长的指向下层表的入口指针及索引,中层表可以看成是进程和线程的稀疏索引。
  • 上层表:共有256个元素,每个元素是4个字节长的指向中层表的入口指针及索引,上层表可以看成是中层表的稀疏索引。

总结起来一个句柄表有一个上层表,一个上层表最多可以有256个中层表的入口指针,每个中层表最多可以有256个下层表的入口指针,每个下层表最多可以有256个进程和线程对象的指针。PspCidTable表可以看成是HANDLE_TBALE_ENTRY项的多级索引。

如上图所示TableCode是指向句柄表的指针,低二位(二进制)记录句柄表的等级:0(00)表示一级表,1(01)表示二级表,2(10)表示三级表。这里的 0xffffdc88-7d09b001 就说名它是一个二级表。

一级表里存放的就是进程和线程对象(加密过的,需要一些计算来解密),二级表里存放的是指向某个一级表的指针,同理三级表存放的是指向二级表的指针。

x64 系统中,每张表的大小是 0x1000(4096),一级表中存放的是 _handle_table_entry 结构(大小 = 16),二级表和三级表存放的是指针(大小 = 8)

我们对 0xffffdc88-7d09b001 抹去低二位,输入dp 0xffffdc887d09b000 输出的结果就是一张二级表,里面存储的就是一级表指针。

继续查看第一张一级表,输入dp 0xffffdc887962a000命令,我们知道一级句柄表是根据进程或线程ID来索引的,且以4累加,所以第一行对应id = 0,第二行对应id = 4。根据尝试,PID = 4的进程是System

所以此处的第二行0xb281de28-3300ffa7就是加密后的System进程的EPROCESS结构,对于Win10系统来说解密算法(value >> 0x10) & 0xfffffffffffffff0是这样的,我们通过代码计算出来。

  1. #include <Windows.h>
  2. #include <iostream>
  3. int _tmain(int argc, _TCHAR* argv[])
  4. {
  5. std::cout << "hello lyshark" << std::endl;
  6. ULONG64 ul_recode = 0xb281de283300ffa7;
  7. ULONG64 ul_decode = (LONG64)ul_recode >> 0x10;
  8. ul_decode &= 0xfffffffffffffff0;
  9. std::cout << "解密后地址: " << std::hex << ul_decode << std::endl;
  10. getchar();
  11. return 0;
  12. }

运行程序得到如下输出,即可知道System系统进程解密后的EPROCESS结构地址是0xffffb281de283300

回到WinDBG调试器,输入命令dt _EPROCESS 0xffffb281de283300解析以下这个结构,输出结果是System进程。

理论知识总结已经结束了,接下来就是如何实现枚举进程线程了,枚举流程如下:

  • 1.首先找到PspCidTable的地址。
  • 2.然后找到HANDLE_TBALE的地址。
  • 3.根据TableCode来判断层次结构。
  • 4.遍历层次结构来获取对象地址。
  • 5.判断对象类型是否为进程对象。
  • 6.判断进程是否有效。

这里先来实现获取PspCidTable函数的动态地址,代码如下。

  1. #include <ntifs.h>
  2. #include <windef.h>
  3. // 获取 PspCidTable
  4. BOOLEAN get_PspCidTable(ULONG64* tableAddr)
  5. {
  6. // 获取 PsLookupProcessByProcessId 地址
  7. UNICODE_STRING uc_funcName;
  8. RtlInitUnicodeString(&uc_funcName, L"PsLookupProcessByProcessId");
  9. ULONG64 ul_funcAddr = MmGetSystemRoutineAddress(&uc_funcName);
  10. if (ul_funcAddr == NULL)
  11. {
  12. return FALSE;
  13. }
  14. DbgPrint("PsLookupProcessByProcessId addr = %p \n", ul_funcAddr);
  15. // 前 40 字节有 call(PspReferenceCidTableEntry)
  16. /*
  17. 0: kd> uf PsLookupProcessByProcessId
  18. nt!PsLookupProcessByProcessId:
  19. fffff802`0841cfe0 48895c2418 mov qword ptr [rsp+18h],rbx
  20. fffff802`0841cfe5 56 push rsi
  21. fffff802`0841cfe6 4883ec20 sub rsp,20h
  22. fffff802`0841cfea 48897c2438 mov qword ptr [rsp+38h],rdi
  23. fffff802`0841cfef 488bf2 mov rsi,rdx
  24. fffff802`0841cff2 65488b3c2588010000 mov rdi,qword ptr gs:[188h]
  25. fffff802`0841cffb 66ff8fe6010000 dec word ptr [rdi+1E6h]
  26. fffff802`0841d002 b203 mov dl,3
  27. fffff802`0841d004 e887000000 call nt!PspReferenceCidTableEntry (fffff802`0841d090)
  28. fffff802`0841d009 488bd8 mov rbx,rax
  29. fffff802`0841d00c 4885c0 test rax,rax
  30. fffff802`0841d00f 7435 je nt!PsLookupProcessByProcessId+0x66 (fffff802`0841d046) Branch
  31. */
  32. ULONG64 ul_entry = 0;
  33. for (INT i = 0; i < 100; i++)
  34. {
  35. // fffff802`0841d004 e8 87 00 00 00 call nt!PspReferenceCidTableEntry (fffff802`0841d090)
  36. if (*(PUCHAR)(ul_funcAddr + i) == 0xe8)
  37. {
  38. ul_entry = ul_funcAddr + i;
  39. break;
  40. }
  41. }
  42. if (ul_entry != 0)
  43. {
  44. // 解析 call 地址
  45. INT i_callCode = *(INT*)(ul_entry + 1);
  46. DbgPrint("i_callCode = %p \n", i_callCode);
  47. ULONG64 ul_callJmp = ul_entry + i_callCode + 5;
  48. DbgPrint("ul_callJmp = %p \n", ul_callJmp);
  49. // 来到 call(PspReferenceCidTableEntry) 内找 PspCidTable
  50. /*
  51. 0: kd> uf PspReferenceCidTableEntry
  52. nt!PspReferenceCidTableEntry+0x115:
  53. fffff802`0841d1a5 488b0d8473f5ff mov rcx,qword ptr [nt!PspCidTable (fffff802`08374530)]
  54. fffff802`0841d1ac b801000000 mov eax,1
  55. fffff802`0841d1b1 f0480fc107 lock xadd qword ptr [rdi],rax
  56. fffff802`0841d1b6 4883c130 add rcx,30h
  57. fffff802`0841d1ba f0830c2400 lock or dword ptr [rsp],0
  58. fffff802`0841d1bf 48833900 cmp qword ptr [rcx],0
  59. fffff802`0841d1c3 0f843fffffff je nt!PspReferenceCidTableEntry+0x78 (fffff802`0841d108) Branch
  60. */
  61. for (INT i = 0; i < 0x120; i++)
  62. {
  63. // fffff802`0841d1a5 48 8b 0d 84 73 f5 ff mov rcx,qword ptr [nt!PspCidTable (fffff802`08374530)]
  64. if (*(PUCHAR)(ul_callJmp + i) == 0x48 && *(PUCHAR)(ul_callJmp + i + 1) == 0x8b && *(PUCHAR)(ul_callJmp + i + 2) == 0x0d)
  65. {
  66. // 解析 mov 地址
  67. INT i_movCode = *(INT*)(ul_callJmp + i + 3);
  68. DbgPrint("i_movCode = %p \n", i_movCode);
  69. ULONG64 ul_movJmp = ul_callJmp + i + i_movCode + 7;
  70. DbgPrint("ul_movJmp = %p \n", ul_movJmp);
  71. // 得到 PspCidTable
  72. *tableAddr = ul_movJmp;
  73. return TRUE;
  74. }
  75. }
  76. }
  77. return FALSE;
  78. }
  79. VOID UnDriver(PDRIVER_OBJECT driver)
  80. {
  81. DbgPrint(("Uninstall Driver Is OK \n"));
  82. }
  83. NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
  84. {
  85. DbgPrint(("hello lyshark \n"));
  86. ULONG64 tableAddr = 0;
  87. get_PspCidTable(&tableAddr);
  88. DbgPrint("PspCidTable Address = %p \n", tableAddr);
  89. Driver->DriverUnload = UnDriver;
  90. return STATUS_SUCCESS;
  91. }

运行后即可得到动态地址,我们可以验证一下是否一致:

继续增加对与三级表的动态解析代码,最终代码如下所示:

  1. #include <ntifs.h>
  2. #include <windef.h>
  3. // 获取 PspCidTable
  4. BOOLEAN get_PspCidTable(ULONG64* tableAddr)
  5. {
  6. // 获取 PsLookupProcessByProcessId 地址
  7. UNICODE_STRING uc_funcName;
  8. RtlInitUnicodeString(&uc_funcName, L"PsLookupProcessByProcessId");
  9. ULONG64 ul_funcAddr = MmGetSystemRoutineAddress(&uc_funcName);
  10. if (ul_funcAddr == NULL)
  11. {
  12. return FALSE;
  13. }
  14. DbgPrint("PsLookupProcessByProcessId addr = %p \n", ul_funcAddr);
  15. // 前 40 字节有 call(PspReferenceCidTableEntry)
  16. /*
  17. 0: kd> uf PsLookupProcessByProcessId
  18. nt!PsLookupProcessByProcessId:
  19. fffff802`0841cfe0 48895c2418 mov qword ptr [rsp+18h],rbx
  20. fffff802`0841cfe5 56 push rsi
  21. fffff802`0841cfe6 4883ec20 sub rsp,20h
  22. fffff802`0841cfea 48897c2438 mov qword ptr [rsp+38h],rdi
  23. fffff802`0841cfef 488bf2 mov rsi,rdx
  24. fffff802`0841cff2 65488b3c2588010000 mov rdi,qword ptr gs:[188h]
  25. fffff802`0841cffb 66ff8fe6010000 dec word ptr [rdi+1E6h]
  26. fffff802`0841d002 b203 mov dl,3
  27. fffff802`0841d004 e887000000 call nt!PspReferenceCidTableEntry (fffff802`0841d090)
  28. fffff802`0841d009 488bd8 mov rbx,rax
  29. fffff802`0841d00c 4885c0 test rax,rax
  30. fffff802`0841d00f 7435 je nt!PsLookupProcessByProcessId+0x66 (fffff802`0841d046) Branch
  31. */
  32. ULONG64 ul_entry = 0;
  33. for (INT i = 0; i < 100; i++)
  34. {
  35. // fffff802`0841d004 e8 87 00 00 00 call nt!PspReferenceCidTableEntry (fffff802`0841d090)
  36. if (*(PUCHAR)(ul_funcAddr + i) == 0xe8)
  37. {
  38. ul_entry = ul_funcAddr + i;
  39. break;
  40. }
  41. }
  42. if (ul_entry != 0)
  43. {
  44. // 解析 call 地址
  45. INT i_callCode = *(INT*)(ul_entry + 1);
  46. DbgPrint("i_callCode = %p \n", i_callCode);
  47. ULONG64 ul_callJmp = ul_entry + i_callCode + 5;
  48. DbgPrint("ul_callJmp = %p \n", ul_callJmp);
  49. // 来到 call(PspReferenceCidTableEntry) 内找 PspCidTable
  50. /*
  51. 0: kd> uf PspReferenceCidTableEntry
  52. nt!PspReferenceCidTableEntry+0x115:
  53. fffff802`0841d1a5 488b0d8473f5ff mov rcx,qword ptr [nt!PspCidTable (fffff802`08374530)]
  54. fffff802`0841d1ac b801000000 mov eax,1
  55. fffff802`0841d1b1 f0480fc107 lock xadd qword ptr [rdi],rax
  56. fffff802`0841d1b6 4883c130 add rcx,30h
  57. fffff802`0841d1ba f0830c2400 lock or dword ptr [rsp],0
  58. fffff802`0841d1bf 48833900 cmp qword ptr [rcx],0
  59. fffff802`0841d1c3 0f843fffffff je nt!PspReferenceCidTableEntry+0x78 (fffff802`0841d108) Branch
  60. */
  61. for (INT i = 0; i < 0x120; i++)
  62. {
  63. // fffff802`0841d1a5 48 8b 0d 84 73 f5 ff mov rcx,qword ptr [nt!PspCidTable (fffff802`08374530)]
  64. if (*(PUCHAR)(ul_callJmp + i) == 0x48 && *(PUCHAR)(ul_callJmp + i + 1) == 0x8b && *(PUCHAR)(ul_callJmp + i + 2) == 0x0d)
  65. {
  66. // 解析 mov 地址
  67. INT i_movCode = *(INT*)(ul_callJmp + i + 3);
  68. DbgPrint("i_movCode = %p \n", i_movCode);
  69. ULONG64 ul_movJmp = ul_callJmp + i + i_movCode + 7;
  70. DbgPrint("ul_movJmp = %p \n", ul_movJmp);
  71. // 得到 PspCidTable
  72. *tableAddr = ul_movJmp;
  73. return TRUE;
  74. }
  75. }
  76. }
  77. return FALSE;
  78. }
  79. /* 解析一级表
  80. BaseAddr:一级表的基地址
  81. index1:第几个一级表
  82. index2:第几个二级表
  83. */
  84. VOID parse_table_1(ULONG64 BaseAddr, INT index1, INT index2)
  85. {
  86. // 遍历一级表(每个表项大小 16 ),表大小 4k,所以遍历 4096/16 = 526 次
  87. PEPROCESS p_eprocess = NULL;
  88. PETHREAD p_ethread = NULL;
  89. INT i_id = 0;
  90. for (INT i = 0; i < 256; i++)
  91. {
  92. if (!MmIsAddressValid((PVOID64)(BaseAddr + i * 16)))
  93. {
  94. DbgPrint("非法地址= %p \n", BaseAddr + i * 16);
  95. continue;
  96. }
  97. ULONG64 ul_recode = *(PULONG64)(BaseAddr + i * 16);
  98. // 解密
  99. ULONG64 ul_decode = (LONG64)ul_recode >> 0x10;
  100. ul_decode &= 0xfffffffffffffff0;
  101. // 判断是进程还是线程
  102. i_id = i * 4 + 1024 * index1 + 512 * index2 * 1024;
  103. if (PsLookupProcessByProcessId(i_id, &p_eprocess) == STATUS_SUCCESS)
  104. {
  105. DbgPrint("进程PID: %d | ID: %d | 内存地址: %p | 对象: %p \n", i_id, i, BaseAddr + i * 0x10, ul_decode);
  106. }
  107. else if (PsLookupThreadByThreadId(i_id, &p_ethread) == STATUS_SUCCESS)
  108. {
  109. DbgPrint("线程TID: %d | ID: %d | 内存地址: %p | 对象: %p \n", i_id, i, BaseAddr + i * 0x10, ul_decode);
  110. }
  111. }
  112. }
  113. /* 解析二级表
  114. BaseAddr:二级表基地址
  115. index2:第几个二级表
  116. */
  117. VOID parse_table_2(ULONG64 BaseAddr, INT index2)
  118. {
  119. // 遍历二级表(每个表项大小 8),表大小 4k,所以遍历 4096/8 = 512 次
  120. ULONG64 ul_baseAddr_1 = 0;
  121. for (INT i = 0; i < 512; i++)
  122. {
  123. if (!MmIsAddressValid((PVOID64)(BaseAddr + i * 8)))
  124. {
  125. DbgPrint("非法二级表指针(1):%p \n", BaseAddr + i * 8);
  126. continue;
  127. }
  128. if (!MmIsAddressValid((PVOID64)*(PULONG64)(BaseAddr + i * 8)))
  129. {
  130. DbgPrint("非法二级表指针(2):%p \n", BaseAddr + i * 8);
  131. continue;
  132. }
  133. ul_baseAddr_1 = *(PULONG64)(BaseAddr + i * 8);
  134. parse_table_1(ul_baseAddr_1, i, index2);
  135. }
  136. }
  137. /* 解析三级表
  138. BaseAddr:三级表基地址
  139. */
  140. VOID parse_table_3(ULONG64 BaseAddr)
  141. {
  142. // 遍历三级表(每个表项大小 8),表大小 4k,所以遍历 4096/8 = 512 次
  143. ULONG64 ul_baseAddr_2 = 0;
  144. for (INT i = 0; i < 512; i++)
  145. {
  146. if (!MmIsAddressValid((PVOID64)(BaseAddr + i * 8)))
  147. {
  148. continue;
  149. }
  150. if (!MmIsAddressValid((PVOID64)* (PULONG64)(BaseAddr + i * 8)))
  151. {
  152. continue;
  153. }
  154. ul_baseAddr_2 = *(PULONG64)(BaseAddr + i * 8);
  155. parse_table_2(ul_baseAddr_2, i);
  156. }
  157. }
  158. VOID UnDriver(PDRIVER_OBJECT driver)
  159. {
  160. DbgPrint(("Uninstall Driver Is OK \n"));
  161. }
  162. NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
  163. {
  164. DbgPrint(("hello lyshark \n"));
  165. ULONG64 tableAddr = 0;
  166. get_PspCidTable(&tableAddr);
  167. DbgPrint("PspCidTable Address = %p \n", tableAddr);
  168. // 获取 _HANDLE_TABLE 的 TableCode
  169. ULONG64 ul_tableCode = *(PULONG64)(((ULONG64)*(PULONG64)tableAddr) + 8);
  170. DbgPrint("ul_tableCode = %p \n", ul_tableCode);
  171. // 取低 2位(二级制11 = 3)
  172. INT i_low2 = ul_tableCode & 3;
  173. DbgPrint("i_low2 = %X \n", i_low2);
  174. // 一级表
  175. if (i_low2 == 0)
  176. {
  177. // TableCode 低 2位抹零(二级制11 = 3)
  178. parse_table_1(ul_tableCode & (~3), 0, 0);
  179. }
  180. // 二级表
  181. else if (i_low2 == 1)
  182. {
  183. // TableCode 低 2位抹零(二级制11 = 3)
  184. parse_table_2(ul_tableCode & (~3), 0);
  185. }
  186. // 三级表
  187. else if (i_low2 == 2)
  188. {
  189. // TableCode 低 2位抹零(二级制11 = 3)
  190. parse_table_3(ul_tableCode & (~3));
  191. }
  192. else
  193. {
  194. DbgPrint("LyShark提示: 错误,非法! ");
  195. return FALSE;
  196. }
  197. Driver->DriverUnload = UnDriver;
  198. return STATUS_SUCCESS;
  199. }

运行如上完整代码,我们可以在WinDBG中捕捉到枚举到的进程信息:

线程信息在进程信息的下面,枚举效果如下:

至此文章就结束了,这里多说一句,实际上ZwQuerySystemInformation枚举系统句柄时就是走的这条双链,枚举系统进程如果使用的是这个API函数,那么不出意外它也是在这些内核表中做的解析。

6.5 Windows驱动开发:内核枚举PspCidTable句柄表的更多相关文章

  1. 驱动开发:内核枚举PspCidTable句柄表

    在上一篇文章<驱动开发:内核枚举DpcTimer定时器>中我们通过枚举特征码的方式找到了DPC定时器基址并输出了内核中存在的定时器列表,本章将学习如何通过特征码定位的方式寻找Windows ...

  2. Windows驱动开发-内核常用内存函数

    搞内存常用函数 C语言 内核 malloc ExAllocatePool memset RtlFillMemory memcpy RtlMoveMemory free ExFreePool

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

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

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

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

  5. windows驱动开发推荐书籍

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

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

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

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

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

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

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

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

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

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

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

随机推荐

  1. 路由分发 路由别名的名称空间 虚拟环境 HTTPresponse JsonResponse request对象获取文件 CBV源码剖析 模板语法传值特性 模板语法过滤器

    目录 路由分发 路由别名冲突 反向解析失败 方式一:名称空间 include 方式二:别名不冲突即可 虚拟环境 python -m venv venv_name 视图层之必会三板斧 HTTPrespo ...

  2. 【每日一题】20.K-th Number (二分 + 尺取)

    关于此题,我们分析一下: 一个区间第k大的数不小于x的条件是什么? 答案就是一个区间内不小于x的数的个数不小于k 那么,我们就会发现,我们其实并不需要知道每个数的值,实际上对我们有用的只有每个数与x的 ...

  3. Nacos注册中心搭建

    1.Nacos服务端搭建(需要有java环境),下载地址:https://github.com/alibaba/Nacos/releases 下载对应操作系统的包解压. 1.1.解压:tar -zxv ...

  4. u-swipe-action 宽度计算的延迟导致组件加载时内部样式错误

    https://toscode.gitee.com/umicro/uView/issues/I1Y50J 左图为电脑显示效果,右图为app显示效果. 原因:u-swipe-action 宽度计算的延迟 ...

  5. uni-app图片上传接口联调

    https://www.bilibili.com/video/BV1jy4y1B7pw?p=159&spm_id_from=pageDriver

  6. freeswitch查看所有通道变量

    概述 freeswitch 是一款好用的开源软交换平台. 实际应用中,我们经常需要对fs中的通道变量操作,包括设置和获取,set & get. 但是,fs中有众多的内部定义通道变量,也有外部传 ...

  7. zzuli1895: 985的0-1串难题

    //解法:用二分查找,如果当前位置是'1',则查找比这个位置多k+1个零的位置,如果当前位置是'0',则查找比当前位置多k个零的位置, 注意要在末尾添个最大的值 #include<iostrea ...

  8. git仓库之gogs安装(docker版/二进制版)

    二进制安装gogs tar zxf gogs_0.11.91_linux_amd64.tar.gz -C /data/gogs chown -R www.www /data/gogs su - www ...

  9. java - Array 数组反序输出

    通过Array类的静态 sort() 方法可实现对数组排序,sort() 方法提供了许多种重载形式,可对任意类型数组进行升序排序. 然而,Array类中没有类似 js 的 reverse   反序输出 ...

  10. Shell-表达式-比较-文件判断-权限判断-条件-逻辑