PsSetCreateProcessNotifyRoutineEx函数创建进程回调
NTSTATUS status = PsSetCreateProcessNotifyRoutineEx(
(PCREATE_PROCESS_NOTIFY_ROUTINE_EX)ProcessNotifyExRoutine, FALSE);
第一个参数是真实处理的回调函数,第二个参数是BOOLEAN Remove。为True时即是卸载回调函数。
VOID ProcessNotifyExRoutine(PEPROCESS pEProcess, HANDLE hProcessId, PPS_CREATE_NOTIFY_INFO CreateInfo)
{
// CreateInfo 为 NULL 时,表示进程退出;不为 NULL 时,表示进程创建
if (NULL == CreateInfo)
{
return;
}
// 获取进程名称
PCHAR pszImageFileName = PsGetProcessImageFileName(pEProcess);
DbgPrint("[%s][%d][%wZ]\n", pszImageFileName, hProcessId, CreateInfo->ImageFileName);
// 禁止指定进程(test.exe)创建
if (0 == _stricmp(pszImageFileName, "test.exe"))
{
// 禁止创建
CreateInfo->CreationStatus = STATUS_UNSUCCESSFUL;
DbgPrint("[禁止创建]\n");
}
}
PS_CREATE_NOTIFY_INFO结构体是该回调实现的关键所在。该结构体包含了要创建进程的所有信息,可以通过该结构体来过滤进程创建,设置创建结果。
typedef struct _PS_CREATE_NOTIFY_INFO {
_In_ SIZE_T Size; //该结构的大小 +0x4
union {
_In_ ULONG Flags; +0x4
struct {
_In_ ULONG FileOpenNameAvailable : 1;
_In_ ULONG IsSubsystemProcess : 1; //进程子系统是否为win32以外的子系统
_In_ ULONG Reserved : 30;
};
};
_In_ HANDLE ParentProcessId; //父进程ID +0x4
_In_ CLIENT_ID CreatingThreadId; //包含创建新进程的线程ID和进程ID +0x8
_Inout_ struct _FILE_OBJECT *FileObject;//指向进程可执行文件对象的指针 +0x4
_In_ PCUNICODE_STRING ImageFileName;//进程名字 +0x4
_In_opt_ PCUNICODE_STRING CommandLine;//进程的cmd命令 +0x4
_Inout_ NTSTATUS CreationStatus;//创建进程的NTSTATUS值 +0x4
} PS_CREATE_NOTIFY_INFO, *PPS_CREATE_NOTIFY_INFO;
不管进程的过滤条件是什么,只要阻止进程创建,肯定设置该结构体变量CreateInfo->CreationStatus = STATUS_UNSUCCESSFUL。
因为IDA的库中没有找到该结构体,所以该结构体的大小需要自己计算下偏移和位置,如上图所示所示偏移大小。
PsLoadImageNotifyRoutineEx函数创建模块加载回调
status = PsSetLoadImageNotifyRoutine(LoadImageNotifyRoutine);与status = PsRemoveLoadImageNotifyRoutine(LoadImageNotifyRoutine);相对。
其中比较重要的是回调函数的实现
VOID LoadImageNotifyRoutine(
PUNICODE_STRING FullImageName,
HANDLE ProcessId,
PIMAGE_INFO ImageInfo)
回调函数这三个参数包含了较多的模块信息。当ProcessID为0时代表加载的是一个驱动模块。第三个参数存储在IMAGE_INFO的结构体中。
1.卸载驱动模块时的步骤:
一、在ImageInfo中获取模块在内存中的加载基地址
二、根据NT_IMAGE_HEADER中IMAGE_OPTIONAL_HEADER中的AddressOfEntryPoint计算出驱动程序的入口点
三、在驱动程序的入口点写入机器码 B8 22 00 00 C0 C3
即mov eax,0xC0000022,ret的汇编。返回STATUS_ACCESS_DENIED状态致使驱动加载失败。
2.卸载DLL时的步骤:
一、声明MmUnmapViewOfSection函数
二、调用KeDelayExecutionThread函数实现延时
三、获取参数pEprocess和pImageBase,调用函数
当加载进程模块时,系统会有一个内部锁,为了避免死锁,在进程模块加载回调时不能进行映射、分配、查询、释放等操作。要想卸载DLL模块,必须等进程中所有模块加载完毕后方可卸载。解决这个问题的办法就是创建多线程延时等待,在进程模块加载完毕的时候再调用MmUnmapViewOfSection函数把模块卸载。
CmRegisterCallBack函数创建注册表回调
NTSTATUS status = CmRegisterCallback(RegisterMonCallback, NULL, &g_liRegCookie);
其中Cookie值用于调用CmUnRegisterCallback卸载回调函数时的一个验证数据值。
NTSTATUS RegisterMonCallback(
In PVOID CallbackContext,
// 操作类型(只是操作编号,不是指针)
In_opt PVOID Argument1,
// 操作详细信息的结构体指针
In_opt PVOID Argument2
)
ObRegisterCallBack函数创建对象回调
ObUnRegisterCallbacks(g_obThreadHandle);
// 设置线程回调函数
NTSTATUS SetThreadCallbacks()
{
NTSTATUS status = STATUS_SUCCESS;
OB_CALLBACK_REGISTRATION obCallbackReg = {
0 };//初始化
OB_OPERATION_REGISTRATION obOperationReg = {
0 };//初始化
RtlZeroMemory(&obCallbackReg, sizeof(OB_CALLBACK_REGISTRATION));
RtlZeroMemory(&obOperationReg, sizeof(OB_OPERATION_REGISTRATION));
// 设置 OB_CALLBACK_REGISTRATION
obCallbackReg.Version = ObGetFilterVersion();
obCallbackReg.OperationRegistrationCount = 1;
obCallbackReg.RegistrationContext = NULL;
RtlInitUnicodeString(&obCallbackReg.Altitude, L"321001");
obCallbackReg.OperationRegistration = &obOperationReg; //这个比较重要
// 设置 OB_OPERATION_REGISTRATION
// Thread 和 Process 的区别所在
obOperationReg.ObjectType = PsThreadType;
obOperationReg.Operations = OB_OPERATION_HANDLE_CREATE | OB_OPERATION_HANDLE_DUPLICATE;
// Thread 和 Process 的区别所在
obOperationReg.PreOperation = (POB_PRE_OPERATION_CALLBACK)(&ThreadPreCall);
// 注册回调函数
status = ObRegisterCallbacks(&obCallbackReg, &g_obThreadHandle);
if (!NT_SUCCESS(status))
{
DbgPrint("ObRegisterCallbacks Error[0x%X]\n", status);
return status;
}
return status;
}
另外驱动程序必须在卸载前注销所有回调例程。
ObRegisterCallbacks函数和PsSetCreateProcessNotifyRoutine函数存在使用条件,它要求驱动程序有数字签名时才能使用此函数。具体数字签名机制如何绕过不再赘述。
回调保护要比HOOK更加稳定正规,但是也容易被关闭掉。