Watch & Learn

Debugwar Blog

Step in or Step over, this is a problem ...

Windows 11 SSDT & ShadowSSDT地址获取问题

2022-10-31 04:42:50

近期有一个项目中需要遍历SSDT与ShadowSSDT,按照网上常规的readmsr(0xc0000082)方式获取这两张表的起始地址,Windows 7到Windows 10均无问题,但是发现Windows 11下无法正常获取。网上搜索没有任何相关的讨论,于是便简单研究了一下,有了此文。

常规获取方式

网上常规的获取方式为使用readmsr指令获取寄存器0xC0000082的内容,google搜索排名第一的是一篇推文参考1


对应的C代码大致如下:

  1. PKSERVICE_TABLE_DESCRIPTOR GetKeServiceDescriptorTableAddress(SEARCH_TABLE_TYPE SearchType)  
  2. {  
  3.     PULONG_PTR KiSystemCall64 = NULL;  
  4.     PUCHAR CurrentAddr = NULL;  
  5.   
  6.     KiSystemCall64 = (PVOID)__readmsr(0xC0000082);  
  7.     for (CurrentAddr = KiSystemCall64; CurrentAddr <= KiSystemCall64 + 1024; ++CurrentAddr)  
  8.     {  
  9.         switch (SearchType)  
  10.         {  
  11.           case KeServiceDescriptorTableType:  
  12.             if (0x4c == *CurrentAddr && 0x8d == *(PUCHAR)(CurrentAddr + 1) && 0x15 == *(PUCHAR)(CurrentAddr + 2))  
  13.             {  
  14.               UHALF_PTR Offset = *(PUHALF_PTR)(CurrentAddr + 3);  
  15.               ULONG_PTR Base = CurrentAddr + 7;  
  16.               return X64_ADDRESS_ADD(Base, Offset);  
  17.             }  
  18.           break;  
  19.           case KeServiceDescriptorTableShadowType:  
  20.             if (0x4c == *CurrentAddr && 0x8d == *(PUCHAR)(CurrentAddr + 1) && 0x1d == *(PUCHAR)(CurrentAddr + 2))  
  21.             {  
  22.               UHALF_PTR Offset = *(PUHALF_PTR)(CurrentAddr + 3);  
  23.               ULONG_PTR Base = CurrentAddr + 7;  
  24.               return X64_ADDRESS_ADD(Base, Offset);  
  25.             }  
  26.           break;  
  27.         }  
  28.     }  
  29. }  

如本文一开始所述,这段代码在Windows 10以下的系统上是没问题的, 可以正确获取到KeServiceDescriptor与KeServiceDescriptorShaodow表的地址,但是在Windows 11下无法正常工作。

问题探究

我们先看一下,Windows 11下readmsr指令获取到的值是什么:

  1. 0: kd> vertarget  
  2. Windows 10 Kernel Version 22000 MP (2 procs) Free x64  
  3. Product: WinNt, suite: TerminalServer SingleUserTS  
  4. Edition build lab: 22000.1.amd64fre.co_release.210604-1628  
  5. Machine Name:  
  6. Kernel base = 0xfffff803`14a00000 PsLoadedModuleList = 0xfffff803`15629710  
  7. Debug session time: Mon Oct 31 12:30:14.915 2022 (UTC + 8:00)  
  8. System Uptime: 0 days 0:17:39.229  
  9. 0: kd> rdmsr 0xc0000082  
  10. msr[c0000082] = fffff803`154b4180  
  11. 0: kd> ln fffff803`154b4180  
  12. Browse module  
  13. Set bu breakpoint  
  14.   
  15. (fffff803`154b4180)   nt!KiSystemCall64Shadow   |  (fffff803`154b5060)   nt!_guard_retpoline_icall_handler  
  16. Exact matches:  

所以可以确定两点:
  1. Windows 11虽然叫11,但是依然使用的是Windows 10的内核,换皮操作系统实锤
  2. Windows 11下MSR[0xC0000082]寄存器中存储的是KiSystemCall64Shadow,而Windows 11之前这里一直都是存放的KiSystemCall64
上面说的第二点,即为在Windows 11下无法通过MSR[0xC0000082]获取KeServiceDescriptor或KeServiceDescriptorShaodow表首地址的直接原因,那么我们真的就无法稳定获取上述两张表的地址了吗?非也~

问题解决

让我们先来看一下几个关键数据结构的基本信息:

  1. 0: kd> rdmsr 0xc0000082  
  2. msr[c0000082] = fffff803`154b4180  
  3. 0: kd> ln fffff803`154b4180  
  4. Browse module  
  5. Set bu breakpoint  
  6.   
  7. (fffff803`154b4180)   nt!KiSystemCall64Shadow   |  (fffff803`154b5060)   nt!_guard_retpoline_icall_handler  
  8. Exact matches:  
  9. 0: kd> x nt!KiSystemCall64  
  10. fffff803`14e24bc0 nt!KiSystemCall64 (KiSystemCall64)  
  11. 0: kd> uf nt!KiSystemCall64Shadow  
  12. Flow analysis was incomplete, some code may be missing  
  13. nt!KiSystemServiceUser:  
  14. fffff803`14e24e1a c645ab02        mov     byte ptr [rbp-55h],2  
  15. fffff803`14e24e1e 65488b1c2588010000 mov   rbx,qword ptr gs:[188h]  
  16. fffff803`14e24e27 0f0d8b90000000  prefetchw [rbx+90h]  
  17. fffff803`14e24e2e 0fae5dac        stmxcsr dword ptr [rbp-54h]  
  18. ......  
  19. nt!KiSystemServiceRepeat:  
  20. fffff803`14e24ef4 4c8d15c5c99d00  lea     r10,[nt!KeServiceDescriptorTable (fffff803`158018c0)]  
  21. fffff803`14e24efb 4c8d1dfe208e00  lea     r11,[nt!KeServiceDescriptorTableShadow (fffff803`15707000)]  
  22. fffff803`14e24f02 f7437880000000  test    dword ptr [rbx+78h],80h  
  23. ......  
  24. nt!KiSystemCall64Shadow+0x24c:  
  25. fffff803`154b43cc 0faee8          lfence  
  26.   
  27. nt!KiSystemCall64Shadow+0x24f:  
  28. fffff803`154b43cf 65c604255308000000 mov   byte ptr gs:[853h],0  
  29. fffff803`154b43d8 e93d0a97ff      jmp     nt!KiSystemServiceUser (fffff803`14e24e1a)  Branch  
  30. 0: kd> uf nt!KiSystemCall64  
  31. Flow analysis was incomplete, some code may be missing  
  32. nt!KiSystemCall64:  
  33. fffff803`14e24bc0 0f01f8          swapgs  
  34. fffff803`14e24bc3 654889242510000000 mov   qword ptr gs:[10h],rsp  
  35. fffff803`14e24bcc 65488b2425a8010000 mov   rsp,qword ptr gs:[1A8h]  
  36. ......  
  37. fffff803`14e24ef4 4c8d15c5c99d00  lea     r10,[nt!KeServiceDescriptorTable (fffff803`158018c0)]  
  38. fffff803`14e24efb 4c8d1dfe208e00  lea     r11,[nt!KeServiceDescriptorTableShadow (fffff803`15707000)]  
  39. fffff803`14e24f02 f7437880000000  test    dword ptr [rbx+78h],80h  
  40. fffff803`14e24f09 7413            je      nt!KiSystemServiceRepeat+0x2a (fffff803`14e24f1e)  Branch  
  41. ......  
  42. fffff803`154b3e19 0f01f8 swapgs
  43. fffff803`154b3e1c 480f07 sysretq

可以看到,在KiSystemCall64Shadow函数的最后,出现了一个非常不常见的指令:lfence。而通过查询该指令得知,其OP Code是一个固定值:0x0faee8,如下图参考2


而在lfence之后的不远处紧跟一条je跳转指令,该指令跳转到:nt!KiSystemServiceUser (fffff803`14e24e1a)。

通过windbg输出,我们可以知道第二个重要信息:nt!KiSystemCall64的地址范围为fffff803`14e24bc0至fffff803`154b3e1c。

OK,到此我们发现KiSystemCall64Shadow最后的跳转,正好会跳转到KiSystemCall64开始的不远处继续执行,而传统获取KeServiceDescriptorTable和KeServiceDescriptorTableShadow的方法,其实就是在KiSystemCall64函数内搜索特征码。

因此,我们可以根据lfence指令定位下面的je跳转,计算出跳转地址后,即可以利用传统搜索KiSystemCall64函数的方式获取我们需要的表结构起始地址了。

Coding Time

废话不多说, 稍微改造一下本文一开始的代码:

  1. PKSERVICE_TABLE_DESCRIPTOR GetKeServiceDescriptorTableAddress(SEARCH_TABLE_TYPE SearchType)  
  2. {  
  3.   PULONG_PTR KiSystemCall64 = NULL;  
  4.   PUCHAR CurrentAddr = NULL;  
  5.   
  6.   KiSystemCall64 = (PVOID)__readmsr(0xC0000082);  
  7.   for (CurrentAddr = KiSystemCall64; CurrentAddr <= KiSystemCall64 + 1024; ++CurrentAddr)  
  8.   {  
  9.     switch (SearchType)  
  10.     {  
  11.       case KeServiceDescriptorTableType:  
  12.         if (0x4c == *CurrentAddr && 0x8d == *(PUCHAR)(CurrentAddr + 1) && 0x15 == *(PUCHAR)(CurrentAddr + 2))  
  13.         {  
  14.           UHALF_PTR Offset = *(PUHALF_PTR)(CurrentAddr + 3);  
  15.           ULONG_PTR Base = CurrentAddr + 7;  
  16.           return X64_ADDRESS_ADD(Base, Offset);  
  17.         }  
  18.       break;  
  19.       case KeServiceDescriptorTableShadowType:  
  20.         if (0x4c == *CurrentAddr && 0x8d == *(PUCHAR)(CurrentAddr + 1) && 0x1d == *(PUCHAR)(CurrentAddr + 2))  
  21.         {  
  22.           UHALF_PTR Offset = *(PUHALF_PTR)(CurrentAddr + 3);  
  23.           ULONG_PTR Base = CurrentAddr + 7;  
  24.           return X64_ADDRESS_ADD(Base, Offset);  
  25.         }  
  26.       break;  
  27.     }  
  28.     // Windows 11, msr[0xC0000082] = nt!KiSystemCall64Shadow  
  29.     // We need search back  
  30.     //  fffff803`154b43c8 4883c408        add     rsp,8  
  31.     //  fffff803`154b43cc 0faee8          lfence  <-- all of these bytes  
  32.     //  fffff803`154b43cf 65c604255308000000 mov   byte ptr gs:[853h],0 <-- last byte  
  33.     //  fffff803`154b43d8 e93d0a97ff      jmp     nt!KiSystemServiceUser (fffff803`14e24e1a)  Branch <-- first 1 byte  
  34.     if (0x00e8ae0f == (UHALF_PTR)((*(PUHALF_PTR)CurrentAddr) & 0x00FFFFFF))  
  35.     {  
  36.       PUCHAR j = NULL;  
  37.       for (j = CurrentAddr; j < CurrentAddr + 100; ++j)  
  38.       {  
  39.         if (0xe9 == *j && 0x00 == *(j - 1))  
  40.         {  
  41.           UHALF_PTR Offset = *(PUHALF_PTR)(j + 1);  
  42.           CurrentAddr = X64_ADDRESS_ADD((ULONG_PTR)j, Offset);  
  43.           KiSystemCall64 = CurrentAddr; 
  44.           break; 
  45.         }  
  46.       }  
  47.     }  
  48.   }  
  49. }  

验证一下结果,重载ntoskrnl.exe,然后验证重载前后的SSDT表是否一致:


没有问题, 打完收工~

参考

  1. For the night is dark and full of shadows.
  2. LFENCE —— Load Fence
Catalog
  • 常规获取方式
  • 问题探究
  • 问题解决
  • Coding Time
  • 参考
  • CopyRight(c) 2020 - 2025 Debugwar.com

    Designed by Hacksign