zoukankan      html  css  js  c++  java
  • 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("驱动加载
    ");
        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进程被创建
    ",ProcessId); 
        }
        else
        {
            DbgPrint("%d进程被销毁
    ",ProcessId); 
        }
    }
    
    
    VOID
    UnloadDriver(PDRIVER_OBJECT DriverObject)
    {
        PsSetCreateProcessNotifyRoutine((PCREATE_PROCESS_NOTIFY_ROUTINE)ProcessCallBack,TRUE);
        DbgPrint("驱动卸载
    ");
    }

    利用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("驱动加载
    ");
        DriverObject->DriverUnload = UnloadDriver;
        PsSetLoadImageNotifyRoutine((PLOAD_IMAGE_NOTIFY_ROUTINE)LoadImageNotifyRoutine);
        return STATUS_SUCCESS;
    }
    
    VOID
    UnloadDriver(PDRIVER_OBJECT DriverObject)
    {
        PsRemoveLoadImageNotifyRoutine((PLOAD_IMAGE_NOTIFY_ROUTINE)LoadImageNotifyRoutine);
        DbgPrint("驱动卸载
    ");
    }
    
    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 驱动加载
    ",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("驱动卸载
    ");
    }
    
    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
    ",szFullImageName);
            if(strstr(szFullImageName, "calc.exe"))
            {
                DbgPrint("calc.exeLoadImage
    ");
            }            
        }
    }
    
    VOID ProcessCallBack(IN HANDLE ParentId,IN HANDLE  ProcessId,IN BOOLEAN  bCreate)
    {
        if (bCreate==TRUE)
        {
            DbgPrint("%d CreateProcessNotify
    ",ProcessId);
    
        }
        else
        {
            DbgPrint("%d ExitProcessNotify
    ",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直接获得文件对象,然后再或完整路径,哈哈!

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

  • 相关阅读:
    php利用__callStatic静态调用同类中非静态方法
    Using $this when not in object context错误原因及解决办法
    fastadmin权限修改
    call_user_func()
    php empty()奇怪现象
    LF will be replaced by CRLF in vendor/
    git放弃本地,强制拉取远程
    tp5防xss攻击方法
    php curl
    关于Mac设置alias别名访问服务器
  • 原文地址:https://www.cnblogs.com/lanrenxinxin/p/4723455.html
Copyright © 2011-2022 走看看