X64驅動:讀取SSDT表基址


前面的驅動編程相關內容都是在32位環境下進行的,驅動程序與應用程序不同,32位的驅動只能運行在32位系統中,64位驅動只能在64位系統中運行,在WIN32環境下,我們可以各種Hook掛鈎各種系統函數,而惡意程序也可以通過加載驅動進行內核層面的破壞(Rootkit),大家都可以亂搞,把好端端的Windows系統搞得亂七八糟,嚴重影響了系統安全性與可靠性。

為了確保系統的安全性與穩定性,微軟從 Windows Vista X64 開始對系統內核增加了一定的限制,其主要增加了兩種保護措施,一是KPP (內核補丁保護),KPP是機制其利用了PG(PatchGuard)技術,PG技術在x64系統下加入了內核哨兵,用於檢測系統內核是否被惡意篡改(打補丁),如果發現被打了補丁,則會導致關鍵結構損毀直接藍屏,二是DSE (驅動強制簽名),DSE技術則是拒絕加載不包含正確簽名的驅動。

在前面的章節《X86驅動:掛接SSDT內核鈎子》我們通過代碼的方式直接讀取 KeServiceDescriptorTable 這個被導出的表結構從而可以直接讀取到SSDT表的基址,而在Win64系統中 KeServiceDescriptorTable 這個表並沒有被導出,所以我們必須手動搜索到它的地址。

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的范圍,所以無論你怎么改,都不可能跨越這個內存空間,而微軟也不希望你掛鈎內核的一些函數,如果非要使用的話,微軟提供了一些回調函數可以實現相應的掛鈎效果。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM