驱动开发:内核读取SSDT表基址

在前面的章节《X86驱动:挂接SSDT内核钩子》我们通过代码的方式直接读取 KeServiceDescriptorTable 这个被导出的表结构从而可以直接读取到SSDT表的基址,而在Win64系统中 KeServiceDescriptorTable 这个表并没有被导出,所以我们必须手动搜索到它的地址。

为了确保系统的安全性与稳定性,微软从 Windows Vista X64 开始对系统内核增加了一定的限制,其主要增加了两种保护措施,一是KPP (内核补丁保护),KPP是机制其利用了PG(PatchGuard)技术,PG技术在x64系统下加入了内核哨兵,用于检测系统内核是否被恶意篡改(打补丁),如果发现被打了补丁,则会导致关键结构损毁直接蓝屏,二是DSE (驱动强制签名),DSE技术则是拒绝加载不包含正确签名的驱动。

1.这里我们可以通过MSR(特别模块寄存器),读取C0000082寄存器,从而得到KiSystemCall64的地址,在内核调试模式下直接输入 rdmsr c0000082 即可读取到该地址,反汇编可看到 nt!KiSystemCall64 函数。

kd> rdmsr c0000082
msr[c0000082] = fffff800`03c72ec0

kd> u fffff800`03c72ec0
nt!KiSystemCall64:
fffff800`03c72ec0 0f01f8          swapgs
fffff800`03c72ec3 654889242510000000 mov   qword ptr gs:[10h],rsp
fffff800`03c72ecc 65488b2425a8010000 mov   rsp,qword ptr gs:[1A8h]
fffff800`03c72ed5 6a2b            push    2Bh
fffff800`03c72ed7 65ff342510000000 push    qword ptr gs:[10h]
fffff800`03c72edf 4153            push    r11
fffff800`03c72ee1 6a33            push    33h
fffff800`03c72ee3 51              push    rcx

2.接着我们从 KiSystemCall64 函数地址开始向下反汇编,可以看到最后 nt!KiSystemServiceRepeat 这个函数里面包含了 nt!KeServiceDescriptorTable (fffff80003eaa840) ,通过 03c72ff2 减去03c72ec0 即可得到SDT表结构与KiSystemCall64函数之间的偏移值 132 (306)

kd> uf KiSystemCall64
Flow analysis was incomplete, some code may be missing
nt!KiSystemCall64:
fffff800`03c72ec0 0f01f8          swapgs
fffff800`03c72ec3 654889242510000000 mov   qword ptr gs:[10h],rsp
fffff800`03c72ecc 65488b2425a8010000 mov   rsp,qword ptr gs:[1A8h]
fffff800`03c72ed5 6a2b            push    2Bh
fffff800`03c72ed7 65ff342510000000 push    qword ptr gs:[10h]
fffff800`03c72edf 4153            push    r11
fffff800`03c72ee1 6a33            push    33h
fffff800`03c72ee3 51              push    rcx
fffff800`03c72ee4 498bca          mov     rcx,r10

nt!KiSystemServiceRepeat:
fffff800`03c72ff2 4c8d1547782300  lea     r10,[nt!KeServiceDescriptorTable (fffff800`03eaa840)]
fffff800`03c72ff9 4c8d1d80782300  lea     r11,[nt!KeServiceDescriptorTableShadow (fffff800`03eaa880)]
fffff800`03c73000 f7830001000080000000 test dword ptr [rbx+100h],80h
fffff800`03c7300a 4d0f45d3        cmovne  r10,r11
fffff800`03c7300e 423b441710      cmp     eax,dword ptr [rdi+r10+10h]
fffff800`03c73013 0f83e9020000    jae     nt!KiSystemServiceExit+0x1a7 (fffff800`03c73302)  Branch

总结一下:我们通过读取C0000082寄存器,能够得到KiSystemCall64的地址,然后从KiSystemCall64的地址开始,往下搜索0x150字节左右(特征码4c8d15),就能得到KeServiceDescriptorTable的地址。

#include <ntddk.h>
#include <windef.h>
#include <intrin.h>

#pragma intrinsic(__readmsr)

VOID UnDriver(PDRIVER_OBJECT driver)
{
	DbgPrint(("驱动程序卸载成功! \n"));
}

ULONGLONG Get_SSTD_Base()
{
	PUCHAR Base = (PUCHAR)__readmsr(0xC0000082);      // 读取C0000082寄存器
	PUCHAR Address = Base + 0x150;                    // 相加偏移
	PUCHAR i = NULL;
	UCHAR b1 = 0, b2 = 0, b3 = 0;                     // 保存特征码
	ULONG templong = 0;
	ULONGLONG addr = 0;                               // 最后获取到的地址
	for (i = Base; i<Address; i++)
	{
		if (MmIsAddressValid(i) && MmIsAddressValid(i + 1) && MmIsAddressValid(i + 2))
		{
			b1 = *i; b2 = *(i + 1); b3 = *(i + 2);
			if (b1 == 0x4c && b2 == 0x8d && b3 == 0x15)   // 判断是否=4c8d15
			{
				memcpy(&templong, i + 3, 4);              // 在i+3位置拷贝,拷贝4字节
				addr = (ULONGLONG)templong + (ULONGLONG)i + 7;
				return addr;
			}
		}
	}
	return 0;
}

NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
	DbgPrint("SSTD Base= %11x", Get_SSTD_Base());
	DriverObject->DriverUnload = UnDriver;
	return STATUS_SUCCESS;
}

3.接着我们则需要获取到SSDT中某个函数的序号,这里以OpenProcess为例:

0:000> u ntdll!NtOpenProcess
ntdll!NtOpenProcess:
77820700 b826000000      mov     eax,23h
77820705 bac04f8377      mov     edx,offset ntdll!Wow64SystemServiceCall (77834fc0)
7782070a ffd2            call    edx
7782070c c21000          ret     10h
7782070f 90              nop

4.读取代码如下.

#include <ntddk.h>
#include <windef.h>
#include <intrin.h>

#pragma intrinsic(__readmsr)

VOID UnDriver(PDRIVER_OBJECT driver)
{
	DbgPrint(("驱动程序卸载成功! \n"));
}

ULONGLONG Get_SSDT_Base()
{
	PUCHAR Base = (PUCHAR)__readmsr(0xC0000082);      // 读取C0000082寄存器
	PUCHAR Address = Base + 0x150;                    // 相加偏移
	PUCHAR i = NULL;
	UCHAR b1 = 0, b2 = 0, b3 = 0;                     // 保存特征码
	ULONG templong = 0;
	ULONGLONG addr = 0;                               // 最后获取到的地址
	for (i = Base; i<Address; i++)
	{
		if (MmIsAddressValid(i) && MmIsAddressValid(i + 1) && MmIsAddressValid(i + 2))
		{
			b1 = *i; b2 = *(i + 1); b3 = *(i + 2);
			if (b1 == 0x4c && b2 == 0x8d && b3 == 0x15)   // 判断是否=4c8d15
			{
				memcpy(&templong, i + 3, 4);              // 在i+3位置拷贝,拷贝4字节
				addr = (ULONGLONG)templong + (ULONGLONG)i + 7;
				return addr;
			}
		}
	}
	return 0;
}

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

ULONGLONG GetSSDTFunction(ULONGLONG Index)
{
	LONG dwTemp = 0;
	ULONGLONG qwTemp = 0, stb = 0, ret = 0;
	PSYSTEM_SERVICE_TABLE ssdt = (PSYSTEM_SERVICE_TABLE)Get_SSDT_Base();
	stb = (ULONGLONG)(ssdt->ServiceTableBase);
	qwTemp = stb + 4 * Index;
	dwTemp = *(PLONG)qwTemp;
	dwTemp = dwTemp >> 4;
	ret = stb + (LONG64)dwTemp;
	return ret;
}

NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
	DbgPrint("OpenProcess=%llx", GetSSDTFunction(0x23));
	DriverObject->DriverUnload = UnDriver;
	return STATUS_SUCCESS;
}

在64位环境下想要任意Hook系统函数是不可能的,因为64位中每个驱动程序都不在同一个4GB空间里,而4字节的整数只能表示4GB的范围,所以无论你怎么改,都不可能跨越这个内存空间,而微软也不希望你挂钩内核的一些函数,如果非要使用的话,微软提供了一些回调函数可以实现相应的挂钩效果。

posted @ 2019-10-09 09:23  lyshark  阅读(2251)  评论(0编辑  收藏  举报

loading... | loading...
博客园 - 开发者的网上家园