当你需要在Windows操作系统中监控进程的启动和退出时,可以使用PsSetCreateProcessNotifyRoutineEx函数来创建一个MyCreateProcessNotifyEx回调函数,该回调函数将在每个进程的创建和退出时被调用。

PsSetCreateProcessNotifyRoutineEx 用于在系统启动后向内核注册一个回调函数,以监视新进程的创建和退出,其函数原型如下:

NTSTATUS PsSetCreateProcessNotifyRoutineEx(
PCREATE_PROCESS_NOTIFY_ROUTINE_EX NotifyRoutine,
BOOLEAN Remove
);

其中,参数NotifyRoutine是一个指向回调函数的指针,该函数将在新进程创建或退出时被调用。参数Remove是一个布尔值,用于指定是否从内核中删除之前注册的回调函数。如果要删除之前注册的回调函数,则将此参数设置为TRUE。如果要注册一个新的回调函数,则将此参数设置为FALSE。

如上PCREATE_PROCESS_NOTIFY_ROUTINE_EX用于接收一个自定义回调,该回调函数的参数传递需要定义为如下所示的样子;

VOID CreateProcessNotifyRoutineEx(
PEPROCESS Process,
HANDLE ProcessId,
PPS_CREATE_NOTIFY_INFO CreateInfo
);

其中,参数Process是一个指向新创建进程的EPROCESS结构的指针。参数ProcessId是新进程的PID(进程ID)。参数CreateInfo是一个指向一个PS_CREATE_NOTIFY_INFO结构的指针,该结构包含了有关新进程的详细信息。如果新进程是由系统启动的,CreateInfo将为空。

回调函数应该在执行完后尽快返回,以避免对系统性能的影响。同时,回调函数不应该调用任何可能导致死锁或系统崩溃的函数。

对于进程的创建与退出,则可通过MyCreateProcessNotifyEx自定义函数的PPS_CREATE_NOTIFY_INFO字段进行判断,如果PPS_CREATE_NOTIFY_INFO不等于NULL则说明该进程是被创建了,反之则说明进程是退出了,有了这些基础知识那么实现监视进程加载将变得很容易,如下案例所示;

#include <ntddk.h>

NTKERNELAPI PCHAR PsGetProcessImageFileName(PEPROCESS Process);
NTKERNELAPI NTSTATUS PsLookupProcessByProcessId(HANDLE ProcessId, PEPROCESS *Process); PCHAR GetProcessNameByProcessId(HANDLE ProcessId)
{
NTSTATUS st = STATUS_UNSUCCESSFUL;
PEPROCESS ProcessObj = NULL;
PCHAR string = NULL;
st = PsLookupProcessByProcessId(ProcessId, &ProcessObj);
if (NT_SUCCESS(st))
{
string = PsGetProcessImageFileName(ProcessObj);
ObfDereferenceObject(ProcessObj);
}
return string;
} VOID MyCreateProcessNotifyEx(PEPROCESS Process, HANDLE ProcessId, PPS_CREATE_NOTIFY_INFO CreateInfo)
{
char ProcName[16] = { 0 };
if (CreateInfo != NULL)
{
strcpy(ProcName, PsGetProcessImageFileName(Process));
DbgPrint("父进程ID: %ld --->父进程名: %s --->进程名: %s---->进程路径:%wZ", CreateInfo->ParentProcessId,
GetProcessNameByProcessId(CreateInfo->ParentProcessId),
PsGetProcessImageFileName(Process),CreateInfo->ImageFileName);
}
else
{
strcpy(ProcName, PsGetProcessImageFileName(Process));
DbgPrint("进程[ %s ] 离开了,程序被关闭了",ProcName);
}
} VOID UnDriver(PDRIVER_OBJECT driver)
{
PsSetCreateProcessNotifyRoutineEx((PCREATE_PROCESS_NOTIFY_ROUTINE_EX)MyCreateProcessNotifyEx, TRUE);
} NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
NTSTATUS status;
status = PsSetCreateProcessNotifyRoutineEx((PCREATE_PROCESS_NOTIFY_ROUTINE_EX)MyCreateProcessNotifyEx, FALSE);
Driver->DriverUnload = UnDriver;
return STATUS_SUCCESS;
}

输出效果图如下所示:

那么如何进行进程监控呢?

进程监控的思路很简单,只需要在如上MyCreateProcessNotifyEx()这个自定义回调函数中进行改进即可,首先通过PsGetProcessImageFileName即将进程的PID转换为进程名,然后就可以通过_stricmp对比该进程是否为我们所需要的,如果发现是calc.exe等特定进程名,则可以将CreateInfo->CreationStatus中的参数修改为STATUS_UNSUCCESSFUL这意味着对象的创建过程未成功完成,从而实现拒绝进行执行的目的;

#include <ntddk.h>

NTKERNELAPI PCHAR PsGetProcessImageFileName(PEPROCESS Process);
NTKERNELAPI NTSTATUS PsLookupProcessByProcessId(HANDLE ProcessId, PEPROCESS *Process); PCHAR GetProcessNameByProcessId(HANDLE ProcessId)
{
NTSTATUS st = STATUS_UNSUCCESSFUL;
PEPROCESS ProcessObj = NULL;
PCHAR string = NULL;
st = PsLookupProcessByProcessId(ProcessId, &ProcessObj);
if (NT_SUCCESS(st))
{
string = PsGetProcessImageFileName(ProcessObj);
ObfDereferenceObject(ProcessObj);
}
return string;
} VOID MyCreateProcessNotifyEx(PEPROCESS Process, HANDLE ProcessId, PPS_CREATE_NOTIFY_INFO CreateInfo)
{
char ProcName[16] = { 0 };
if (CreateInfo != NULL)
{
strcpy(ProcName, PsGetProcessImageFileName(Process));
if (!_stricmp(ProcName, "calc.exe"))
{
CreateInfo->CreationStatus = STATUS_UNSUCCESSFUL;
}
}
} VOID UnDriver(PDRIVER_OBJECT driver)
{
PsSetCreateProcessNotifyRoutineEx((PCREATE_PROCESS_NOTIFY_ROUTINE_EX)MyCreateProcessNotifyEx, TRUE);
DbgPrint(("驱动卸载成功"));
} NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
NTSTATUS status;
status = PsSetCreateProcessNotifyRoutineEx((PCREATE_PROCESS_NOTIFY_ROUTINE_EX)MyCreateProcessNotifyEx, FALSE);
Driver->DriverUnload = UnDriver;
DbgPrint("驱动加载成功!");
return STATUS_SUCCESS;
}

将上方代码编译,当我们加载驱动程序以后,再次打开C:\Windows\System32\calc.exe 计算器进程则提示无法打开,我们的驱动已经成功的拦截了本次的请求。

与进程检测类似,如果要检测线程创建则只需要通过PsSetCreateThreadNotifyRoutine创建线程回调即可,PsSetCreateThreadNotifyRoutine 函数的原型如下:

NTKERNELAPI
VOID
PsSetCreateThreadNotifyRoutine(
_Inout_ PCREATE_THREAD_NOTIFY_ROUTINE NotifyRoutine,
_In_ BOOLEAN Remove
);

PsSetCreateThreadNotifyRoutine 它允许注册一个回调函数,以便在新的线程被创建时被调用。该函数有两个参数:

  • 第一个参数:是一个指向回调函数的指针,这个回调函数将在新的线程被创建时被调用。
  • 第二个参数:是一个布尔值,表示是否将此回调函数添加到一个已有的回调列表中。如果此参数为TRUE,则将该回调函数添加到列表中,如果为FALSE,则将替换掉已有的回调函数。

当一个新的线程被创建时,操作系统会调用所有已注册的回调函数,并将新线程的ThreadID和进程ID作为参数传递给回调函数。这些参数可以用来识别新线程所属的进程以及新线程本身的标识符。

对于PCREATE_THREAD_NOTIFY_ROUTINE来说,它指向一个回调函数,用于通知进程中新线程的创建。该函数指针的定义如下:

typedef VOID (*PCREATE_THREAD_NOTIFY_ROUTINE) (
_In_ HANDLE ProcessId,
_In_ HANDLE ThreadId,
_In_ BOOLEAN Create
);

回调函数的参数说明如下:

  • ProcessId:新线程所属进程的进程ID。
  • ThreadId:新线程的线程ID。
  • Create:布尔值,指示新线程是创建还是销毁。如果为TRUE,则表示新线程已创建;如果为FALSE,则表示新线程已销毁。

在 PsSetCreateThreadNotifyRoutine 函数中注册的回调函数应该符合这个函数指针的定义,以便在新线程被创建或销毁时被调用。

而当调用结束后,用户需要通过PsRemoveCreateThreadNotifyRoutine来删除已注册的回调函数,目前该函数只需要一个参数,只需要传入注册时的函数指针即可;

NTKERNELAPI
VOID
PsRemoveCreateThreadNotifyRoutine(
_Inout_ PCREATE_THREAD_NOTIFY_ROUTINE NotifyRoutine
);

通过上述知识点的总结,相信你也可以很容易的编写处线程检测相关代码片段,具体代码如下:

#include <ntddk.h>

NTKERNELAPI PCHAR PsGetProcessImageFileName(PEPROCESS Process);
NTKERNELAPI NTSTATUS PsLookupProcessByProcessId(HANDLE ProcessId, PEPROCESS *Process); VOID MyCreateThreadNotify(HANDLE ProcessId, HANDLE ThreadId, BOOLEAN Create)
{
PEPROCESS eprocess = NULL;
// 通过此函数拿到程序的EPROCESS结构
PsLookupProcessByProcessId(ProcessId, &eprocess);
if (Create)
{
DbgPrint("线程TID: %1d --> 所属进程名: %s --> 进程PID: %1d \n", ThreadId, PsGetProcessImageFileName(eprocess), PsGetProcessId(eprocess));
}
else
{
DbgPrint("%s 线程已退出...", ThreadId);
}
} VOID UnDriver(PDRIVER_OBJECT driver)
{
PsRemoveCreateThreadNotifyRoutine(MyCreateThreadNotify);
DbgPrint(("驱动卸载成功"));
} NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
NTSTATUS status;
status = PsSetCreateThreadNotifyRoutine(MyCreateThreadNotify);
DbgPrint("PsSetCreateThreadNotifyRoutine: %x", status);
Driver->DriverUnload = UnDriver;
return STATUS_SUCCESS;
}

输出效果图如下所示:

4.4 Windows驱动开发:内核监控进程与线程创建的更多相关文章

  1. Windows驱动开发-内核常用内存函数

    搞内存常用函数 C语言 内核 malloc ExAllocatePool memset RtlFillMemory memcpy RtlMoveMemory free ExFreePool

  2. C++第三十三篇 -- 研究一下Windows驱动开发(一)内部构造介绍

    因为工作原因,需要做一些与网卡有关的测试,其中涉及到了驱动这一块的知识,虽然程序可以运行,但是不搞清楚,心里总是不安,觉得没理解清楚.因此想看一下驱动开发.查了很多资料,看到有人推荐Windows驱动 ...

  3. Windows驱动开发(中间层)

    Windows驱动开发 一.前言 依据<Windows内核安全与驱动开发>及MSDN等网络质料进行学习开发. 二.初步环境 1.下载安装WDK7.1.0(WinDDK\7600.16385 ...

  4. [Windows驱动开发](一)序言

    笔者学习驱动编程是从两本书入门的.它们分别是<寒江独钓——内核安全编程>和<Windows驱动开发技术详解>.两本书分别从不同的角度介绍了驱动程序的制作方法. 在我理解,驱动程 ...

  5. windows驱动开发推荐书籍

    [作者] 猪头三 个人网站 :http://www.x86asm.com/ [序言] 很多人都对驱动开发有兴趣,但往往找不到正确的学习方式.当然这跟驱动开发的本土化资料少有关系.大多学的驱动开发资料都 ...

  6. windows 驱动开发入门——驱动中的数据结构

    最近在学习驱动编程方面的内容,在这将自己的一些心得分享出来,供大家参考,与大家共同进步,本人学习驱动主要是通过两本书--<独钓寒江 windows安全编程> 和 <windows驱动 ...

  7. Windows驱动——读书笔记《Windows驱动开发技术详解》

    =================================版权声明================================= 版权声明:原创文章 谢绝转载  请通过右侧公告中的“联系邮 ...

  8. Windows驱动开发-IRP的完成例程

    <Windows驱动开发技术详解 >331页, 在将IRP发送给底层驱动或其他驱动之前,可以对IRP设置一个完成例程,一旦底层驱动将IRP完成后,IRP完成例程立刻被处罚,通过设置完成例程 ...

  9. C++第三十八篇 -- 研究一下Windows驱动开发(二)--WDM式驱动的加载

    基于Windows驱动开发技术详解这本书 一.简单的INF文件剖析 INF文件是一个文本文件,由若干个节(Section)组成.每个节的名称用一个方括号指示,紧接着方括号后面的就是节内容.每一行就是一 ...

  10. Windows 驱动开发 - 5

    上篇<Windows 驱动开发 - 4>我们已经完毕了硬件准备. 可是我们还没有详细的数据操作,比如接收读写操作. 在WDF中进行此类操作前须要进行设备的IO控制,已保持数据的完整性. 我 ...

随机推荐

  1. 2021 年如何学习 Android,一位 5 年中大厂老哥跟你聊聊

    本文首发我的微信公众号:程序员徐公 大家好,我是程序员徐公,加上实习,有五年中大厂经验.今天,我们一起来聊一聊如何自学 Android. 光阴似箭,日月如梭,时间真的过得飞快. 加上实习,从事 And ...

  2. JSP | IDEA中部署tomcat,运行JSP文件,编译后的JSP文件存放地点总结

    首先保证你正常部署了Tomcat,并且正常在浏览器中运行了JSP文件. 参考博客:Here 那么Tomcat编译后的JSP文件(_jsp.class 和 _jsp.java)的存放地点: (一)一般存 ...

  3. AtCoder Beginner Contest 172 (C题前缀和 + 二分,D题筛因子,E题容斥定理)

    AB水题, C - Tsundoku 题目描述 有两摞书,一摞有 $n$ 本,从上至下每本需阅读 $a_i$ 分钟,一摞有 $m$ 本,从上至下每本需阅读 $b_i$ 分钟,问最多能在 $k$ 分钟内 ...

  4. VScode 中 Code Runner 插件乱码问题

    安装好,Code Runner 插件,可以很方便的运行多种语言的文本,但是提示和输出经常会报错 进入 File - > Preference -> setting, 然后在输入框搜索 se ...

  5. linux ntp时间服务器搭建

    工作中经验遇到搭建时间服务器的任务,如何搭建网上找的例子总是有些许问题,如下自己动手操作一遍总结一下,方便自己和后来人直接上手使用. 准备工作:192.168.0.1   服务端: ntp服务器192 ...

  6. P1439-DP【绿】

    轻敌了啊...题目一共只有几句话但我却忽略了一个重大信息... 总之我显示写出了时空复杂度都是n^2级别的朴素递推算法,这没什么,基本功而已,然后50分 我试了试滚动数组,把空间复杂度降到了n级别,但 ...

  7. C语言基础之基础的输入输出

    前言 学一门编程语言,不能编写让用户输入数据然后输出处理后的数据的程序那么就等于没学,而在C语言中可以用printf() 和 scanf() 函数进行输入和输出操作.这两个函数是内置的库函数,定义在 ...

  8. 每天学五分钟 Liunx 0001 | 存储篇:swap

    swap swap ,内存交换空间,它是磁盘上的一块空间,主要作用是为了防止物理内存不足. CPU 从内存中读取数据.当内存的空间不足时, CPU 难以读取到数据,导致程序无法正常工作.所以诞生了 s ...

  9. python · ssh · SQL | python 连接远程 SQL 数据库

    python 连接本地 SQL 的 教程存档. 如果要连接远程的 SQL 数据库,需要先开一个 ssh 连接,在 ssh 连接里写 pymysql 的 connect 代码. 代码如下: ''' pi ...

  10. 如何与chatgpt共存

    作为程序员,专注于创造性劳动,而把重复性劳动任务交给chatgpt,要成为 需求 和 chatgpt的桥梁. 人工智能比如chatgpt越来越强,提问能力是人类的天赋,提问能力更为重要.