CVE-2024-22058 Ivanti LanDesk LPE 漏洞分析

在这篇文章中,我们描述了在 Ivanti LanDesk 软件中发现的一个漏洞,以及如何利用它通过任意代码执行来实现本地权限提升。

Ivanti 在 2024 年 5 月 28 日的公告中披露了这个漏洞,并分配了 CVE-2024-22058。

这个漏洞影响 Ivanti Endpoint Manager (EPM) 2021.1 SU5 及之前的版本,EPM 2022 及以后版本中不存在此漏洞。

根本原因分析

在客户端通过端口 9535/TCP 建立与 LanDesk 应用程序的连接后,子程序 sub_4A7A20 开始通过该连接接收数据的过程。这是通过子程序 sub_4A7C70(从现在起称为 ConnectionReceiver),它调用 ws2_32!recv 来完成的。

在第一次调用 ConnectionReceiver 期间,应用程序从客户端接收了总共 12 个字节。这 12 个字节类似于数据包头。

ConnectionReceiver 返回后,接收到的数据的第一个 DWORD 与静态值 0x31484352 进行比较,该值等于字符 1HCR

如果此检查成功,它将比较下一个 DWORD,该 DWORD 可以假定为存储数据包主体的大小。

大小字段的值可以设置为 0x7FFC,否则将被设置为此上限:

CVE-2024-22058 Ivanti LanDesk LPE 漏洞分析

之后,发出另一个对 ConnectionReceiver 的调用,数据包大小现在是函数参数之一。

这意味着下一次调用 ws2_32!recvlen 参数可以高达 0x7FFC

再次,数据包头中的一个值用于另外两个检查,这次是字节比较而不是整个 DWORD。

为了沿着导致错误的代码路径,将该值设置为 0xA,这导致采取第一个跳转并不采取第二个跳转:

CVE-2024-22058 Ivanti LanDesk LPE 漏洞分析

跟随执行将导致最终调用 sub_473570 的节点:

CVE-2024-22058 Ivanti LanDesk LPE 漏洞分析

虽然这个子程序包含相对较多的代码,但到达我们感兴趣的函数调用是相当直接的。

mov esi, ecx
cmp dword ptr [esi+7Ch], 0
jnz loc_4738B5

在这个检查中,并没有验证数据包,而是检查了在执行过程中很早就创建的堆对象。接下来的检查,依旧使用之前的数据包中检索到的值。

loc_4735DB 处,应用程序再次将 EDX 与 0xA 进行比较,这意味着它与之前的检查几乎相同。

CVE-2024-22058 Ivanti LanDesk LPE 漏洞分析

跟随那个跳转会导致跳到右下角的基本块(在图形视图中),该基本块调用 sub_473070 并传入两个参数。

第一个参数是来自数据包中另一个字段的值,很可能与数据包大小有关。第二个参数是数据包主体的起始地址。

这个子程序包含一个 switch-case 结构,并允许发送数据包的用户通过在数据包中指定操作码来决定采取的代码路径。可以假设这个函数是某种“选项菜单”,允许你选择想要触发的功能。

CVE-2024-22058 Ivanti LanDesk LPE 漏洞分析

通过将操作码值设置为 0x15,可以到达易受攻击的代码。

这将导致执行流程到达 loc_463185(跳转表案例 21),它调用 sub_472F80 并传入两个函数参数。

第一个参数是来自数据包中大小字段(0x4b29)的值;另一个参数再次是数据包主体的起始地址。

子程序 sub_472F80 相当简单。如果大小字段中的值(由用户指定)大于 3,将通过调用 strncpy 执行字符串复制操作。

不是将源数据写入栈或堆,目标是 issuser.exe(LanDesk)映像本身 .data 段中的一个变量:

CVE-2024-22058 Ivanti LanDesk LPE 漏洞分析

issuser+0x72f9b:
00472f9b 68e0047e00      push    offset issuser+0x3e04e0 (007e04e0)
0:005> !address 007e04e0


[...]
Usage:                  Image
Base Address:           007de000
End Address:            007e5000
Region Size:            00007000 (  28.000 kB)
State:                  00001000          MEM_COMMIT
Protect:                00000004          PAGE_READWRITE
Type:                   01000000          MEM_IMAGE
Allocation Base:        00400000
Allocation Protect:     00000080          PAGE_EXECUTE_WRITECOPY
Image Path:             issuser.exe
Module Name:            issuser

IDA 已经标出这个变量是一个大小为 260 的字符数组。尽管如此,由于可以使用用户控制的大小字段来指定复制的字符数量,并且没有边界检查,所以很容易溢出这个数组。

尽管这并不影响可利用性,但人们可能需要考虑,在地址 0x7e5000 处,经过 19,232 字节后,内存最终会变成 READ_ONLY

为了避免在使用 strncpy 时碰到 READ_ONLY 页面,数据包头中的 size 字段应该设置为 0x4b20 或更小。

然而,我们利用这个错误的方法涉及通过尝试写入确切的那些 READ_ONLY 页面来引起多次访问违规。

在目标内存地址——大小为 260 的字符数组——和 READ_ONLY 内存页面之间有多个包含数据和函数指针的地址。

如果执行了 __CxxFrameHandler3,那么其中一个函数指针最终会被调用。

当尝试使用 strncpy 函数写入 READ_ONLY 内存页面时,将启动一个 C++ 特定的异常处理器——__CxxFrameHandler3,而不是 Windows 结构化异常处理器(SEH)。

覆盖上述函数指针,然后使 __CxxFrameHandler3 被调用,最终将导致控制指令指针。

CVE-2024-22058 Ivanti LanDesk LPE 漏洞分析

如上图中 IDA 的邻近视图图所示,__CxxFrameHandler3 最终将导致对 ___vcrt_FlsGetValue 的调用,然后调用 try_get_function

static void* __cdecl try_get_function(
    function_id        const id,
    char               const* const name,
    module_id const* const first_module_id,
    module_id const* const last_module_id
) noexcept

try_get_function 是 MSVC vcruntime 的一部分,在高层次上可以与 GetProcAddress 相比较。

在这种情况下,try_get_function 被调用时使用的函数 id 为 2

CVE-2024-22058 Ivanti LanDesk LPE 漏洞分析

try_get_function 中,这个函数 ID 被用来从 .data 段获取一个函数指针。

在这种情况下,最终获取函数指针的虚拟地址是 4 * 2 + 0x7E3068 = 0x7e3070

CVE-2024-22058 Ivanti LanDesk LPE 漏洞分析

考虑到我们能够从 0x7e04e0 写入任意数据直到 0x7e5000,就有可能覆盖 try_get_function 检索到的这个函数指针。

函数指针返回给调用函数 (___vcrt_FlsGetValue) 后,然后使用间接函数调用来调用它:

CVE-2024-22058 Ivanti LanDesk LPE 漏洞分析

这样,就有可能通过覆盖被调用的函数指针来劫持指令指针,无论是用任意数据还是一个内存地址。

利用

LanDesk 二进制文件既没有使用 CFG 保护,也没有使用 ASLR,所以我们只需要绕过 DEP。

与 LanDesk 一起提供的 RollingLog.dll 被选为 ROP 工具的来源,因为这个模块相比其他 LanDesk DLLs 不太可能被重新定位。

在大多数情况下,RollingLog.dll 会被加载到虚拟地址 0x10000000

issuser.exe 导入了 kernel32!VirtualProtectkernel32!VirtualAlloc,这两个函数可以用来绕过 DEP。

在这种情况下,我们决定使用 VirtualAlloc 来设置持有数据包的栈内存为可执行。

尽管有多个函数指针我们可以覆盖并用于劫持控制流,但我们决定覆盖地址 0x7e3070 处的一个。

为了让 LanDesk 调用那个函数指针,需要实际触发一个异常。走这条路最简单的方法是使用前面提到的 strncpy 调用,并尝试写入 READ_ONLY 内存。

这样做将导致 __CxxFrameHandler3 被调用,这是一个 C++ 特定的函数,将尝试处理异常。

__CxxFrameHandler3 函数接着调用 __InternalCxxFrameHandler,它又调用 ___vcrt_getptd,它立即调用 ___vcrt_getptd_noexit

最后,___vcrt_getptd_noexit 调用 ___vcrt_FlsGetValue 其中包含间接函数调用:

CVE-2024-22058 Ivanti LanDesk LPE 漏洞分析

跟随执行直到包含检索到的函数指针的 call esi 指令,结果就是控制指令指针:

CVE-2024-22058 Ivanti LanDesk LPE 漏洞分析

此时,可以使用如 retn 7420 这样的栈迁移工具,返回到 ws2_32!recv 函数存储在栈上的初始数据包。

执行这个工具后,栈指针将指向 ROP 链的开始,当 __vcrt_FlsGetValue 尝试返回给它的调用函数时,实际上将返回到我们的 ROP 链。

[...]
payload = b"\x15"
payload += pack('<H',0x4b29)    # size for strncpy


payload += b'A' * 0x2b90
payload += pack('<L', rollinglog_base + 0x13294) # retn 0x7420 (stack pivoting gadget)
payload += b'1' * (0x41d5 - len(va))
payload += va                   # ROP skeleton


''' ROP CHAIN GOES HERE '''
# This chunk obtains a pointer to the ROP skeleton and saves it
rop = pack('<L', rollinglog_base + 0x3918)      # push esp ; sbb eax, 0xE58B0000 ; pop ebp ; ret ;
[...]

在 ROP 链执行后,包含 shellcode 的栈内存页将被标记为 PAGE_EXECUTE_READWRITE,我们就可以实现任意代码的执行:

CVE-2024-22058 Ivanti LanDesk LPE 漏洞分析

CVE-2024-22058 Ivanti LanDesk LPE 漏洞分析

时间线

  • 2024.02.06:首次尝试联系 Ivanti
  • 2024.02.13:第二次尝试联系 Ivanti
  • 2024.02.19:第三次尝试联系 Ivanti
  • 2024.02.21:收到 Ivanti 的首次回复
  • 2024.02.23:Ivanti 确认了漏洞
  • 2024.03.07:Ivanti 预留了 CVE
  • 2024.04.29:跟进 Ivanti
  • 2024.05.28:Ivanti 公告中公开披露
  • 2024.05.29:发布这篇博客文章


原文始发于微信公众号(3072):CVE-2024-22058 Ivanti LanDesk LPE 漏洞分析

版权声明:admin 发表于 2024年5月31日 下午1:08。
转载请注明:CVE-2024-22058 Ivanti LanDesk LPE 漏洞分析 | CTF导航

相关文章