驱动开发:Win10内核枚举SSDT表基址

三年前面朝黄土背朝天的我,写了一篇如何在Windows 7系统下枚举内核SSDT表的文章《驱动开发:内核读取SSDT表基址》三年过去了我还是个单身狗,开个玩笑,微软的Windows 10系统已经覆盖了大多数个人PC终端,以前的方法也该进行迭代更新了,或许在网上你能够找到类似的文章,但我可以百分百肯定都不能用,今天LyShark将带大家一起分析Win10 x64最新系统SSDT表的枚举实现。

看一款闭源ARK工具的枚举效果:

驱动开发:Win10内核枚举SSDT表基址插图

直接步入正题,首先SSDT表中文为系统服务描述符表,SSDT表的作用是把应用层与内核联系起来起到桥梁的作用,枚举SSDT表也是反内核工具最基本的功能,通常在64位系统中要想找到SSDT表,需要先找到KeServiceDescriptorTable这个函数,由于该函数没有被导出,所以只能动态的查找它的地址,庆幸的是我们可以通过查找msr(c0000082)这个特殊的寄存器来替代查找KeServiceDescriptorTable这一步,在新版系统中查找SSDT可以归纳为如下这几个步骤。

  • rdmsr c0000082 -> KiSystemCall64Shadow -> KiSystemServiceUser -> SSDT

首先第一步通过rdmsr C0000082 MSR寄存器得到KiSystemCall64Shadow的函数地址,计算KiSystemCall64ShadowKiSystemServiceUser偏移量,如下图所示。

  • 得到相对偏移6ed53180(KiSystemCall64Shadow) - 6ebd2a82(KiSystemServiceUser) = 1806FE
  • 也就是说 6ed53180(rdmsr) - 1806FE = KiSystemServiceUser

驱动开发:Win10内核枚举SSDT表基址插图1

如上当我们找到了KiSystemServiceUser的地址以后,在KiSystemServiceUser向下搜索可找到KiSystemServiceRepeat里面就是我们要找的SSDT表基址。

其中fffff8036ef8c880则是SSDT表的基地址,紧随其后的fffff8036ef74a80则是SSSDT表的基地址。

驱动开发:Win10内核枚举SSDT表基址插图2

那么如果将这个过程通过代码的方式来实现,我们还需要使用《驱动开发:内核枚举IoTimer定时器》中所使用的特征码定位技术,如下我们查找这段特征。

// 署名权
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: me@lyshark.com

#include 
#pragma intrinsic(__readmsr)

ULONGLONG ssdt_address = 0;

// 获取 KeServiceDescriptorTable 首地址
ULONGLONG GetLySharkCOMKeServiceDescriptorTable()
{
    // 设置起始位置
    PUCHAR StartSearchAddress = (PUCHAR)__readmsr(0xC0000082) - 0x1806FE;

    // 设置结束位置
    PUCHAR EndSearchAddress = StartSearchAddress + 0x100000;
    DbgPrint("[LyShark Search] 扫描起始地址: %p --> 扫描结束地址: %p n", StartSearchAddress, EndSearchAddress);

    PUCHAR ByteCode = NULL;

    UCHAR OpCodeA = 0, OpCodeB = 0, OpCodeC = 0;
    ULONGLONG addr = 0;
    ULONG templong = 0;

    for (ByteCode = StartSearchAddress; ByteCode DriverUnload = UnDriver;
    return STATUS_SUCCESS;
}

如上代码中所提及的步骤我想不需要再做解释了,这段代码运行后即可输出SSDT表的基址。

驱动开发:Win10内核枚举SSDT表基址插图3

如上通过调用GetLySharkCOMKeServiceDescriptorTable()得到SSDT地址以后我们就需要对该地址进行解密操作。

得到ServiceTableBase的地址后,就能得到每个服务函数的地址。但这个表存放的并不是SSDT函数的完整地址,而是其相对于ServiceTableBase[Index]>>4的数据,每个数据占四个字节,所以计算指定Index函数完整地址的公式是;

  • 在x86平台上: FuncAddress = KeServiceDescriptorTable + 4 * Index
  • 在x64平台上:FuncAddress = [KeServiceDescriptorTable+4*Index]>>4 + KeServiceDescriptorTable

如下汇编代码就是一段解密代码,代码中rcx寄存器传入SSDT的下标,而rdx寄存器则是传入SSDT表基址。

  48:8BC1                  | mov rax,rcx                             |  rcx=index
  4C:8D12                  | lea r10,qword ptr ds:[rdx]              |  rdx=ssdt
  8BF8                     | mov edi,eax                             |
  C1EF 07                  | shr edi,7                               |
  83E7 20                  | and edi,20                              |
  4E:8B1417                | mov r10,qword ptr ds:[rdi+r10]          |
  4D:631C82                | movsxd r11,dword ptr ds:[r10+rax*4]     |
  49:8BC3                  | mov rax,r11                             |
  49:C1FB 04               | sar r11,4                               |
  4D:03D3                  | add r10,r11                             |
  49:8BC2                  | mov rax,r10                             |
  C3                       | ret                                     |

有了解密公式以后代码的编写就变得很容易,如下是读取SSDT的完整代码。

// 署名权
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: me@lyshark.com

#include 
#pragma intrinsic(__readmsr)

typedef struct _SYSTEM_SERVICE_TABLE
{
    PVOID     ServiceTableBase;
    PVOID     ServiceCounterTableBase;
    ULONGLONG   NumberOfServices;
    PVOID     ParamTableBase;
} SYSTEM_SERVICE_TABLE, *PSYSTEM_SERVICE_TABLE;

ULONGLONG ssdt_base_aadress;
PSYSTEM_SERVICE_TABLE KeServiceDescriptorTable;

typedef UINT64(__fastcall *SCFN)(UINT64, UINT64);
SCFN scfn;

// 解密算法
VOID DecodeSSDT()
{
    UCHAR strShellCode[36] = "x48x8BxC1x4Cx8Dx12x8BxF8xC1xEFx07x83xE7x20x4Ex8Bx14x17x4Dx63x1Cx82x49x8BxC3x49xC1xFBx04x4Dx03xD3x49x8BxC2xC3";
    /*
    48:8BC1                  | mov rax,rcx                             |  rcx=index
    4C:8D12                  | lea r10,qword ptr ds:[rdx]              |  rdx=ssdt
    8BF8                     | mov edi,eax                             |
    C1EF 07                  | shr edi,7                               |
    83E7 20                  | and edi,20                              |
    4E:8B1417                | mov r10,qword ptr ds:[rdi+r10]          |
    4D:631C82                | movsxd r11,dword ptr ds:[r10+rax*4]     |
    49:8BC3                  | mov rax,r11                             |
    49:C1FB 04               | sar r11,4                               |
    4D:03D3                  | add r10,r11                             |
    49:8BC2                  | mov rax,r10                             |
    C3                       | ret                                     |
    */
    scfn = ExAllocatePool(NonPagedPool, 36);
    memcpy(scfn, strShellCode, 36);
}

// 获取 KeServiceDescriptorTable 首地址
ULONGLONG GetKeServiceDescriptorTable()
{
    // 设置起始位置
    PUCHAR StartSearchAddress = (PUCHAR)__readmsr(0xC0000082) - 0x1806FE;

    // 设置结束位置
    PUCHAR EndSearchAddress = StartSearchAddress + 0x8192;
    DbgPrint("扫描起始地址: %p --> 扫描结束地址: %p n", StartSearchAddress, EndSearchAddress);

    PUCHAR ByteCode = NULL;

    UCHAR OpCodeA = 0, OpCodeB = 0, OpCodeC = 0;
    ULONGLONG addr = 0;
    ULONG templong = 0;

    for (ByteCode = StartSearchAddress; ByteCode ServiceTableBase;
    dwtmp = (ULONG)(FuncAddr - (ULONGLONG)ServiceTableBase);
    return dwtmp DriverUnload = UnDriver;
    return STATUS_SUCCESS;
}

运行后即可得到SSDT下标为51的函数也就是得到NtOpenFile的绝对地址和相对地址。

驱动开发:Win10内核枚举SSDT表基址插图4

你也可以打开ARK工具,对比一下是否一致,如下图所示,LyShark的代码是没有任何问题的。

驱动开发:Win10内核枚举SSDT表基址插图5

文章来源于互联网:驱动开发:Win10内核枚举SSDT表基址

THE END
分享
二维码