导语:在2016年12月,FortiGuard Labs发现并报告了Microsoft Windows Server中的WINS Server远程内存损坏漏洞。
漏洞概要
在2016年12月,FortiGuard Labs发现并报告了Microsoft Windows Server中的WINS Server远程内存损坏漏洞。在2017年6月,微软向FortiGuard实验室答复说:“要修复程序漏洞需要对代码进行全面彻底的检查,WINS所提供的功能会被DNS所取代,微软已经建议客户将其迁移出去。也就是说,由于修复漏洞所需要的工作量,Microsoft不会修补此漏洞。相反,Microsoft是建议用户使用DNS来替换WINS。
此漏洞影响的版本涉及到Windows Server 2008,2012和2016。之所以存在此漏洞是因为在处理格式错误的WINS数据包时会触发远程内存损坏。
在本文中,我想分享一下这个漏洞的一些细节。
漏洞复现
要复现此漏洞,请按照以下步骤操作。
1. 在受影响的Windows Server版本中的“服务器管理器”中安装“WINS服务器”。本文中,我们正在使用的是Windows Server 2016.按照“服务器管理器”的安装向导,然后选中“WINS服务器”选项。请参见图1中的操作。
图1.安装WINS Server服务
2. 打开“控制面板” – >“管理工具” – >“WINS”。然后导航到“WINS” – >“复制伙伴”,右键单击“复制伙伴”,然后选择“新建复制伙伴”。见下图中的操作。
图2.创建WINS复制伙伴
3. 在弹出的对话框中,输入WINS服务器IP地址,然后单击确定。请注意,WINS服务器的IP地址必须是攻击者的主机的IP地址。在我的测试中,IP地址为10.0.0.1。请参见图3中的操作。
图3.输入WINS服务器IP地址
4. 在另一台机器上,如Windows 7 x64(这台主机的IP地址必须是你在步骤3中输入的WINS服务器的IP地址),然后在CLI中运行PoC,如“trigger_poc.py”。发送数据包后,你就可以看到Windows Server 2016上的WINS Server服务会停止工作,或者会使用新的pid自动重新启动进程。你可以继续运行PoC,直到由于远程内存的损坏而导致WINS Server服务完全停止工作。捕获的攻击报文如图4所示。
图4.捕获的攻击报文
漏洞分析
存在此漏洞的根本原因在于Windows Server未正确处理多个挂起的WINS-Replication会话。所以让我们先来看看捕获的数据包。参见图5。
图5.攻击数据包中的数据
当使用复制命令WREPL_REPL_UPDATE2(0x00000005)处理多个(大于 3个)待处理的WINS-Replication会话时,就会触发此漏洞。它导致了“int 29h”,错误代码为0xC0000409。参见图6。
图6.“int 29h”,错误代码为0xC0000409
“int 29h”是在Windows 8或更新版本中发现的新的安全声明。它有许多断言。在这里,断言在伪代码中检查了以下条件:损坏的列表指针。
if (((Entry->Flink)->Blink) != Entry){ mov ecx,3 int 29h }
当我们建立了多个挂起的WINS-Replication会话时,列表指针将被破坏。根本原因是将同一个缓冲区多次释放到了列表池。请参阅以下代码:
.text:00007FF7F87E35D4 DeallocEnt proc near ; CODE XREF: CommAssocDeallocAssoc+2Ap .text:00007FF7F87E35D4 ; CommAssocDeallocDlg+2Ap .text:00007FF7F87E35D4 ; DATA XREF: ... .text:00007FF7F87E35D4 .text:00007FF7F87E35D4 arg_0 = qword ptr 8 .text:00007FF7F87E35D4 arg_8 = qword ptr 10h .text:00007FF7F87E35D4 arg_10 = qword ptr 18h .text:00007FF7F87E35D4 arg_20 = qword ptr 28h .text:00007FF7F87E35D4 arg_28 = qword ptr 30h .text:00007FF7F87E35D4 .text:00007FF7F87E35D4 mov [rsp+arg_0], rbx .text:00007FF7F87E35D9 mov [rsp+arg_8], rsi .text:00007FF7F87E35DE mov [rsp+arg_10], r8 .text:00007FF7F87E35E3 push rdi .text:00007FF7F87E35E4 sub rsp, 20h .text:00007FF7F87E35E8 mov rbx, r9 .text:00007FF7F87E35EB mov rsi, r8 .text:00007FF7F87E35EE mov rdi, rdx ---> rdi points to the head of the list (rdx, named entry A here) which equals sAssocQueHd global variable .text:00007FF7F87E35F1 mov rcx, r8 ; lpCriticalSection .text:00007FF7F87E35F4 call cs:__imp_EnterCriticalSection .text:00007FF7F87E35FA nop .text:00007FF7F87E35FB inc dword ptr [rbx] .text:00007FF7F87E35FD mov eax, [rbx] .text:00007FF7F87E35FF mov rbx, [rsp+28h+arg_20] ; ---> rbx points to the entry B, which will be deallocated .text:00007FF7F87E3604 mov [rbx+10h], eax .text:00007FF7F87E3607 mov rax, [rdi+8] .text:00007FF7F87E360B cmp [rax], rdi .text:00007FF7F87E360E jz short loc_7FF7F87E3617 .text:00007FF7F87E3610 mov ecx, 3 .text:00007FF7F87E3615 int 29h ; Win8: RtlFailFast(ecx) .text:00007FF7F87E3617 .text:00007FF7F87E3617 loc_7FF7F87E3617: ; CODE XREF: DeallocEnt+3Aj .text:00007FF7F87E3617 mov [rbx], rdi ---> entry B’s Blink points to entry A .text:00007FF7F87E361A mov [rbx+8], rax ---> entry B’s Flink points to entry C .text:00007FF7F87E361E mov [rax], rbx ----> set entry C’s Blink to entry B .text:00007FF7F87E3621 mov [rdi+8], rbx ----> set entry A’s Flink to entry B .text:00007FF7F87E3625 mov rdi, [rsp+28h+arg_28] .text:00007FF7F87E362A cmp dword ptr [rdi], 64h ...
在上面的列表中,Flink指针指向列表中的下一个复习,Blink指针指向列表中的上一个对象。
在PoC中,在会话1,2,3之后发送两个数据包并保持处于挂起状态(用过发送TCP重置数据包这两个会话不会被终止),首先使用B指针调用释放分配函数,例如0x1f11306ff70 。A(ListHead)指针始终等于全局变量sAssocQueHd。分配是在父函数中完成的。所以在B被释放之后,C将不存在于第一个释放列表中。关系如下表所示。
之后用B指针第二次调用释放分配函数,它仍然等于0x1f11306ff70。A(ListHead)等于全局变量sAssocQueHd。所以在B被释放之后,没有添加新的条目。条目关系如下图所示。
你可以清楚地看到, 释放分配函数被调用了两次,B中的列表元素Flink和Blink指向了它们自己。这会导致列表错误。
那么这个deallocate函数将被第三次调用。所以“int 29h”由于列表指针检查被破坏而触发。
if (((Entry->Flink)->Blink) != Entry) { mov ecx,3 int 29h }
在以下函数中处理带有Assoc_Ctx的WREPL_REPL_UPDATE2数据包时,对象指针将会设置为相同的指针:
.text:00007FF7F87E0190 ProcTcpMsg proc near ; CODE XREF: MonTcp+4C5p .text:00007FF7F87E0190 ; DATA XREF: .pdata:00007FF7F88077D4o .text:00007FF7F87E0190 ...... .text:00007FF7F87E0284 loc_7FF7F87E0284: ; CODE XREF: ProcTcpMsg+EDj .text:00007FF7F87E0284 mov ecx, [r15+4] ---> netlong here was obtained from the second packet (Wirehark parses it as "WINS-Replication WREPL_REPL_UPDATE2"), "Assoc_Ctx"="00 00 00 3f". .text:00007FF7F87E0288 call cs:__imp_ntohl .text:00007FF7F87E028E mov esi, eax ---> here esi=0x3f .text:00007FF7F87E0290 mov ecx, [r15+8] ; netlong ...... .text:00007FF7F87E0382 loc_7FF7F87E0382: ; CODE XREF: ProcTcpMsg+1EAj .text:00007FF7F87E0382 lea ecx, [rsi-1] .text:00007FF7F87E0385 mov rax, qword ptr cs:xmmword_7FF7F8804D28 .text:00007FF7F87E038C mov rbx, [rax+rcx*8] ; ---> here rbx = poi(7FF7F8804D28)+0x3e*8, because rcx is obtained from Assoc_Ctx=0x3f -1 .text:00007FF7F87E0390 xor esi, esi
从上面的代码段可以看出,一旦获得了对象指针poi(poi(7FF7F8804D28)+0x3e*8
,最终的目标指针(之前传递给条目B的指针)在第一次调用释放分配函数时是确定的,它被分配了B指针。在第二次和第三次调用释放函数时,最终的目标指针(入口B指针)保持不变。在我的测试中,它是1306 1306 ff70 f1。这将导致内存损坏。
注意:poi是获取地址的值的函数。
总而言之,该漏洞是由于复制命令WREPL_REPL_UPDATE2(这是触发此漏洞的关键)处理多个(> 3)挂起的WINS复制会话所导致的。其结果是相同的列表条目被多次释放,这就导致了远程内存损坏。
漏洞缓解措施
我们鼓励那些易受攻击的Microsoft Windows Server的所有用户立即从WINS服务器进行迁移。另外,针对此漏洞,已经部署了Fortinet IPS解决方案的企业组织已经通过MS.Windows.WINS.Server.Remote.Memory.Corruption签名得到保护。