驱动开发:Win10内核枚举SSDT表基址 - lyshark
source link: https://www.cnblogs.com/LyShark/p/16804220.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
驱动开发:Win10内核枚举SSDT表基址
三年前面朝黄土背朝天的我,写了一篇如何在Windows 7
系统下枚举内核SSDT
表的文章《驱动开发:内核读取SSDT表基址》
三年过去了我还是个单身狗
,开个玩笑,微软的Windows 10
系统已经覆盖了大多数个人PC终端,以前的方法也该进行迭代更新了,或许在网上你能够找到类似的文章,但我可以百分百肯定都不能用,今天LyShark
将带大家一起分析Win10 x64
最新系统SSDT
表的枚举实现。
看一款闭源ARK工具的枚举效果:
直接步入正题,首先SSDT
表中文为系统服务描述符表,SSDT表的作用
是把应用
层与内核
层联系起来
起到桥梁
的作用,枚举SSDT表
也是反内核
工具最基本的功能,通常在64位
系统中要想找到SSDT
表,需要先找到KeServiceDescriptorTable
这个函数,由于该函数没有被导出,所以只能动态的查找它的地址,庆幸的是我们可以通过查找msr(c0000082)
这个特殊的寄存器来替代查找KeServiceDescriptorTable
这一步,在新版系统中查找SSDT可以归纳为如下这几个步骤。
- rdmsr c0000082 -> KiSystemCall64Shadow -> KiSystemServiceUser -> SSDT
首先第一步通过rdmsr C0000082
MSR寄存器得到KiSystemCall64Shadow
的函数地址,计算KiSystemCall64Shadow
与KiSystemServiceUser
偏移量,如下图所示。
- 得到相对偏移
6ed53180(KiSystemCall64Shadow) - 6ebd2a82(KiSystemServiceUser) = 1806FE
- 也就是说
6ed53180(rdmsr) - 1806FE = KiSystemServiceUser
如上当我们找到了KiSystemServiceUser
的地址以后,在KiSystemServiceUser
向下搜索可找到KiSystemServiceRepeat
里面就是我们要找的SSDT
表基址。
其中fffff8036ef8c880
则是SSDT表
的基地址,紧随其后的fffff8036ef74a80
则是SSSDT表
的基地址。
那么如果将这个过程通过代码的方式来实现,我们还需要使用《驱动开发:内核枚举IoTimer定时器》
中所使用的特征码定位技术,如下我们查找这段特征。
// 署名权
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: [email protected]
#include <ntifs.h>
#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 < EndSearchAddress; ByteCode++)
{
// 使用MmIsAddressValid()函数检查地址是否有页面错误
if (MmIsAddressValid(ByteCode) && MmIsAddressValid(ByteCode + 1) && MmIsAddressValid(ByteCode + 2))
{
OpCodeA = *ByteCode;
OpCodeB = *(ByteCode + 1);
OpCodeC = *(ByteCode + 2);
// 对比特征值 寻找 nt!KeServiceDescriptorTable 函数地址
/*
nt!KiSystemServiceRepeat:
fffff803`6ebd2b94 4c8d15e59c3b00 lea r10,[nt!KeServiceDescriptorTable (fffff803`6ef8c880)]
fffff803`6ebd2b9b 4c8d1dde1e3a00 lea r11,[nt!KeServiceDescriptorTableShadow (fffff803`6ef74a80)]
fffff803`6ebd2ba2 f7437880000000 test dword ptr [rbx+78h],80h
fffff803`6ebd2ba9 7413 je nt!KiSystemServiceRepeat+0x2a (fffff803`6ebd2bbe) Branch
*/
if (OpCodeA == 0x4c && OpCodeB == 0x8d && OpCodeC == 0x15)
{
// 获取高位地址fffff802
memcpy(&templong, ByteCode + 3, 4);
// 与低位64da4880地址相加得到完整地址
addr = (ULONGLONG)templong + (ULONGLONG)ByteCode + 7;
return addr;
}
}
}
return 0;
}
VOID UnDriver(PDRIVER_OBJECT driver)
{
DbgPrint(("驱动程序卸载成功! \n"));
}
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
DbgPrint("hello lyshark.com");
ssdt_address = GetLySharkCOMKeServiceDescriptorTable();
DbgPrint("[LyShark] SSDT = %p \n", ssdt_address);
DriverObject->DriverUnload = UnDriver;
return STATUS_SUCCESS;
}
如上代码中所提及的步骤我想不需要再做解释了,这段代码运行后即可输出SSDT表的基址。
如上通过调用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: [email protected]
#include <ntifs.h>
#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] = "\x48\x8B\xC1\x4C\x8D\x12\x8B\xF8\xC1\xEF\x07\x83\xE7\x20\x4E\x8B\x14\x17\x4D\x63\x1C\x82\x49\x8B\xC3\x49\xC1\xFB\x04\x4D\x03\xD3\x49\x8B\xC2\xC3";
/*
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 < EndSearchAddress; ByteCode++)
{
// 使用MmIsAddressValid()函数检查地址是否有页面错误
if (MmIsAddressValid(ByteCode) && MmIsAddressValid(ByteCode + 1) && MmIsAddressValid(ByteCode + 2))
{
OpCodeA = *ByteCode;
OpCodeB = *(ByteCode + 1);
OpCodeC = *(ByteCode + 2);
// 对比特征值 寻找 nt!KeServiceDescriptorTable 函数地址
// LyShark.com
// 4c 8d 15 e5 9e 3b 00 lea r10,[nt!KeServiceDescriptorTable (fffff802`64da4880)]
// 4c 8d 1d de 20 3a 00 lea r11,[nt!KeServiceDescriptorTableShadow (fffff802`64d8ca80)]
if (OpCodeA == 0x4c && OpCodeB == 0x8d && OpCodeC == 0x15)
{
// 获取高位地址fffff802
memcpy(&templong, ByteCode + 3, 4);
// 与低位64da4880地址相加得到完整地址
addr = (ULONGLONG)templong + (ULONGLONG)ByteCode + 7;
return addr;
}
}
}
return 0;
}
// 得到函数相对偏移地址
ULONG GetOffsetAddress(ULONGLONG FuncAddr)
{
ULONG dwtmp = 0;
PULONG ServiceTableBase = NULL;
if (KeServiceDescriptorTable == NULL)
{
KeServiceDescriptorTable = (PSYSTEM_SERVICE_TABLE)GetKeServiceDescriptorTable();
}
ServiceTableBase = (PULONG)KeServiceDescriptorTable->ServiceTableBase;
dwtmp = (ULONG)(FuncAddr - (ULONGLONG)ServiceTableBase);
return dwtmp << 4;
}
// 根据序号得到函数地址
ULONGLONG GetSSDTFunctionAddress(ULONGLONG NtApiIndex)
{
ULONGLONG ret = 0;
if (ssdt_base_aadress == 0)
{
// 得到ssdt基地址
ssdt_base_aadress = GetKeServiceDescriptorTable();
}
if (scfn == NULL)
{
DecodeSSDT();
}
ret = scfn(NtApiIndex, ssdt_base_aadress);
return ret;
}
VOID UnDriver(PDRIVER_OBJECT driver)
{
DbgPrint(("驱动程序卸载成功! \n"));
}
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
DbgPrint("hello lyshark.com \n");
ULONGLONG ssdt_address = GetKeServiceDescriptorTable();
DbgPrint("SSDT基地址 = %p \n", ssdt_address);
// 根据序号得到函数地址
ULONGLONG address = GetSSDTFunctionAddress(51);
DbgPrint("[LyShark] NtOpenFile地址 = %p \n", address);
// 得到相对SSDT的偏移量
DbgPrint("函数相对偏移地址 = %p \n", GetOffsetAddress(address));
DriverObject->DriverUnload = UnDriver;
return STATUS_SUCCESS;
}
运行后即可得到SSDT
下标为51
的函数也就是得到NtOpenFile
的绝对地址和相对地址。
你也可以打开ARK工具,对比一下是否一致,如下图所示,LyShark
的代码是没有任何问题的。
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK