驱动回调的实现与逆向

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更加稳定正规,但是也容易被关闭掉。

猜你喜欢

转载自blog.csdn.net/qq_43312649/article/details/109310931