在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 : ;
__in ULONG Reserved : ;
};
};
__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[] = {};
if (CreateInfo)
{
if (GetProcessPathBySectionObject(EProcess,wzProcessPath)==TRUE)
{
if (wcscmp(wzProcessPath,L"C:\\Windows\\System32\\calc.exe")==)
{
CreateInfo->CreationStatus = STATUS_UNSUCCESSFUL;
}
}
}
else
{
//这里是一个进程退出的请求
}
} VOID UnloadDriver(PDRIVER_OBJECT DriverObject)
{
ULONG i = ;
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 : ; //code addressing mode
ULONG SystemModeImage : ; //system mode image
ULONG ImageMappedToAllPids : ; //mapped in all processes
ULONG Reserved : ;
};
};
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 : ; // Code addressing mode
ULONG SystemModeImage : ; // System mode image
ULONG ImageMappedToAllPids : ; // Image mapped into all processes
ULONG ExtendedInfoPresent : ; // IMAGE_INFO_EX available
ULONG Reserved : ;
};
};
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[]={}; 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[]={};
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直接获得文件对象,然后再或完整路径,哈哈!

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

Windows 回调监控 <一>的更多相关文章

  1. Windows 回调监控 <二>

    在之前的文章Windows 回调监控 <一> 总结了关于CreateProcessNotify,CreateProcessNotifyEx和LoadImageNotify一些用法,之后产生 ...

  2. paip.windows io监控总结

    paip.windows io监控总结 io的主要参数是个.disk queue length 作者Attilax  艾龙,  EMAIL:1466519819@qq.com 来源:attilax的专 ...

  3. C# Windows IPSEC监控(仅此一家,别无分店)

    Windows IPSEC监控,使用C#编写,输出为一行字符串,可以按照既有IPSEC规则生成模板 using System; using System.Diagnostics; using Syst ...

  4. windows 进程监控 Procmon.exe

    windows 进程监控 Procmon.exe window下一个程序打开太慢,可以用此程序监控.在哪一步慢了,读取文件还是注册表. ProcessMonitor3.2 Process Monito ...

  5. Windows性能计数器监控实践

    Windows性能计数器(Performance Counter)是Windows提供的一种系统功能,它能实时采集.分析系统内的应用程序.服务.驱动程序等的性能数据,以此来分析系统的瓶颈.监控组件的表 ...

  6. python对 windows系统监控插件

    在python编程的windows系统监控中,需要监控监控硬件信息需要两个模块:WMI 和 pypiwin32 .

  7. Win64 驱动内核编程-15.回调监控注册表

    回调监控注册表 在 WIN32 平台上,监控注册表的手段通常是 SSDT HOOK.不过用 SSDT HOOK 的方式监控注册表实在是太麻烦了,要 HOOK 一大堆函数,还要处理一些 NT6 系统有而 ...

  8. Win64 驱动内核编程-14.回调监控文件

    回调监控文件 使用 ObRegisterCallbacks 实现保护进程,其实稍微 PATCH 下内核,这个函数还能实现文件操作监视.但可惜只能在 WIN7X64 上用.因为在 WIN7X64 上 P ...

  9. Win64 驱动内核编程-12.回调监控进线程创建和退出

    回调监控进线程创建和退出 两个注册回调的函数:PsSetCreateProcessNotifyRoutine   进程回调PsSetCreateThreadNotifyRoutine    线程回调分 ...

随机推荐

  1. Java动态替换InetAddress中DNS的做法简单分析1

    在java.net包描述中, 简要说明了一些关键的接口. 其中负责networking identifiers的是Addresses. 这个类的具体实现类是InetAddress, 底层封装了Inet ...

  2. 【EF Code First】 一对多、多对多的多重关系配置

    这里使用用户表(User)和项目(Project)表做示例 有这样一个需求: 用户与项目的关系是:一个用户可以发多个项目,可以参加多个项目,而项目可以有多个参与成员和一个发布者 [其中含1-n和n-n ...

  3. 在windows上使用symfony创建简易的CMS系统(一)

    http://blog.csdn.net/kunshan_shenbin/article/details/7164675 参考自:http://xsymfony.801.cxne.net/forum. ...

  4. SCSF智能客户端学习笔记(一)

    什么是智能客户端 要了解智能客户端,首先要认识瘦客户端技术和胖客户端技术各自的优缺点. 对于前者,典型的应用就是使用浏览器,通过输入URL远程访问服务端,并向服务端发送命令,获取服务端的资源,然后在客 ...

  5. homework-04 抓瞎

    程序截图 这是基本版本截图....空都没填上 四个角的话 可以留下四个单词 最后添上就行 程序思路 在一个大平面上先一个中心,然后从中心向四周填词 每次填词时,寻找一个使得矩阵最小的 代目如下 #in ...

  6. CAD字体显示错乱问题解决方案

    最近这两天一直在画竣工图,用CAD用得挺多的,所以老是发现一些问题.今天在打开别人发过来的图纸时,我看到竟然还有钢筋符号无法显示…… 像这种问题的解决,据我所知就两种方法: 一.替换使用的字体 首先选 ...

  7. ThinkPHP运算符与PHP运算符对照表

    ThinkPHP运算符与PHP运算符对照表 ThinkPHP标签 说明及对应PHP标签 备注 eq 等于(=)(==:用于模板判断时) 可用于查询条件与模板判断 neq 不等于(!=) 可用于查询条件 ...

  8. 1、android orm之greendao

    前提:最近写android项目,android自带数据库api有点复杂,于是偷懒用了greendao.greendao好处自己查,这里不赘述,但是零基础的的我用起来还是费了3天的功夫,取之于网络,特在 ...

  9. CSS水平导航条和纵向导航条

    问题描述:         使用CSS制作水平导航条和纵向导航条   问题解决:        (1)水平导航条            1.1 效果预览:                   1.2 ...

  10. Facebook 和 Google 如何激发工程师的创造力

    原文链接:http://kb.cnblogs.com/page/193450/ 今天终于“朝圣”了两个伟大的公司——Facebook和Google,对创造力和驱动力的来源有了更多的理解,尤其是对于典型 ...