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