The Unpatched LSASS Remote Denial of Service (MS16-137)

In November 8, 2016 Microsoft released a security update for Windows Authentication Methods (MS16-137) which included 3 CVEs:

  1. Virtual Secure Mode Information Disclosure Vulnerability CVE-2016-7220
  2. Local Security Authority Subsystem Service Denial of Service Vulnerability CVE-2016-7237
  3. Windows NTLM Elevation of Privilege Vulnerability CVE-2016-7238

Talking specifically about CVE-2016-7237, this fix was applied to "lsasrv.dll", which affected the LSASS service.

The vulnerability affected all Windows versions, either 32 or 64 bits, and was reported and later described in more detail by Laurent Gaffié (@PythonResponder) the same day that the fix was published. He also published proof-of-concept (PoC) code triggering the vulnerability.

Here details about that:
http://g-laurent.blogspot.com.ar/2016/11/ms16-137-lsass-remote-memory-corruption.html

And here is the PoC:
https://github.com/lgandx/PoC/tree/master/LSASS

Using the same PoC, looks like this vulnerability can still be reproduced on Windows Server 2008 R2 and Windows 7:

Image
lsass-crash

Text

The bug

Basically, this vulnerability was reported as remote Denial of Service, where the crash is produced via a NULL-Pointer dereference.

When the LSASS service crashes, the target is automatically restarted after 60 seconds, which is not very nice when it's a production server.

Citing a part of the blog post:
"This allows an attacker to remotely allocate a huge chunk of memory, for a message never larger than 20000 chars ..."

Basically, this vulnerability is exploited via SMB1 or SMB2 through the "Session Setup Request" packet when the target receives a Security Blog with a huge "Simple Protected Negotiation" size.

We can see the following picture where the size is 0x80808080:

Image
server message block protocol - 80 80 80 80 80

Text

This size (evil size) is read by the "NegGetExpectedBufferLength" function, which is the child of "NegAcceptLsaModeContext":

Image
NegGetExpectedBufferLength

Text

If this function returns the value 0x90312 (SEC_I_CONTINUE_NEEDED), then the huge allocation will be made by the "LsapAllocateLsaHeap" function.

Image
7598a9e

Text

As this allocation is close to 4GB, this will probably fail.
If the allocation fails, one of the necessary conditions to reproduce the NULL-Pointer dereference will be reached.

Diffing

Diffing "lsasrv.dll" v6.1.7601.23545 Vs v6.1.7601.23571, we can see the CVE-2016-7237 fix in the "NegpBuildMechListFromCreds" function:

Image
diffing lsarv cve-2016-7237

Text

Basically, the fix checks that a structure pointer that contains a pointer to a CRITICAL_SECTION object won't be NULL.

There was a misunderstanding here about the vulnerability, because according to the PoC released by Laurent Gaffié, the problem WASN'T in the structure pointer, but rather in one field of the CRITICAL_SECTION object pointed by this structure, which is NULL when the huge allocation fails !

To be clear, the check of the NULL pointer should probably have been here:

Image
null pointer should have been here

Text

Triggering Windows 8.1/10 ?

Although the public PoC doesn't trigger the vulnerability in Windows 8.1 or Windows 10, the researcher and Microsoft declared these Windows versions as vulnerable.

Let see why.

As I said before, the "NegGetExpectedBufferLength" function reads the evil size from the SMB packet.

Now, this function has to return the 0x90312 value (SEC_I_CONTINUE_NEEDED) to produce the fail in the huge allocation.

Unfortunately, in the latest Windows versions, an extra check was added in this function which compares the evil size against 0xffff (64KB).

If the evil size is greater, this function won't return the 0x90312 value, but rather this will return the 0xC00000BB value (STATUS_NOT_SUPPORTED), which won't produce any allocation fail resulting in the vulnerability not being triggered.

On the other hand, if we use the evil size with a value less or equal than 0xffff (64KB), the allocation won't fail and again, the vulnerability won't be triggered.

So, why are Windows 8.1 and Windows 10 vulnerable?

Although the bug is triggered when a memory allocation fails, that doesn't mean that the allocation has to be giant, but rather that the LSASS service doesn't have enough available memory to allocate.

I had been able to confirm that this vulnerability can be triggered in Windows 7 and 2008 R2 by establishing several SMB connections and sending evil sizes with values like 0x1000000 (16 MB).

The problem is that in the case of the latest Windows versions, it's not possible to use this kind of sizes, because as I said before, the limit is 64KB.

So, the only way to trigger this vulnerability should be by producing a memory exhaustion in the LSASS service.

It may be possible to do so by finding a controllable malloc in the LSASS authentication process, creating multiple connections and producing a memory exhaustion until the "LsapAllocateLsaHeap" function fails.

Maybe, this memory exhaustion condition could be easily reached in local scenarios. 

Final notes

I realized that the fix wasn't working when I tried to understand why the public PoC wasn't working against Windows 10.

It's surprising to see that nobody else noticed that –that we know of-, and that a considerable amount of Windows users have been unprotected for more than 2 months since the public exploit was released.

As of January 10th, Microsoft decided to release a new security bulletin including a patch for the affected systems (MS17-004).

If we diff against the latest "lsasrv.dll" version (v6.1.7601.23642), we can see that the vulnerability was fixed by changing the "NegGetExpectedBufferLength" function.

Basically, the same 64KB packet size check used by Windows 8.1 and Windows 10 was now added to the rest of the Windows versions.

Image
lsarv version neggetexpectedbuggerlength