32位系统上获取SSDT表地址以及从中获取指定SSDT函数的地址

DemonGan

发布日期: 2019-02-24 20:56:20 浏览量: 1166
评分:
star star star star star star star star star star_border
*转载请注明来自write-bug.com

背景

SSDT 全称为 System Services Descriptor Table,即系统服务描述符表。SSDT 表的作用就是把 ring3 的 WIN32 API 函数和 ring0 的内核 API 函数联系起来。对于ring3下的一些API,最终会对应于 ntdll.dll 里一个 Ntxxx 函数,例如 CreateFile,最终调用到 ntdll.dll 里的 NtCreateFile 这个函数。NtCreateFile最终将系统服务号放入EAX,然后 CALL 系统的服务分发函数 KiSystemService,进入到内核当中。从 ring3 到 ring0,最终在 ring0 当中通过传入的 EAX 得到对应的同名系统服务的内核地址,这样就完成了一次系统服务的调用。SSDT 并不仅仅只包含一个庞大的地址索引表,它还包含着一些其它有用的信息,诸如地址索引的基地址、服务函数个数等。

SSDT 通过修改此表的函数地址可以对常用 Windows 函数进行 HOOK,从而实现对一些核心的系统动作进行过滤、监控的目的。一些HIPS、防毒软件、系统监控、注册表监控软件往往会采用此接口来实现自己的监控模块。

本质上,其实 SSDT 就是一个用来保存 Windows 系统服务地址的数组而已 。

32 位系统和 64 位上,获取 SSDT 表的方式并不相同,获取 SSDT 表中的函数地址也不相同。现在,我就分别对其进行极讲解介绍,并形成文档。本文主要讲解的是 32 位系统下,编程实现获取 SSDT 表的地址,以及获取 SSDT 表函数对应的内核地址。

实现原理

获取 SSDT 表的地址

在 32 位系统中,SSDT 表是内核 Ntoskrnl.exe 导出的一张表,导出符号为 KeServiceDescriptorTable,该表含有一个指针指向SSDT中包含 Ntoskrnl.exe 实现的核心服务。所以,我们要想在 32 位系统上获取 SSDT 表地址,直接获取 Ntoskrnl.exe 导出符号 KeServiceDescriptorTable 即可。

SSDT 表结构为:

  1. #pragma pack(1)
  2. typedef struct _SERVICE_DESCIPTOR_TABLE
  3. {
  4. PULONG ServiceTableBase; // SSDT基址
  5. PULONG ServiceCounterTableBase;// SSDT中服务被调用次数计数器
  6. ULONG NumberOfService; // SSDT服务个数
  7. PUCHAR ParamTableBase; // 系统服务参数表基址
  8. }SSDTEntry, *PSSDTEntry;
  9. #pragma pack()

所以,从 Ntoskrnl.exe 获取导出符号 KeServiceDescriptorTable 的代码如下:

  1. extern SSDTEntry __declspec(dllimport) KeServiceDescriptorTable;

获取 SSDT 表函数地址

在 32 位系统中,SSDT 包含了所有内核导出函数的地址。每个地址长度为 4 字节。所以要获得 SSDT 中某个函数的地址,如下代码所示:

  1. KeServiceDescriptorTable.ServiceTableBase + SSDT函数索引号*4
  2. // 或者
  3. KeServiceDescriptorTable.ServiceTableBase[SSDT函数索引号]

SSDT 函数索引号可以从 ntdll.dll 文件中获取,当 ring3 级 API 函数最终进入 ring0 级的时候,它会先将 SSDT函数索引号 mov 给 eax 寄存器。所以,我们获取 ntdll.dll 导出函数的地址,从中获取 SSDT 函数索引号。具体的实现过程分析过程,可以参考我写的《内核内存映射文件之获取SSDT函数索引号》这篇文章。

编码实现

获取SSDT函数索引号

  1. // 从 ntdll.dll 中获取 SSDT 函数索引号
  2. ULONG GetSSDTFunctionIndex(UNICODE_STRING ustrDllFileName, PCHAR pszFunctionName)
  3. {
  4. ULONG ulFunctionIndex = 0;
  5. NTSTATUS status = STATUS_SUCCESS;
  6. HANDLE hFile = NULL;
  7. HANDLE hSection = NULL;
  8. PVOID pBaseAddress = NULL;
  9. // 内存映射文件
  10. status = DllFileMap(ustrDllFileName, &hFile, &hSection, &pBaseAddress);
  11. if (!NT_SUCCESS(status))
  12. {
  13. KdPrint(("DllFileMap Error!\n"));
  14. return ulFunctionIndex;
  15. }
  16. // 根据导出表获取导出函数地址, 从而获取 SSDT 函数索引号
  17. ulFunctionIndex = GetIndexFromExportTable(pBaseAddress, pszFunctionName);
  18. // 释放
  19. ZwUnmapViewOfSection(NtCurrentProcess(), pBaseAddress);
  20. ZwClose(hSection);
  21. ZwClose(hFile);
  22. return ulFunctionIndex;
  23. }
  24. // 内存映射文件
  25. NTSTATUS DllFileMap(UNICODE_STRING ustrDllFileName, HANDLE *phFile, HANDLE *phSection, PVOID *ppBaseAddress)
  26. {
  27. NTSTATUS status = STATUS_SUCCESS;
  28. HANDLE hFile = NULL;
  29. HANDLE hSection = NULL;
  30. OBJECT_ATTRIBUTES objectAttributes = { 0 };
  31. IO_STATUS_BLOCK iosb = { 0 };
  32. PVOID pBaseAddress = NULL;
  33. SIZE_T viewSize = 0;
  34. // 打开 DLL 文件, 并获取文件句柄
  35. InitializeObjectAttributes(&objectAttributes, &ustrDllFileName, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL);
  36. status = ZwOpenFile(&hFile, GENERIC_READ, &objectAttributes, &iosb,
  37. FILE_SHARE_READ, FILE_SYNCHRONOUS_IO_NONALERT);
  38. if (!NT_SUCCESS(status))
  39. {
  40. KdPrint(("ZwOpenFile Error! [error code: 0x%X]", status));
  41. return status;
  42. }
  43. // 创建一个节对象, 以 PE 结构中的 SectionALignment 大小对齐映射文件
  44. status = ZwCreateSection(&hSection, SECTION_MAP_READ | SECTION_MAP_WRITE, NULL, 0, PAGE_READWRITE, 0x1000000, hFile);
  45. if (!NT_SUCCESS(status))
  46. {
  47. ZwClose(hFile);
  48. KdPrint(("ZwCreateSection Error! [error code: 0x%X]", status));
  49. return status;
  50. }
  51. // 映射到内存
  52. status = ZwMapViewOfSection(hSection, NtCurrentProcess(), &pBaseAddress, 0, 1024, 0, &viewSize, ViewShare, MEM_TOP_DOWN, PAGE_READWRITE);
  53. if (!NT_SUCCESS(status))
  54. {
  55. ZwClose(hSection);
  56. ZwClose(hFile);
  57. KdPrint(("ZwMapViewOfSection Error! [error code: 0x%X]", status));
  58. return status;
  59. }
  60. // 返回数据
  61. *phFile = hFile;
  62. *phSection = hSection;
  63. *ppBaseAddress = pBaseAddress;
  64. return status;
  65. }
  66. // 根据导出表获取导出函数地址, 从而获取 SSDT 函数索引号
  67. ULONG GetIndexFromExportTable(PVOID pBaseAddress, PCHAR pszFunctionName)
  68. {
  69. ULONG ulFunctionIndex = 0;
  70. // Dos Header
  71. PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pBaseAddress;
  72. // NT Header
  73. PIMAGE_NT_HEADERS pNtHeaders = (PIMAGE_NT_HEADERS)((PUCHAR)pDosHeader + pDosHeader->e_lfanew);
  74. // Export Table
  75. PIMAGE_EXPORT_DIRECTORY pExportTable = (PIMAGE_EXPORT_DIRECTORY)((PUCHAR)pDosHeader + pNtHeaders->OptionalHeader.DataDirectory[0].VirtualAddress);
  76. // 有名称的导出函数个数
  77. ULONG ulNumberOfNames = pExportTable->NumberOfNames;
  78. // 导出函数名称地址表
  79. PULONG lpNameArray = (PULONG)((PUCHAR)pDosHeader + pExportTable->AddressOfNames);
  80. PCHAR lpName = NULL;
  81. // 开始遍历导出表
  82. for (ULONG i = 0; i < ulNumberOfNames; i++)
  83. {
  84. lpName = (PCHAR)((PUCHAR)pDosHeader + lpNameArray[i]);
  85. // 判断是否查找的函数
  86. if (0 == _strnicmp(pszFunctionName, lpName, strlen(pszFunctionName)))
  87. {
  88. // 获取导出函数地址
  89. USHORT uHint = *(USHORT *)((PUCHAR)pDosHeader + pExportTable->AddressOfNameOrdinals + 2 * i);
  90. ULONG ulFuncAddr = *(PULONG)((PUCHAR)pDosHeader + pExportTable->AddressOfFunctions + 4 * uHint);
  91. PVOID lpFuncAddr = (PVOID)((PUCHAR)pDosHeader + ulFuncAddr);
  92. // 获取 SSDT 函数 Index
  93. #ifdef _WIN64
  94. ulFunctionIndex = *(ULONG *)((PUCHAR)lpFuncAddr + 4);
  95. #else
  96. ulFunctionIndex = *(ULONG *)((PUCHAR)lpFuncAddr + 1);
  97. #endif
  98. break;
  99. }
  100. }
  101. return ulFunctionIndex;
  102. }

获取SSDT表地址

  1. #pragma pack(1)
  2. typedef struct _SERVICE_DESCIPTOR_TABLE
  3. {
  4. PULONG ServiceTableBase; // SSDT基址
  5. PULONG ServiceCounterTableBase; // SSDT中服务被调用次数计数器
  6. ULONG NumberOfService; // SSDT服务个数
  7. PUCHAR ParamTableBase; // 系统服务参数表基址
  8. }SSDTEntry, *PSSDTEntry;
  9. #pragma pack()
  10. // 直接获取 SSDT
  11. extern SSDTEntry __declspec(dllimport) KeServiceDescriptorTable;

获取SSDT函数地址

  1. // 获取 SSDT 函数地址
  2. PVOID GetSSDTFunction(PCHAR pszFunctionName)
  3. {
  4. UNICODE_STRING ustrDllFileName;
  5. ULONG ulSSDTFunctionIndex = 0;
  6. PVOID pFunctionAddress = NULL;
  7. RtlInitUnicodeString(&ustrDllFileName, L"\\??\\C:\\Windows\\System32\\ntdll.dll");
  8. // 从 ntdll.dll 中获取 SSDT 函数索引号
  9. ulSSDTFunctionIndex = GetSSDTFunctionIndex(ustrDllFileName, pszFunctionName);
  10. // 根据索引号, 从SSDT表中获取对应函数地址
  11. pFunctionAddress = (PVOID)KeServiceDescriptorTable.ServiceTableBase[ulSSDTFunctionIndex];
  12. // 显示
  13. DbgPrint("[%s][Index:%d][Address:0x%p]\n", pszFunctionName, ulSSDTFunctionIndex, pFunctionAddress);
  14. return pFunctionAddress;
  15. }

程序测试

在 Win7 32 位系统下,驱动程序正常执行:

在 Win10 32 位系统下,驱动程序正常执行:

总结

32 位下的 SSDT 表已经由 Ntoskrnl.exe 导出,我们直接获取导出符号 KeServiceDescriptorTable 就能获取 SSDT 表。其中,要注意 SSDT 结构体是按 1 字节大小对齐的。

对于 SSDT 函数索引号的获取,可以从 ntdll.dll 的导出函数中获取,因为 ntdll.dll 导出函数的开头,总是将 SSDT 函数索引号 mov 到 eax 寄存器,所以,我们可以直接根据 ntdll.dll 的导出函数地址,获取 SSDT 函数索引号。

参考

参考自《Windows黑客编程技术详解》一书

上传的附件 cloud_download SSDTFunction_32_Test.7z ( 9.32kb, 2次下载 )

发送私信

这一切都不是我的,但总有一天,会是我的

73
文章数
67
评论数
最近文章
eject