Zerologon (CVE-2020–1472) — Turning Microsoft’s Patch to a Snort Rule (and a little extra)

Yoav Elata
Reflectiz
Published in
5 min readOct 19, 2020

--

Introduction

As part of my work I’m required to constantly be on the hunt for new threats and vulnerabilities in the cyber landscape. The most serious of these in the last couple of months was CVE-2020–1472, which was revealed to the public in August’s Patch Tuesday and received the appropriate name, “Zerologon”. The publication received a CVSS score of 10(!) and kept everyone in the cyber defence industry on their toes. The vulnerability was found by Tom Tervoort, a Senior Security Specialist at Secura, and a detailed report was published last month (https://www.secura.com/uploads/whitepapers/Zerologon.pdf).

There are two immediate measures one should take when addressing the challenge of protecting an organization against this sort of vulnerability. The first would be to patch it across the organization’s fleet as fast as possible, and the second would be to make sure any attempts to exploit it are detected. And that’s what the company that I consult did. Nevertheless, while patching went seamlessly, we experienced some hiccups in the detection front. I took it upon myself to come up with Snort rules that would aim at more specific IOCs within the packets’ payload so as to avoid raising any unnecessary alarms.

My obvious starting point was Secura’s whitepaper. The relevant requests to monitor are:

  1. NetrServerReqChallenge
  2. NetrServerAuthenticate3

Specially crafting both of these is necessary for the exploitation to succeed. The former contains the Client Challenge parameter that must contain 8 identical bytes (with some exceptions), and the latter the Sign\Seal flag that must be set to 0, as well as the eight 0x00 bytes of the Credential Challenge. I emphasized the “8 identical bytes” part as, while it can be derived from one of the paragraphs in Secura’s publication, it isn’t stated explicitly that the Client Challenge need not be eight 0x00 bytes for the attack to potentially succeed; any 8 identical bytes would work without reducing the probability of success.

Exploitation flowchart
Exploitation flowchart

Getting to Work

The two rules I wrote correspond to the two foregoing requests.

NetrServerReqChallenge

alert tcp any any -> $HOME_NET any (msg:"OS-WINDOWS Microsoft Windows Netlogon crafted NetrServerReqChallenge elevation of privilege attempt"; \
content:"|04 00|"; depth:2; offset:22; sid:xxxxxxxx; rev:1; \
detection_filter:track by_src, count 10, seconds 60; \
byte_jump:0,0,relative,from_end,post_offset -8; \
byte_extract:1,0,test,relative; \
byte_test:1,=,test,0,relative; \
byte_test:1,=,test,1,relative; \
byte_test:1,=,test,2,relative; \
byte_test:1,=,test,3,relative;)

​The rule looks for Opnum 4 located at offset 22 in the packet’s payload (Opnum 4 is the request ID of NetrServerReqChallenge).

Next, Snort jumps 8 bytes from the end of the payload, landing on the first byte of the Client Challenge:

The Client Credential is stored in the last 8 bytes of the NetrServerReqChallenge request

The byte_extract function takes the first of these 8 bytes and compares them to the next 4 bytes using byte_test, so in essence we are checking if the first 5 bytes are identical.

This is in line with the way Microsoft patched the vulnerability and with the finding that this attack can be carried out using a Client Challenge that is comprised of any 8 consecutive identical bytes.

Taken from https://blog.0patch.com/2020/09/micropatch-for-zerologon-perfect.html
From Tal Be’ery’s awesome post

NetrServerAuthenticate3

alert tcp any any -> $HOME_NET any (msg:"OS-WINDOWS Microsoft Windows Netlogon crafted NetrServerAuthenticate3 elevation of privilege attempt"; \ 
content:"|1a 00|"; depth:2; offset:22; sid:xxxxxxxx; rev:1; \
detection_filter:track by_src, count 10, seconds 60; \
byte_jump:0,0,from_end,post_offset -1; \
byte_test:1,=,0,0,relative,bitmask 0xC0; \
byte_jump:0,0,relative,post_offset -11; \
content:"|00 00 00 00 00 00 00 00|"; distance:0;)

​This rule focuses on the NetrServerAuthenticate3 request, which contains the Client Credential parameter that is supposedly encrypted by applying the ComputerNetlogonCredential function to the Client Challenge. I say “supposedly” as the Client Credential is manually set to 8 bytes of 0x00 as part of the exploitation.

The rule looks for Opnum 26 (0x1a), which is the request ID of NetrServerAuthenticate3 (also located at offset 22 in the packet’s payload).

Within this request the client can decide whether to set the Secure RPC flag (referred to as “sign&seal” in the whitepaper), which needs to be set to 0 for the exploitation to succeed. The negotiation flags are stored in the last 4 bytes of the payload and the Secure RPC flag (called “Authenticate RPC supported” in wireshark) is the last flag to be set (the payload is transferred in little endian but parsed in reverse by wireshark):

The corresponding hexadecimal representation of the last byte that designates the last three flags (in this specific instance) is 0x21=0b00100001.

A break down of the binary number to the corresponding flags looks like this:

  1. 0b00 — Secure RPC (aka Authenticated RPC supported)
  2. 0b10 — Authenticated RPC via lsass supported
  3. 0b0001 — AES supported

In the image above (which was extracted from a pcap file that captured a simulated Zerologon attack) we can see that the first flag (Secure RPC) is indeed not set (i.e. set to 0).

The way the rule tests whether this flag is set or not is by extracting the last byte of the payload (in this case the 0x21 byte), AND-bitmasking it to 0xC0(=0b11000000) and comparing the result to 0. We can see why this works:

00100001 //first 3 flags
11000000 //AND-bitmask
________
00000000 //result

If either the first or second bit are set to 1 the bitmask will not result in 0.

You might wonder why not simply run a content search for 0x212fffff. The reason is that this value is simply the settings of the standard flags observed from a Windows 10 client, with only the sign/seal flag disabled. Other clients can (and do) use other settings for the negotiation flags and therefore a more generic search is required.

The last step in the rule checks whether the Client Credential (which is stored in the 8 bytes preceding the negotiation flags) is set to 8 bytes of 0x00.

If both the conditions above are met, the alert will be triggered.

Update

After deployment the rules needed a little more tuning so below are the final, revised, versions:

NetrServerReqChallenge

alert tcp any any -> $HOME_NET any (sid:XXXXX; gid:1; flow:established,to_server; dce_iface:12345678–1234-ABCD-EF00–01234567CFFB; dce_opnum:4; content:”|04 00|”; offset:22; depth:2; byte_jump:0,0,relative,from_end,post_offset -8; byte_extract:1,0,test,relative; byte_test:1,=,test,0,relative; byte_test:1,=,test,1,relative; byte_test:1,=,test,2,relative; byte_test:1,=,test,3,relative; detection_filter:track by_src, count 10, seconds 60; msg:”(Local Rule)OS-WINDOWS Microsoft Windows Netlogon crafted NetrServerReqChallenge elevation of privilege attempt”; classtype:attempted-admin; rev:XXXX; )

NetrServerAuthenticate3

alert tcp any any -> $HOME_NET any (sid:XXXX; gid:1; flow:established,to_server; dce_iface:12345678–1234-ABCD-EF00–01234567CFFB; dce_opnum:26; content:”|1a 00|”; offset:22; depth:2; byte_jump:0,0,from_end,post_offset -1; byte_test:1,=,0,0,relative,bitmask 0xC0; byte_jump:0,0,relative,post_offset -11; content:”|00 00 00 00 00 00 00 00|”; distance:0; detection_filter:track by_src, count 10, seconds 60; msg:”(Local Rule)OS-WINDOWS Microsoft Windows Netlogon crafted NetrServerAuthenticate3 elevation of privilege attempt”; classtype:attempted-admin; rev:XXXX; )

--

--

Yoav Elata
Reflectiz

Cyber enthusiast, CSOC Manager and Senior Analyst at BugSec, B.A. in Philosophy, Cognitive science, and Computer science. Autodidacticism is a guiding principle