Windows 回調監控 <一>

在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直接得到文件對象,而後再或完整路徑,哈哈!

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

相關文章
相關標籤/搜索