通过查找非权限进程里的泄漏句柄来查找权限升级和UAC
发布时间:2022-04-29 10:40:21 所属栏目:安全 来源:互联网
导读:在某些情况下,具有高完整性或系统完整性的进程会向权限进程/线程/令牌请求句柄,然后生成低完整性进程。如果这些句柄足够强大,且类型正确,并且由子进程继承,我们可以从另一个进程复制它们,然后滥用它们来升级权限或绕过UAC。在这篇文章中,我们将介绍如
|
在某些情况下,具有高完整性或系统完整性的进程会向权限进程/线程/令牌请求句柄,然后生成低完整性进程。如果这些句柄足够强大,且类型正确,并且由子进程继承,我们可以从另一个进程复制它们,然后滥用它们来升级权限或绕过UAC。在这篇文章中,我们将介绍如何寻找和滥用这种漏洞。 介绍 本质上,这个想法是看看我们是否可以自动找到拥有高完整性(也就是提升)或SYSTEM进程的权限句柄的非权限进程,然后检查我们是否可以作为一个非权限用户附加到这些进程上,并复制这些句柄,以便以后滥用它们。我们的工具会受到哪些限制? 1.它必须作为中等完整性进程运行; 2. 进程令牌中没有 SeDebugPrivilege(中等完整性的进程默认没有这个权限); 3. 没有 UAC 绕过,因为它也必须适用于非管理用户; 这个过程有点复杂,我们将经历的步骤或多或少如下: 1.枚举所有进程持有的所有句柄; 2.过滤掉我们不感兴趣的句柄,现在我们只关注进程、线程和令牌的句柄,因为它们更容易被武器化; 3.过滤掉引用低完整性进程/线程/令牌的句柄; 4.过滤掉完整性大于中等的进程持有的句柄,除非获得SeDebugPrivilege,否则我们不能附加到它们上,这违背了本文的目的; 5.复制其余的句柄并将它们导入我们的进程,并试图滥用它们来升级权限或者至少绕过UAC; 当然,我们不太可能在一台全新的Windows设备上满足这些条件,所以为了避免这个问题,我将使用一个我专门为此目的编写的易受攻击的应用程序。 句柄处理 正如我在这个Twitter线程中简要讨论的那样,Windows是一个基于对象的操作系统,这意味着每个实体(进程、线程、互斥锁等)在内核中都以数据结构的形式有一个“对象”表示。例如,对于进程,该数据结构的类型是_EPROCESS。作为存在于内核空间的数据,普通的用户模式代码无法直接与这些数据结构交互,因此操作系统公开了一个间接机制,该机制依赖于特殊的HANDLE类型变量以及用于服务的 SC_HANDLE 等派生类型。句柄只不过是内核空间表中的索引,对每个进程来说都是私有的。表中的每一项都包含了它所指向的对象的地址以及该句柄对该对象的访问级别。这个表由每个进程的_EPROCESS结构的ObjectTable成员(它的类型是_HANDLE_TABLE*,因此它指向一个_HANDLE_TABLE)指向。 为了更容易理解,让我们看一个例子。要获得进程的句柄,我们可以使用OpenProcess Win32 API,定义如下: 它需要3个参数: dwDesiredAccess是一个DWORD,它指定了我们希望对我们试图打开的进程拥有的访问级别; bInheritHandle是一个布尔值,如果设置为TRUE,将使句柄可继承,这意味着调用进程在子进程生成时将返回的句柄复制给子进程(以防我们的程序调用CreateProcess之类的函数); dwProcessId是一个DWORD,用于指定我们想打开哪个进程(通过提供它的PID); 在下一行中,我将尝试打开系统进程(它始终具有 PID 4)的句柄,向内核指定我希望句柄拥有尽可能少的特权,只需要查询有关信息的子集进程(PROCESS_QUERY_LIMITED_INFORMATION),并且我希望该程序的子进程继承返回的句柄(TRUE)。 OpenProcess返回的System进程的句柄(如果它没有因为某种原因失败)被放入hProcess变量中以供以后使用。 在后台,内核执行一些安全检查,如果这些检查通过,则获取所提供的PID,解析相关_EPROCESS结构的地址,并将其复制到句柄表中的一个新条目中。之后,它将访问掩码(即提供的访问级别)复制到相同的条目中,并将条目值返回给调用代码。 当你调用其他函数(如OpenThread和OpenToken)时,也会发生类似的事情。 查看句柄 正如我们前面介绍的,句柄本质上是表的索引。每个条目都包含句柄所引用对象的地址以及句柄的访问级别。我们可以使用 Process Explorer 或 Process Hacker 等工具查看这些信息: 从这个 Process Explorer 屏幕截图中,我们可以获得一些信息: 红框:句柄所指的对象类型; 蓝色框:句柄值(表项的实际索引); 黄色框:句柄所指对象的地址; 绿色框:访问掩码及其解码值(访问掩码是在 Windows.h 头文件中定义的宏),这告诉我们在对象上授予句柄持有者哪些特权; 有很多方法可以获得这些信息,不一定需要使用在内核模式下运行的代码。在这些方法中,最实用和最有用的是依赖原生 API NtQuerySystemInformation,当调用它时传递 SystemHandleInformation (0x10) 值作为其第一个参数,返回一个指向 SYSTEM_HANDLE 变量数组的指针,其中每个变量都引用一个由系统上的进程打开的句柄。 让我们来看看用c++实现它的一种可能的方法。 在这段代码中,我们使用以下变量: queryInfoStatus 将保存 NtQuerySystemInformation 的返回值; tempHandleInfo 将保存有关系统 NtQuerySystemInformation 为我们获取的所有句柄的数据; handleInfoSize 是对所说数据量的“猜测”。不要担心,因为每次 NtQuerySystemInformation 将返回 STATUS_INFO_LENGTH_MISMATCH 时这个变量都会加倍,这是一个告诉我们分配的空间不够的值; handleInfo 是指向内存位置的指针 NtQuerySystemInformation 将填充我们需要的数据; 不要对这里的 while 循环感到困惑,正如我们所说,我们只是反复调用函数,直到分配的内存空间足够大,可以容纳所有的数据。在使用Windows本机API时,这种类型的操作非常普遍。 NtQuerySystemInformation 获取的数据可以通过简单的迭代来解析,如下所示: 从代码中可以看出,变量句柄是 SYSTEM_HANDLE 类型的结构(自动从代码中删除)有许多成员提供有关它所引用的句柄的有用信息。最有趣的成员是: ProcessId:持有句柄的进程; Handle:持有句柄本身的进程内部的句柄值; Object:句柄指向的对象在内核空间中的地址; ObjectTypeNumber:一个未记录的 BYTE 变量,用于标识句柄所指对象的类型。为了解释它,需要进行一些逆向工程和挖掘,只要说进程由值 0x07 标识,线程由 0x08 标识,令牌由 0x05 标识就足够了; GrantedAccess 句柄授予的对内核对象的访问级别,对于进程,你可以找到诸如 PROCESS_ALL_ACCESS、PROCESS_CREATE_PROCESS 等值。 完成后,我们使用 CreateToolhelp32Snapshot 获取有关进程的系统状态快照,并指定我们只需要进程(通过 TH32CS_SNAPPROCESS 参数)。这个快照被分配给快照变量,它的类型是 wil::unique_handle,它是 WIL 库的一个 C++ 类,它使我们摆脱了在使用句柄后必须正确清理句柄的负担。完成后,我们定义并初始化一个名为 processEntry 的 PROCESSENTRY32W 变量,一旦我们开始遍历快照,它将保存我们正在检查的进程的信息。 通过调用 Process32FirstW 并用快照中第一个进程的数据填充 processEntry。对于每个进程,我们尝试在其 PID 上使用 PROCESS_QUERY_LIMITED_INFORMATION 调用 OpenProcess,如果成功,我们将句柄 - PID 对存储在 mHandleId 映射中。 在每个 while 循环中,我们执行 Process32NextW 并用新进程填充 processEntry 变量,直到它返回 false 并且我们退出循环。现在,我们的句柄和它们指向的进程的 PID 之间有一个 1 对 1 的映射。现在进入第二阶段! 现在是获取所有系统句柄并过滤掉不属于我们进程的句柄的时候了,我们已经了解了如何检索所有句柄,现在只需检查每个 SYSTEM_HANDLE 并将其 ProcessId 成员与我们的进程的 PID 进行比较,可通过恰当命名的 GetCurrentProcessId 函数获得。然后,我们以与处理句柄-PID 对类似的方式存储属于我们进程的那些 SYSTEM_HANDLE 的 Object 和 Handle 成员的值,使用我们称为 mAddressHandle 的映射。 如果你在非特权进程中找到具有至少一个此访问掩码的特权进程的句柄,那非常幸运。让我们看看我们如何做到这一点 在这段代码中,我们首先定义一个名为 vSysHandle 的 std::vector,它将保存有趣的 SYSTEM_HANDLE。之后,我们开始对 NtQuerySystemInformation 返回的数据进行常规迭代,只是这次我们跳过了当前进程持有的句柄。然后,我们通过我编写的名为 GetTargetIntegrityLevel 的帮助函数检查持有我们当前正在分析的句柄的进程的完整性级别。这个函数基本上返回一个 DWORD,告诉我们与它作为参数接收的 PID 相关联的令牌的完整性级别,并且改编自许多在线可用的 PoC 和 MSDN 函数。 一旦我们检索到进程的完整性级别,我们要确保它小于高完整性,因为我们感兴趣的是持有感兴趣的句柄的中完整性或低完整性进程,我们还要确保我们正在处理的SYSTEM_HANDLE类型是进程(0x7)。检查后,我们转到检查句柄授予的访问权限。如果句柄不是PROCESS_ALL_ACCESS或不包含任何指定的标志,则跳过它。否则,我们更进一步,检索句柄所指进程的 PID,并获取其完整性级别。如果它是高完整性或更高的(例如SYSTEM),我们将SYSTEM_HANDLE保存在我们的vsyhandle中供以后使用。 首先,我们打开持有权限句柄的进程,然后复制该句柄 这是相当简单的,首先,你使用PROCESS_DUP_HANDLE访问权限打开进程,这是复制句柄所需的最小权限,然后在该进程上调用DuplicateHandle,告诉函数你希望复制保存在syhandle中的句柄,并将其保存到clonedHandle变量中的当前进程中。 通过这种方式,我们的进程现在处于权限句柄的控制之下,我们可以使用它来生成一个新进程,把它的父进程伪装成该句柄所指向的权限进程,从而使新进程继承它的安全上下文,并获得命令shell等。 (编辑:江门站长网) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |
站长推荐


