回调钩子的发展历史
微软为了windwos生态体系的快速发展,放开了驱动开发的权限,第三方驱动可以加载到内核并享有内核权限。在早期32位内核中,微软并未对这些驱动做限制,任何产品都能加载自己的驱动到内核,导致内核钩子满天飞,系统不稳定。

历史的发展总是曲折前进的, 在x64内核上增加了PG和签名校验
PatchGuard 补丁防护
描述:Patch Guard(简称PG)是Windows x64系统中用于保护内核代码完整性和安全性的保护机制,能够防止任何不受信任的代码或驱动程序修改内核代码,从而防止系统破坏和恶意软件的传播。Patch Guard在系统启动时进行验证,并在系统运行过程中定期执行检查以确保内核代码的完整性。如果发现任何不正确的修改,Patch Guard会使系统蓝屏并重启系统以确保安全性,蓝屏代码为0x109。
原理:读取内核中的数据,并与系统初始化时的值进行比较。
Windows 驱动签名机制
数字签名通过加密技术绑定驱动程序与开发者身份。当用户安装驱动时,Windows系统会验证签名中的证书链,确认其是否由受信任的第三方机构颁发。这一过程确保驱动程序的发布者身份真实可信,杜绝了恶意软件伪装成合法驱动的风险。
Windows NT/2000/XP(32 位时代)
- 首个公开版本出现在 Windows NT 4.0,通过
PsSetCreateProcessNotifyRoutine 注册回调,仅支持进程创建/终止通知。
回调钩子最早出现在32位内核,因为32位内核随便hook,导致回调使用率不高,64位PG和签名引入后加上微软提供了更完善的回调钩子,回调钩子使用率得以上升。
回调钩子的使用
作用:监控或拦截事件;例:在回调监控下,恶意软件再去伪装和隐藏进程都是没有意义的。
提供的回调有很多种,如进程回调,线程回调,模块加载回调,注册回调...
这里拿进程回调举例
NTSTATUS PsSetCreateProcessNotifyRoutine(
[in] PCREATE_PROCESS_NOTIFY_ROUTINE NotifyRoutine,
[in] BOOLEAN Remove
);
NTSTATUS PsSetLoadImageNotifyRoutineEx(
[in] PLOAD_IMAGE_NOTIFY_ROUTINE NotifyRoutine,
[in] ULONG_PTR Flags
);
- NotifyRoutine:指向回调函数的指针。这个回调函数必须符合
PS_CREATE_PROCESS_NOTIFY_ROUTINE 类型,它定义了当创建新进程时应该调用的函数。 - Remove:一个布尔值,如果设置为TRUE,则表示要移除之前注册的回调函数;如果设置为FALSE,则表示注册一个新的回调函数
EX版本(x64增加)相比没有EX版本,提供了更多的进程信息,并且可以拦截进程启动,非EX版本只能监控无法直接拦截,就像无能的丈夫。
#include <ntifs.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));
DbgPrintEx(77,0,"父进程ID: %ld --->父进程名: %s --->进程名: %s---->进程路径:%wZ", CreateInfo->ParentProcessId,
GetProcessNameByProcessId(CreateInfo->ParentProcessId),
PsGetProcessImageFileName(Process), CreateInfo->ImageFileName);
}
else
{
strcpy(ProcName, PsGetProcessImageFileName(Process));
DbgPrintEx(77,0,"进程[ %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);
DbgPrintEx(77, 0, "MyCreateProcessNotifyEx[ %p ] \r\n", MyCreateProcessNotifyEx);
Driver->DriverUnload = UnDriver;
return status;
}
typedef struct _PS_CREATE_NOTIFY_INFO {
_In_ SIZE_T Size;
union {
_In_ ULONG Flags;
struct {
_In_ ULONG FileOpenNameAvailable : 1;
_In_ ULONG IsSubsystemProcess : 1;
_In_ ULONG Reserved : 30;
};
};
_In_ HANDLE ParentProcessId;
_In_ CLIENT_ID CreatingThreadId;
_Inout_ struct _FILE_OBJECT *FileObject;
_In_ PCUNICODE_STRING ImageFileName;
_In_opt_ PCUNICODE_STRING CommandLine;
_Inout_ NTSTATUS CreationStatus;
} PS_CREATE_NOTIFY_INFO, *PPS_CREATE_NOTIFY_INFO;其中CreationStatus返回失败可以阻止进程创建。

破解 PsSetCreateProcessNotifyRoutineEx 函数的使用限制
内核通过 MmVerifyCallbackFunction 验证此回调是否合法, 但此函数只是简单的验证了一下 DriverObject->DriverSection->Flags 的值是不是为 0x20:
使用此函数, 一定要设置 IMAGE_OPTIONAL_HEADER 中的 DllCharacterisitics 字段设置为:IMAGE_DLLCHARACTERISITICS_FORCE_INTEGRITY 属性,该属性是一个驱动强制签名属性。
右击项目,选择属性
选中配置属性中的链接器,点击命令行
在其它选项中输入: /INTEGRITYCHECK 表示设置; /INTEGRITYCHECK:NO 表示不设置
破除回调钩子
1.PspNotifyEnableMask 标志位
PspNotifyEnableMask的不同位控制不同类型回调的启用状态16:
- 第0位:模块加载回调
- 第1位:进程回调(PsSetCreateProcessNotifyRoutine)
- 第2位:进程回调(PsSetCreateProcessNotifyRoutineEx)
- 第3位:线程回调

2.数组PspCreateProcessNotifyRoutine 清空
参考wrk得知回调储存在数组中

x64下最大数组数量


3.抢占注册
有事没事先注册64个
4.强行ret
改别人驱动代码,强行把回调函数ret
5.PsSetCreateProcessNotifyRoutine
获取回调函数地址,给他注销
PsSetCreateProcessNotifyRoutineEx((PCREATE_PROCESS_NOTIFY_ROUTINE_EX)MyCreateProcessNotifyEx, TRUE);
注意在PspCreateProcessNotifyRoutine数组中直接储存的不是回调函数地址,而是结构体地址具体可以参考wrk

typedef struct _EX_CALLBACK_ROUTINE_BLOCK {
EX_RUNDOWN_REF RundownProtect;
PEX_CALLBACK_FUNCTION Function;
PVOID Context;
} EX_CALLBACK_ROUTINE_BLOCK, *PEX_CALLBACK_ROUTINE_BLOCK;其中 PEX_CALLBACK_FUNCTION Function; 是回调函数的地址
[培训]Windows内核深度攻防:从Hook技术到Rootkit实战!
最后于 2025-7-26 21:56
被只会逆一点点编辑
,原因: