by RSS Honggang Ren  |  Jun 14, 2017  |  Filed in: Security Research

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.

  1. 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

  1. 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

  1. 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

  1. 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.

by RSS Honggang Ren  |  Jun 14, 2017  |  Filed in: Security Research