mips基础知识
mips操作总结
寄存器布局
| 寄存器名 | 编号 | 用途 | 关注点 |
|---|---|---|---|
| $zero | $0 | 恒为 0 | 无法修改,常用于清零操作 |
| $a0 - $a3 | $4 - $7 | 参数传递 | 函数的前 4 个参数,超过的压栈 |
| $v0 - $v1 | $2 - $3 | 返回值 | 寻找漏洞后的函数执行结果检查 |
| $t0 - $t9 | $8 - $15 | 临时变量 | 类似于 eax/ebx,随意使用 |
| $s0 - $s7 | $16 - $23 | 静态变量 | 函数调用后需恢复(Callee-saved) |
| $gp | $28 | 全局指针 | 访问全局数据 |
| $sp | $29 | 栈指针 | 栈溢出的核心:指向当前栈顶 |
| $fp | $30 | 帧指针 | 局部变量定位 |
| $ra | $31 | 返回地址 | 最核心! 相当于 x86 的 EIP 返回值。劫持它就控制了执行流 |
$t (Temporary, 寄存器 8-15, 24-25): * 临时工。调用别的函数时,这些寄存器的值不保证还能留着。如果你的函数里用了 $t0,紧接着执行了 jal 调用另一个函数,回来后 $t0 的值可能已经被改了。
$s (Saved, 寄存器 16-23): * 正式工。调用别的函数时,这些值必须保持不变。如果一个函数想用 $s0,它必须先把原来的值存到栈里,用完再恢复(即“谁用谁负责还原”)。
延迟槽 (Delay Slot) —— 重点!
这是 MIPS 的特殊之处。当 beq 或 bne 条件成立需要跳转时,紧跟在分支指令后面的那一条指令仍然会被执行,然后才跳转到目标位置。
Pwn 贴士: 在分析漏洞函数时,如果你看到一条
bne指令,别忘了看它下面那一行做了什么。有时候关键的寄存器赋值就在延迟槽里完成。
R型,I型,J型
根据指令格式的不同,可以分为R型,I型,J型
在 x86 中,指令长度是可变的(1 字节到 15 字节都有)。但在 MIPS 里,为了让 CPU 处理指令像流水线一样快,所有指令必须整整齐齐都是 32 位。
1. R 型指令 (Register, 寄存器型)
用途: 纯粹的寄存器间运算(加减乘除、逻辑运算、移位)。 特点: 所有的操作数都在寄存器里,不涉及内存,也不涉及具体的常数。
- 结构拆解:
- op (6位): 操作码。R 型指令的这个字段全为 0。
- rs, rt (各5位): 两个源寄存器(输入的两个数)。
- rd (5位): 目的寄存器(存结果的地方)。
- shamt (5位): 移位量(仅用于
sll,srl等移位指令,平时为 0)。 - funct (6位): 关键字段。因为 op 字段被占用了,真正的动作(是加法还是减法)由最后这 6 位决定。

- I 型指令 (Immediate, 立即数型)
用途: 涉及常数的运算、内存访问(装载/存储)以及条件分支。 特点: 指令中直接包含了一个 16 位的数值。
- 结构拆解:
- op (6位): 操作码。直接决定动作(如
addi为 8,lw为 35)。 - rs (5位): 源寄存器。
- rt (5位): 目标寄存器(对于
lw)或第二个源寄存器(对于beq)。 - immediate (16位): 立即数。存放一个常数、内存偏移量或跳转的相对地址。
- op (6位): 操作码。直接决定动作(如

- J 型指令 (Jump, 跳转型)
用途: 用于大范围的无条件跳转(如函数调用或 goto)。 特点: 结构最简单,为了跳得足够远,它把剩下的 26 位全给了目标地址。
- 结构拆解:
- op (6位): 操作码(如
j为 2,jal为 3)。 - address (26位): 跳转的目标地址。
- op (6位): 操作码(如

总结
参数寄存器 (
$a0 - $a3):函数调用的前 4 个参数放在这里。如果你想调用system("/bin/sh"),你必须想办法把"/bin/sh"的地址塞进$a0。返回值寄存器 (
$v0, $v1):函数执行完的结果放在这。返回地址寄存器 (
$ra):Pwn 的终极目标。函数执行完jr $ra跳转回调用者。在 C 语言中,当
main函数调用vuln()时,汇编层面会执行jal(Jump and Link)指令:MIPS Assembler
1
2
3 ># main 函数内部
>jal 0x400500 <-- 调用 vuln 函数
># 下一条指令的地址 A (假设是 0x400404)关键动作:
jal指令在跳转到0x400500的瞬间,会自动把“下一条指令的地址 A”存入$ra寄存器。这样vuln执行完后,才知道该回到哪里继续运行。栈指针寄存器 (
$sp):指向当前栈顶。MIPS 没有push/pop,全是靠addi $sp, $sp, -imm来手动开辟空间。零寄存器 (
$zero):值永远为 0。常用于清零或赋值操作。
demo
1 | # ---------------------------------------------------------------- |

如何在find_max:内部调用函数要通过sw把返回地址存在栈上
通过lw取出(相当于在sp+4的位置上临时储存返回地址)
1 | find_max: |
XYCTF2024_EZ1.0?例题
gdb调试流程
启动 QEMU (Server 端)
1 | qemu-mipsel-static -g 1234 ./mips |
再开一个终端进行gdb调试
1 | gdb-multiarch ./mips#启动gdb并连接 |
杀死所有QEMU进程
1 | killall -9 qemu-mipsel-static |
分析

输入地址是在sp+0x18处

从汇编可以看到返回地址是在sp+0x5c处
所以垃圾字节应该填充0x44个
也可以通过cyclic 0x100去看
或者根据fp的上一个是ret(每道题的情况不一定,根据实际情况分析)

找到偏移之后通过read写shellcode到bss,然后让它跳转过去执行
写shellcode
由于read的参数a1是通过fp去找的,fp是在sp+0x40处,所以用下面来覆盖参数地址和ret的地址
-0x60是由于函数在返回之前把sp又加了我回去,0x40+4是前面b’a’*0x40+p32(bss+0x200-0x60+0x40+4)的偏移
1 | payload = b'a'*0x40+p32(bss+0x200-0x60+0x40+4)+p32(read_addr) |
exp
1 | from pwn import * |