Eternal Synergy Exploit Analysis | MSRC Blog
引言
近期我们宣布了一系列博客文章,深入分析ShadowBrokers在2017年4月发布的漏洞利用程序;特别是其中一些较少被研究的漏洞利用。本周我们将研究Eternal Synergy,这是一个SMBv1认证漏洞利用。这个漏洞特别有趣,因为许多利用步骤纯粹基于数据包操作,而非本地shellcode执行。与其他SMB漏洞一样,该漏洞也在MS17-010中作为CVE-2017-0143得到修复。该漏洞利用适用于Windows 8及更早版本,但无法按原样在新平台上工作。
本文包含四个主要部分。我们将深入探讨漏洞原理,随后讨论如何将漏洞武器化以创建读/写/执行原语,这些原语在整个漏洞利用中被用作构建块。接下来,我们将逐步讲解EternalSynergy的执行过程,看看这些原语如何被用于实现完整的漏洞利用。最后,我们将简要讨论近期缓解措施对所述利用技术的影响。
漏洞:CVE-2017-0143
该漏洞的根本原因在于确定SMB消息是否属于事务的一部分时,未考虑消息的命令类型。换句话说,只要SMB头部的UID、PID、TID和OtherInfo字段与相应事务字段匹配,该消息就会被视为该事务的一部分。
通常,OtherInfo字段存储MID。然而,对于SMB_COM_WRITE_ANDX消息,它存储的是FID。这造成了潜在的消息类型混淆:给定一个现有的SMB_COM_WRITE_ANDX事务,一个MID等于事务FID的传入SMB消息将被包含在该事务中。
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
|
PTRANSACTION
SrvFindTransaction (
IN PCONNECTION Connection,
IN PSMB_HEADER Header,
IN USHORT Fid OPTIONAL
)
{
...
//
// 如果这是一个多片段事务SMB,所有片段的MID必须匹配。
// 如果是多片段WriteAndX协议,片段使用FID。
//
if (Header->Command == SMB_COM_WRITE_ANDX) {
targetOtherInfo = Fid;
} else {
targetOtherInfo = SmbGetAlignedUshort( &Header->Mid );
}
...
//
// 遍历事务列表,寻找与新事务具有相同标识的事务。
//
for ( listEntry = Connection->PagedConnection->TransactionList.Flink;
listEntry != &Connection->PagedConnection->TransactionList;
listEntry = listEntry->Flink ) {
thisTransaction = CONTAINING_RECORD(
listEntry,
TRANSACTION,
ConnectionListEntry
);
if ( ( thisTransaction->Tid == SmbGetAlignedUshort( &Header->Tid ) ) &&
( thisTransaction->Pid == SmbGetAlignedUshort( &Header->Pid ) ) &&
( thisTransaction->Uid == SmbGetAlignedUshort( &Header->Uid ) ) &&
( thisTransaction->OtherInfo == targetOtherInfo ) ) {
...
// 找到了具有相同标识的事务
...
}
...
}
|
利用
当SMB消息到达时,相应的处理程序会将其内容复制到对应的事务缓冲区,即InData。SMB_COM_TRANSACTION_SECONDARY处理程序假设InData地址指向缓冲区的起始位置。
1
2
3
4
5
6
7
|
if ( dataCount != 0 ) {
RtlMoveMemory(
transaction->InData + dataDisplacement,
(PCHAR)header + dataOffset,
dataCount
);
}
|
然而,对于SMB_COM_WRITE_ANDX事务,每次接收到该事务的SMB消息时,InData地址都会更新为指向现有数据的末尾。
1
2
3
4
5
6
7
|
RtlCopyMemory(transaction->InData, writeAddress, writeLength );
//
// 更新事务数据指针到下一个WriteAndX数据缓冲区的位置。
//
transaction->InData += writeLength;
|
利用数据包混淆,攻击者可以将SMB_COM_TRANSACTION_SECONDARY消息插入到SMB_COM_WRITE_ANDX事务中。在这种情况下,InData将指向缓冲区起始位置之后,因此SMB_COM_TRANSACTION_SECONDARY处理程序在复制传入消息数据时会溢出缓冲区。
接管事务
漏洞利用中使用的RWX原语的构建块通过利用前一节描述的消息混淆来接管事务结构。首先,通过SMB_COM_TRANSACTION客户端消息分配一个“控制”事务。
1
2
3
4
5
6
7
8
9
10
11
12
|
kd> dt srv!TRANSACTION 0xfffff8a00167f010
...
+0x080 InData : 0xfffff8a0`0167f110
+0x088 OutData : (null)
...
+0x0a4 DataCount : 0x0
+0x0a8 TotalDataCount : 0x5100
...
+0x0ba Tid : 0x800
+0x0bc Pid : 0xab9e
+0x0be Uid : 0x800
+0x0c0 OtherInfo : 0x4000
|
然后,发送一个精心构造的SMB_COM_WRITE_ANDX消息,以利用数据包混淆。结果,控制事务的InData指针被破坏,指向缓冲区起始位置之后。在这种情况下,偏移了0x200字节。
1
2
3
4
5
6
7
8
9
10
11
12
|
kd> dt srv!TRANSACTION 0xfffff8a00167f010
...
+0x080 InData : 0xfffff8a0`0167f310
+0x088 OutData : (null)
...
+0x0a4 DataCount : 0x200
+0x0a8 TotalDataCount : 0x5100
...
+0x0ba Tid : 0x800
+0x0bc Pid : 0xab9e
+0x0be Uid : 0x800
+0x0c0 OtherInfo : 0x4000
|
接下来,向同一事务发送SMB_COM_TRANSACTION_SECONDARY消息,并利用损坏的InData指针修改相邻的“受害者”事务。我们稍后重新讨论目标写入地址的计算方法。
1
2
3
4
5
6
7
|
if ( dataCount != 0 ) {
RtlMoveMemory(
transaction->InData + dataDisplacement,
(PCHAR)header + dataOffset,
dataCount
);
}
|
传入消息的dataDisplacement足够大,可以到达相邻事务。
1
2
|
kd> dv dataDisplacement
dataDisplacement = 0x5020
|
具体来说,它将用攻击者控制的值(在这种情况下为0)覆盖事务的OtherInfo字段,以便所有未来使用MID=0发送的消息都将定向到受害者事务。下面我们看到覆盖发生前的受害者事务。
1
2
3
4
5
6
7
8
9
|
kd> dt srv!TRANSACTION 0xfffff8a00167f310+0x5020-0xc0
...
+0x080 InData : 0xfffff8a0`0168436c
+0x088 OutData : 0xfffff8a0`01684ffc
...
+0x0ba Tid : 0x800
+0x0bc Pid : 0xab9f
+0x0be Uid : 0x800
+0x0c0 OtherInfo : 0x8ccb
|
接管受害者事务后,漏洞利用可以可预测地继续破坏同一或其他事务中的字段,并通过向事务发送消息可靠地触发它们。请注意,要使此技术工作,攻击者必须能够可预测地分配一对相邻事务。
远程任意写入原语
任意写入原语允许攻击者修改受害者系统上的内存内容,并作为此漏洞利用中使用的其余技术的基础。为了破坏内存,它利用了前一节中描述的技术。具体来说,写入原语分两步构建:
在步骤#1中,受害者事务InData缓冲区地址被覆盖,使其指向目标写入地址。
接下来在步骤#2中,攻击者可以通过向受害者事务发送消息来覆盖任意内核内存。接收到消息后,其内容将被复制到InData缓冲区;然而,在我们的情况下,缓冲区地址已被破坏,因此内容被复制到攻击者控制的地址。下面是一个示例数据包,其中“额外字节参数”中包含的shellcode将被复制到受害者机器上。
远程任意读取原语
任意读取原语允许攻击者从目标系统远程读取任何内存地址的内容。要使用此原语,攻击者必须成功:
接管连接,并建立写入原语,如上所述。
泄漏有效的TRANSACTION结构
如图6所示,我们有一个控制事务与用于写入原语的Victim#1事务相邻,以及一个Victim#2事务。Message#1使用写入原语破坏Victim#1 InData缓冲区地址,使其指向Victim#2基地址。这意味着任何定向到Victim#1事务的消息都将导致在消息的“数据位移”字段指定的偏移处破坏Victim#2事务。Victim#2是泄漏的TRANSACTION结构,其基地址可以通过其内容推断。
OutData
其余消息包含Transaction Secondary命令(0x26),并使用相同的TID、PID和UID。消息#2-5目标Victim#1事务(MID=0)并执行Victim#2事务特定字段的覆盖。下表总结了每条消息所做的修改:
消息# |
偏移量 |
事务字段 |
覆盖值 |
5 |
0x001 |
BlockHeader.State |
0x2 |
3 |
0x054 |
Timeout |
0x40000023 |
2 |
0x088 |
OutData |
攻击者指定地址(例如0xfffff88004b78150) |
2 |
0x090 |
SetupCount |
0x4 |
2 |
0x094 |
MaxSetupCount |
0x0 |
2 |
0x098 |
ParameterCount |
0x0 |
2 |
0x09c |
TotalParameterCount |
0x0 |
2 |
0x0a0 |
MaxParameterCount |
0x10 |
2 |
0x0a4 |
DataCount |
0x0 |
2 |
0x0a8 |
TotalDataCount |
0x0 |
2 |
0x0ac |
MaxDataCount |
0x20000 |
4 |
0x0e3 |
Executing |
0x0 |
图7 - 在任意读取操作期间被破坏的Victim#2事务字段列表
作为示例,下面是发送以执行远程读取操作的消息。有效负载在“额外字节参数”中指定,目标地址在“数据位移”中,大小在“数据计数”字段中。
消息#5是一个虚拟数据包,具有非零MID目标Victim#2事务,发送以触发服务器响应。在该响应消息期间,被破坏的DataOut指针的内存地址内容被复制并发送回SMB客户端。此类消息的示例如下:
代码执行
前一节讨论的技术操作于内存;漏洞利用仍然需要一种方式来改变控制流并以可重复的方式调用执行。漏洞利用通过破坏SMB消息处理程序的指针来实现这一点。
首先,它使用写入原语覆盖srv!SrvTransaction2DispatchTable的条目0xe,使其指向执行目标地址。这是一个包含SMB消息处理程序指针的调度表。这个特定条目,目标TRANS2_SESSION_SETUP子命令处理程序,很方便,因为它未实现,因此不被“正常”SMB流量使用。下一节将介绍如何发现此全局指针并将其泄漏回攻击者。
接下来,向受害者发送类型为SMB_COM_TRANSACTION2且子命令设置为TRANS2_SESSION_SETUP的消息,触发被破坏的函数指针的执行。此消息的目标事务不重要。示例如下:
整体组合
在本节中,我们将逐步讲解漏洞利用,看看上述构建块如何结合以实现远程内核代码执行。
在此阶段,从受害者机器泄漏TRANSACTION结构。此结构以两种方式使用。首先,它包含指针(例如EndpointSpinLock),作为发现其他有用地址的基础。其次,它被用作Victim#2事务,因为为了构建读取原语,攻击者需要有效TRANSACTION结构的详细信息。用于泄漏指针的方法类似于Eternal Champion漏洞利用中描述的方法。
下面是包含泄漏池内存的SMB_COM_TRANSACTION消息的内容。泄漏的TRANSACTION结构从偏移0xb0开始。我们可以看到,除其他外,它包含事务TID、PID、UID和OtherInfo。此外,诸如InData(偏移0x130)之类的指针允许攻击者确定事务的基本内存地址。
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
|
0000 ff 53 4d 42 a0 00 00 00 00 98 03 c0 00 00 00 00 .SMB............
0010 00 00 00 00 00 00 00 00 00 08 37 ca 00 08 56 15 ..........7...V.
0020 12 00 00 00 04 00 00 00 c0 10 00 00 00 00 00 00 ................
0030 48 00 00 00 04 00 00 00 58 01 00 00 48 00 00 00 H.......X...H...
0040 b8 10 00 00 00 59 01 00 fc 84 36 3a 10 77 98 5a .....Y....6:.w.Z
0050 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0060 00 01 02 03 46 72 61 67 00 00 00 00 00 00 00 00 ....Frag........
0070 20 51 00 00 00 00 00 00 00 00 00 00 00 00 00 00 Q..............
0080 02 01 01 00 46 72 65 65 00 00 00 00 00 00 00 00 ....Free........
0090 01 01 eb 03 4c 53 74 72 30 a1 07 00 83 fa ff ff ....LStr0.......
00a0 8c 0e 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00b0 0c 02 8c 0e 00 00 00 00 d0 2d e6 00 83 fa ff ff .........-......
00c0 90 bb e5 00 83 fa ff ff d0 2d 46 02 a0 f8 ff ff .........-F.....
00d0 d0 8d 41 02 a0 f8 ff ff 48 00 56 02 a0 f8 ff ff ..A.....H.V.....
00e0 48 c0 55 02 a0 f8 ff ff 00 00 00 00 00 00 00 00 H.U.............
00f0 00 00 02 00 00 00 00 00 68 b2 57 02 a0 f8 ff ff ........h.W.....
0100 6d 39 00 00 ff ff ff ff 00 00 00 00 00 00 00 00 m9..............
0110 6c b2 57 02 a0 f8 ff ff 00 00 00 00 00 00 00 00 l.W.............
0120 6c b2 57 02 a0 f8 ff ff fc b2 57 02 a0 f8 ff ff l.W.......W.....
0130 6c b2 57 02 a0 f8 ff ff fc bf 57 02 a0 f8 ff ff l.W.......W.....
0140 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0150 00 0d 00 00 00 00 00 00 90 00 00 00 00 00 00 00 ................
0160 00 00 00 00 01 01 00 00 00 00 00 0837 ca 00 08 ............7...
0170 5a 15 00 00 00 00 00 00 00 00 00 00 00 00 00 00 Z...............
0180 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0190 01 01 01 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
|
发送一系列SMB_COM_TRANSACTION消息以分配一对相邻的控制-受害者事务。具体来说,“groom”数据包包含精心构造的SMB消息以创建数据包混淆,换句话说,有资格成为控制事务。“Bride”数据包创建候选用于破坏的事务,即受害者事务。
漏洞利用控制一个相邻的受害者事务,用于R/W原语。
多次执行读取原语,以发现srv!SrvTransaction2DispatchTable全局指针的位置,用于触发shellcode执行。
再次多次执行读取原语以发现ntoskrnl.exe的基址。上面找到的RWX内存用作暂存页,其中写入和执行shellcode并存储返回值。此页面存在是因为ntoskrnl.exe中有一个RWX节