一.前言
实验环境是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的值 |
Argument1 | REG_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大小的内存来保存相应的内容,根据以上的分析可以得出下面的结论
所以可以想到注册表监控的回调函数是用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编辑
,原因: