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直接得到文件对象,而后再或完整路径,哈哈!

下一篇总结注册表回调,线程建立回调和手动注册回调监控特定对象。

相关文章
相关标签/搜索