Windows 回調監控 <一>


在x86的體系結構中,我們常用hook關鍵的系統調用來達到對系統的監控,但是對於x64的結構,因為有PatchGuard的存在,對於一些系統關鍵點進行hook是很不穩定的,在很大幾率上會導致藍屏的發生,而且在Vista之后的操作系統中,還提供了ObRegisterCallbacks()函數注冊自定義的回調對特定的對象進行監控。本文就是對在ring0經常使用的幾種回調進行一個小結。

                                                          進程創建回調

要監控系統進程的創建,我們可以hook NtCreateProcess或者是更為底層的PspCreateProcess。但是最好的方法是利用系統提供的回調,這樣可以增強程序的兼容性和健壯性,首先我們要注冊一個回調函數,使用WDK提供的API接口函數PsSetCreateProcessNotifyRoutine

NTSTATUS
  PsSetCreateProcessNotifyRoutine(
    IN PCREATE_PROCESS_NOTIFY_ROUTINE  NotifyRoutine,
    IN BOOLEAN  Remove
    );

NotifyRoutine是個函數指針,函數原型如下:

Remove表示是增加一個回調還是刪除一個回調,TRUE表示刪除,FALSE表示增加

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包含了進程成創建的主要信息:

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創建:

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直接獲得文件對象,然后再或完整路徑,哈哈!

下一篇總結注冊表回調,線程創建回調和手動注冊回調監控特定對象。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM