Windows x64内核

x64内核的回调函数

因为x64再想要去Hook例如各种门,SSDT表之类的重要数据结构变得异常困难

为了在内核模式下提供合法的扩展和拦截机制,微软提供了许多回调函数,这些回调允许驱动开发者在特定事件发生时执行自定义代码,而不必直接修改关键的内核数据结构。

还有一点,如果没有加载数字签名,调用以下API可能会报错,我们需要在编译选项加一些东西:/INTEGRITYCHECK

1725546149626

进程监控

  • API: PsSetCreateProcessNotifyRoutine / PsSetCreateProcessNotifyRoutineEx

  • 描述:

    • PsSetCreateProcessNotifyRoutine:注册一个回调函数,当进程创建或终止时,系统会调用此回调函数。该函数已被PsSetCreateProcessNotifyRoutineEx替代。
    • PsSetCreateProcessNotifyRoutineEx:与前者类似,但提供了更多信息,如父进程ID、命令行参数等。

具体代码,用于检测任何进程的创建与卸载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
#include "header.h"

VOID CreateProcessNotifyRoutine(
_In_ HANDLE ParentId,
_In_ HANDLE ProcessId,
_In_ BOOLEAN Create
)
{
ParentId;
PEPROCESS Process_ptr = NULL;
PsLookupProcessByProcessId(ProcessId, &Process_ptr);

if (Process_ptr == NULL)
{
DbgPrint("获取进程信息失败!\n");
return;
}

if (Create) //如果是创建进程
{
UCHAR* processName = (UCHAR*)PsGetProcessImageFileName(Process_ptr);
DbgPrint("进程%s被创建\n", processName);
}
else //如果是创建进程
{
UCHAR* processName = (UCHAR*)PsGetProcessImageFileName(Process_ptr);
DbgPrint("进程%s被卸载\n", processName);
}
}


NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath)
{
UNREFERENCED_PARAMETER(RegistryPath);
KdPrint(("Create!"));
KdBreakPoint();
// 驱动程序卸载例程&注册例程
DriverObject->DriverUnload = DriverUnloadRoutine;
//创建设备
NTSTATUS status;
PDEVICE_OBJECT DeviceObject = NULL;
UNICODE_STRING DeviceName;
RtlInitUnicodeString(&DeviceName, L"\\Device\\MyDevice");
status = IoCreateDevice(
DriverObject, // 驱动程序对象
0, // 设备扩展大小
&DeviceName, // 设备名称
FILE_DEVICE_UNKNOWN, // 设备类型
0, // 设备特征
FALSE, // 非独占设备
&DeviceObject // 返回的设备对象指针
);

if (!NT_SUCCESS(status))
{
KdPrint(("Failed to create device: %X\n", status));
return status;
}
KdPrint(("Device created successfully\n"));

UNICODE_STRING symbolicLink = RTL_CONSTANT_STRING(L"\\??\\MyDevice_Link");
status = IoCreateSymbolicLink(&symbolicLink, &DeviceName);
if (!NT_SUCCESS(status))
{
KdPrint(("Failed to create device: %X\n", status));
return status;
}
KdPrint(("Device created successfully\n"));


PsSetCreateProcessNotifyRoutine((_In_ PCREATE_PROCESS_NOTIFY_ROUTINE)CreateProcessNotifyRoutine, FALSE); //注册回调函数,如果为TRUE,则从通知列表中移除回调函数;如果为FALSE,则将其添加到列表中。


return STATUS_SUCCESS;
}


VOID DriverUnloadRoutine(IN PDRIVER_OBJECT DriverObject)
{
if (DriverObject->DeviceObject != NULL)
{
UNICODE_STRING symbolicLink = RTL_CONSTANT_STRING(L"\\??\\MyDevice_Link");
IoDeleteSymbolicLink(&symbolicLink);
IoDeleteDevice(DriverObject->DeviceObject);
}
PsSetCreateProcessNotifyRoutine((_In_ PCREATE_PROCESS_NOTIFY_ROUTINE)CreateProcessNotifyRoutine, TRUE);
DbgPrint("Driver unloaded\n");
}

如果我们需要阻止一个软件开启,我们可以通过PsSetCreateProcessNotifyRoutineEx进行拦截

1
2
3
4
5
6
7
8
9
10
VOID PcreateProcessNotifyRoutineEx(
PEPROCESS Process,
HANDLE ProcessId,
PPS_CREATE_NOTIFY_INFO CreateInfo //如果是卸载进程,CreateInfo为NULL
);

NTSTATUS PsSetCreateProcessNotifyRoutineEx(
[in] PCREATE_PROCESS_NOTIFY_ROUTINE_EX NotifyRoutine,
[in] BOOLEAN Remove
);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
VOID PcreateProcessNotifyRoutineEx(
PEPROCESS Process,
HANDLE ProcessId,
PPS_CREATE_NOTIFY_INFO CreateInfo //如果是卸载进程,CreateInfo为NULL
)
{
Process;
ProcessId;
CreateInfo;
UCHAR* processName = (UCHAR*)PsGetProcessImageFileName(Process);
if (!CreateInfo)
{
KdPrint(("%s被卸载\n", (char*)processName));
}
else
{
if (!strcmp("calc.exe", (char*)processName))
{
CreateInfo->CreationStatus = 0xC0000008; //一启动计算器会弹窗
}
}
}

1725588082040

提一嘴,PcreateProcessNotifyRoutineEx这里的Process指向一个_EPROCESS结构体,可以通过 !process 0 0去获取到

1725587356561

我们通过WRK分析一下这个函数PsSetCreateProcessNotifyRoutine

1725589312944

发现如果注册过回调函数,那么将会按照顺序进行调用

而且PG保护是不会保护这玩意的,毕竟这玩意一直在变

如果我们把PsSetCreateProcessNotifyRoutine里面的数据全抹掉,那么之前注册的回调将会全部失效

例如一些杀毒软件,禁止人为的卸载,就在注册一个回调,禁止我们手动卸载。
这时候我们将这里面的数据全部抹掉,这时候就可以顺利地删除了

  1. 线程监控
  • API: PsSetCreateThreadNotifyRoutine
  • 描述: 注册一个回调函数,当线程创建或终止时,系统会调用此回调函数。这在监控线程生命周期和操作时非常有用。
  1. 注册表监控
  • API: CmRegisterCallback / CmRegisterCallbackEx

  • 描述:

    • CmRegisterCallback:允许驱动程序在注册表操作(如创建、删除、查询、设置值等)发生时获得通知。该函数已被CmRegisterCallbackEx替代。
    • CmRegisterCallbackEx:扩展了前者的功能,支持命名空间隔离等高级特性。
  1. 文件监控
  • API: 文件系统过滤驱动 (File System Filter Driver)

  • 描述:

    • 文件监控通常通过编写文件系统过滤驱动实现。微软提供了Filter Manager框架(如FltRegisterFilter API),简化了开发过程。过滤驱动可以监控文件的打开、读写、删除、重命名等操作。
  1. 网络监控
  • API: WFP (Windows Filtering Platform)

  • 描述:

    • 通过Windows过滤平台(WFP)提供的API,可以监控和过滤网络数据包。例如,使用FwpmEngineOpen来打开过滤引擎,然后通过回调函数来处理特定的网络事件,如数据包传输、连接建立等。
  1. 模块监控
  • API: PsSetLoadImageNotifyRoutine
  • 描述: 注册一个回调函数,当模块(包括驱动程序、DLL等)加载到内存中时,系统会调用此回调函数。通过此API可以监控系统中加载的所有模块。
  1. 开关机监控
  • API: IoRegisterShutdownNotification / IoRegisterLastChanceShutdownNotification

  • 描述:

    • IoRegisterShutdownNotification:注册一个回调函数,确保在系统关机或重启时,驱动程序能够接收到通知,并进行必要的清理工作。
    • IoRegisterLastChanceShutdownNotification:类似于前者,但该回调在关机的最后阶段被调用,允许执行更紧急的任务。

对象监控

  • API: ObRegisterCallbacks
  • 描述:
    • ObRegisterCallbacks允许注册回调函数,监控和控制对特定对象类型(如进程、线程)的访问。通过这种方式,可以在对象创建、引用、销毁时获得通知或修改访问权限。

在WDK(Windows Driver Kit)开发中,ObRegisterCallbacks 函数用于注册回调函数,这些回调函数可以拦截对指定对象类型(如进程或线程)进行访问的请求。通过该函数,你可以设置回调,以便在对象句柄被创建或被重复使用(即使用现有句柄进行操作)时进行过滤和处理。

具体来说,ObRegisterCallbacks 注册的回调可以拦截以下操作:

  1. 句柄创建(Pre-Operation Callback):在目标对象的句柄创建之前调用,可以检查并修改请求的访问权限,或者拒绝句柄创建。这可以用来阻止某些进程对其他进程的句柄创建请求,限制其访问目标进程或线程的能力。
  2. 句柄重复使用(Post-Operation Callback):在句柄已经成功创建之后调用,这允许驱动在句柄创建成功后执行额外的操作,例如记录相关信息或进一步修改访问权限。

ObRegisterCallbacks 主要用于访问控制,例如拦截并限制对特定进程或线程对象的访问,常用于安全类驱动或反作弊系统,以防止恶意软件或调试器访问和控制某些系统资源。

这是参考代码 ProcessProtect-main.zip

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
OB_OPERATION_REGISTRATION obOperationRegistrations[2] = { {0}, {0} };
OB_CALLBACK_REGISTRATION obCallbackRegistration = { 0 };
UNICODE_STRING altitude = { 0 };
PVOID RegistrationHandle = NULL;

NTSTATUS
InitObRegistration()
{
// 配置第一个对象类型:进程对象 (PsProcessType)
// 设置操作类型为句柄创建 (OB_OPERATION_HANDLE_CREATE) 和句柄复制 (OB_OPERATION_HANDLE_DUPLICATE)
obOperationRegistrations[0].ObjectType = PsProcessType;
obOperationRegistrations[0].Operations |= OB_OPERATION_HANDLE_CREATE; // 允许处理进程句柄创建
obOperationRegistrations[0].Operations |= OB_OPERATION_HANDLE_DUPLICATE; // 允许处理进程句柄复制
obOperationRegistrations[0].PreOperation = PreOperationCallback; // 设置句柄操作的预处理回调函数

// 配置第二个对象类型:线程对象 (PsThreadType)
// 同样设置操作类型为句柄创建和句柄复制
obOperationRegistrations[1].ObjectType = PsThreadType;
obOperationRegistrations[1].Operations |= OB_OPERATION_HANDLE_CREATE; // 允许处理线程句柄创建
obOperationRegistrations[1].Operations |= OB_OPERATION_HANDLE_DUPLICATE; // 允许处理线程句柄复制
obOperationRegistrations[1].PreOperation = PreOperationCallback; // 设置句柄操作的预处理回调函数

// 初始化 Unicode 字符串 Altitude,用于指定过滤器的高度。高度的值决定了回调的优先级。
// 数字越大,优先级越高。这里设置为 "1000"。
RtlInitUnicodeString(&altitude, L"1000");

// 初始化回调注册结构体的版本,使用 ObGetFilterVersion 函数获取当前操作系统支持的回调过滤版本。
obCallbackRegistration.Version = ObGetFilterVersion();

// 设置操作注册数。这里注册了两个操作类型:进程和线程,因此数量是 2。
obCallbackRegistration.OperationRegistrationCount = 2;

// 不需要上下文信息,设置为 NULL。
obCallbackRegistration.RegistrationContext = NULL;

// 设置回调注册的高度,使用之前初始化的 altitude 字符串。
obCallbackRegistration.Altitude = altitude;

// 指向操作注册的数组(包含进程和线程的注册信息)。
obCallbackRegistration.OperationRegistration = obOperationRegistrations;

// 注册回调函数,通过 ObRegisterCallbacks 函数向内核注册进程和线程句柄操作的回调。
// 注册成功后返回 NTSTATUS 值,指示操作是否成功。
return ObRegisterCallbacks(&obCallbackRegistration, &RegistrationHandle);
}

OB_PREOP_CALLBACK_STATUS
PreOperationCallback(_In_ PVOID RegistrationContext,
_Inout_ POB_PRE_OPERATION_INFORMATION PreInfo)
{
UNREFERENCED_PARAMETER(RegistrationContext);



// 根据进程 ID 查找 EPROCESS 结构
PEPROCESS requestingProcess = IoGetCurrentProcess();
UCHAR* requestingProcessName = PsGetProcessImageFileName(requestingProcess);


//获取进程
PEPROCESS process = (PEPROCESS)PreInfo->Object;



if (PreInfo->ObjectType == *PsThreadType)
{
process = IoThreadToProcess((PETHREAD)PreInfo->Object);
}
else if (PreInfo->ObjectType == *PsProcessType)
{
process = (PEPROCESS)PreInfo->Object;
}
else
{
//都不是我们需要的类型,直接return
return OB_PREOP_SUCCESS;
}

//获取进程名
PUCHAR processName = PsGetProcessImageFileName(process);

if (_stricmp((char*)processName, "notepad.exe") != 0)
{
//不是我们关心的进程,直接return
return OB_PREOP_SUCCESS;
}

if (PreInfo->Operation == OB_OPERATION_HANDLE_CREATE)
{
if (!(_strnicmp((char*)requestingProcessName, "cheatengine-x86_64.exe", 6))) //对比是否是CE
{
PreInfo->Parameters->CreateHandleInformation.DesiredAccess = 0;
}
}

if (PreInfo->Operation == OB_OPERATION_HANDLE_DUPLICATE)
{
if (!(_strnicmp((char*)requestingProcessName, "cheatengine-x86_64.exe", 6)))
{
PreInfo->Parameters->DuplicateHandleInformation.DesiredAccess = 0;
}
}

return OB_PREOP_SUCCESS;
}

VOID
UnInitObRegistration()
{
if (RegistrationHandle)
{
ObUnRegisterCallbacks(RegistrationHandle); //卸载
RegistrationHandle = NULL;
}
}