通过CmRegisterCallback学习注册表监控与反注册表监控
2021-11-16 19:00:32 Author: mp.weixin.qq.com(查看原文) 阅读量:30 收藏


本文为看雪论坛优秀文章
看雪论坛作者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+7DjPAGE: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+6CjPAGE: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+5BjPAGE: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
官方微博:看雪安全
商务合作:[email protected]

球分享

球点赞

球在看

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


文章来源: http://mp.weixin.qq.com/s?__biz=MjM5NTc2MDYxMw==&mid=2458405024&idx=1&sn=a8617e12ff4d8e39d4fe89dae2cdf577&chksm=b18f782a86f8f13c05b91d743d6a09035c01d8a24b8d1c012cc68ccb2ff72d8bb45151b4a7fb#rd
如有侵权请联系:admin#unsafe.sh