WINS Server Remote Memory Corruption Vulnerability in Microsoft Windows Server
Credit to Author: Honggang Ren| Date: Wed, 14 Jun 2017 16:19:29 +0000
Summary
In December 2016, FortiGuard Labs discovered and reported a WINS Server remote memory corruption vulnerability in Microsoft Windows Server. In June of 2017, Microsoft replied to FortiGuard Labs, saying, "a fix would require a complete overhaul of the code to be considered comprehensive. The functionality provided by WINS was replaced by DNS and Microsoft has advised customers to migrate away from it." That is, Microsoft will not be patching this vulnerability due to the amount of work that would be required. Instead, Microsoft is recommending that users replace WINS with DNS.
This vulnerability affects Windows Server 2008, 2012, and 2016 versions. The vulnerability exists because a remote memory corruption is triggered when handling malformed WINS packets.
In this blog, I want to share the details of this vulnerability.
Reproducing the Vulnerability
To reproduce this vulnerability, follow the steps below.
- Install "WINS Server" in "Server Manager" on an affected version of the Windows Server. For this case, we are using Windows Server 2016. Follow the "Server Manager" install wizard, then check the option "WINS Server." See the screenshot in Figure 1.
Figure 1. Installing WINS Server service
- Open "Control Panel" -> "Administrative Tools" -> "WINS." Then navigate to "WINS" -> "Replication Partners," right click "Replication Partners," and choose "New Replication Partner." See the screenshot in Figure
Figure 2. Creating WINS Replication Partner
- In the popup dialog, input the WINS server IP address and click OK. Please note, the WINS server IP address must be the attacker’s host IP address. In my test, the IP address is 10.0.0.1. See the screenshot in Figure 3.
Figure 3. Inputting the WINS Server IP address
- On another machine, such as Windows 7 x64 (its IP address must be the WINS server IP address you input in step 3), run the PoC in CLI, like "trigger_poc.py". After the packet is sent, you can see that the WINS Server service on Windows Server 2016 stops working or the process automatically restarts with a new pid. You can continue running the PoC until the WINS Server service stops working due to a remote memory corruption. Figure 4 shows the capture of the attack packets.
Figure 4. The capture of the attack packets
Analysis
This vulnerability exists because Windows Server doesn’t properly deal with multiple pending WINS-Replication sessions. So let’s take a look at the packet capture first. See Figure 5.
Figure 5. The data in the attack packet
The vulnerability is triggered when dealing with multiple (>3) pending WINS-Replication sessions with replication command WREPL_REPL_UPDATE2 (0x00000005). It results in “int 29h” with error code 0xC0000409. See Figure 6.
Figure 6. “int 29h” with error code 0xC0000409
The “int 29h” is the New Security Assertion found in Windows 8 or newer versions. It has many assertion items. Here, the assertion checks the following condition in pseudo code: corrupted list pointers.
if (((Entry->Flink)->Blink) != Entry) { mov ecx,3 int 29h }
When we establish multiple pending WINS-Replication sessions, the list pointers become corrupted. The root cause is that the same buffer is deallocated to the list pool multiple times. See the following code:
.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 ...
In the above list, the Flink pointer points to the next entry in the list, and the Blink pointer points to the previous entry in the list.
In the PoC, after session 1,2,3 send two packets and stay in the pending state (the session is not terminated by a TCP reset packet), the deallocate function will be first called with the entry B pointer, for example, 0x1f11306ff70. The entry A (ListHead) always equals the sAssocQueHd global variable. The assignment is done in the parent function. So after entry B is deallocated, entry C doesn’t exist in the first deallocation. The entry relationship is shown in following chart.
Then the deallocate function will be called the second time with entry B pointer, which still equals 0x1f11306ff70. Entry A (ListHead) equals the sAssocQueHd global variable. So after entry B is deallocated, no new entry is added. The entry relationship is shown in the following chart.
As you can clearly see, after the deallocate function is called twice, both the Flink and Blink of List Element Entry B point to themselves. This results in the list error.
Then the deallocate function will be called the third time. So “int 29h” is triggered due to the corrupted list pointer check.
if (((Entry->Flink)->Blink) != Entry) { mov ecx,3 int 29h }
The object pointer is set to the same pointer when dealing with Assoc_Ctx of WREPL_REPL_UPDATE2 packet in the following function:
.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
From the above code snippet you can see that once the object pointer poi(poi(7FF7F8804D28)+0x3e*8 is obtained, the final target pointer (the previously passed entry B pointer) is determinate in the first deallocate function call. It is assigned to entry B pointer. In the second and third deallocate function calls, the final target pointer (entry B pointer) keeps same. In my test, it is 000001f1`1306ff70. This causes the list corruption.
Note: poi is a function of getting the value of the address.
In summary, the vulnerability is caused by multiple (>3) pending WINS-Replication sessions with the replication command WREPL_REPL_UPDATE2 (this is the key to triggering this vulnerability.) The result is that the same list entry is deallocated multiple times, which causes remote memory corruption.
Mitigation
All users of vulnerable Microsoft Windows Server are encouraged to migrate away from the WINS server immediately. Additionally, organizations that have deployed Fortinet IPS solutions are already protected from this vulnerability with the signature MS.Windows.WINS.Server.Remote.Memory.Corruption.