前言
進(jìn)程保護(hù)是眾多AV或者病毒都要所具備的基礎(chǔ)功能,本文就0環(huán)下通過SSDT來對進(jìn)程進(jìn)行保護(hù)進(jìn)行探究,SSDT也不是什么新技術(shù),但作為學(xué)習(xí),老的技術(shù)我們同樣需要掌握。
什么是SSDT
SSDT 的全稱是System Services Descriptor Table,系統(tǒng)服務(wù)描述符表。
首先要明確的是他是一張表,通過windbg查看這張表。
dd KeServiceDescriptorTable
這個表就是一個把 Ring3 的 Win32 API 和 Ring0 的內(nèi)核 API 聯(lián)系起來。
當(dāng)我們在r 3調(diào)用一個API時,實際上調(diào)用的是一個接口,這里拿ReadProcessMemory舉例。
ReadProcessMemory函數(shù)在kernel32.dll中導(dǎo)出,通過斷點可以找到對應(yīng)的反匯編代碼。在匯編代碼中,可以看到ReadProcessMemory調(diào)用了ntdll.dll中的ZwReadVirtualMemory函數(shù)。
在ZwReadVirtualMemory函數(shù)開始的地方下斷點。
bp ZwReadVirtualMemory
實際上功能代碼也沒有在ZwReadVirtualMemory函數(shù)中實現(xiàn),只是拿著一個索引號并跳轉(zhuǎn)到一個地址。
這個索引號實際上就是SSDT表中的索引號,回到windbg,我們現(xiàn)在拿到索引號0xBA去SSDT表中找。
kd> dd KeServiceDescriptorTable
80553fa0 80502b8c 00000000 0000011c 80503000
80553fb0 00000000 00000000 00000000 00000000
80553fc0 00000000 00000000 00000000 00000000
80553fd0 00000000 00000000 00000000 00000000
80553fe0 00002710 bf80c0b6 00000000 00000000
80553ff0 f8b67a80 f82e7b60 821bfa90 806e2f40
80554000 00000000 00000000 22bc349b 00000001
80554010 afa8a15b 01d7eb4f 00000000 00000000
kd> dd 80502b8c + 0xba*4
80502e74 805aa712 805c99e0 8060ea76 8060c43c
80502e84 8056f0d2 8063ab56 8061aca8 8061d332
80502e94 8059b804 8059c7cc 8059c1d4 8059baee
80502ea4 805bf456 80598d62 8059908e 805bf264
80502eb4 806064b6 8051ee82 8061cc3e 805cbd40
80502ec4 805cbc22 8061cd3a 8061ce20 8061cf48
80502ed4 8059a07c 8060db50 8060db50 805c892a
80502ee4 8063d80e 8060be28 80607fb8 8060882a
kd> u 805aa712
可以看到在0環(huán)調(diào)用的是NtReadVirtualMemory,這實際上才是真正實現(xiàn)功能的地方。而SSDT將r 3和r 0聯(lián)系到一起。
SSDT結(jié)構(gòu)
在 NT 4.0 以上的 windows 操作系統(tǒng)中,默認(rèn)就存在兩個系統(tǒng)服務(wù)描述表,這兩個調(diào)度表對應(yīng)了兩類不同的系統(tǒng)服務(wù),
這兩個調(diào)度表為:KeServiceDescriptorTable 和 KeServiceDescriptorTableShadow,
其中 KeServiceDescriptorTable 主要是處理來自 Ring3 層 Kernel32.dll 中的系統(tǒng)調(diào)用,
而 KeServiceDescriptorTableShadow 則主要處理來自 User32.dll 和 GDI32.dll 中的系統(tǒng)調(diào)用,
并且 KeServiceDescriptorTable 在 ntoskrnl.exe(Windows 操作系統(tǒng)內(nèi)核文件,包括內(nèi)核和執(zhí)行體層)是導(dǎo)出的,
而 KeServiceDescriptorTableShadow 則是沒有被 Windows 操作系統(tǒng)所導(dǎo)出,
而關(guān)于 SSDT 的全部內(nèi)容則都是通過 KeServiceDescriptorTable 來完成的。
SSDT表的結(jié)構(gòu)通過結(jié)構(gòu)體表示為如下:
typedef struct _KSERVICE_TABLE_DESCRIPTOR
{
KSYSTEM_SERVICE_TABLE ntoskrnl; // ntoskrnl.exe 的服務(wù)函數(shù)
KSYSTEM_SERVICE_TABLE win32k; // win32k.sys 的服務(wù)函數(shù)(GDI32.dll/User32.dll 的內(nèi)核支持)
KSYSTEM_SERVICE_TABLE notUsed1;
KSYSTEM_SERVICE_TABLE notUsed2;
} KSERVICE_TABLE_DESCRIPTOR, * PKSERVICE_TABLE_DESCRIPTOR;
其中每一項又是一個結(jié)構(gòu)體:KSYSTEM_SERVICE_TABLE。通過結(jié)構(gòu)體表示為如下:
typedef struct _KSYSTEM_SERVICE_TABLE
{
PULONG ServiceTableBase; // SSDT (System Service Dispatch Table)的基地址
PULONG ServiceCounterTableBase; // 用于 checked builds, 包含 SSDT 中每個服務(wù)被調(diào)用的次數(shù)
ULONG NumberOfService; // 服務(wù)函數(shù)的個數(shù), NumberOfService * 4 就是整個地址表的大小
ULONG ParamTableBase; // SSPT(System Service Parameter Table)的基地址
} KSYSTEM_SERVICE_TABLE, * PKSYSTEM_SERVICE_TABLE;
通過看圖形化界面可以更加直觀,下圖是ntoskrnl.exe和win32k.sys的服務(wù)函數(shù)結(jié)構(gòu)。
HOOK SSDT
有了上面的知識儲備,理解SSDT HOOK就很容易了。
當(dāng)3環(huán)程序執(zhí)行后,操作系統(tǒng)拿著索引去SSDT表中找對應(yīng)的0環(huán)程序,這時我們就可以在SSDT表中做點手腳,將某一個api函數(shù)的指針改成我們自己函數(shù)的指針,這樣執(zhí)行的將會是我們自己的代碼。
首先需要定義我們自己的函數(shù)
ULONG g_Pid = 568;
NTSTATUS NTAPI MyOpenProcess(PHANDLE ProcessHandle, ACCESS_MASK DesiredAccess,
POBJECT_ATTRIBUTES ObjectAttributes, PCLIENT_ID ClientId)
{
NTSTATUS status;
status = STATUS_SUCCESS;
//當(dāng)此進(jìn)程為要保護(hù)的進(jìn)程時
if (ClientId->UniqueProcess == (HANDLE)g_Pid)
{
//設(shè)為拒絕訪問
DesiredAccess = 0;
}
return NtOpenProcess(ProcessHandle, DesiredAccess, ObjectAttributes, ClientId);
}
g_Pid定義為全局的,我們想保護(hù)哪個進(jìn)程就將該進(jìn)程的pid賦值給g_Pid。
比如這里就保護(hù)Dbgview.exe。
這里函數(shù)準(zhǔn)備好以后,就要將該函數(shù)的指針覆蓋原來NtOpenProcess的指針。但是需要注意的是:我們自己改自己的代碼是不用管權(quán)限的,改別人的代碼很有可能這塊內(nèi)存是只讀的,并不可寫。
那么本質(zhì)上就是SSDT對應(yīng)的物理頁是只讀的,這里有兩種辦法,我們都知道物理頁的內(nèi)存R/W位的屬性是由PDE和PTE相與而來的,那么我們就可以改變SSDT對應(yīng)的PDE和PTE的R/W屬性,將物理頁設(shè)置為可讀可寫的。通過CR4寄存器判斷是2-9-9-12分頁還是10-10-12分頁。
if(RCR4 & 0x00000020)
{//說明是2-9-9-12分頁
KdPrint(("2-9-9-12分頁 %pn",RCR4));
KdPrint(("PTE1 %pn",*(Dword*)(0xC0000000 + ((HookFunAddr >> 9) & 0x007FFFF8))));
*(DWORD64*)(0xC0000000 + ((HookFunAddr >> 9) & 0x007FFFF8)) |= 0x02;
KdPrint(("PTE1 %pn",*(DWORD*)(0xC0000000 + ((HookFunAddr >> 9) & 0x007FFFF8))));
}
else
{//說明是10-10-12分頁
KdPrint(("10-10-12分頁n"));
KdPrint(("PTE1 %pn",*(DWORD*)(0xC0000000 + ((HookFunAddr >> 10) & 0x003FFFFC))));
*(DWORD*)(0xC0000000 + ((HookFunAddr >> 10) & 0x003FFFFC)) |= 0x02;
KdPrint(("PTE2 %pn",*(DWORD*)(0xC0000000 + ((HookFunAddr >> 10) & 0x003FFFFC))));
}
還有一種方式就是通過Cr0寄存器。CR0寄存器的第16位叫做保護(hù)屬性位,控制著頁的讀或?qū)憣傩浴?/p>
WP為1 時, 不能修改只讀的內(nèi)存頁 , WP為0 時, 可以修改只讀的內(nèi)存頁。那么我們就可以將WP位置為0,暫時關(guān)閉可讀屬性。
VOID PageProtectOn()
{
__try
{
_asm
{
mov eax, cr0
or eax, 10000h
mov cr0, eax
sti
}
}
__except (1)
{
DbgPrint("PageProtectOn執(zhí)行失敗!");
}
}
VOID PageProtectOff()
{
__try
{
_asm
{
cli
mov eax, cr0
and eax, not 10000h //and eax,0FFFEFFFFh
mov cr0, eax
}
}
__except (1)
{
DbgPrint("PageProtectOff執(zhí)行失敗!");
}
}
可以修改SSDT表后,就要寫函數(shù)來修改NtOpenProcess指針,也就是我們的HOOK函數(shù)。
NTSTATUS _hook()
{
NTSTATUS status;
status = STATUS_SUCCESS;
PageProtectOff();
PoldAddress = KeServiceDescriptorTable->ntoskrnl.ServiceTableBase[0x7a];
KeServiceDescriptorTable->ntoskrnl.ServiceTableBase[0x7a] = (ULONG)MyOpenProcess;
PageProtectOn();
return status;
}
在修改SSDT表前先關(guān)閉物理頁保護(hù),修改完后要開啟物理頁保護(hù),保證其他任務(wù)能夠順利完成。這里的索引可以通過ida或者debug工具去看。
然后就是卸載鉤子,用于驅(qū)動卸載的時候使用。
NTSTATUS _unhook()
{
NTSTATUS status;
status = STATUS_SUCCESS;
PageProtectOff();
KeServiceDescriptorTable->ntoskrnl.ServiceTableBase[0x7a] = PoldAddress;
PageProtectOn();
return status;
}
最后是入口和卸載函數(shù)
VOID DriverUnload(PDRIVER_OBJECT driver)
{
_unhook();
DbgPrint("卸載了。。。。。n");
}
NTSTATUS DriverEntry(PDRIVER_OBJECT driver, PUNICODE_STRING reg_path)
{
_hook();
DbgPrint("跑起來了。。。n");
driver->DriverUnload = DriverUnload;
return STATUS_SUCCESS;
}
最后編譯,加載驅(qū)動,當(dāng)我們嘗試用任務(wù)管理器殺死Dbgview時會被拒絕。
如果通過taskkill同樣不行。
后記
在SSDT上寫鉤子,在0環(huán)是最低級的方式,可以看到編寫代碼十分簡單,但是也是非常容易被檢測的,比如我們通過PChunter這樣的內(nèi)核工具去看一下。
可以看到NtOpenProcess赫然在列。實際上SSDT已作為基礎(chǔ)需要被了解。
本文由SD原創(chuàng)發(fā)布
轉(zhuǎn)載,請參考轉(zhuǎn)載聲明,注明出處: https://www.anquanke.com/post/id/262577
安全客 - 有思想的安全新媒體