核心程式設計之SSDTHook(3)Hook NtCreateSection監控所有可執行模組載入
在上兩篇博文中,我介紹了SSDTHook的原理,並給出了一個例項--通過Hook NtOpenProcess來實現程序保護:http://blog.csdn.net/zuishikonghuan/article/details/50765464
這次我們玩個更好玩的,攔截所有可執行模組的載人!殺軟都有這種功能,當一個程式執行時,他能得到具體路徑,檢查是否安全後,還可以通知使用者詢問是否允許執行。
這麼有趣的功能,要如何實現呢,其實出了hook也有辦法,但是我們現在研究的是hook,就hook ssdt吧,那麼問題來了,要hook那個系統服務函式呢?在使用者模式建立程序,大家一下就會想到CreateProcess,然後想到CreateProcessAsUser等,正好SSDT裡有一個NtCreateProcess,很多人想當然就想到要hook這個(其實我當時也是),其實我們有一個捷徑,我們可以hook這個函式:NtCreateSection(NT 5.x,2000-XP)或NtCreateUserProcess(NT 6.x,NT 10.x,Vista-W10)
我們首先來看看NtCreateSection的原型:
NTSTATUS ZwCreateSection( _Out_ PHANDLE SectionHandle, _In_ ACCESS_MASK DesiredAccess, _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes, _In_opt_ PLARGE_INTEGER MaximumSize, _In_ ULONG SectionPageProtection, _In_ ULONG AllocationAttributes, _In_opt_ HANDLE FileHandle );
Windows載入所有的可執行模組在引數 SectionPageProtection 和 AllocationAttributes 與普通的對映檔案有所區別,具有以下特點:
(AllocationAttributes == SEC_IMAGE) && (SectionPageProtection & PAGE_EXECUTE)
首先我們需要得到檔案物件的指標,由於我們在NtCreateSection裡可以得到檔案控制代碼,所以可以用ObReferenceObjectByHandle得到檔案物件的指標,然後要得到檔案路徑有兩種方法,第一種方法比較複雜:FILE_OBJECT中有一個成員FileName,是不包含卷名稱的檔案路徑,然後,通過FILE_OBJECT裡有一個成員DeviceObject,然後通過ObQueryNameString、RtlUnicodeStringToAnsiString、RtlVolumeDeviceToDosName得到卷名稱,第二種方法就很簡單了,直接呼叫IoQueryFileDosDeviceName就行了。本例中我們用第二種方法,因此不解釋了,各位直接看程式碼吧。
另外還需要說明的一點是,我們在核心中得到的路徑是以“\??\”開頭的,這是為何呢?還記得之前驅動開發的博文中建立裝置和符號連線嗎,磁碟裝置上有卷裝置,而X盤中的X:就是卷裝置的符號連線,符號連線在核心下是以“\??\”開頭的,使用者模式下是以“\\.\”開頭的。
另外,可能還會有人問怎麼在得到路徑後通知應用程式,以便應用程式檢查是否安全以及通知使用者呢?可以在應用程式中用一個執行緒一直讀我們建立的裝置,驅動派遣函式中阻塞I/O請求(可以用Event,KeWaitForSingleObject等,在後續的驅動開發博文中講),然後在hook裡面啟用事件,然後應用程式讀取得到路徑,執行緒通訊,再次讀取,驅動程式再次阻塞I/O請求……和輪詢相比,消耗的系統資源很少,而且非常簡單。
好了,該說的都已經說了,不該說的也說了,最後上程式碼,註釋也比較豐富,還不明白就看程式碼吧:
#include <Ntifs.h>
#include <ntddk.h>
extern "C" VOID DriverUnload(PDRIVER_OBJECT pDriverObject);
extern "C" NTSTATUS DefDispatchRoutine(PDEVICE_OBJECT pDevObj, PIRP pIrp);
extern "C" NTSTATUS IoctlDispatchRoutine(PDEVICE_OBJECT pDevObj, PIRP pIrp);
#define IOCTL1 CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS)
#define SEC_IMAGE 0x1000000
typedef struct _DEVICE_EXTENSION {
UNICODE_STRING SymLinkName; //我們定義的裝置擴充套件裡只有一個符號連結名成員
} DEVICE_EXTENSION, *PDEVICE_EXTENSION;
//我們將 NtCreateSection hook 到自己的函式
NTSTATUS NTAPI MyNtCreateSection(PHANDLE SectionHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes,
PLARGE_INTEGER MaximumSize, ULONG SectionPageProtection, ULONG AllocationAttributes, HANDLE FileHandle);
//KeServiceDescriptorTable 中我們感興趣的結構
typedef struct _KESERVICE_DESCRIPTOR_TABLE
{
PULONG ServiceTableBase;
PULONG ServiceCounterTableBase;
ULONG NumberOfServices;
PUCHAR ParamTableBase;
}KESERVICE_DESCRIPTOR_TABLE, *PKESERVICE_DESCRIPTOR_TABLE;
//ntoskrnl.exe (ntoskrnl.lib) 匯出的 KeServiceDescriptorTable
extern "C" extern PKESERVICE_DESCRIPTOR_TABLE KeServiceDescriptorTable;
//關閉頁面保護
void PageProtectClose()
{
__asm{
cli;
mov eax, cr0;
and eax, not 10000h;
mov cr0, eax;
}
}
//啟用頁面保護
void PageProtectOpen()
{
__asm{
mov eax, cr0;
or eax, 10000h;
mov cr0, eax;
sti;
}
}
//根據 ZwXXXX的地址 獲取服務函式在 SSDT 中所對應的服務的索引號
#define SYSTEMCALL_INDEX(ServiceFunction) (*(PULONG)((PUCHAR)ServiceFunction + 1))
ULONG oldNtCreateSection;//之前的NtCreateSection
ULONG ProtectProcessID = 0;
#pragma code_seg("INIT")
extern "C" NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegistryPath)
{
DbgPrint("DriverEntry\r\n");
pDriverObject->DriverUnload = DriverUnload;//註冊驅動解除安裝函式
//註冊派遣函式
pDriverObject->MajorFunction[IRP_MJ_CREATE] = DefDispatchRoutine;
pDriverObject->MajorFunction[IRP_MJ_CLOSE] = DefDispatchRoutine;
pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = IoctlDispatchRoutine;
NTSTATUS status;
PDEVICE_OBJECT pDevObj;
PDEVICE_EXTENSION pDevExt;
//建立裝置名稱的字串
UNICODE_STRING devName;
RtlInitUnicodeString(&devName, L"\\Device\\MySSDTHookDevice");
//建立裝置
status = IoCreateDevice(pDriverObject, sizeof(DEVICE_EXTENSION), &devName, FILE_DEVICE_UNKNOWN, 0, TRUE, &pDevObj);
if (!NT_SUCCESS(status))
return status;
pDevObj->Flags |= DO_BUFFERED_IO;//將裝置設定為緩衝裝置
pDevExt = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;//得到裝置擴充套件
//建立符號連結
UNICODE_STRING symLinkName;
RtlInitUnicodeString(&symLinkName, L"\\??\\MySSDTHookDevice_link");
pDevExt->SymLinkName = symLinkName;
status = IoCreateSymbolicLink(&symLinkName, &devName);
if (!NT_SUCCESS(status))
{
IoDeleteDevice(pDevObj);
return status;
}
//Hook SSDT
PageProtectClose();
//得到原來的地址,記錄在 oldNtCreateSection
oldNtCreateSection = KeServiceDescriptorTable->ServiceTableBase[SYSTEMCALL_INDEX(ZwCreateSection)];
//修改SSDT中 NtCreateSection 的地址,使其指向 MyNtCreateSection
KeServiceDescriptorTable->ServiceTableBase[SYSTEMCALL_INDEX(ZwCreateSection)] = (ULONG)&MyNtCreateSection;
DbgPrint("Old Addr:0x%X\r\n", oldNtCreateSection);
PageProtectOpen();
return STATUS_SUCCESS;
}
DRIVER_UNLOAD DriverUnload;
extern "C" VOID DriverUnload(PDRIVER_OBJECT pDriverObject)
{
PageProtectClose();
//修改SSDT中 NtCreateSection 的地址,使其指向 oldNtCreateSection
//也就是在驅動解除安裝時恢復原來的地址
KeServiceDescriptorTable->ServiceTableBase[SYSTEMCALL_INDEX(ZwCreateSection)] = oldNtCreateSection;
PageProtectOpen();
PDEVICE_OBJECT pDevObj;
pDevObj = pDriverObject->DeviceObject;
PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;//得到裝置擴充套件
//刪除符號連結
UNICODE_STRING pLinkName = pDevExt->SymLinkName;
IoDeleteSymbolicLink(&pLinkName);
//刪除裝置
IoDeleteDevice(pDevObj);
}
extern "C" NTSTATUS DefDispatchRoutine(PDEVICE_OBJECT pDevObj, PIRP pIrp)
{
pIrp->IoStatus.Status = STATUS_SUCCESS;
pIrp->IoStatus.Information = 0;
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
extern "C" NTSTATUS IoctlDispatchRoutine(PDEVICE_OBJECT pDevObj, PIRP pIrp)
{
NTSTATUS status = STATUS_SUCCESS;
//得到I/O堆疊的當前這一層,也就是IO_STACK_LOCATION結構的指標
PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(pIrp);
ULONG in_size = stack->Parameters.DeviceIoControl.InputBufferLength;//得到輸入緩衝區的大小
ULONG code = stack->Parameters.DeviceIoControl.IoControlCode;//得到控制碼
PVOID buffer = pIrp->AssociatedIrp.SystemBuffer;//得到緩衝區指標
switch (code)
{
case IOCTL1:
DbgPrint("Get ioctl code 1\r\n");
break;
default:
status = STATUS_INVALID_VARIANT;
//如果是沒有處理的IRP,則返回STATUS_INVALID_VARIANT,這意味著使用者模式的I/O函式失敗,但並不會設定GetLastError
}
// 完成IRP
pIrp->IoStatus.Status = status;//設定IRP完成狀態,會設定使用者模式下的GetLastError
pIrp->IoStatus.Information = 0;//設定操作的位元組
IoCompleteRequest(pIrp, IO_NO_INCREMENT);//完成IRP,不增加優先順序
return status;
}
NTSTATUS NTAPI MyNtCreateSection(PHANDLE SectionHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes,
PLARGE_INTEGER MaximumSize, ULONG SectionPageProtection, ULONG AllocationAttributes, HANDLE FileHandle){
if ((AllocationAttributes == SEC_IMAGE) && (SectionPageProtection & PAGE_EXECUTE)){
if (FileHandle){
PFILE_OBJECT FileObject;
NTSTATUS status;
if ((status = ObReferenceObjectByHandle(FileHandle, 0, NULL, KernelMode, (PVOID*)&FileObject, NULL)) == STATUS_SUCCESS){
POBJECT_NAME_INFORMATION FilePath;
if ((status = IoQueryFileDosDeviceName(FileObject, &FilePath)) == STATUS_SUCCESS){
DbgPrint("FilePath: %ws\r\n", FilePath->Name.Buffer);
ExFreePool(FilePath);// IoQueryFileDosDeviceName 獲取的 OBJECT_NAME_INFORMATION 需要手動釋放
}
else DbgPrint("E: IoQueryFileDosDeviceName failed with code 0x%X\r\n", status);
ObDereferenceObject(FileObject);//使獲取到的 FileObject 引用計數減1
}
else DbgPrint("E: ObReferenceObjectByHandle failed with code 0x%X\r\n", status);
}
else DbgPrint("E: FileHandle is NULL.\r\n");
}
//定義一個函式指標 _NtCreateSection, 根據 oldNtCreateSection 記錄的真實函式的地址進行 Call
//也就是說其他程序直接交還給系統的 NtCreateSection 處理
typedef NTSTATUS(NTAPI * _NtCreateSection)(PHANDLE SectionHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes,
PLARGE_INTEGER MaximumSize, ULONG SectionPageProtection, ULONG AllocationAttributes, HANDLE FileHandle);
_NtCreateSection _oldNtCreateSection = (_NtCreateSection)oldNtCreateSection;
return _oldNtCreateSection(SectionHandle, DesiredAccess, ObjectAttributes, MaximumSize, SectionPageProtection, AllocationAttributes, FileHandle);
}