首页
社区
课程
招聘
[原创]通过CmRegisterCallback学习注册表监控与反注册表监控
2021-10-26 19:03 28262

[原创]通过CmRegisterCallback学习注册表监控与反注册表监控

2021-10-26 19:03
28262

一.前言

实验环境是Win7 X86系统。

曾经在这篇文章中常见的几种DLL注入技术说过,通过修改注册表的内容可以实现AppInit_DLLs注入。那么本文的实验是通过CmRegisterCallback来实现对注册表修改的监控以此来阻止修改。并通过对CmRegisterCallback的逆向分析来实现对监控函数的删除。

二.注册表监控

在Windows系统中,可以通过使用CmRegisterCallback来设置注册表监控的回调函数,该函数在文档中的定义如下

NTSTATUS
  CmRegisterCallback(
    IN PEX_CALLBACK_FUNCTION  Function,
    IN PVOID  Context,
    OUT PLARGE_INTEGER  Cookie 
    );
参数说明
Function指向RegistryCallback例程的指针,这个参数就是用来监控注册表操作的回调函数
Context配置管理器将作为CallbackContext参数传递给RegistryCallback例程中由驱动程序定义的值。此处设为NULL就好
Cookie指向LARGE_INTEGER变量的指针,该变量接收标识回调例程的值。当注册回调例程的时候,次值将作为Cookie参数传递给CmUnRegisterCallback

PEX_CALLBACK_FUNCTION回调函数在文档中的定义如下,该函数的返回值为STATUS_SUCCESS之外的错误码的时候,则表示系统会拒绝操作相应的注册表。

NTSTATUS 
  RegistryCallback(
    __in PVOID  CallbackContext,
    __in_opt PVOID  Argument1,
    __in_opt PVOID  Argument2 
    );
参数说明
CallbackContext
在注册该RegistryCallback例程时,驱动程序作为Context参数传递给CmRegisterCallback的值
Argument1REG_NOTIFY_CLASS联合体类型的值,用于标识正在执行的注册表的操作类型,以及是否在执行注册表操作之前或之后调用RegistryCallback例程
Argument2指向特定于注册表操作信息的结构指针。结构的类型取决于Argument1中的REG_NOTIFY_CLASS类型值

其中的REG_NOTIFY_CLASS定义如下

typedef enum _REG_NOTIFY_CLASS {
    RegNtDeleteKey,
    RegNtPreDeleteKey = RegNtDeleteKey,
    RegNtSetValueKey,
    RegNtPreSetValueKey = RegNtSetValueKey,
    RegNtDeleteValueKey,
    RegNtPreDeleteValueKey = RegNtDeleteValueKey,
    RegNtSetInformationKey,
    RegNtPreSetInformationKey = RegNtSetInformationKey,
    RegNtRenameKey,
    RegNtPreRenameKey = RegNtRenameKey,
    RegNtEnumerateKey,
    RegNtPreEnumerateKey = RegNtEnumerateKey,
    RegNtEnumerateValueKey,
    RegNtPreEnumerateValueKey = RegNtEnumerateValueKey,
    RegNtQueryKey,
    RegNtPreQueryKey = RegNtQueryKey,
    RegNtQueryValueKey,
    RegNtPreQueryValueKey = RegNtQueryValueKey,
    RegNtQueryMultipleValueKey,
    RegNtPreQueryMultipleValueKey = RegNtQueryMultipleValueKey,
    RegNtPreCreateKey,
    RegNtPostCreateKey,
    RegNtPreOpenKey,
    RegNtPostOpenKey,
    RegNtKeyHandleClose,
    RegNtPreKeyHandleClose = RegNtKeyHandleClose,
    //
    // .Net only
    //    
    RegNtPostDeleteKey,
    RegNtPostSetValueKey,
    RegNtPostDeleteValueKey,
    RegNtPostSetInformationKey,
    RegNtPostRenameKey,
    RegNtPostEnumerateKey,
    RegNtPostEnumerateValueKey,
    RegNtPostQueryKey,
    RegNtPostQueryValueKey,
    RegNtPostQueryMultipleValueKey,
    RegNtPostKeyHandleClose,
    RegNtPreCreateKeyEx,
    RegNtPostCreateKeyEx,
    RegNtPreOpenKeyEx,
    RegNtPostOpenKeyEx,
    //
    // new to Windows Vista
    //
    RegNtPreFlushKey,
    RegNtPostFlushKey,
    RegNtPreLoadKey,
    RegNtPostLoadKey,
    RegNtPreUnLoadKey,
    RegNtPostUnLoadKey,
    RegNtPreQueryKeySecurity,
    RegNtPostQueryKeySecurity,
    RegNtPreSetKeySecurity,
    RegNtPostSetKeySecurity,
    //
    // per-object context cleanup
    //
    RegNtCallbackObjectContextCleanup,
    //
    // new in Vista SP2 
    //
    RegNtPreRestoreKey,
    RegNtPostRestoreKey,
    RegNtPreSaveKey,
    RegNtPostSaveKey,
    RegNtPreReplaceKey,
    RegNtPostReplaceKey,

    MaxRegNtNotifyClass //should always be the last enum
} REG_NOTIFY_CLASS;

其中的几个比较常用的类型,它们的意义,以及对应的Argument2的结构体的内容如下

REG_NOTIFY_CLASS的值意义Argument2结构体
RegNtPreCreateKey创建注册表之前PREG_CREATE_KEY_INFORMATION
RegNtPreOpenKey打开注册表之前PREG_CREATE_KEY_INFORMATION
RegNtPreDeleteKey删除键之前PREG_DELETE_KEY_INFORMATION
RegNtPreDeleteValueKey删除键值之前PREG_DELETE_VALUE_KEY_INFORMATION
RegNtPreSetValueKey修改键值之前PREG_SET_VALUE_KEY_INFORMATION

PREG_CREATE_KEY_INFORMATION结构体定义如下

typedef struct _REG_CREATE_KEY_INFORMATION {
    PUNICODE_STRING     CompleteName; // IN
    PVOID               RootObject;   // IN
    PVOID               ObjectType;   // new to Windows Vista
    ULONG               CreateOptions;// new to Windows Vista
    PUNICODE_STRING     Class;        // new to Windows Vista
    PVOID               SecurityDescriptor;// new to Windows Vista
    PVOID               SecurityQualityOfService;// new to Windows Vista
    ACCESS_MASK         DesiredAccess;// new to Windows Vista
    ACCESS_MASK         GrantedAccess;// new to Windows Vista
			  		                  // to be filled in by callbacks 
                                      // when bypassing native code
    PULONG              Disposition;  // new to Windows Vista
                                      // on pass through, callback should fill 
                                      // in disposition
    PVOID               *ResultObject;// new to Windows Vista
				                      // on pass through, callback should return 
                                      // object to be used for the return handle
    PVOID               CallContext;  // new to Windows Vista
    PVOID               RootObjectContext;  // new to Windows Vista
    PVOID               Transaction;  // new to Windows Vista
    PVOID               Reserved;     // new to Windows Vista
} REG_CREATE_KEY_INFORMATION, REG_OPEN_KEY_INFORMATION,*PREG_CREATE_KEY_INFORMATION, *PREG_OPEN_KEY_INFORMATION;

关键的两个成员

成员含义

CompleteName

指向包含新注册表项路径的UNICODE_字符串结构的指针。路径可以是绝对的,也可以是相对的。如果路径是绝对路径,则此结构包含以“\”字符开头的完全限定路径。对于绝对路径,RootObject成员指定\注册表项,它是注册表树的根目录。如果路径是相对的,则路径以“\”以外的字符开头,并且与RootObject成员指定的键相对
RootObject指向注册表项对象的指针,该对象用作CompleteName成员指定的路径的根

PREG_DELETE_KEY_INFORMATION的结构体定义如下

typedef struct _REG_DELETE_KEY_INFORMATION {
    PVOID    Object;                      // IN
    PVOID    CallContext;  // new to Windows Vista
    PVOID    ObjectContext;// new to Windows Vista
    PVOID    Reserved;     // new to Windows Vista
} REG_DELETE_KEY_INFORMATION, *PREG_DELETE_KEY_INFORMATION

成员Object指向要删除的注册表项的注册表项对象的指针。

PREG_DELETE_VALUE_KEY_INFORMATION结构体定义如下

typedef struct _REG_DELETE_KEY_INFORMATION {
    PVOID    Object;                      // IN
    PVOID    CallContext;  // new to Windows Vista
    PVOID    ObjectContext;// new to Windows Vista
    PVOID    Reserved;     // new to Windows Vista
} REG_DELETE_KEY_INFORMATION, *PREG_DELETE_KEY_INFORMATION
成员含义
Object指向要删除其值项的项的注册表项对象的指针
Value指向UNICODE_字符串结构的指针,该结构包含要删除的值项的名称

PREG_SET_VALUE_KEY_INFORMATION结构体定义如下

typedef struct _REG_SET_VALUE_KEY_INFORMATION {
    PVOID               Object;                         // IN
    PUNICODE_STRING     ValueName;                      // IN
    ULONG               TitleIndex;                     // IN
    ULONG               Type;                           // IN
    PVOID               Data;                           // IN
    ULONG               DataSize;                       // IN
    PVOID               CallContext;  // new to Windows Vista
    PVOID               ObjectContext;// new to Windows Vista
    PVOID               Reserved;     // new to Windows Vista
} REG_SET_VALUE_KEY_INFORMATION, *PREG_SET_VALUE_KEY_INFORMATION;
成员含义
Object指向其值项即将更改的项的注册表项对象的指针
ValueName指向UNICODE_字符串结构的指针,其中包含要更改的值项的名称

根据上面的内容可以知道,回调函数中是可以获取要操作的注册表键的对象的,所以可以使用ObQueryNameString函数来获得键操作的键的名称,该函数在文档中的定义如下

NTSTATUS
  ObQueryNameString(
    IN PVOID  Object,
    OUT POBJECT_NAME_INFORMATION  ObjectNameInfo,
    IN ULONG  Length,
    OUT PULONG  ReturnLength
    );
参数含义
Object指向请求名称的对象的指针。此参数是必需的,不能为NULL。
ObjectNameInfo指向接收对象名称信息的调用方分配的缓冲区的OBJECT_NAME_INFORMATION指针
Length参数二的缓冲区大小
ReturnLength实际返回到缓冲区中的数据大小

OBJECT_NAME_INFORMATION的定义如下

typedef struct _OBJECT_NAME_INFORMATION {
    UNICODE_STRING Name;
} OBJECT_NAME_INFORMATION, *POBJECT_NAME_INFORMATION;

只有一个UNICODE_STRING的Name参数,保存了返回的名称。

想要实现对监控函数的删除,可以使用CmUnRegisterCallback函数来实现,该函数在文档中的定义如下。它只有一个参数,就是前面设置监控函数时候指定的Cookie的地址

NTSTATUS
  CmUnRegisterCallback(
    IN LARGE_INTEGER  Cookie);

根据上面的内容就可以实现对注册表的监控来拒绝AppInit_DLLs的注入,具体实现代码如下

#include <ntifs.h>

VOID DriverUnload(IN PDRIVER_OBJECT driverObject);
// 未导出函数声明
PUCHAR PsGetProcessImageFileName(PEPROCESS pEProcess);	//根据EPROCESS获取进程名称
NTSTATUS ObQueryNameString(PVOID Object, POBJECT_NAME_INFORMATION ObjectNameInfo, ULONG Length, PULONG ReturnLength);	//根据对象获取名称
NTSTATUS RegistryCallback(__in PVOID  CallbackContext, __in_opt PVOID  Argument1, __in_opt PVOID  Argument2);	//回调函数
BOOLEAN GetRegisterPath(PUNICODE_STRING pRegPath, PVOID pRegObj);	//获取注册表的完整路径
//注册表回调使用的Cookie
LARGE_INTEGER g_liRegCookie;

NTSTATUS DriverEntry(IN PDRIVER_OBJECT driverObject, IN PUNICODE_STRING registryPath)
{
	NTSTATUS status = STATUS_SUCCESS;
	
	status = CmRegisterCallback(RegistryCallback, NULL, &g_liRegCookie);
	if (!NT_SUCCESS(status))
	{
		DbgPrint("回调函数设置失败 0x%X\r\n", status);
	}
	else
	{
		DbgPrint("回调函数设置成功\r\n");
	}
exit:
	driverObject->DriverUnload = DriverUnload;
	return STATUS_SUCCESS;
}

NTSTATUS RegistryCallback(__in PVOID  CallbackContext, __in_opt PVOID  Argument1, __in_opt PVOID  Argument2)
{
	NTSTATUS status = STATUS_SUCCESS;
	UNICODE_STRING uStrRegPath = { 0 };	//保存注册表完整路径
	// 保存操作码的类型
	REG_NOTIFY_CLASS uOpCode = (REG_NOTIFY_CLASS)Argument1;
	// 保存当前操作注册表的进程EPROCESS
	PEPROCESS pEProcess = NULL;
	PUCHAR pProcName = NULL;
	BOOLEAN bNeedProtected = FALSE;
	PWCHAR pValue = NULL;

	pEProcess = PsGetCurrentProcess();
	if (pEProcess != NULL)
	{
		pProcName = PsGetProcessImageFileName(pEProcess);
	}

	// 申请内存用来保存注册表路径
	uStrRegPath.Length = 0;
	uStrRegPath.MaximumLength = 1024 * sizeof(WCHAR);
	uStrRegPath.Buffer = (PWCHAR)ExAllocatePool(NonPagedPool, uStrRegPath.MaximumLength);

	if (uStrRegPath.Buffer == NULL)
	{
		DbgPrint("ExAllocatePool Error");
		goto exit;
	}

	RtlZeroMemory(uStrRegPath.Buffer, uStrRegPath.MaximumLength);

	switch (uOpCode)
	{
		// 创建注册表之前
		case RegNtPreCreateKey:
		{
			if (!GetRegisterPath(&uStrRegPath, ((PREG_CREATE_KEY_INFORMATION)Argument2)->RootObject))
			{
				DbgPrint("获取注册表路径失败\r\n");
			}
			// 显示
			// DbgPrint("[RegNtPreCreateKey][%wZ][%wZ]\n", &uStrRegPath, ((PREG_CREATE_KEY_INFORMATION)Argument2)->CompleteName);
			break;
		}
		// 打开注册表之前
		case RegNtPreOpenKey:
		{
			if (!GetRegisterPath(&uStrRegPath, ((PREG_CREATE_KEY_INFORMATION)Argument2)->RootObject))
			{
				DbgPrint("获取注册表路径失败\r\n");
			}
			// 显示
			// DbgPrint("[RegNtPreOpenKey][%wZ][%wZ]\n", &uStrRegPath, ((PREG_CREATE_KEY_INFORMATION)Argument2)->CompleteName);
			break;
		}
		// 修改键值之前
		case RegNtPreSetValueKey:
		{
			if (!GetRegisterPath(&uStrRegPath, ((PREG_SET_VALUE_KEY_INFORMATION)Argument2)->Object))
			{
				DbgPrint("获取注册表路径失败\r\n");
			}
			
			pValue = ((PREG_SET_VALUE_KEY_INFORMATION)Argument2)->ValueName->Buffer;
			//判断是否需要保护
			if (wcsstr(pValue, L"AppInit_DLLs") || wcsstr(pValue, L"LoadAppInit_DLLs")) 
			{
				DbgPrint("[RegNtPreSetValueKey][%wZ][%ws]\r\n", &uStrRegPath, pValue);
				if (pProcName) DbgPrint("进程:%s试图修改注册表,拦截成功\r\n", pProcName);
				status = STATUS_ACCESS_DENIED;	//对操作进行拦截
			}
			break;
		}
		default:
		{
			break;
		}
	}

exit:
	if (uStrRegPath.Buffer)
	{
		ExFreePool(uStrRegPath.Buffer);
		uStrRegPath.Buffer = NULL;
	}
	return status;
}

BOOLEAN GetRegisterPath(PUNICODE_STRING pRegPath, PVOID pRegObj)
{
	NTSTATUS status = STATUS_SUCCESS;
	BOOLEAN bRet = TRUE;
	ULONG uSize = 0, uRetLength = 0;
	PVOID pRegName = NULL;	

	if (!MmIsAddressValid(pRegObj) || pRegPath == NULL)
	{
		bRet = FALSE;
		goto exit;
	}

	uSize = 512;
	pRegName = ExAllocatePool(NonPagedPool, uSize);
	if (pRegName == NULL)
	{
		DbgPrint("ExAllocatePool Error\r\n");
		bRet = FALSE;
		goto exit;
	}

	//根据注册表对象获取注册表路径
	status = ObQueryNameString(pRegObj, (POBJECT_NAME_INFORMATION)pRegName, uSize, &uRetLength);
	if (!NT_SUCCESS(status))
	{
		if (pRegName) ExFreePool(pRegName);
		DbgPrint("ObQueryNameString Error 0x%X\r\n", status);
		bRet = FALSE;
		goto exit;
	}

	//将获得的路径拷贝到目标地址
	RtlCopyUnicodeString(pRegPath, (PUNICODE_STRING)pRegName);
	
exit:
	if (pRegName) ExFreePool(pRegName);
	return bRet;
}

VOID DriverUnload(IN PDRIVER_OBJECT driverObject)
{
	NTSTATUS status = STATUS_SUCCESS;

	if (g_liRegCookie.QuadPart > 0)
	{
		status = CmUnRegisterCallback(g_liRegCookie);
		if (!NT_SUCCESS(status))
		{
			DbgPrint("删除回调函数失败0x%X\r\n", status);
		}
		else
		{
			DbgPrint("删除回调函数成功\r\n");
		}
	}
	DbgPrint("驱动卸载完成\r\n");
}

三.逆向分析CmRegisterCallback

接下来通过IDA分析CmRegisterCallback来看看是如何保存注册表的回调函数。IDA中对CmRegisterCallback的反汇编如下

PAGE:0069E3EE ; NTSTATUS __stdcall CmRegisterCallback(PEX_CALLBACK_FUNCTION Function, PVOID Context, PLARGE_INTEGER Cookie)
PAGE:0069E3EE                 public CmRegisterCallback
PAGE:0069E3EE CmRegisterCallback proc near
PAGE:0069E3EE
PAGE:0069E3EE Function        = dword ptr  8
PAGE:0069E3EE Context         = dword ptr  0Ch
PAGE:0069E3EE Cookie          = dword ptr  10h
PAGE:0069E3EE
PAGE:0069E3EE                 mov     edi, edi
PAGE:0069E3F0                 push    ebp
PAGE:0069E3F1                 mov     ebp, esp
PAGE:0069E3F3                 push    [ebp+Cookie]
PAGE:0069E3F6                 mov     eax, offset Init_Data ; 将Init_Data的地址赋给eax
PAGE:0069E3FB                 push    1
PAGE:0069E3FD                 push    [ebp+Context]
PAGE:0069E400                 push    [ebp+Function]
PAGE:0069E403                 call    CmCRegisterCallback
PAGE:0069E408                 pop     ebp
PAGE:0069E409                 retn    0Ch
PAGE:0069E409 CmRegisterCallback endp

这个函数做了两件事情,将Init_Data的赋值赋给eax,将需要的参数入栈以后调用CmCRegisterCallback函数。经过分析以后发现,这个Init_Data是用来赋值一些数据,不过在这里用处不大,入栈的1也是。

继续看CmCRegisterCallback的反汇编代码

PAGE:0069E417 CmCRegisterCallback proc near           ; CODE XREF: CmRegisterCallbackEx+2C↑p
PAGE:0069E417                                         ; CmRegisterCallback+15↑p
PAGE:0069E417
PAGE:0069E417 RegFunc         = dword ptr  8
PAGE:0069E417 Context         = dword ptr  0Ch
PAGE:0069E417 arg_one         = dword ptr  10h
PAGE:0069E417 arg_Cookie      = dword ptr  14h
PAGE:0069E417
PAGE:0069E417                 mov     edi, edi
PAGE:0069E419                 push    ebp
PAGE:0069E41A                 mov     ebp, esp
PAGE:0069E41C                 push    ecx
PAGE:0069E41D                 push    ebx
PAGE:0069E41E                 mov     ebx, [ebp+arg_Cookie] ; 注意这里将Cookie的地址赋给ebx
PAGE:0069E421                 push    esi
PAGE:0069E422                 push    edi
PAGE:0069E423                 push    'bcMC'          ; Tag
PAGE:0069E428                 push    30h             ; NumberOfBytes
PAGE:0069E42A                 push    PagedPool       ; PoolType
PAGE:0069E42C                 mov     edi, eax        ; 将结构体赋给edi
PAGE:0069E42E                 call    ExAllocatePoolWithTag
PAGE:0069E433                 mov     esi, eax        ; 申请到得内存地址赋给esi
PAGE:0069E435                 test    esi, esi
PAGE:0069E437                 jnz     short loc_69E443 ; 将前向指针和后向指针指向自己
PAGE:0069E439                 mov     eax, STATUS_INSUFFICIENT_RESOURCES
PAGE:0069E43E                 jmp     loc_69E4CD
PAGE:0069E443 ; ---------------------------------------------------------------------------
PAGE:0069E443
PAGE:0069E443 loc_69E443:                             ; CODE XREF: CmCRegisterCallback+20↑j
PAGE:0069E443                 mov     [esi+LIST_ENTRY.Blink], esi ; 将前向指针和后向指针指向自己
PAGE:0069E446                 mov     [esi+LIST_ENTRY.Flink], esi ; 这里可以知道申请到的地址前8位是个LIST_ENTRY
PAGE:0069E448                 lea     eax, [esi+28h]
PAGE:0069E44B                 mov     [eax+4], eax
PAGE:0069E44E                 mov     [eax], eax
PAGE:0069E450                 mov     eax, [ebp+Context]
PAGE:0069E453                 and     dword ptr [esi+8], 0
PAGE:0069E457                 mov     [esi+18h], eax  ; 申请得内存偏移0x18得地方保存Context
PAGE:0069E45A                 mov     eax, [ebp+RegFunc]
PAGE:0069E45D                 mov     [esi+1Ch], eax  ; 申请到的地址偏移0x1C的地方保存回调函数
PAGE:0069E460                 movzx   eax, word ptr [edi]
PAGE:0069E463                 mov     [esi+22h], ax
PAGE:0069E467                 mov     [esi+20h], ax
PAGE:0069E46B                 movzx   eax, word ptr [edi]
PAGE:0069E46E                 push    'acMC'          ; Tag
PAGE:0069E473                 push    eax             ; NumberOfBytes
PAGE:0069E474                 push    PagedPool       ; PoolType
PAGE:0069E476                 call    ExAllocatePoolWithTag
PAGE:0069E47B                 mov     [esi+24h], eax
PAGE:0069E47E                 test    eax, eax
PAGE:0069E480                 jnz     short loc_69E489
PAGE:0069E482                 mov     edi, STATUS_INSUFFICIENT_RESOURCES
PAGE:0069E487                 jmp     short loc_69E4B4
PAGE:0069E489 ; ---------------------------------------------------------------------------
PAGE:0069E489
PAGE:0069E489 loc_69E489:                             ; CODE XREF: CmCRegisterCallback+69↑j
PAGE:0069E489                 movzx   ecx, word ptr [edi]
PAGE:0069E48C                 push    ecx
PAGE:0069E48D                 push    dword ptr [edi+4]
PAGE:0069E490                 push    eax
PAGE:0069E491                 call    memcpy
PAGE:0069E496                 add     esp, 0Ch
PAGE:0069E499                 push    [ebp+arg_one]
PAGE:0069E49C                 mov     eax, esi        ; 申请到的地址赋给eax
PAGE:0069E49E                 call    SetRegisterCallBack
PAGE:0069E4A3                 mov     edi, eax
PAGE:0069E4A5                 mov     eax, [esi+10h]  ; 将申请到的内存偏移0x14的内容放入eax
PAGE:0069E4A8                 mov     [ebx], eax      ; 此时ebx是Cookie的地址,赋值前4位
PAGE:0069E4AA                 mov     eax, [esi+14h]  ; 这两句就是赋值后4位,因为一个COOKIE占8位
PAGE:0069E4AD                 mov     [ebx+4], eax    ; 根据这四句就可以知道,esi偏移0x10的地址存放的就是Cookie
PAGE:0069E4B0                 test    edi, edi
PAGE:0069E4B2                 jge     short loc_69E4CB
PAGE:0069E4B4
PAGE:0069E4B4 loc_69E4B4:                             ; CODE XREF: CmCRegisterCallback+70↑j
PAGE:0069E4B4                 mov     eax, [esi+24h]
PAGE:0069E4B7                 test    eax, eax
PAGE:0069E4B9                 jz      short loc_69E4C3
PAGE:0069E4BB                 push    0               ; Tag
PAGE:0069E4BD                 push    eax             ; P
PAGE:0069E4BE                 call    ExFreePoolWithTag
PAGE:0069E4C3
PAGE:0069E4C3 loc_69E4C3:                             ; CODE XREF: CmCRegisterCallback+A2↑j
PAGE:0069E4C3                 push    0               ; Tag
PAGE:0069E4C5                 push    esi             ; P
PAGE:0069E4C6                 call    ExFreePoolWithTag
PAGE:0069E4CB
PAGE:0069E4CB loc_69E4CB:                             ; CODE XREF: CmCRegisterCallback+9B↑j
PAGE:0069E4CB                 mov     eax, edi
PAGE:0069E4CD
PAGE:0069E4CD loc_69E4CD:                             ; CODE XREF: CmCRegisterCallback+27↑j
PAGE:0069E4CD                 pop     edi
PAGE:0069E4CE                 pop     esi
PAGE:0069E4CF                 pop     ebx
PAGE:0069E4D0                 pop     ecx
PAGE:0069E4D1                 pop     ebp
PAGE:0069E4D2                 retn    10h
PAGE:0069E4D2 CmCRegisterCallback endp

可以看到,系统分配0x30大小的内存来保存相应的内容,根据以上的分析可以得出下面的结论

  • 最开始的8个字节保存的是一个LIST_ENTRY类型的双向链表

  • 偏移0x10保存的是COOKIE

  • 偏移0x1C保存的Context

  • 偏移0x18保存的是回调函数的地址

所以可以想到注册表监控的回调函数是用LIST_ENTRY双向链表来一个个连起来的。而真正将分配的这块内存加入链表的函数则是SetRegisterCallback函数,以下是这个函数的反汇编分析

PAGE:0069F34D SetRegisterCallBack proc near           ; CODE XREF: CmCRegisterCallback+87↑p
PAGE:0069F34D
PAGE:0069F34D var_4           = dword ptr -4
PAGE:0069F34D arg_One         = byte ptr  8
PAGE:0069F34D
PAGE:0069F34D                 mov     edi, edi
PAGE:0069F34F                 push    ebp
PAGE:0069F350                 mov     ebp, esp
PAGE:0069F352                 push    ecx
PAGE:0069F353                 push    ecx
PAGE:0069F354                 and     [ebp+var_4], 0
PAGE:0069F358                 push    ebx
PAGE:0069F359                 push    esi
PAGE:0069F35A                 mov     esi, eax        ; 申请到的地址赋给esi
PAGE:0069F35C                 mov     eax, large fs:124h
PAGE:0069F362                 dec     word ptr [eax+84h]
PAGE:0069F369                 push    edi
PAGE:0069F36A                 mov     ecx, offset unk_56925C
PAGE:0069F36F                 mov     eax, ecx
PAGE:0069F371                 lock bts dword ptr [eax], 0
PAGE:0069F376                 jnb     short loc_69F37D
PAGE:0069F378                 call    ExfAcquirePushLockExclusive
PAGE:0069F37D
PAGE:0069F37D loc_69F37D:                             ; CODE XREF: SetRegisterCallBack+29↑j
PAGE:0069F37D                 add     dword ptr CurrentTime, 1
PAGE:0069F384                 mov     eax, dword ptr CurrentTime
PAGE:0069F389                 mov     ebx, offset ListBegin ; ListBegin就是这个链表的首地址
PAGE:0069F389                                         ; 这里将链表头地址赋给ebx
PAGE:0069F38E                 adc     dword ptr CurrentTime+4, 0
PAGE:0069F395                 mov     [esi+10h], eax  ; 前面说过esi偏移0x10的地方保存的是Cookie
PAGE:0069F395                                         ; 所以这里就是在初始化cookie
PAGE:0069F398                 mov     eax, dword ptr CurrentTime+4
PAGE:0069F39D                 mov     [esi+14h], eax
PAGE:0069F3A0                 mov     edi, ListBegin  ; 将ListBegin中保存的内容赋给edi
PAGE:0069F3A6                 cmp     edi, ebx        ; 判断edi的值是不是ListBegin的地址,如果是则跳转到增加链表的代码
PAGE:0069F3A6                                         ; 由此可以知道链表的最后一个结构体的指向的下一个成员是ListBegin
PAGE:0069F3A8                 jz      short loc_69F3DE

在这个ListBegin就是链表头的地址,里面保存的就是第一个链表的地址。首先会将它保存的内容取出判断保存的是否就是ListBegin的首地址,如果是的话就说明到了链表的尾部,接下来就会跳转到loc_69F3DE来把结构加进链表。如果不是的话,它会执行以下的循环,其中的一部分是

PAGE:0069F3AA loc_69F3AA:                             ; CODE XREF: SetRegisterCallBack+7D↓j
PAGE:0069F3AA                 lea     eax, [esi+20h]
PAGE:0069F3AD                 push    eax
PAGE:0069F3AE                 lea     eax, [edi+20h]
PAGE:0069F3B1                 push    eax
PAGE:0069F3B2                 call    RtlCompareAltitudes
PAGE:0069F3B7                 test    eax, eax
PAGE:0069F3B9                 jnz     short loc_69F3C4
PAGE:0069F3BB                 cmp     [ebp+arg_One], al
PAGE:0069F3BE                 jnz     short loc_69F3C6 
PAGE:0069F3C0                 jmp     short loc_69F3D5
PAGE:0069F3C2 ; ---------------------------------------------------------------------------
PAGE:0069F3C2                 jmp     short loc_69F3C6 
PAGE:0069F3C4 ; ---------------------------------------------------------------------------
PAGE:0069F3C4
PAGE:0069F3C4 loc_69F3C4:                             ; CODE XREF: SetRegisterCallBack+6C↑j
PAGE:0069F3C4                 jl      short loc_69F3CC
PAGE:0069F3C6
PAGE:0069F3C6 loc_69F3C6:                             ; CODE XREF: SetRegisterCallBack+71↑j
PAGE:0069F3C6                                         ; SetRegisterCallBack+75↑j
PAGE:0069F3C6                 mov     edi, [edi+LIST_ENTRY.Flink] ; 继续取下一个结构的地址
PAGE:0069F3C8                 cmp     edi, ebx        ; 判断是否到链表头了,不是的话跳上去继续找下一个
PAGE:0069F3C8                                         ; 这部分代码就是在将edi移到链表的尾部
PAGE:0069F3CA                 jnz     short loc_69F3AA

这个循环就是不断的取链表中的下一个数据直到链表尾。

找到以后,程序就会在loc_69F3DE处把数据加到链表里面

PAGE:0069F3DE loc_69F3DE:                             ; CODE XREF: SetRegisterCallBack+5B↑j
PAGE:0069F3DE                                         ; SetRegisterCallBack+81↑j ...
PAGE:0069F3DE                 mov     eax, [edi+LIST_ENTRY.Blink]
PAGE:0069F3E1                 mov     ecx, [eax+LIST_ENTRY.Flink]
PAGE:0069F3E3                 mov     [esi+LIST_ENTRY.Flink], ecx
PAGE:0069F3E5                 mov     [esi+LIST_ENTRY.Blink], eax
PAGE:0069F3E8                 mov     [ecx+LIST_ENTRY.Blink], esi
PAGE:0069F3EB                 xor     ecx, ecx
PAGE:0069F3ED                 mov     [eax+LIST_ENTRY.Flink], esi

四.反注册表监控

根据上面分析可以知道,注册表的监控回调函数被以链表的形式保存到了内存中,其中这个链表头是ListBegin。接下来只要找到这个链表头并且遍历这个链表,取出COOKIE就可以实现回调函数的删除。

而想要找到这个链表头,首先需要在CmRegisterCallback中使用0xE8找到CmcRegisterCallback

随后需要在CmCRegisterCallback中使用0x8BC6E8找到SetRegisterCallback

最终才在SetRegisterCallback中使用0xBB找到这个链表头

找到链表头,就可以遍历链表查看所有的注册表监控的数据。并且在偏移0x10的地方,保存了COOKIE的数据,就可以使用它来删除回调。具体代码如下

#include <ntifs.h>

VOID DriverUnload(IN PDRIVER_OBJECT driverObject);
PULONG GetRegisterList();

NTSTATUS DriverEntry(IN PDRIVER_OBJECT driverObject, IN PUNICODE_STRING registryPath)
{
	NTSTATUS status = STATUS_SUCCESS;
	PULONG pHead = NULL;
	PLIST_ENTRY pListEntry = NULL;
	PLARGE_INTEGER  pLiRegCookie = NULL;
	LARGE_INTEGER test;
	PULONG pFuncAddr = NULL;

	DbgPrint("驱动加载完成\r\n");
	pHead = GetRegisterList();
	if (pHead != NULL)
	{
		pListEntry = (PLIST_ENTRY)*pHead;
		
		while ((ULONG)pListEntry != (ULONG)pHead)
		{
			if (!MmIsAddressValid(pListEntry)) break;
			
			pLiRegCookie = (PLARGE_INTEGER)((ULONG)pListEntry + 0x10);
			pFuncAddr = (PULONG)((ULONG)pListEntry + 0x1C);
			//判断地址是否有效
			if (MmIsAddressValid(pFuncAddr) &&  MmIsAddressValid(pLiRegCookie))
			{
				status = CmUnRegisterCallback(*pLiRegCookie);
				if (NT_SUCCESS(status))
				{
					DbgPrint("删除注册表回调成功,函数地址为0x%X\r\n", *pFuncAddr);
				}
			}
			pListEntry = pListEntry->Flink;
		}
	}

exit:
	driverObject->DriverUnload = DriverUnload;
	return STATUS_SUCCESS;
}

PULONG GetRegisterList()
{
	PULONG pListEntry = NULL;
	PUCHAR pCmRegFunc = NULL, pCmcRegFunc = NULL, pSetRegFunc = NULL;
	UNICODE_STRING uStrFuncName = RTL_CONSTANT_STRING(L"CmRegisterCallback");

	pCmRegFunc = (PUCHAR)MmGetSystemRoutineAddress(&uStrFuncName);

	if (pCmRegFunc == NULL)
	{
		DbgPrint("MmGetSystemRoutineAddress Error\r\n");
		goto exit;
	}
	
	while (*pCmRegFunc != 0xC2)
	{
		if (*pCmRegFunc == 0xE8)
		{
			pCmcRegFunc = (PUCHAR)((ULONG)pCmRegFunc + 5 + *(PULONG)(pCmRegFunc + 1));
			break;
		}
		pCmRegFunc++;
	}
	
	if (pCmcRegFunc == NULL)
	{
		DbgPrint("GetCmcRegFunc Error\r\n");
		goto exit;
	}

	while (*pCmcRegFunc != 0xC2)
	{
		if (*pCmcRegFunc == 0x8B && *(pCmcRegFunc +1) == 0xC6 && *(pCmcRegFunc + 2) == 0xE8)
		{
			pSetRegFunc = (PUCHAR)((ULONG)pCmcRegFunc + 2 + 5 + *(PULONG)(pCmcRegFunc + 3));
			break;
		}
		pCmcRegFunc++;
	}

	if (pSetRegFunc == NULL)
	{
		DbgPrint("GetSetRegFunc Error\r\n");
		goto exit;
	}

	while (*pSetRegFunc != 0xC2)
	{
		if (*pSetRegFunc == 0xBB)
		{
			pListEntry = (PULONG)*(PULONG)(pSetRegFunc + 1);
			break;
		}
		pSetRegFunc++;
	}
exit:
	return pListEntry;
}

VOID DriverUnload(IN PDRIVER_OBJECT driverObject)
{
	DbgPrint("驱动卸载完成\r\n");
}

五.实验结果

当没有开启注册表监控的时候,可以顺利的对注册表进行修改

而开启监控以后,对注册表的修改就会被拦截下来


而当删除监控以后,又可以成功对注册表进行修改


[CTF入门培训]顶尖高校博士及硕士团队亲授《30小时教你玩转CTF》,视频+靶场+题目!助力进入CTF世界

最后于 2021-11-30 09:59 被1900编辑 ,原因:
收藏
点赞8
打赏
分享
最新回复 (5)
雪    币: 504
活跃值: (3061)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
wem 2021-12-5 10:25
2
0
谢谢
雪    币: 15881
活跃值: (1080)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
killbr 2021-12-6 00:05
3
0
@1900  希望加入对目标程序写注册表的call地址拦截功能,并得到虚拟地址
雪    币: 22393
活跃值: (25272)
能力值: ( LV15,RANK:910 )
在线值:
发帖
回帖
粉丝
1900 6 2021-12-6 09:28
4
0
killbr @1900 希望加入对目标程序写注册表的call地址拦截功能,并得到虚拟地址
那需要去HOOK函数了  不能用这种方法
雪    币: 3789
活跃值: (5078)
能力值: ( LV8,RANK:127 )
在线值:
发帖
回帖
粉丝
Ally Switch 1 2022-10-19 11:51
5
0
PREG_DELETE_VALUE_KEY_INFORMATION 结构体贴错了
雪    币: 243
活跃值: (3256)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
wx_好一脚头球 2023-3-21 20:49
6
0
E1696        无法打开 源 文件 "ntifs.h",这个报错怎么解决
游客
登录 | 注册 方可回帖
返回