首页
论坛
课程
招聘
[原创] CVE-2020-1350 Windows DNS Server 远程代码执行漏洞分析利用技术分享
2021-1-29 21:09 4436

[原创] CVE-2020-1350 Windows DNS Server 远程代码执行漏洞分析利用技术分享

2021-1-29 21:09
4436

漏洞简讯

2020年7月,微软公开发布了Windows DNS Server远程代码执行漏洞,漏洞编号为CVE-2020-1350,该漏洞影响 2003 到 2019 年发布的所有 Windows Server 版本,CVSS 评分为满分 10 分。微软在公告中指出,该漏洞可引发蠕虫式传播。Windows DNS Server 在处理特制的 SIG 响应包时,存在远程代码执行漏洞,未经身份验证的攻击者可通过维护一个域名并设置指向恶意服务器的 NS 记录,通过向目标 DNS 服务器查询该域名的 SIG 来利用此漏洞,成功利用此漏洞的远程攻击者可在目标系统上以 SYSTEM 账户权限执行任意代码。经研判,该漏洞无需交互、不需要身份认证且 Windows DNS Server 默认配置可触发。目前,互联网上已出现该漏洞相关细节、POC 以及漏洞利用视频。

漏洞分析

此漏洞公开后,Check Point 发布了相关的分析文章,传送门:
https://research.checkpoint.com/2020/resolving-your-way-into-domain-admin:-exploiting-a-17-year-old-bug-in-windows-dns-servers。如果已了解漏洞原理可直接跳过本节。

 

以下为存在漏洞的函数 SigWireRead,SigWireRead 函数用来缓存 SIG 记录,当用户发起 SIG 查询时,DNS Server 首先查询有没有缓存记录,如果没有查询到或者该记录的 TTL 到期,则会向相应的 NS 服务器请求数据,这时候 DNS Server 会调用 SigWireRead 函数来存储这个记录。该漏洞是由整数溢出引发的堆溢出,漏洞位置位于倒数第 10 行和倒数第 3 行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
_BYTE *__fastcall SigWireRead(__int64 a1, __int64 pMsg, __int64 pchData, unsigned __int16 a4)
{
  __int64 v4; // rbx
  unsigned __int64 pchEnd; // rdi
  unsigned __int8 *signameData; // r8
  unsigned __int8 *v7; // rax
  unsigned __int8 *v8; // rbp
  size_t sigLength; // rdi
  _BYTE *v10; // rax
  _BYTE *v11; // rsi
  unsigned __int8 nameSigner; // [rsp+30h] [rbp-138h]
 
  v4 = pchData;
  pchEnd = pchData + a4;
  signameData = (unsigned __int8 *)(pchData + 0x12);
  if ( (unsigned __int64)signameData >= pchEnd )
    return 0i64;
  v7 = Name_PacketNameToCountNameEx(&nameSigner, pMsg, signameData, pchEnd)
  v8 = v7;
  if ( !v7 )
    return 0i64;
  sigLength = pchEnd - (_QWORD)v7;
  v10 = RR_AllocateEx((unsigned __int16)sigLength + (unsigned __int16)nameSigner + 0x14, 0, 0);  //整数溢出
  v11 = v10;                                 
  if ( !v10 )
    return 0i64;
  *(_OWORD *)(v10 + 0x38) = *(_OWORD *)v4;
  *((_WORD *)v10 + 0x24) = *(_WORD *)(v4 + 0x10);
  Name_CopyCountName(v10 + 0x4A, &nameSigner);
  memcpy(&v11[(unsigned __int8)v11[0x4A] + 0x4C], v8, sigLength);  //堆溢出
  return v11;
}

RR_AllocateEx 函数的第一个参数为 sigLength + nameSigner + 0x14,由于其大小为16 bits(仅使用 CX),可导致在计算参数时产生整数溢出,分配较小的缓冲区,后续使用 memcpy 函数向缓冲区复制大量数据导致堆溢出。

 

 

通过对比发现,关键变量 nameSigner 对应了数据包中 Signer's name 表示的域名长度,sigLength 则对应了 Signature 字段数据的长度。实际上 Signer's name 字段为 C0 0C,但解析成了 yyyyyyyyt.fun,这是因为使用了 DNS 名称压缩。 C0 表示启用名称压缩,后面的 0C 代表了数据相对于 DNS 报文的偏移,Name_PacketNameToCountNameEx 函数会处理这个,并将处理结果放在 &nameSigner 处。

 

 

正常 DNS 响应包大小不会超过 0xFFFF,基于 UDP 的 DNS 大小被限制到 512 字节,即使采用 TCP 连接,DNS 消息长度也被限制为 2 个字节,也就是 0xFFFF,如下图所示,这 0xFFFF 字节数据除了包含真实的 Signer's name 以及 Signature,还包括 DNS 头部和原始查询等信息,这些数据加起来已经明显超过0x14了,在这种情况下, sigLength + nameSigner + 0x14 小于 0x10000,因而不能直接产生溢出。

 

 

所以嘛,还需要观察一下 Name_PacketNameToCountNameEx 函数,以 "yyyyyyyyt.fun" 为例,经过该函数处理后 nameSigner 为 0xF,表示后面有 0xF 个字节,后面的 2 表示后面有两组数据,一组为 9 字节长度的 yyyyyyyyt,另一组为 3 字节长度的 fun。计算出的 nameSigner 会参与后续计算。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
0:001>
dns!SigWireRead+0x3c:
00007ff7`a9daf90c e86755fcff      call    dns!Name_PacketNameToCountNameEx (00007ff7`a9d74e78)
 
0:001> db r8
000002ea`760b445d  c0 0c 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
0:001> r rcx
rcx=000000f775b7f1f0
 
0:001> p
dns!SigWireRead+0x41:
00007ff7`a9daf911 488be8          mov     rbp,rax
0:001> db rcx    // &nameSigner
000000f7`75b7f1f0  0f 02 09 79 79 79 79 79-79 79 79 74 03 66 75 6e  ...yyyyyyyyt.fun
000000f7`75b7f200  00 be 6c 75 ea 02 00 00-00 00 00 00 00 00 00 00  ..lu............
 
0:001> u rip l6
dns!SigWireRead+0x4e:
00007ff7`a9daf91e 482bf8          sub     rdi,rax
00007ff7`a9daf921 6683c114        add     cx,14h
00007ff7`a9daf925 33d2            xor     edx,edx
00007ff7`a9daf927 6603cf          add     cx,di
00007ff7`a9daf92a 4533c0          xor     r8d,r8d
00007ff7`a9daf92d e8263c0800      call    dns!RR_AllocateEx (00007ff7`a9e33558)
 
0:001> r cx
cx=f
0:001> ? rdi-rax
Evaluate expression: 65472 = 00000000`0000ffc0
0:001> ? cx + ffc0 + 14    //无法整数溢出
Evaluate expression: 65507 = 00000000`0000ffe3

正常情况下,即使 DNS 响应包长度为 0xFFFF 也无法触发整数溢出,但由于攻击者可控制 DNS 响应数据,可通过设置 Signer's name 中的偏移,使其指向一个较大的数来增加 nameSigner。由于每一组的长度不能超过 0x40(Name_PacketNameToCountNameEx 函数中有判断),所以我选择查询 9.yyyyyyyyt.fun。当偏移由 0xC 变为 0xD,要处理的数据就由 "01 39 09 79 79 79 79 79 79 79 79 74 03 66 75 6e" 变成了 "39 09 79 79 79 79 79 79 79 79 74 03 66 75 6e"。Name_PacketNameToCountNameEx 函数执行完成后 nameSigner 为 0x3b(0x39+1+1)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
0:001>
dns!Name_PacketNameToCountNameEx+0xdd:
00007ff7`a9d74f55 493bf6          cmp     rsi,r14
0:001> ub rip
dns!Name_PacketNameToCountNameEx+0xbb:
00007ff7`a9d74f33 4c8d46ff        lea     r8,[rsi-1]
00007ff7`a9d74f37 0fb606          movzx   eax,byte ptr [rsi]
00007ff7`a9d74f3a 488db530190000  lea     rsi,[rbp+1930h]
00007ff7`a9d74f41 440fb6ca        movzx   r9d,dl
00007ff7`a9d74f45 6641c1e108      shl     r9w,8
00007ff7`a9d74f4a 66440bc8        or      r9w,ax
00007ff7`a9d74f4e 410fb7c1        movzx   eax,r9w
00007ff7`a9d74f52 4803f0          add     rsi,rax    //rax=0xd
 
0:001> db rsi l40  
000002ea`760b442d  39 09 79 79 79 79 79 79-79 79 74 03 66 75 6e 00  9.yyyyyyyyt.fun.
000002ea`760b443d  00 18 00 01 c0 0c 00 18-00 01 00 00 00 00 ff d2  ................
000002ea`760b444d  00 01 05 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
000002ea`760b445d  00 00 c0 0d 00 00 00 00-00 00 00 00 00 00 00 00  ................
 
0:001> gu    //函数返回
dns!SigWireRead+0x41:
00007ff7`a9daf911 488be8          mov     rbp,rax
 
0:001> db rcx l40  // &nameSigner
000000f7`75b7f1f0  3b 01 39 09 79 79 79 79-79 79 79 79 74 03 66 75  ;.9.yyyyyyyyt.fu
000000f7`75b7f200  6e 00 00 18 00 01 c0 0c-00 18 00 01 00 00 00 00  n...............
000000f7`75b7f210  ff d2 00 01 05 00 00 00-00 00 00 00 00 00 00 00  ................
000000f7`75b7f220  00 00 00 00 c0 0d 00 00-00 00 00 00 00 7f 00 00  ................

在这种情况下,sigLength + nameSigner + 0x14 的结果为 0x1000d,超过了 0xFFFF,成功整数溢出。虽然调用 RR_AllocateEx 函数时请求的大小为 0xd,但实际分配了 0x68 大小的缓冲区,因为分配的堆需要容纳 0x10 大小的自定义头部、0x38大小的 RR 头部以及重组的 DNS RR 数据(申请时的 0xd)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
0:001> u rip l6
dns!SigWireRead+0x4e:
00007ff7`a9daf91e 482bf8          sub     rdi,rax
00007ff7`a9daf921 6683c114        add     cx,14h
00007ff7`a9daf925 33d2            xor     edx,edx
00007ff7`a9daf927 6603cf          add     cx,di
00007ff7`a9daf92a 4533c0          xor     r8d,r8d
00007ff7`a9daf92d e8263c0800      call    dns!RR_AllocateEx (00007ff7`a9e33558)
0:001> r cx
cx=3b
0:001> ? cx + rdi - rax + 14    //整数溢出
Evaluate expression: 65549 = 00000000`0001000d
……
0:001>
dns!SigWireRead+0x5d:
00007ff7`a9daf92d e8263c0800      call    dns!RR_AllocateEx (00007ff7`a9e33558)
0:001> r rcx
rcx=000000000000000d
 
//dns!Mem_Alloc
00007ff7`a9e32f3a 3b3dfc850a00    cmp     edi,dword ptr [dns!StandardAllocLists+0xc (00007ff7`a9edb53c)]
00007ff7`a9e32f40 488d1de9850a00  lea     rbx,[dns!StandardAllocLists (00007ff7`a9edb530)]
00007ff7`a9e32f47 760c            jbe     dns!Mem_Alloc+0xe9 (00007ff7`a9e32f55)
00007ff7`a9e32f49 488bc3          mov     rax,rbx
00007ff7`a9e32f4c 4883c358        add     rbx,58h
00007ff7`a9e32f50 3b7864          cmp     edi,dword ptr [rax+64h] ds:00007ff7`a9edb594=00000068
 
0:001> dq dns!StandardAllocLists + 58  
00007ff7`a9edb588  000002ea`758c5b18 00000068`00000001   // 0x68 大小的 AllocLists
00007ff7`a9edb598  0000000b`00000027 0000009c`00000004
00007ff7`a9edb5a8  0000004e`000000df 00000000`00003f60
00007ff7`a9edb5b8  ffffffff`ffffffff 00000000`ffffffff
00007ff7`a9edb5c8  00000000`00000000 00000000`00000000
00007ff7`a9edb5d8  00000000`00001388 000002ea`752c5098   // 0x88 大小的 AllocLists
00007ff7`a9edb5e8  00000088`00000002 00000004`0000001e
00007ff7`a9edb5f8  000000d2`00000007 00000021`000000ef
 
0:001> gu
dns!SigWireRead+0x62:
00007ff7`a9daf932 488bf0          mov     rsi,rax
0:001> db rax-10  // 分配到 0x2ea758c5b18,长度为 0x68
000002ea`758c5b18  1c 00 00 00 bb 1a 69 00-ef 0c 0c 0c 0c 0c 0c fe  ......i.........
000002ea`758c5b28  00 00 00 00 00 00 00 00-00 80 00 00 00 00 0d 00  ................
000002ea`758c5b38  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
000002ea`758c5b48  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
000002ea`758c5b58  00 00 00 00 00 00 00 00-18 04 02 62 30 03 6f 72  ...........b0.or
000002ea`758c5b68  67 0b 61 66 69 6c 69 61-73 2d 6e 73 74 03 6f 72  g.afilias-nst.or
000002ea`758c5b78  67 00 00 00 00 00 00 00-00 00 00 00 ee 22 69 00  g............"i.
000002ea`758c5b88  e8 5b 8c 75 ea 02 00 00-ef 0b 0b fe ef 0b 0b fe  .[.u............

SigWireRead 函数在后面会将 RR 数据重组(图2框里的数据),但由于 Signer's name 实际指向的数据的长度就超过了 0xd,所以在 Name_CopyCountName 处就会产生溢出,覆盖到邻接的堆块。

 

 

 

以下为后续调用 memcpy 函数向不可访问区域写数据时触发崩溃的场景:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
0:001>
dns!SigWireRead+0x98:
00007ff7`a9daf968 e8324c0d00      call    dns!memcpy (00007ff7`a9e8459f)
 
0:001> db 2ea`758c5b18
000002ea`758c5b18  1c 00 00 00 bb 1a 69 00-ef 0c 0c 0c 0c 0c 0c fe  ......i.........
000002ea`758c5b28  00 00 00 00 00 00 00 00-00 80 00 00 00 00 0d 00  ................
000002ea`758c5b38  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
000002ea`758c5b48  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
000002ea`758c5b58  00 00 00 00 00 00 00 00-00 01 05 00 00 00 00 00  ................
000002ea`758c5b68  00 00 00 00 00 00 00 00-00 00 3b 01 39 09 79 79  ..........;.9.yy
000002ea`758c5b78  79 79 79 79 79 79 74 03-66 75 6e 00 00 18 00 01  yyyyyyt.fun.....    //已经溢出了
000002ea`758c5b88  c0 0c 00 18 00 01 00 00-00 00 ff d2 00 01 05 00  ................
 
0:001> r r8    //但还是要复制0xffbe长度的数据
r8=000000000000ffbe
 
0:001> p
(868.8fc): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
msvcrt!memcpy+0xb4:
00007fff`98f74a34 660f7f49f0      movdqa  xmmword ptr [rcx-10h],xmm1 ds:000002ea`758cb000=????????????????????????????????

漏洞利用

DNS 堆管理

RR_AllocateEx 函数被用来申请 RR 对象空间,在函数内部会将申请的长度加上 a2(例,SigWireRead 调用 RR_AllocateEx 时将 a2 设置为0),然后再加上 0x38(RR 对象头长度),然后调用 Mem_Alloc 函数。

 

 

如下所示,在 Mem_Alloc 函数中会将传入的 length 加 0x10(自定义堆头大小)。如果这个长度大于0xA0,会调用 allocMemory -> HeapAlloc 进行分配。而如果长度在 0xA0 以内,则从 StandardAllocLists 中分配合适大小的堆,如果当前 StandardAllocLists 中没有合适大小的堆,就会调用 allocMemory -> HeapAlloc 函数请求 v14[3] * v14[4] 大小的堆,并将它们分开并链接起来供 StandardAllocLists 查询。

 

 

StandardAllocLists 维护四个不同大小的堆链,包括 0x50、0x68、0x88 和 0xA0。例,如果 v8 在 0x89 到 0xA0 之间,则会分配到 0xA0 大小的 DNS 堆:
index 0:0x50 0x33(个数)= 0xFF0(0x00〜0x50)
index 1:0x68
0x27(个数)= 0xFD8(0x51〜0x68)
index 2:0x88 0x1e(个数)= 0xFF0(0x69〜0x88)
index 3:0xA0
0x19(个数)= 0xFA0(0x89〜0xA0)

 

对象的分配与释放

由前面信息可知,RRSIG 对象包括 0x10 的自定义头部、0x38 字节的对象头部以及不确定长度的对象数据(包括 0x12 长度的数据、Signer's name 以及 Signature)。通过调整发送的响应包的 Signature 的长度可分配到目标大小的堆,如下,RRSIG 对象被分配到 0xA0 大小的块上。由于 RR 对象的结构已知,所以也很容易伪造。

 

 

当缓存信息超时后,将会自动释放该 RR 对象。可将目标 DNS 响应数据的 TTL 设置为 0,并在短时间内再次请求,从而释放原有的堆,同时也会重新申请一个新的堆来储存数据。以下为释放后的堆,其偏移 8 处指向下一个大小为 0xA0 的可用的堆。由于其大小在 0xA0 以内,因而会被 StandardAllocLists 记录(0xA0 大小,索引 3),也是遵循后进先出的原则。

 

避免立即崩溃

为了避免漏洞触发时引发崩溃,可以多次请求服务器使其分配很多 RR 对象(保证这些对象在漏洞利用时不会被释放)。然后释放中间的一个对象,使得触发漏洞时,RRSIG 对象可以申请到之前释放的块。

 

1、分配大量 0x1000F 块(加上 0x10 字节的 _Heap_Entry 实际上是 0x10020),然后释放中间的一个 0x1000F 块
2、分配大小为 0xF05F 的块,从而分割出 0xFB0 大小的剩余可用空间
3、接下来再请求大量 0xA0 块就可以使目标 RR 缓冲区分配到这个 0xFB0 大小的块(_Heap_Entry 0x10 + 0xFA0)

 

这样可保证向重占位的 0xFB0 大小的块中的某个释放后的对象处复制超过 0xFFFF 大小的数据也不会触发崩溃。

 

 

某次测试时的布局如下,我们可以释放 sp_115 所在堆,然后利用漏洞申请相同大小的堆,并覆盖后面的数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
0:019> db 17ef599f300 lfa0
0000017e`f599f300  00 00 00 00 00 00 00 00-42 71 5e fc f9 36 db 10  ........Bq^..6..
0000017e`f599f310  70 b7 65 e7 bb 22 a3 00-ef 0c 0c 0c 0c 0c 0c fe  p.e.."..........
0000017e`f599f320  00 00 00 00 00 00 00 00-61 80 10 00 18 00 57 00  ........a.....W.
0000017e`f599f330  57 02 00 00 57 02 00 00-00 00 00 00 01 00 00 00  W...W...........
0000017e`f599f340  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
0000017e`f599f350  00 00 00 00 00 00 00 00-00 01 05 00 00 00 00 00  ................
0000017e`f599f360  00 00 00 00 00 00 00 00-00 00 16 03 06 73 70 5f  .............sp_
0000017e`f599f370  31 31 35 09 79 79 79 79-79 79 79 79 74 03 66 75  115.yyyyyyyyt.fu
0000017e`f599f380  6e 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  n...............
0000017e`f599f390  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
0000017e`f599f3a0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
0000017e`f599f3b0  00 00 00 00 bb 22 a3 00-ef 0c 0c 0c 0c 0c 0c fe  ....."..........
0000017e`f599f3c0  00 00 00 00 00 00 00 00-61 80 00 00 18 00 57 00  ........a.....W.
0000017e`f599f3d0  57 02 00 00 c7 19 00 00-00 00 00 00 01 00 00 00  W...............
0000017e`f599f3e0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
0000017e`f599f3f0  00 00 00 00 00 00 00 00-00 01 05 00 00 00 00 00  ................
0000017e`f599f400  00 00 00 00 00 00 00 00-00 00 16 03 06 73 70 5f  .............sp_
0000017e`f599f410  31 31 36 09 79 79 79 79-79 79 79 79 74 03 66 75  116.yyyyyyyyt.fu
0000017e`f599f420  6e 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  n...............
0000017e`f599f430  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
0000017e`f599f440  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
0000017e`f599f450  00 00 00 00 bb 22 a3 00-ef 0c 0c 0c 0c 0c 0c fe  ....."..........
0000017e`f599f460  00 00 00 00 00 00 00 00-61 80 00 00 18 00 57 00  ........a.....W.
0000017e`f599f470  57 02 00 00 c7 19 00 00-00 00 00 00 01 00 00 00  W...............
0000017e`f599f480  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
0000017e`f599f490  00 00 00 00 00 00 00 00-00 01 05 00 00 00 00 00  ................
......
数据泄露

通过设置 RR 对象中的 rr_size 可泄露后面对象的堆数据。当客户端发起 SIG 请求时,DNS Server 会从缓存中读取数据。如下,在 SigWireWrite 函数中,rr_size 可控制读取数据的长度,但要需要保证 rr_size - nameSigner - 0x14 的大小不超过 v4 - v9(Wire_TestForBytesRemaining 函数控制)。

 

 

将 sp_116 的 rr_size 设置为 0x1a0(本来是 0x57)然后请求其 SIG 可获取sp_117 和 sp_118 范围内的数据,如下:

 

代码执行

当使用 RR_Free 函数释放超时的 RR 对象时,会判断 wtype 是否为 2(NS)或 6(SOA),如果是的话,就会调用 Timeout_FreeWithFunctionEx,在该函数中调用 Mem_Alloc 申请 0x38 大小的堆,用于存放对象指针(pItem)、释放函数指针(pFreeFunction)等等。对象释放时会在Timeout_CleanupDelayedFreeList 函数中调用 FreeFunction,且参数为 pItem,不过这通常需要一段时间。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// DNS!RR_Free
  v4 = *(_WORD *)(v1 + 0xC);
  if ( v4 != 2 && v4 != 6 && (unsigned __int16)(v4 + 0xFF) > 1u || (v5 = *(_WORD *)(v1 + 0xA), v5 & 0x200) )
  { ...... }
  else
  {
        *(_WORD *)(v1 + 0xA) = v5 | 0x200;
        Timeout_FreeWithFunctionEx(v1, (__int64)RR_Free, (__int64)"nanoserver\\ds\\dns\\server\\server\\record.c", 168);
        _InterlockedAdd(&dword_1401CCF4C, 1u);
  }
 
//DNS!Timeout_FreeWithFunctionEx
  if ( rr_object )
  {
    v4 = (CHAR *)a3;
    v5 = (void *)rr_free;
    v6 = a4;
    v7 = (void *)rr_object;
    ......
    v8 = (DnsTimeoutObject *)Mem_Alloc(0x28, 7i64, (__int64)"nanoserver\\ds\\dns\\server\\server\\timeout.c", 1619);
    v9 = v8;
    if ( v8 )
    {
      v8->Tag = 0xDE1AEDFE;
      v8->pItem = v7;          //偏移0x8处设置需要释放的对象指针
      v8->pFreeFunction = v5;  // 偏移0x10处设置RR_FREE函数指针
      v8->pszFile = v4;
      v8->LineNo = v6;
 
//DNS!Timeout_CleanupDelayedFreeList
    if ( v5->pFreeFunction )
    {
      _InterlockedIncrement(&dword_1401DB464)
      ((void (__usercall *)(__int64 (*)(void)@<rcx>))v5->pFreeFunction)((__int64 (*)(void))v6->pItem);
      }
    else
    {
      Mem_Free((_QWORD *)v6->pItem, 0i64, 0i64, (__int64)"nanoserver\\ds\\dns\\server\\server\\timeout.c", 571);
    }

如果可以将 DnsTimeoutObject(0x38大小)分配到可控的位置,再利用溢出覆盖 pItem 和 pFreeFunction,就可以在对象释放时控制执行流程(允许执行带一个参数的函数)。而且,原始的 pFreeFunction 指向 dns!RR_Free,可通过将其泄露从而进一步获取 DNS 基址(这需要收集不同版本的偏移)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
0:021> db 17e`f599f630 l50
0000017e`f599f630  00 00 00 00 bb 07 50 00-ef 0c 0c 0c 0c 0c 0c fe  ......P.........
0000017e`f599f640  10 16 03 e7 7e 01 00 00-70 9f 02 e7 7e 01 00 00  ....~...p...~...
0000017e`f599f650  b0 4a e4 d8 f7 7f 00 00-b8 9f f6 d8 f7 7f 00 00  .J..............
0000017e`f599f660  fe ed 1a de a8 00 00 00-00 00 00 00 00 00 00 00  ................
0000017e`f599f670  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
 
0:021> u 7ff7`d8e44ab0    //偏移 0x10 处(不包括头部)的函数指针
dns!RR_Free:
00007ff7`d8e44ab0 4885c9          test    rcx,rcx
00007ff7`d8e44ab3 0f8434020000    je      dns!RR_Free+0x23d (00007ff7`d8e44ced)
00007ff7`d8e44ab9 48895c2408      mov     qword ptr [rsp+8],rbx
00007ff7`d8e44abe 48896c2410      mov     qword ptr [rsp+10h],rbp
00007ff7`d8e44ac3 4889742418      mov     qword ptr [rsp+18h],rsi
00007ff7`d8e44ac8 57              push    rdi
00007ff7`d8e44ac9 4154            push    r12
00007ff7`d8e44acb 4156            push    r14
 
0:021> db 17e`e7029f70-10  //偏移 0x8 处的一个 SOA 对象指针
0000017e`e7029f60  00 00 00 00 bb 22 8a 00-ef 0c 0c 0c 0c 0c 0c fe  ....."..........
0000017e`e7029f70  00 00 00 00 00 00 00 00-61 80 10 22 06 00 2d 00  ........a.."..-.
0000017e`e7029f80  ae 01 00 00 ae 01 00 00-00 00 00 00 01 00 00 00  ................
0000017e`e7029f90  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
0000017e`e7029fa0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
0000017e`e7029fb0  00 00 00 00 00 00 00 00-00 00 00 00 01 00 00 14  ................
0000017e`e7029fc0  03 04 73 70 5f 30 09 79-79 79 79 79 79 79 79 74  ..sp_0.yyyyyyyyt
0000017e`e7029fd0  03 66 75 6e 00 00 00 00-00 00 00 00 00 00 00 00  .fun............

先前,研究人员已经找到了 CFG 允许执行的且只需要一个参数的 dns!NsecDNSRecordConvert 函数,它可以将第一个参数偏移0x20处的字符串复制到新开辟的区域,这允许我们进行任意地址读取。我们可以通过设置 v1 偏移 0x28 处的数据使 Rpc_AllocateRecord 函数申请到我们可控的区域,然后再使用数据泄露思路进行读取。

 

 

为了获得 msvcrt!system 函数(CFG允许)的地址,可通过上述思路进一步泄露 msvcrt!memcpy 函数地址,然后根据偏移计算出 system 函数地址。但这还需要一个可控的参数地址,可通过连续释放两个对象,然后泄露后一个对象空间来获得堆指针,将 dns!_imp_memcpy 地址布局到该地址偏移 0x20 处。成功获取 msvcrt!system 函数地址后,采用同样的思路调用 system 可获得任意命令执行。

 

总结

Windows DNS Server 在处理特制的 SIG 响应包时,存在远程代码执行漏洞,未经身份验证的攻击者可通过向目标 DNS 服务器查询并响应 SIG 请求来利用此漏洞,成功利用此漏洞的远程攻击者可在目标系统上以 SYSTEM 账户权限执行任意代码。本文对此漏洞进行分析,并参考链接 [2-3] 中的漏洞利用思路,在实验环境下拿到了 SYSTEM SHELL,如下所示。PS:实验环境是指将 DNS 服务器转发器配置为攻击者控制的 NS 服务器。由于真实环境下的 DNS 服务器会处理大量请求,此思路可能无法很好地应用于实战。

 

参考链接

[1] https://msrc.microsoft.com/update-guide/vulnerability/CVE-2020-1350

 

[2] http://blog.diffense.co.kr/2020/12/04/Sigred.html

 

[3] https://datafarm-cybersecurity.medium.com/exploiting-sigred-cve-2020-1350-on-windows-server-2012-2016-2019-80dd88594228

 

[4] https://research.checkpoint.com/2020/resolving-your-way-into-domain-admin:-exploiting-a-17-year-old-bug-in-windows-dns-servers

 

[5] https://msrc.microsoft.com/update-guide/vulnerability/CVE-2020-1350

 

[6] https://mp.weixin.qq.com/s/4HBVvDuq_HWXop14ENUSuA


第五届安全开发者峰会(SDC 2021)议题征集正式开启!

收藏
点赞1
打赏
分享
最新回复 (3)
雪    币: 2498
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
mb_xghoecki 活跃值 2021-2-1 01:53
2
1
坛里有你更精 ,感谢分享
雪    币: 7436
活跃值: 活跃值 (6340)
能力值: ( LV12,RANK:210 )
在线值:
发帖
回帖
粉丝
pureGavin 活跃值 2 2021-2-1 08:46
3
0
感谢分享
雪    币: 535
活跃值: 活跃值 (112)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
枯叶孤舞 活跃值 2021-2-1 09:23
4
0
感谢分享
游客
登录 | 注册 方可回帖
返回