Bypass PPL | 绕过 Userland 的 LSA 保护


免责声明



本文仅用于技术讨论与学习,利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,文章作者不为此承担任何责任。

只供对已授权的目标使用测试,对未授权目标的测试作者不承担责任,均由使用本人自行承担。


Bypass PPL | 绕过 Userland 的 LSA 保护

文章正文




James Forshaw 曾发表了一篇文章,他在文章中简要提到了一个可以用来以管理员身份将任意代码注入 PPL 的技巧。然而,我觉得这篇文章没有得到应有的关注,因为它从字面上描述了绕过 PPL(包括 LSA 保护)的潜在 Userland 漏洞利用。

介绍

当我偶然发现以下博客文章时,我正在对受保护的进程进行一些研究:Windows Exploitation Tricks: Exploiting Arbitrary Object Directory Creation for Local Elevation of Privilege。这篇文章由 James Forshaw 于 2018 年 8 月在 Project Zero 的博客上撰写。正如标题所暗示的那样,目的是讨论特定的特权升级技巧,而不是 PPL 绕过。然而,下面这句话立刻引起了我的注意:

滥用 DefineDosDevice API 实际上还有第二个用途,它是 Protected Process Light (PPL) bypass 的管理员。

据我所知,迄今为止发布的所有绕过 PPL 的公共工具都涉及使用驱动程序以在内核中执行任意代码(我在上一篇文章中提到的 pypykatz 除外)。不过,在他的博客文章中,James Forshaw 不经意地给了我们一个 Userland 绕过技巧,而且渗透测试社区似乎没有注意到它。

这篇文章的目的是更详细地讨论这项技术。我将首先回顾 PPL 过程背后的一些关键概念,我还将解释 PP(受保护过程)和 PPL(受保护过程光)之间的主要区别之一。然后,我们将看到管理员如何利用这种细微差别。最后,我将介绍我开发的工具来利用此漏洞并在不使用任何内核代码的情况下转储任何 PPL 的内存。

背景

我已经在我的个人博客上列出了 PP(L) 背后的所有核心原则:您真的了解 LSA 保护 (RunAsPPL) 吗?. 所以,我建议先阅读这篇文章,但这里有一个 TL;DR。

PP(L) 概念

当 PP 模型首次随 Windows Vista 引入时,进程要么受保护,要么不受保护。然后,从 Windows 8.1 开始,PPL 模型扩展了这一概念并引入了保护级别。直接后果是某些 PP(L) 现在可以比其他的受到更多保护。最基本的规则是,不受保护的进程只能使用一组非常受限的访问标志(例如PROCESS_QUERY_LIMITED_INFORMATION. 如果他们请求更高级别的访问权限,系统将返回Access is Denied错误。

对于 PP(L)s,它有点复杂。他们可以请求的访问级别取决于他们自己的保护级别。此保护级别部分由文件数字证书中的特殊 EKU 字段决定。当一个受保护的进程被创建时,保护信息被存储在EPROCESS内核结构中的一个特殊值中。该值存储保护级别(PP 或 PPL)和签名者类型(例如:Antimalware、Lsa、WinTcb 等)。签名者类型在 PP(L) 之间建立了一种层次结构。以下是适用于 PP(L) 的基本规则:

  • • 如果 PP 的签名者类型大于或等于,则 PP 可以打开具有完全访问权限的 PP 或 PPL。

  • • 如果 PPL 的签名者类型大于或等于,则 PPL 可以打开具有完全访问权限的 PPL。

  • • PPL 无法打开具有完全访问权限的 PP,无论其签名者类型如何。

例如,当启用 LSA 保护时,lsass.exe作为 PPL 执行,您将使用Process Explorer观察到以下保护级别:PsProtectedSignerLsa-Light。如果你想访问它的内存,你需要调用OpenProcess并指定PROCESS_VM_READ访问标志。如果调用进程不受保护,则此调用将立即失败并显示Access is Denied错误,而不管用户的权限如何。但是,如果调用进程是具有更高级别的 PPL (WinTcb例如),相同的调用会成功(只要用户显然具有适当的权限)。正如你所理解的,如果我们能够创建这样一个进程并在其中执行任意代码,那么即使启用了 LSA 保护,我们也将能够访问 LSASS。问题是:我们能否在不使用任何内核代码的情况下实现这一目标?

PP 与 PPL

例如,PP(L) 模型有效地防止未受保护的进程访问具有扩展访问权限OpenProcess的受保护进程。这会阻止简单的内存访问,但是我没有提到这种保护的另一个方面。它还可以防止未签名的 DLL 被这些进程加载。这是有道理的,否则整个安全模型将毫无意义,因为您可以使用任何形式的 DLL 劫持并将任意代码注入您自己的 PPL 进程。这也解释了为什么在启用 LSA Protection 时要特别注意第三方认证模块。

不过这一规则有一个例外!这可能就是 PP 和 PPL 最大的区别所在。如果您了解 Windows 上的 DLL 搜索顺序,您就会知道,当一个进程被创建时,它首先会遍历“已知 DLL”列表,然后继续搜索应用程序的目录、系统目录等等……在此搜索顺序,“已知 DLL”步骤是一个特殊步骤,通常不考虑 DLL 劫持攻击,因为用户无法控制它。不过,在我们的案例中,这一步恰恰是 PPL 流程的“致命弱点”。

“已知 DLL”是 Windows 应用程序最常加载的 DLL。因此,为了提高整体性能,它们被预加载到内存中(即被缓存)。如果您想查看“已知 DLL”的完整列表,您可以使用WinObj并查看KnownDlls对象管理器中目录的内容。

Bypass PPL | 绕过 Userland 的 LSA 保护
WinObj - 已知的 DLL

由于这些 DLL 已经在内存中,如果您使用Process Monitor检查典型 Windows 应用程序的文件操作,您应该看不到它们。但是,当涉及到受保护的进程时,情况有点不同。我将SgrmBroker.exe在这里举个例子。

Bypass PPL | 绕过 Userland 的 LSA 保护

受保护进程加载的已知 DLL

正如我们在Process Explorer中看到的那样,SgrmBroker.exe它是作为受保护的进程 (PP) 启动的。当进程启动时,最先加载的 DLL 是kernel32.dllKernelBase.dll,它们都是…… “已知 DLL”。是的,在 PP 的情况下,即使是“已知的 DLL”也是从磁盘加载的,这意味着每个文件的数字签名总是经过验证的。但是,如果您使用 PPL 进行相同的测试,您将不会在Process Monitor中看到这些 DLL,因为在这种情况下它们的行为与正常进程一样。

这个事实特别有趣,因为 DLL 的数字签名仅在文件映射时验证,即在创建节时验证。这意味着,如果您能够向KnownDlls目录添加任意条目,则您可以注入任意 DLL 并在 PPL 中执行未签名的代码。

添加一个条目KnownDlls说起来容易做起来难,因为微软已经考虑过这种攻击媒介。正如 James Forshaw 在他的博客文章中所解释的那样,KnownDlls对象目录标有特殊的进程信任标签,如下面的屏幕截图所示。

Bypass PPL | 绕过 Userland 的 LSA 保护
KnownDlls 目录进程信任标签


正如您可能想象的那样,根据标签的名称,只有级别高于或等于WinTcb(实际上是 PPL 的最高级别)的受保护进程才能请求对此目录的写访问权限。但一切都没有丢失,因为这正是 JF 发现的巧妙技巧发挥作用的地方。

MS-DOS 设备名称

正如介绍中提到的,James Forshaw 发现的技术依赖于 API 函数的使用DefineDosDevice,并且涉及一些不易掌握的 Windows 内部机制。因此,在处理方法本身之前,我将首先在这里回顾其中的一些概念。

定义 DosDevice?

这是DefineDosDevice函数的原型:

BOOL DefineDosDeviceW(
  DWORD   dwFlags,
  LPCWSTR lpDeviceName,
  LPCWSTR lpTargetPath
);

顾名思义,DefineDosDeviceAPI 的目的是按字面定义 MS-DOS 设备名称。MS-DOS 设备名称是对象管理器中的符号链接,其名称格式为DosDevicesDEVICE_NAME(例如:),如文档DosDevicesC:中所述。因此,此函数允许您将实际的“设备”映射到“DOS 设备”。例如,当您插入外部驱动器或 USB 密钥时,就会发生这种情况。设备会自动分配一个驱动器号,例如. 您可以通过调用获得相应的映射。E:``QueryDosDevice

WCHAR path[MAX_PATH + 1];

if (QueryDosDevice(argv[1], path, MAX_PATH)) {
    wprintf(L"%ws -> %wsn", argv[1], path);
}

Bypass PPL | 绕过 Userland 的 LSA 保护
查询 MS-DOS 设备的映射


在上面的示例中,目标设备是DeviceHarddiskVolume5MS-DOS 设备名称是E:. 但是等一下,我说过 MS-DOS 设备名称的格式是DosDevicesDEVICE_NAME. 所以,这不能只是一个驱动器号。不用担心,有一个解释。对于DefineDosDeviceQueryDosDevice,该DosDevices部分是隐式的。这些函数会自动在“设备名称”前加上??. 因此,如果您提供E:设备名称,它们将在内部使用 NT 路径??E:。即便如此,你会告诉我那??仍然不是DosDevices,这将是一个有效的观点。WinObj 将再次帮助我们解开这个“谜团”。在对象管理器的根目录下,我们可以看到它DosDevices只是一个符号链接,指向??. 结果,DosDevicesE:->??E:,因此我们可以将它们视为同一事物。这个符号链接实际上是出于遗留原因而存在的,因为在旧版本的 Windows 中,只有一个 DOS 设备目录。

Bypass PPL | 绕过 Userland 的 LSA 保护
WinObj - DosDevices 符号链接


本地 DOS 设备目录

路径前缀??本身具有非常特殊的含义。它表示用户的本地 DOS 设备目录,因此根据当前用户的上下文引用对象管理器中的不同位置。具体??是指全路径SessionsDosDevices0000000-XXXXXXXX,其中XXXXXXXX是用户的登录认证 ID。但是有一个例外,因为NT AUTHORITYSYSTEM,??指的是GLOBAL??。这个概念非常重要,所以我将举两个例子来说明它。第一个是我之前使用的 USB 密钥,第二个是我通过资源管理器手动安装的 SMB 共享。

对于 USB 密钥,我们已经看到它??E:是指向DeviceHarddiskVolume5. 由于它是由 装载的SYSTEM,因此该链接应该存在于 中GLOBAL??。让我们用 WinObj 来验证一下。

Bypass PPL | 绕过 Userland 的 LSA 保护
WinObj - GLOBAL??E: 符号链接

一切都好!现在,让我们将“SMB 共享”映射到驱动器盘符,看看会发生什么。

Bypass PPL | 绕过 Userland 的 LSA 保护
映射网络驱动器

这次以登录用户的身份挂载了驱动器,所以??应该参考SessionsDosDevices0000000-XXXXXXXX,但是 的值是XXXXXXXX多少呢?为了找到它,我将使用Process Hacker并检查我explorer.exe进程的主要访问令牌的高级属性。

Bypass PPL | 绕过 Userland 的 LSA 保护
Process Hacker - Explorer 的令牌高级属性

身份验证 ID0x1abce所以应该在内部创建符号链接SessionsDosDevices0000000-0001abce。再一次,让我们用WinObj验证一下。

Bypass PPL | 绕过 Userland 的 LSA 保护
WinObj - SMB 共享符号链接

在那里!符号链接确实是在此目录中创建的。

为什么要定义 DosDevice?

正如我们在前一部分中看到的,设备映射操作包括在调用者的 DOS 设备目录中创建一个简单的符号链接。任何用户都可以这样做,因为它只影响他们的会话。但是有一个问题,因为低权限用户只能创建“临时”内核对象,一旦所有句柄都关闭,这些对象就会被删除。要解决这个问题,必须将对象标记为“永久”,但这需要SeCreatePermanentPrivilege他们没有的特定权限 ( )。因此,此操作必须由具有此功能的特权服务执行。

Bypass PPL | 绕过 Userland 的 LSA 保护
符号链接标记为“永久”


正如 JF 在他的博客文章中所述,DefineDosDevice它只是 RPC 方法调用的包装器。该方法由 CSRSS 服务对外暴露,在BaseSrvDefineDosDevice内部实现BASESRV.DLL。此服务的特别之处在于它作为具有保护级别的 PPL 运行WinTcb

Bypass PPL | 绕过 Userland 的 LSA 保护

作为 PPL (WinTcb) 运行的 CSRSS 服务


虽然这是我们利用的要求,但这并不是关于DefineDosDevice. 更有趣的是, 的值lpDeviceName没有经过过滤。这意味着您不必提供驱动器号,例如E:. 我们将看到我们如何利用它来欺骗 CSRSS 服务在任意位置创建任意符号链接,例如KnownDlls.

利用 DefineDosDevice

在这一部分中,我们将深入研究该DefineDosDevice功能。我们将看到它里面有什么样的弱点,以及我们如何利用它来实现我们的目标。

DefineDosDevice 的内部工作原理

在他的文章中,JF 完成了所有繁重的工作,他将BaseSrvDefineDosDevice函数逆向并为我们提供了相应的伪代码。你可以在这里查看。如果你这样做,你应该注意到在第 4 步有一个小错误,它应该是CsrImpersonateClient(),而不是CsrRevertToSelf()。无论如何,我不会复制粘贴他的代码,而是尝试使用图表来提供高级概述。

Bypass PPL | 绕过 Userland 的 LSA 保护

在这个流程图中,我用不同的颜色突出显示了一些元素。模拟函数是橙色的,符号链接创建步骤是蓝色的。最后,我用红色突出显示了我们需要采取的关键路径。

首先,我们可以看到 CSRSS 服务试图??DEVICE_NAME在模拟调用者(即 RPC 客户端)时打开。主要目标是先删除已存在的符号链接。但除此之外,该服务还将检查符号链接是否为“全局”。为此,此处未表示的内部函数仅检查对象的“真实”路径是否以GLOBAL??. 如果是这样,模拟将在其余的执行过程中被禁用,服务将不会在NtCreateSymbolicLinkObject()调用之前模拟客户端,这意味着符号链接将由 CSRSS 服务本身创建。最后,如果此操作成功,服务会将对象标记为“永久”正如我之前提到的。

漏洞?

此时您可能已经意识到存在一种 TOCTOU(Time-of-Check Time-of-Use)漏洞。用于打开符号链接的路径和用于创建它的路径相同:??DEVICE_NAME. 然而,“打开”操作总是在模拟用户时完成,而“创建”操作可能直接完成,就SYSTEM好像模拟被禁用一样。而且,如果您还记得我之前解释过的内容,您就会知道它??代表用户的本地 dos 设备目录,因此会根据用户的身份解析为不同的路径。所以,虽然在这两种情况下使用了相同的路径,但在现实中它很可能指的是完全不同的位置!

为了利用这种行为,我们必须解决以下挑战:我们需要找到一个“设备名称”,该名称解析为我们在服务模拟客户端时控制的“全局对象”。KnownDllsFOO.dll当模拟被禁用时,这个相同的“设备名称”必须解析为。这听起来有点棘手,但我们将逐步完成。

让我们先从最简单的部分开始。我们需要为DEVICE_NAMEin 确定一个值,??DEVICE_NAME以便此路径解析为KnownDllsFOO.dll调用方为 SYSTEM 时的值。我们也知道在这种情况下??解决。GLOBAL??

如果你查看GLOBAL??目录的内容,你会发现里面有一个非常方便的对象。

Bypass PPL | 绕过 Userland 的 LSA 保护

在此目录中,GLOBALROOT对象是指向空路径的符号链接。这意味着路径如??GLOBALROOT将转换为 just ,它是对象管理器的根(因此名称为“全局根”)。如果我们将此原则应用于我们的“设备名称”,我们知道这??GLOBALROOTKnownDllsFOO.DLL将解析为KnownDllsFOO.dll调用者何时SYSTEM。这是解决问题的一部分!

现在,我们知道我们应该GLOBALROOTKnownDllsFOO.DLL为函数调用提供“设备名称” DefineDosDevice(请记住,??它将自动添加到该值之前)。如果我们希望 CSRSS 服务禁用模拟,我们还知道符号链接对象必须被视为“全局”对象,因此它的路径必须以GLOBAL??. 所以,问题是:如何将一条路径转换??GLOBALROOTKnownDllsFOO.DLLGLOBAL??KnownDllsFOO.dll?解决方案实际上非常简单,因为这几乎就是符号链接的定义!当该服务模拟用户时,我们知道它??指的是该特定用户的本地 DOS 设备目录,因此您所要做的就是创建一个指向 的符号链接,??GLOBALROOTGLOBAL??此而已。

总而言之,当路径由以下用户打开时SYSTEM

??GLOBALROOTKnownDllsFOO.dll
-> SessionsDosDevices0000000-XXXXXXXXGLOBALROOTKnownDllsFOO.dll

SessionsDosDevices0000000-XXXXXXXXGLOBALROOTKnownDllsFOO.dll
-> GLOBAL??KnownDllsFOO.dll

另一方面,如果通过以下方式打开相同的路径SYSTEM

??GLOBALROOTKnownDllsFOO.dll
-> GLOBAL??GLOBALROOTKnownDllsFOO.dll

GLOBAL??GLOBALROOTKnownDllsFOO.dll
-> KnownDllsFOO.dll

最后一件事需要注意。在检查对象是否“全局”之前,它必须首先存在,否则最初的“打开”操作只会失败。因此,我们需要在调用之前确保它GLOBAL??KnownDllsFOO.dll是一个现有的符号链接对象DefineDosDevice

Bypass PPL | 绕过 Userland 的 LSA 保护

这里有一个小问题。管理员不能在GLOBAL??. 这不是真正的问题;这只是为我们的利用增加了一个额外的步骤,因为我们将不得不暂时提升到SYSTEM第一。作为SYSTEM,我们将能够首先在其中创建一个假KnownDlls目录GLOBAL??,然后在其中创建一个虚拟符号链接对象,其中包含我们要劫持的 DLL 的名称。

全面利用

有很多信息需要消化,所以在我们讨论最后的注意事项之前,这里是对利用步骤的简短回顾。在此列表中,我们假设我们正在以管理员身份执行漏洞利用。

  1. 1. 提升为SYSTEM,否则我们将无法在里面创建对象GLOBAL??

  2. 2. 创建对象目录GLOBAL??KnownDlls 以模拟实际KnownDlls目录。

  3. 3. 创建符号链接GLOBAL??KnownDllsFOO.dll,其中FOO.dll是我们要劫持的 DLL 的名称。请记住,重要的是链接本身的名称,而不是它的目标。

  4. 4. 放弃SYSTEM特权并恢复到我们的管理员用户上下文。

  5. 5. 在当前用户的 DOS 设备目录中创建一个名为GLOBALROOT并指向的符号链接GLOBAL??。这一步不能做,SYSTEM因为我们想GLOBALROOT在我们自己的 DOS 目录中创建一个假链接。

  6. 6. 这是此漏洞利用的核心。DefineDosDevice以值GLOBALROOTKnownDllsFOO.dll作为设备名称调用。此设备的目标路径是 DLL 的位置,但我将在下一部分中介绍它。

这是最后一步 CSRSS 服务内部发生的情况。它首先接收值GLOBALROOTKnownDllsFOO.dll并在其前面添加,??这样就产生了设备名称??GLOBALROOTKnownDllsFOO.dll。然后,它会尝试在模拟客户端的同时打开相应的符号链接对象。

??GLOBALROOTKnownDllsFOO.dll
-> SessionsDosDevices0000000-XXXXXXXXGLOBALROOTKnownDllsFOO.dll
-> GLOBAL??KnownDllsFOO.dll

由于该对象存在,它将检查它是否是全局的。如您所见,对象的“真实”路径以 开始,GLOBAL??因此它确实被认为是全局的,并且在执行的其余部分禁用模拟。当前链接被删除并创建一个新链接,但这一次,RPC 客户端没有被模拟,所以操作是在 CSRSS 服务本身的上下文中完成的SYSTEM

??GLOBALROOTKnownDllsFOO.dll
-> GLOBAL??GLOBALROOTKnownDllsFOO.dll
-> KnownDllsFOO.dll

开始了!KnownDllsFOO.dll该服务使用我们控制的目标路径创建符号链接。

通过已知 DLL 进行 DLL 劫持

现在我们知道如何向KnownDlls目录添加任意条目,我们应该回到我们最初的问题,以及我们的漏洞利用约束。

要劫持哪个 DLL?

我们想要在 PPL 中执行任意代码,理想情况下使用签名者类型“WinTcb”。所以,我们需要先找到一个合适的可执行候选者。据我所知,在 Windows 10 上,四个内置二进制文件可以在这种保护级别下执行:wininit.exe、、和。并且不能在 Win32 模式下执行,所以我们可以消除它们。我做了一些测试,但让这个二进制文件以具有调试权限的管理员身份运行是个坏主意。事实上,它很有可能会将自己标记为关键进程,这意味着当它终止时,系统可能会因 BSOD 而崩溃。services.exe``smss.exe``csrss.exe``smss.exe``csrss.exe``wininit.exe

这让我们只剩下一个潜在的候选人:services.exe。事实证明,这是达到我们目的的完美人选。它的主要功能非常容易反编译和理解。下面是相应的伪代码。

int wmain()
{
    HANDLE hEvent;
    hEvent = OpenEvent(SYNCHRONIZE, FALSE, L"Global\SC_AutoStartComplete");
    if (hEvent) {
        CloseHandle(hEvent);
    } else {
        RtlSetProcessIsCritical(TRUE, NULL, FALSE);
        if (NT_SUCCESS(RtlInitializeCriticalSection(&CriticalSection))
            SvcctrlMain();
    }
    return 0;
}

它首先尝试打开一个全局Event对象。如果这有效,则关闭句柄,进程终止。SvcctrlMain()仅当此Event对象不存在时才执行实际的 main 函数。这是有道理的,这种简单的同步机制确保services.exe不会执行两次,这非常适合我们的用例,因为我们不想弄乱服务控制管理器(services.exe是 SCM 使用的图像文件)。

Bypass PPL | 绕过 Userland 的 LSA 保护

WinObj - SC_AutoStartComplete 全局事

现在,为了第一眼看到加载的 DLL services.exe,我们可以使用带有几个过滤器的进程监视器。

Bypass PPL | 绕过 Userland 的 LSA 保护

进程监视器 - 由 services.exe 加载的 DLL


从这个输出中,我们知道services.exe加载了三个 DLL(它们不是已知的 DLL),但仅靠这些信息是不够的。我们还需要找到导入了哪些函数。所以,我们需要看一下 PE 的导入表。

Bypass PPL | 绕过 Userland 的 LSA 保护
IDA - 导入服务表.exe

在这里,我们可以看到只有一个函数是从dpapi.dll:导入的CryptResetMachineCredentials。因此,这是最容易劫持的 DLL。我们只需要记住我们必须导出这个函数,否则我们制作的 DLL 将不会被加载。

但就这么简单吗?最简洁的答案是不”。在对 Windows 的各种安装进行一些测试后,我意识到这种行为并不一致。在某些版本的 Windows 10dpapi.dll上,由于某种原因根本没有加载。此外,services.exe在 Windows 8.1 上导入的 DLL 也完全不同。最后,我不得不考虑所有这些差异,以便构建一个适用于所有最新版本的 Windows(包括服务器版本)的工具,但您了解了总体思路。

DLL 文件映射

在前面的部分中,我们看到了如何欺骗 CSRSS 服务创建任意符号链接对象,KnownDlls但我故意省略了一个重要部分:链接的目标路径。

符号链接实际上可以指向对象管理器中的任何类型的对象,但在我们的例子中,我们必须模仿库作为已知 DLL 加载的行为。这意味着目标必须是 Section 对象,而不是 DLL 文件路径等。

正如我们之前看到的,“已知 DLL”是存储在对象目录中的 Section 对象,KnownDlls这也是 DLL 搜索顺序中的第一个位置。因此,如果一个程序加载了一个名为 DLLFOO.dll并且 Section 对象KnownDllsFOO.dll存在,那么加载器将使用这个图像而不是再次映射该文件。在我们的例子中,我们必须手动执行此步骤。“手动”一词有点不合适,因为如果我们以“合法方式”执行此操作,我们实际上不必自己映射文件。

Section可以通过调用创建一个对象NtCreateSection。此本机 API 函数需要一个AllocationAttributes参数,该参数通常设置为SEC_COMMITSEC_IMAGE。设置时SEC_IMAGE,我们可以指定我们要将以前打开的文件映射为可执行映像文件。因此,它会被正确地自动映射到内存中。但这意味着我们必须嵌入一个 DLL,将其写入磁盘,打开它CreateFile以获取文件句柄,最后调用NtCreateSection. 对于概念验证,这很好,但我想更进一步,找到更优雅的解决方案。

另一种方法是在内存中做所有事情。与著名的 Process Hollowing 技术类似,我们必须创建一个Section具有足够内存空间的对象来存储我们的 DLL 映像的内容,然后解析 NT 标头以识别 PE 中的每个部分并适当地映射它们,这就是加载程序做。这是一个相当乏味的过程,我不想走这么远。不过,在进行研究时,我偶然发现了@_ForrestOrr 发表的一篇关于“ DLL Hollowing 的非常有趣的博文。在他的概念验证中,他使用了Transactional NTFS(又名 TxF)用他自己的有效负载替换现有 DLL 文件的内容,而无需真正修改磁盘上的内容。唯一的要求是您必须对目标文件具有写入权限。

在我们的例子中,我们假设我们有管理员权限,所以这是完美的。我们可以在系统目录中打开一个 DLL 作为事务,用我们的有效负载 DLL 替换它的内容,最后在NtCreateSection带有标志的 API 函数调用中使用打开的句柄SEC_IMAGE。但我确实说过,我们仍然需要对目标文件具有写权限,即使我们并没有真正修改文件本身。这是一个问题,因为系统文件属于TrustedInstaller,不是吗?由于我们假设我们拥有管理员权限,因此我们可以提升到,TrustedInstaller但有一个更简单的解决方案。事实证明其中的一些(DLL)文件C:WindowsSystem32实际上属于SYSTEM, 所以我们只需要在这个目录中搜索合适的候选人。我们还应该确保它的大小足够大,以便我们可以用我们自己的有效载荷替换它的内容。

作为 SYSTEM 进行利用?

在漏洞利用部分,我坚持DefineDosDevice必须以除 以外的任何用户身份调用 API 函数SYSTEM,否则整个“技巧”将无法奏效。但是,如果我们已经SYSTEM拥有并且没有管理员帐户怎么办。我们可以创建一个临时的本地管理员帐户,但这会很蹩脚。更好的做法是简单地模拟现有用户。例如,我们可以模拟LOCAL SERVICEor NETWORK SERVICE,因为它们都有自己的 DOS 设备目录。

假设我们拥有“调试”和“模拟”权限,我们可以列出当前进程,找到一个以 身份运行的进程,LOCAL SERVICE复制主令牌并临时模拟该用户。就这么简单。

无论我们是以管理员身份SYSTEM还是以管理员身份执行漏洞利用,在这两种情况下,我们都必须在两个身份之间来回切换,而不能失去对事物的追踪。

结论

在这篇文章中,我们看到了管理员如何利用看似良性的 API 函数,最终使用一些非常聪明的技巧将任意代码注入到最高级别的 PPL 中。我在参考ProcDump的新工具PPLdump中实现了这项技术。假设你有管理员或权限,它允许你转储任何 PPL 的内存,包括启用 LSA 保护时的 LSASS。SYSTEM

这个最初于 2018 年发布的“漏洞”仍未修补。如果您想知道原因,可以查看 Microsoft Bug Bounty 计划中的Windows 安全服务标准部分。您会发现,即使是非管理员绕过 PPL 也不是可维修的问题。

Bypass PPL | 绕过 Userland 的 LSA 保护
Windows 安全服务标准


通过在独立工具中实施这项技术,我学到了很多关于 Windows 内部机制的知识,而我以前并没有真正有机会了解这些知识。作为回报,我在这篇博文中涵盖了很多这些方面。但如果 James Forshaw ( @tiraniddo ) 等伟大的安全研究人员没有通过他们的各种出版物分享他们的知识,这肯定是不可能的。所以,我想再次对他说声谢谢。

原文地址:

https://itm4n.github.io/bypassing-lsa-protection-userland/

文章来源:Z2O安全攻防

如侵权请私信联系删除

原文始发于微信公众号(EchoSec):Bypass PPL | 绕过 Userland 的 LSA 保护

版权声明:admin 发表于 2023年1月10日 上午11:37。
转载请注明:Bypass PPL | 绕过 Userland 的 LSA 保护 | CTF导航

相关文章

暂无评论

暂无评论...