在x86的體系結構中,咱們經常使用hook關鍵的系統調用來達到對系統的監控,可是對於x64的結構,由於有PatchGuard的存在,對於一些系統關鍵點進行hook是很不穩定的,在很大概率上會致使藍屏的發生,並且在Vista以後的操做系統中,還提供了ObRegisterCallbacks()函數註冊自定義的回調對特定的對象進行監控。本文就是對在ring0常常使用的幾種回調進行一個小結。html
進程建立回調app
要監控系統進程的建立,咱們能夠hook NtCreateProcess或者是更爲底層的PspCreateProcess。可是最好的方法是利用系統提供的回調,這樣能夠加強程序的兼容性和健壯性,首先咱們要註冊一個回調函數,使用WDK提供的API接口函數PsSetCreateProcessNotifyRoutine函數
NTSTATUS
PsSetCreateProcessNotifyRoutine(
IN PCREATE_PROCESS_NOTIFY_ROUTINE NotifyRoutine,
IN BOOLEAN Remove
);
NotifyRoutine是個函數指針,函數原型以下:測試
Remove表示是增長一個回調仍是刪除一個回調,TRUE表示刪除,FALSE表示增長spa
VOID (*PCREATE_PROCESS_NOTIFY_ROUTINE) ( IN HANDLE ParentId, IN HANDLE ProcessId, IN BOOLEAN Create );
下面是使用這個回調的一個小例子:操作系統
#include <ntifs.h> VOID ProcessCallBack(IN HANDLE ParentId,IN HANDLE ProcessId,IN BOOLEAN bCreate); VOID UnloadDriver(PDRIVER_OBJECT DriverObject); NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegisterPath) { NTSTATUS Status = STATUS_SUCCESS; DbgPrint("驅動加載\r\n"); DriverObject->DriverUnload = UnloadDriver; Status = PsSetCreateProcessNotifyRoutine((PCREATE_PROCESS_NOTIFY_ROUTINE)ProcessCallBack,FALSE); return STATUS_SUCCESS; } VOID ProcessCallBack(IN HANDLE ParentId,IN HANDLE ProcessId,IN BOOLEAN bCreate) { if (bCreate==TRUE) { DbgPrint("%d進程被建立\r\n",ProcessId); } else { DbgPrint("%d進程被銷燬\r\n",ProcessId); } } VOID UnloadDriver(PDRIVER_OBJECT DriverObject) { PsSetCreateProcessNotifyRoutine((PCREATE_PROCESS_NOTIFY_ROUTINE)ProcessCallBack,TRUE); DbgPrint("驅動卸載\r\n"); }
利用PsSetCreateProcessNotifyRoutine咱們能夠知道哪些進程被建立,哪些被銷燬,可是對於一些目標進程不能進行攔截,好比說,若是在咱們hook NtCreateProcess的狀況下,是能夠防止calc.exe建立的,而PsSetCreateProcessNotifyRoutine只能讓咱們知道calc.exe建立了,卻不能阻止它,因此咱們要用到另外一個API:PsSetCreateProcessNotifyRoutineEx,也就是PsSetCreateProcessNotifyRoutine的「升級版」,能夠用來對進程的建立進行攔截,先看函數的聲明:線程
NTSTATUS
PsSetCreateProcessNotifyRoutineEx(
IN PCREATE_PROCESS_NOTIFY_ROUTINE_EX NotifyRoutine,
IN BOOLEAN Remove
);
對比與PsSetCreateProcessNotifyRoutine第一參數,也就是回調的函數指針類型發生了變化:指針
//回調函數 VOID CreateProcessNotifyEx( __inout PEPROCESS Process, //要建立的進程的進程體 __in HANDLE ProcessId, //進程的ID __in_opt PPS_CREATE_NOTIFY_INFO CreateInfo //新進程的信息 );
CreateInfo包含了進程成建立的主要信息:code
typedef struct _PS_CREATE_NOTIFY_INFO { __in SIZE_T Size; //_PS_CREATE_NOTIFY_INFO結構體的大小 union { __in ULONG Flags; struct { __in ULONG FileOpenNameAvailable : 1; __in ULONG Reserved : 31; }; }; __in HANDLE ParentProcessId; //新進程的父進程ID __in CLIENT_ID CreatingThreadId; //結構體中包含進程ID和線程ID __inout struct _FILE_OBJECT *FileObject; //新進程的exe文件的文件對象 __in PCUNICODE_STRING ImageFileName; //exe文件名稱 __in_opt PCUNICODE_STRING CommandLine; __inout NTSTATUS CreationStatus; //進程建立的狀態 } PS_CREATE_NOTIFY_INFO, *PPS_CREATE_NOTIFY_INFO;
而咱們要阻止一個新進程的建立,就是要改變CreateInfo中的CreationStatus的值,下面的小例子就是阻止calc.exe建立:htm
GetProcessPathBySectionObject()自封裝的函數,來自 進程完整路徑得到
#include <ntifs.h> NTSTATUS RegisterProcessFilter(); VOID ProcessCallBackEx(PEPROCESS EProcess,HANDLE ProcessId,PPS_CREATE_NOTIFY_INFO CreateInfo); VOID UnloadDriver(PDRIVER_OBJECT DriverObject); NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject,PUNICODE_STRING RegisterPath) { PDEVICE_OBJECT DeviceObject; NTSTATUS Status; ULONG i; DriverObject->DriverUnload = UnloadDriver; // RegisterProcessFilter(); return STATUS_SUCCESS; } NTSTATUS RegisterProcessFilter() { NTSTATUS Status; Status = PsSetCreateProcessNotifyRoutineEx((PCREATE_PROCESS_NOTIFY_ROUTINE_EX)ProcessCallBackEx,FALSE); //添加一個 進程 建立的回調Notity if (!NT_SUCCESS(Status)) { return Status; } Status; } VOID ProcessCallBackEx(PEPROCESS EProcess,HANDLE ProcessId,PPS_CREATE_NOTIFY_INFO CreateInfo) { NTSTATUS Status; WCHAR wzProcessPath[512] = {0}; if (CreateInfo) { if (GetProcessPathBySectionObject(EProcess,wzProcessPath)==TRUE) { if (wcscmp(wzProcessPath,L"C:\\Windows\\System32\\calc.exe")==0) { CreateInfo->CreationStatus = STATUS_UNSUCCESSFUL; } } } else { //這裏是一個進程退出的請求 } } VOID UnloadDriver(PDRIVER_OBJECT DriverObject) { ULONG i = 0; PDEVICE_OBJECT NextObject = NULL; PDEVICE_OBJECT CurrentObject = NULL; CurrentObject = DriverObject->DeviceObject; while (CurrentObject != NULL) { NextObject = CurrentObject->NextDevice; IoDeleteDevice(CurrentObject); CurrentObject = NextObject; } PsSetCreateProcessNotifyRoutineEx(ProcessCallBackEx,TRUE); return; }
映像加載回調
當以個可執行文件被加載或者是映射進內存的時候,咱們註冊的回調函數被調用。這樣就能夠作不少事了,好比對於當目標進程被建立映射exe文件時,咱們能夠處理目標進程的導入表來達到注入的目的,由於當有新進程建立時,咱們的回調函數是處於新進程的Context之中;也能夠對咱們本身的進程進行保護來防止注入,例如當有可執行文件被映射時,咱們能夠判斷ProcessId是否爲咱們本身的進程,若是是咱們本身的進程,在獲得可執行文件被映射的基地址以後,調用ZwUnmapViewOfSection()來取消映射,達到反注入的目的。
VOID (*PLOAD_IMAGE_NOTIFY_ROUTINE) ( IN PUNICODE_STRING FullImageName, //被映射的可執行文件的名稱 IN HANDLE ProcessId, // where image is mapped //映射的進程,若是是.sys,就爲0 IN PIMAGE_INFO ImageInfo //映射信息的結構體 );
typedef struct _IMAGE_INFO { union { ULONG Properties; struct { ULONG ImageAddressingMode : 8; //code addressing mode ULONG SystemModeImage : 1; //system mode image ULONG ImageMappedToAllPids : 1; //mapped in all processes ULONG Reserved : 22; }; }; PVOID ImageBase; //映射的虛擬地址 ULONG ImageSelector; ULONG ImageSize; //映射的大小 ULONG ImageSectionNumber; } IMAGE_INFO, *PIMAGE_INFO;
咱們能夠經過ImageInfo中的信息獲得映像被映射的基地址,來反注入。而IMAGE_INFO結構在Vista 以後又有擴展:
//Vista 以後的定義 typedef struct _IMAGE_INFO { union { ULONG Properties; struct { ULONG ImageAddressingMode : 8; // Code addressing mode ULONG SystemModeImage : 1; // System mode image ULONG ImageMappedToAllPids : 1; // Image mapped into all processes ULONG ExtendedInfoPresent : 1; // IMAGE_INFO_EX available ULONG Reserved : 21; }; }; PVOID ImageBase; ULONG ImageSelector; SIZE_T ImageSize; ULONG ImageSectionNumber; } IMAGE_INFO, *PIMAGE_INFO;
主要的變化就是增長了ExtendedInfoPresent位,若是ExtendedInfoPresent置1的話,則IMAGE_INFO只是IMAGE_INFO_EX的一部分,能夠經過CONTAINING_RECORD來得到整個IMAGE_INFO_EX結構。
#define CONTAINING_RECORD(addr,type,field) ((type*)((unsigned char*)addr - (unsigned long)&((type*)0)->field)) //addr: 結構體中某個成員變量的地址 //type: 結構體的原型 //field: 結構體的某個成員(與前面相同)
typedef struct _IMAGE_INFO_EX { SIZE_T Size; //IMAGE_INFO_EX結構體的大小 IMAGE_INFO ImageInfo; struct _FILE_OBJECT *FileObject; //映像文件的文件對象 } IMAGE_INFO_EX, *PIMAGE_INFO_EX;
下面是一個簡單的使用LoadImageNotify來監控驅動加載的例子:
NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegisterPath) { DbgPrint("驅動加載\r\n"); DriverObject->DriverUnload = UnloadDriver; PsSetLoadImageNotifyRoutine((PLOAD_IMAGE_NOTIFY_ROUTINE)LoadImageNotifyRoutine); return STATUS_SUCCESS; } VOID UnloadDriver(PDRIVER_OBJECT DriverObject) { PsRemoveLoadImageNotifyRoutine((PLOAD_IMAGE_NOTIFY_ROUTINE)LoadImageNotifyRoutine); DbgPrint("驅動卸載\r\n"); } VOID LoadImageNotifyRoutine(PUNICODE_STRING FullImageName,HANDLE ProcessId,PIMAGE_INFO ImageInfor) { PVOID DriverEntryAddress = NULL; char szFullImageName[260]={0}; if(!ProcessId && FullImageName!=NULL && MmIsAddressValid(FullImageName)) { DbgPrint("%wZ 驅動加載\r\n",FullImageName); } }
咱們以前提到過,能夠在進程建立映射.exe文件時利用LoadImageNotify來改變它的導入表,咱們這裏小結了兩種回調,就又產生了新的問題:在進程建立時,是CreateProcessNotify先執行,仍是LoadImageNotify先執行?
咱們用一個小例子來試驗,就以「calc.exe」爲例:
NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryString) { DriverObject->DriverUnload = DriverUnload; PsSetLoadImageNotifyRoutine((PLOAD_IMAGE_NOTIFY_ROUTINE)LoadImageNotifyRoutine); PsSetCreateProcessNotifyRoutine((PCREATE_PROCESS_NOTIFY_ROUTINE)ProcessCallBack,FALSE); return STATUS_SUCCESS; } VOID DriverUnload(IN PDRIVER_OBJECT DriverObject) { PsRemoveLoadImageNotifyRoutine((PLOAD_IMAGE_NOTIFY_ROUTINE)LoadImageNotifyRoutine); PsSetCreateProcessNotifyRoutine((PCREATE_PROCESS_NOTIFY_ROUTINE)ProcessCallBack,TRUE); DbgPrint("驅動卸載\r\n"); } VOID LoadImageNotifyRoutine(PUNICODE_STRING FullImageName,HANDLE ProcessId,PIMAGE_INFO ImageInfor) { PVOID DriverEntryAddress = NULL; char szFullImageName[260]={0}; NTSTATUS Status = STATUS_UNSUCCESSFUL; PEPROCESS EProcess; if (ProcessId) { UnicodeToChar(FullImageName,szFullImageName); // DbgPrint("FullImageName:%s\r\n",szFullImageName); if(strstr(szFullImageName, "calc.exe")) { DbgPrint("calc.exeLoadImage\r\n"); } } } VOID ProcessCallBack(IN HANDLE ParentId,IN HANDLE ProcessId,IN BOOLEAN bCreate) { if (bCreate==TRUE) { DbgPrint("%d CreateProcessNotify\r\n",ProcessId); } else { DbgPrint("%d ExitProcessNotify\r\n",ProcessId); } } VOID UnicodeToChar(PUNICODE_STRING uniSource, CHAR *szDest) { ANSI_STRING ansiTemp; RtlUnicodeStringToAnsiString(&ansiTemp,uniSource,TRUE); strcpy(szDest,ansiTemp.Buffer); RtlFreeAnsiString(&ansiTemp); }
測試的結果就是CreateProcessNotifyRoutine先執行!
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
今天作dll加載檢測的時候又遇到的一個問題:當dll被加載,執行咱們的回調函數時,被得到dll的全路徑這個問題卡住了,由於若是是Windows本身的dll文件在FullImageName中是有盤符的,通常是/SystemRoot/../ , 而咱們本身的dll文件是沒有盤符的,只有一個目錄的路徑,在Vista以後的EX結構中是存在文件對象,咱們能夠得到完整路徑,可是XP下沒有,問題就來了,怎麼才能得到完整路徑呢?嘿嘿,萬萬沒想到啊,這個FullImageName的指針指向的就是文件對象中的FullImageName,咱們能夠直接經過FullImageName直接得到文件對象,而後再或完整路徑,哈哈!
下一篇總結註冊表回調,線程建立回調和手動註冊回調監控特定對象。