查看原文
其他

通过CmRegisterCallback学习注册表监控与反注册表监控

1900 看雪学苑 2022-07-01


本文为看雪论坛优秀文章
看雪论坛作者ID:1900


1


简介


实验环境是Win7 X86系统。

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


2


注册表监控


在Windows系统中,可以通过使用CmRegisterCallback来设置注册表监控的回调函数,该函数在文档中的定义如下:
NTSTATUS CmRegisterCallback( IN PEX_CALLBACK_FUNCTION Function, IN PVOID Context, OUT PLARGE_INTEGER Cookie );


PEX_CALLBACK_FUNCTION回调函数在文档中的定义如下,该函数的返回值为STATUS_SUCCESS之外的错误码的时候,则表示系统会拒绝操作相应的注册表。
NTSTATUS RegistryCallback( __in PVOID CallbackContext, __in_opt PVOID Argument1, __in_opt PVOID Argument2 );


其中的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的结构体的内容如下:


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;

关键的两个成员:


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



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;


根据上面的内容可以知道,回调函数中是可以获取要操作的注册表键的对象的,所以可以使用ObQueryNameString函数来获得键操作的键的名称,该函数在文档中的定义如下:
NTSTATUS ObQueryNameString( IN PVOID Object, OUT POBJECT_NAME_INFORMATION ObjectNameInfo, IN ULONG Length, OUT PULONG 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); //获取注册表的完整路径//注册表回调使用的CookieLARGE_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");}


3


逆向分析CmRegisterCallback


接下来通过IDA分析CmRegisterCallback来看看是如何保存注册表的回调函数。IDA中对CmRegisterCallback的反汇编如下:
PAGE:0069E3EE ; NTSTATUS __stdcall CmRegisterCallback(PEX_CALLBACK_FUNCTION Function, PVOID Context, PLARGE_INTEGER Cookie)PAGE:0069E3EE public CmRegisterCallbackPAGE:0069E3EE CmRegisterCallback proc nearPAGE:0069E3EEPAGE:0069E3EE Function = dword ptr 8PAGE:0069E3EE Context = dword ptr 0ChPAGE:0069E3EE Cookie = dword ptr 10hPAGE:0069E3EEPAGE:0069E3EE mov edi, ediPAGE:0069E3F0 push ebpPAGE:0069E3F1 mov ebp, espPAGE:0069E3F3 push [ebp+Cookie]PAGE:0069E3F6 mov eax, offset Init_Data ; 将Init_Data的地址赋给eaxPAGE:0069E3FB push 1PAGE:0069E3FD push [ebp+Context]PAGE:0069E400 push [ebp+Function]PAGE:0069E403 call CmCRegisterCallbackPAGE:0069E408 pop ebpPAGE:0069E409 retn 0ChPAGE:0069E409 CmRegisterCallback endp

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

继续看CmCRegisterCallback的反汇编代码:
PAGE:0069E417 CmCRegisterCallback proc near ; CODE XREF: CmRegisterCallbackEx+2C↑pPAGE:0069E417 ; CmRegisterCallback+15↑pPAGE:0069E417PAGE:0069E417 RegFunc = dword ptr 8PAGE:0069E417 Context = dword ptr 0ChPAGE:0069E417 arg_one = dword ptr 10hPAGE:0069E417 arg_Cookie = dword ptr 14hPAGE:0069E417PAGE:0069E417 mov edi, ediPAGE:0069E419 push ebpPAGE:0069E41A mov ebp, espPAGE:0069E41C push ecxPAGE:0069E41D push ebxPAGE:0069E41E mov ebx, [ebp+arg_Cookie] ; 注意这里将Cookie的地址赋给ebxPAGE:0069E421 push esiPAGE:0069E422 push ediPAGE:0069E423 push 'bcMC' ; TagPAGE:0069E428 push 30h ; NumberOfBytesPAGE:0069E42A push PagedPool ; PoolTypePAGE:0069E42C mov edi, eax ; 将结构体赋给ediPAGE:0069E42E call ExAllocatePoolWithTagPAGE:0069E433 mov esi, eax ; 申请到得内存地址赋给esiPAGE:0069E435 test esi, esiPAGE:0069E437 jnz short loc_69E443 ; 将前向指针和后向指针指向自己PAGE:0069E439 mov eax, STATUS_INSUFFICIENT_RESOURCESPAGE:0069E43E jmp loc_69E4CDPAGE:0069E443 ; ---------------------------------------------------------------------------PAGE:0069E443PAGE:0069E443 loc_69E443: ; CODE XREF: CmCRegisterCallback+20↑jPAGE:0069E443 mov [esi+LIST_ENTRY.Blink], esi ; 将前向指针和后向指针指向自己PAGE:0069E446 mov [esi+LIST_ENTRY.Flink], esi ; 这里可以知道申请到的地址前8位是个LIST_ENTRYPAGE:0069E448 lea eax, [esi+28h]PAGE:0069E44B mov [eax+4], eaxPAGE:0069E44E mov [eax], eaxPAGE:0069E450 mov eax, [ebp+Context]PAGE:0069E453 and dword ptr [esi+8], 0PAGE:0069E457 mov [esi+18h], eax ; 申请得内存偏移0x18得地方保存ContextPAGE:0069E45A mov eax, [ebp+RegFunc]PAGE:0069E45D mov [esi+1Ch], eax ; 申请到的地址偏移0x1C的地方保存回调函数PAGE:0069E460 movzx eax, word ptr [edi]PAGE:0069E463 mov [esi+22h], axPAGE:0069E467 mov [esi+20h], axPAGE:0069E46B movzx eax, word ptr [edi]PAGE:0069E46E push 'acMC' ; TagPAGE:0069E473 push eax ; NumberOfBytesPAGE:0069E474 push PagedPool ; PoolTypePAGE:0069E476 call ExAllocatePoolWithTagPAGE:0069E47B mov [esi+24h], eaxPAGE:0069E47E test eax, eaxPAGE:0069E480 jnz short loc_69E489PAGE:0069E482 mov edi, STATUS_INSUFFICIENT_RESOURCESPAGE:0069E487 jmp short loc_69E4B4PAGE:0069E489 ; ---------------------------------------------------------------------------PAGE:0069E489PAGE:0069E489 loc_69E489: ; CODE XREF: CmCRegisterCallback+69↑jPAGE:0069E489 movzx ecx, word ptr [edi]PAGE:0069E48C push ecxPAGE:0069E48D push dword ptr [edi+4]PAGE:0069E490 push eaxPAGE:0069E491 call memcpyPAGE:0069E496 add esp, 0ChPAGE:0069E499 push [ebp+arg_one]PAGE:0069E49C mov eax, esi ; 申请到的地址赋给eaxPAGE:0069E49E call SetRegisterCallBackPAGE:0069E4A3 mov edi, eaxPAGE:0069E4A5 mov eax, [esi+10h] ; 将申请到的内存偏移0x14的内容放入eaxPAGE:0069E4A8 mov [ebx], eax ; 此时ebx是Cookie的地址,赋值前4位PAGE:0069E4AA mov eax, [esi+14h] ; 这两句就是赋值后4位,因为一个COOKIE占8位PAGE:0069E4AD mov [ebx+4], eax ; 根据这四句就可以知道,esi偏移0x10的地址存放的就是CookiePAGE:0069E4B0 test edi, ediPAGE:0069E4B2 jge short loc_69E4CBPAGE:0069E4B4PAGE:0069E4B4 loc_69E4B4: ; CODE XREF: CmCRegisterCallback+70↑jPAGE:0069E4B4 mov eax, [esi+24h]PAGE:0069E4B7 test eax, eaxPAGE:0069E4B9 jz short loc_69E4C3PAGE:0069E4BB push 0 ; TagPAGE:0069E4BD push eax ; PPAGE:0069E4BE call ExFreePoolWithTagPAGE:0069E4C3PAGE:0069E4C3 loc_69E4C3: ; CODE XREF: CmCRegisterCallback+A2↑jPAGE:0069E4C3 push 0 ; TagPAGE:0069E4C5 push esi ; PPAGE:0069E4C6 call ExFreePoolWithTagPAGE:0069E4CBPAGE:0069E4CB loc_69E4CB: ; CODE XREF: CmCRegisterCallback+9B↑jPAGE:0069E4CB mov eax, ediPAGE:0069E4CDPAGE:0069E4CD loc_69E4CD: ; CODE XREF: CmCRegisterCallback+27↑jPAGE:0069E4CD pop ediPAGE:0069E4CE pop esiPAGE:0069E4CF pop ebxPAGE:0069E4D0 pop ecxPAGE:0069E4D1 pop ebpPAGE:0069E4D2 retn 10hPAGE:0069E4D2 CmCRegisterCallback endp

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

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

  • 偏移0x10保存的是COOKIE

  • 偏移0x1C保存的Context

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


所以可以想到注册表监控的回调函数是用LIST_ENTRY双向链表来一个个连起来的。而真正将分配的这块内存加入链表的函数则是SetRegisterCallback函数,以下是这个函数的反汇编分析:
PAGE:0069F34D SetRegisterCallBack proc near ; CODE XREF: CmCRegisterCallback+87↑pPAGE:0069F34DPAGE:0069F34D var_4 = dword ptr -4PAGE:0069F34D arg_One = byte ptr 8PAGE:0069F34DPAGE:0069F34D mov edi, ediPAGE:0069F34F push ebpPAGE:0069F350 mov ebp, espPAGE:0069F352 push ecxPAGE:0069F353 push ecxPAGE:0069F354 and [ebp+var_4], 0PAGE:0069F358 push ebxPAGE:0069F359 push esiPAGE:0069F35A mov esi, eax ; 申请到的地址赋给esiPAGE:0069F35C mov eax, large fs:124hPAGE:0069F362 dec word ptr [eax+84h]PAGE:0069F369 push ediPAGE:0069F36A mov ecx, offset unk_56925CPAGE:0069F36F mov eax, ecxPAGE:0069F371 lock bts dword ptr [eax], 0PAGE:0069F376 jnb short loc_69F37DPAGE:0069F378 call ExfAcquirePushLockExclusivePAGE:0069F37DPAGE:0069F37D loc_69F37D: ; CODE XREF: SetRegisterCallBack+29↑jPAGE:0069F37D add dword ptr CurrentTime, 1PAGE:0069F384 mov eax, dword ptr CurrentTimePAGE:0069F389 mov ebx, offset ListBegin ; ListBegin就是这个链表的首地址PAGE:0069F389 ; 这里将链表头地址赋给ebxPAGE:0069F38E adc dword ptr CurrentTime+4, 0PAGE:0069F395 mov [esi+10h], eax ; 前面说过esi偏移0x10的地方保存的是CookiePAGE:0069F395 ; 所以这里就是在初始化cookiePAGE:0069F398 mov eax, dword ptr CurrentTime+4PAGE:0069F39D mov [esi+14h], eaxPAGE:0069F3A0 mov edi, ListBegin ; 将ListBegin中保存的内容赋给ediPAGE:0069F3A6 cmp edi, ebx ; 判断edi的值是不是ListBegin的地址,如果是则跳转到增加链表的代码PAGE:0069F3A6 ; 由此可以知道链表的最后一个结构体的指向的下一个成员是ListBeginPAGE:0069F3A8 jz short loc_69F3DE

在这个ListBegin就是链表头的地址,里面保存的就是第一个链表的地址。首先会将它保存的内容取出判断保存的是否就是ListBegin的首地址。

如果是的话就说明到了链表的尾部,接下来就会跳转到loc_69F3DE来把结构加进链表。如果不是的话,它会执行以下的循环,其中的一部分是:
PAGE:0069F3AA loc_69F3AA: ; CODE XREF: SetRegisterCallBack+7D↓jPAGE:0069F3AA lea eax, [esi+20h]PAGE:0069F3AD push eaxPAGE:0069F3AE lea eax, [edi+20h]PAGE:0069F3B1 push eaxPAGE:0069F3B2 call RtlCompareAltitudesPAGE:0069F3B7 test eax, eaxPAGE:0069F3B9 jnz short loc_69F3C4PAGE:0069F3BB cmp [ebp+arg_One], alPAGE:0069F3BE jnz short loc_69F3C6 PAGE:0069F3C0 jmp short loc_69F3D5PAGE:0069F3C2 ; ---------------------------------------------------------------------------PAGE:0069F3C2 jmp short loc_69F3C6 PAGE:0069F3C4 ; ---------------------------------------------------------------------------PAGE:0069F3C4PAGE:0069F3C4 loc_69F3C4: ; CODE XREF: SetRegisterCallBack+6C↑jPAGE:0069F3C4 jl short loc_69F3CCPAGE:0069F3C6PAGE:0069F3C6 loc_69F3C6: ; CODE XREF: SetRegisterCallBack+71↑jPAGE:0069F3C6 ; SetRegisterCallBack+75↑jPAGE: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↑jPAGE: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], ecxPAGE:0069F3E5 mov [esi+LIST_ENTRY.Blink], eaxPAGE:0069F3E8 mov [ecx+LIST_ENTRY.Blink], esiPAGE:0069F3EB xor ecx, ecxPAGE:0069F3ED mov [eax+LIST_ENTRY.Flink], esi


4


反注册表监控


根据上面分析可以知道,注册表的监控回调函数被以链表的形式保存到了内存中,其中这个链表头是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");}


5


实验结果


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


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


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





 


看雪ID:1900

https://bbs.pediy.com/user-home-835440.htm

*本文由看雪论坛 1900 原创,转载请注明来自看雪社区




# 往期推荐

1.Android APP漏洞之战——权限安全和安全配置漏洞详解

2.详细分析CVE-2021-40444远程命令执行漏洞

3.通过对PsSetCreateProcessNotifyRoutineEx的逆向分析得出的结果来实现反进程监控

4.分享一个基本不可能被检测到的hook方案

5.由2021ByteCTF引出的intent重定向浅析

6.全网最详细CVE-2014-0502 Adobe Flash Player双重释放漏洞分析



公众号ID:ikanxue
官方微博:看雪安全
商务合作:wsc@kanxue.com



球分享

球点赞

球在看



点击“阅读原文”,了解更多!

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存