1. 程式人生 > >核心程式設計之SSDTHook(3)Hook NtCreateSection監控所有可執行模組載入

核心程式設計之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)

當一個可執行模組載人時,系統會先呼叫NtCreateFile來開啟檔案,建立檔案物件,然後呼叫NtCreateSection,再呼叫NtCreateProcess,然後再完成一些其他工作,比如建立執行緒,通知Win32子系統,因此我們可以Hook這個系統服務函式來攔截可執行模組的載人,但在Vista以後,系統不會再呼叫NtCreateSection,而是整個過程統統交給了NtCreateUserProcess來處理,但是這不以為著在Vista+對NtCreateSection做hook一點價值都沒有,其實,DLL的載人記憶體也需要經過這一個過程,因此我們可以通過對這個函式做hook實現對DLL載人的監控和攔截,再退一步說,如果我們想在XP上監控和攔截程序的建立,就要hook NtCreateSection。

我們首先來看看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);
}