雨落空林 发表于 2017-6-1 17:25:40

Windows 回调监控 <一>

在x86的体系结构中,我们常用hook关键的系统调用来达到对系统的监控,但是对于x64的结构,因为有PatchGuard的存在,对于一些系统关键点进行hook是很不稳定的,在很大几率上会导致蓝屏的发生,而且在Vista之后的操作系统中,还提供了ObRegisterCallbacks()函数注册自定义的回调对特定的对象进行监控。本文就是对在ring0经常使用的几种回调进行一个小结。
                            进程创建回调
要监控系统进程的创建,我们可以hook NtCreateProcess或者是更为底层的PspCreateProcess。但是最好的方法是利用系统提供的回调,这样可以增强程序的兼容性和健壮性,首先我们要注册一个回调函数,使用WDK提供的API接口函数PsSetCreateProcessNotifyRoutine

NTSTATUS
PsSetCreateProcessNotifyRoutine(
    IN PCREATE_PROCESS_NOTIFY_ROUTINENotifyRoutine,
    IN BOOLEANRemove
    );

NotifyRoutine是个函数指针,函数原型如下:
Remove表示是增加一个回调还是删除一个回调,TRUE表示删除,FALSE表示增加

VOID
(*PCREATE_PROCESS_NOTIFY_ROUTINE) (
    IN HANDLEParentId,
    IN HANDLEProcessId,
    IN BOOLEANCreate
    );

下面是使用这个回调的一个小例子:


#include <ntifs.h>
VOID ProcessCallBack(IN HANDLE ParentId,IN HANDLEProcessId,IN BOOLEANbCreate);
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 HANDLEProcessId,IN BOOLEANbCreate)
{
    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_EXNotifyRoutine,
    IN BOOLEANRemove
    );


对比与PsSetCreateProcessNotifyRoutine第一参数,也就是回调的函数指针类型发生了变化:


//回调函数
VOID
CreateProcessNotifyEx(
    __inout PEPROCESSProcess,            //要创建的进程的进程体
    __in HANDLEProcessId,              //进程的ID
    __in_opt PPS_CREATE_NOTIFY_INFOCreateInfo  //新进程的信息
    );


CreateInfo包含了进程成创建的主要信息:


typedef struct _PS_CREATE_NOTIFY_INFO {
__in SIZE_TSize;    //_PS_CREATE_NOTIFY_INFO结构体的大小
union {
    __in ULONGFlags;
    struct {
      __in ULONGFileOpenNameAvailable : 1;
      __in ULONGReserved : 31;
    };
};
__in HANDLEParentProcessId;           //新进程的父进程ID
__in CLIENT_IDCreatingThreadId;      //结构体中包含进程ID和线程ID
__inout struct _FILE_OBJECT*FileObject;   //新进程的exe文件的文件对象
__in PCUNICODE_STRINGImageFileName;       //exe文件名称
__in_opt PCUNICODE_STRINGCommandLine;
__inout NTSTATUSCreationStatus;         //进程创建的状态
} PS_CREATE_NOTIFY_INFO, *PPS_CREATE_NOTIFY_INFO;


而我们要阻止一个新进程的创建,就是要改变CreateInfo中的CreationStatus的值,下面的小例子就是阻止calc.exe创建:
GetProcessPathBySectionObject()自封装的函数,来自进程完整路径获得


#include <ntifs.h>
NTSTATUSRegisterProcessFilter();
VOID ProcessCallBackEx(PEPROCESSEProcess,HANDLEProcessId,PPS_CREATE_NOTIFY_INFOCreateInfo);
VOID UnloadDriver(PDRIVER_OBJECT DriverObject);


NTSTATUS DriverEntry(PDRIVER_OBJECTDriverObject,PUNICODE_STRINGRegisterPath)
{
    PDEVICE_OBJECT    DeviceObject;
    NTSTATUS      Status;
    ULONG            i;   
    DriverObject->DriverUnload = UnloadDriver;//   
      RegisterProcessFilter();   
    return STATUS_SUCCESS;
}

NTSTATUSRegisterProcessFilter()
{
    NTSTATUSStatus;
    Status = PsSetCreateProcessNotifyRoutineEx((PCREATE_PROCESS_NOTIFY_ROUTINE_EX)ProcessCallBackEx,FALSE);   //添加一个 进程 创建的回调Notity
    if (!NT_SUCCESS(Status))
    {
      return Status;
    }
    Status;
}

VOID
ProcessCallBackEx(PEPROCESSEProcess,HANDLEProcessId,PPS_CREATE_NOTIFY_INFOCreateInfo)
{
    NTSTATUSStatus;
    WCHARwzProcessPath = {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_OBJECTCurrentObject = 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_STRINGFullImageName,        //被映射的可执行文件的名称
    IN HANDLEProcessId, // where image is mapped   //映射的进程,如果是.sys,就为0
    IN PIMAGE_INFOImageInfo              //映射信息的结构体
    );



typedef struct_IMAGE_INFO {
    union {
      ULONGProperties;
      struct {
            ULONG ImageAddressingMode: 8; //code addressing mode
            ULONG SystemModeImage      : 1; //system mode image
            ULONG ImageMappedToAllPids : 1; //mapped in all processes
            ULONG Reserved             : 22;
      };
    };
    PVOIDImageBase;            //映射的虚拟地址
    ULONGImageSelector;
    ULONGImageSize;            //映射的大小
    ULONGImageSectionNumber;
} 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)&amp;((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,HANDLEProcessId,PIMAGE_INFOImageInfor)
{
    PVOID DriverEntryAddress = NULL;
    char szFullImageName={0};

    if(!ProcessId &amp;&amp; FullImageName!=NULL &amp;&amp; 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,HANDLEProcessId,PIMAGE_INFOImageInfor)
{
    PVOID DriverEntryAddress = NULL;
    char szFullImageName={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 HANDLEProcessId,IN BOOLEANbCreate)
{
    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(&amp;ansiTemp,uniSource,TRUE);   

    strcpy(szDest,ansiTemp.Buffer);
    RtlFreeAnsiString(&amp;ansiTemp);
}





测试的结果就是CreateProcessNotifyRoutine先执行!

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
今天做dll加载检测的时候又遇到的一个问题:当dll被加载,执行我们的回调函数时,被获得dll的全路径这个问题卡住了,因为如果是Windows自己的dll文件在FullImageName中是有盘符的,一般是/SystemRoot/../ , 而我们自己的dll文件是没有盘符的,只有一个目录的路径,在Vista之后的EX结构中是存在文件对象,我们可以获得完整路径,但是XP下没有,问题就来了,怎么才能获得完整路径呢?嘿嘿,万万没想到啊,这个FullImageName的指针指向的就是文件对象中的FullImageName,我们可以直接通过FullImageName直接获得文件对象,然后再或完整路径,哈哈!
页: [1]
查看完整版本: Windows 回调监控 &lt;一&gt;