Wednesday, February 2, 2022

Powershell Empire - Detection with Zeek

Transitioning to Zeek, let's now see what we can learn from a quick analysis. First up, the version of Zeek I'm using.

┌──(root💀securitynik)-[/tmp/zeek]
└─# zeek4 --version
zeek4 version 4.2.0-dev.448

Let's look at this from 3 different perspectives. First, let's simply run Zeek and take a look at the logs that have been created based on our Indicators of Compromise (IoC). Running Zeek against the pcap file.

┌──(root💀securitynik)-[/tmp/zeek]
└─# zeek --no-checksums -r /home/securitynik/packets/empire-full-session.pcap 

Looking at the logs created.

┌──(root💀securitynik)-[/tmp/zeek]
└─# ls *.log
conn.log  dns.log  files.log  http.log  packet_filter.log  pe.log  reporter.log  ssh.log  weird.log

In a production environment, you will have lots more logs. So we don't want to go through all the logs. Let's leverage grep to find our IoCs..

┌──(root💀securitynik)-[/tmp/zeek]
└─# grep --perl-regexp "\/(?i)(news|login|admin).*?php" *.log --color=always | more
...
http.log:1637431443.514058      CcC0YB2heOkR9Gnpi4      10.0.0.110      1650    10.0.0.107      443     3       POST    10.0.0.107
/news.php       -       1.1     securitynik-launcher-bat-User-Agent     -       206     44506   200     OK      -       -       (empty) -       -       -     F4wiHG1bpwW03v71vh       -       -       FghBlt1qKU0utYSHd7      -       -
http.log:1637431506.697538      CcC0YB2heOkR9Gnpi4      10.0.0.110      1650    10.0.0.107      443     4       GET     10.0.0.107
/admin/get.php  -       1.1     Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko    -       0       1279    200     OK      -       -     (empty)  -       -       -       -       -       -       FvSNLf3mGluOXZ5Qu       -       text/html
http.log:1637431566.836488      CcC0YB2heOkR9Gnpi4      10.0.0.110      1650    10.0.0.107      443     5       GET     10.0.0.107
/login/process.php      -       1.1     Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko    -       0       1279    200     OK      -     -(empty) -       -       -       -       -       -       F6xbg04XsiJmRygrwc      -       text/html
...

At this point, we identify Zeek has logs pertaining to this activity. Let's figure out which logs this activity is found in.

┌──(root💀securitynik)-[/tmp/zeek]
└─# grep --perl-regexp "\/(?i)(news|login|admin).*?php" *.log | awk --field-separator ':' '{ print $1 }' | sort | uniq --count | sort --numeric --reverse      
   1807 http.log

Looks like activity was only seen in the http.log. Focusing here a bit more, extracting a few key fields.


┌──(root💀securitynik)-[/tmp/zeek]
└─# zeek-cut -d ts uid id.orig_h id.orig_p id.resp_h id.resp_p method host uri  < http.log | grep --perl-regexp "\/(?i)(news|login|admin).*?php"
ts                                  uid                 id.orig_h   id.orig_p   id.resp_h   id.resp_p  method  host             uri
....
2021-11-20T20:21:40-0500        Cp6N4630HCYa17LUa8      10.0.0.110      1697    10.0.0.107      443     GET     10.0.0.107      /news.php
2021-11-20T20:22:20-0500        CwazkE3YeyHUnSxQ7f      10.0.0.110      1723    10.0.0.107      443     GET     10.0.0.107      /admin/get.php
2021-11-20T20:22:38-0500        C9qPFB1lfO6u42p4G3      10.0.0.110      1699    10.0.0.107      443     GET     10.0.0.107      /login/process.php
...

Picking on the first UID, to see where else activity has been identified.

┌──(root💀securitynik)-[/tmp/zeek]
└─# grep "Cp6N4630HCYa17LUa8" *.log | cut --fields 1 --delimiter ":" | sort | uniq --count | sort --numeric --reverse
    315 files.log
    311 http.log
      1 conn.log

Looking at the 1 entry in the conn.log, while there is a lot to see, extracting a few fields and looking at the duration, shows this activity lasted 18437.626252 seconds or 307 hours or 5 days.

┌──(root💀securitynik)-[/tmp/zeek]
└─# zeek-cut -d ts uid id.orig_h id.orig_p id.resp_h id.resp_p proto service duration orig_bytes resp_bytes < conn.log | grep "C9qPFB1lfO6u42p4G3"                                           
2021-11-20T18:02:04-0500        C9qPFB1lfO6u42p4G3      10.0.0.110      1699    10.0.0.107      443     tcp     http    18437.626252    55444   462342

Looking at the files.log, I did not really find anything of interest. 

┌──(root💀securitynik)-[/tmp/zeek]
└─# grep "Cp6N4630HCYa17LUa8" files.log | more
1637449322.213963       FbvsAO3tTXJzOyuaj4      10.0.0.107      10.0.0.110      Cp6N4630HCYa17LUa8      HTTP    0       (empty) text/html       -       0.000479        -       F       1279    1279    0       0       F       -       -       -       -       -       -
        -
1637449382.306875       FkJSID2jB0CMQfrcZb      10.0.0.107      10.0.0.110      Cp6N4630HCYa17LUa8      HTTP    0       (empty) text/html       -       0.000484        -       F       1279    1279    0       0       F       -       -       -       -       -       -
        -
1637449442.421711       F4DJ7Z1wfsWwZ0INka      10.0.0.107      10.0.0.110      Cp6N4630HCYa17LUa8      HTTP    0       (empty) text/html       -       0.000375        -       F       1279    1279    0       0       F       -       -       -       -       -       -
        -
1637449502.518040       Fnn8vklGr2q1mTGt2       10.0.0.107      10.0.0.110      Cp6N4630HCYa17LUa8      HTTP    0       (empty) text/html       -       0.000484        -       F       1279    1279    0       0       F       -       -       -       -       -       -

....

Additionally, no files were actually extracted as can be seen by the absenece of the extracted folder.

┌──(root💀securitynik)-[/tmp/zeek]
└─# ls -l extracted_files/
ls: cannot access 'extracted_files/': No such file or directory

We can spend more time poking around. However, the objective was to do some basic detection and we have achieved that. 

Second, let's take a look at a Zeek signature to detect our IoC. 

While Zeek can be used for signature detection, it is not the recommended way to use Zeek. However, since it does provide the capability to perform some basic signature based detection, then let's take advantage of this feature.

Couple of different ways to do this. Let's take one.

# This signature is about detecting PowerShell Empire in my lab

signature powershellEmpire
    {
	# look into the IP Header at offset 6 for tcp 
	header ip[9] == 0x06
			
        # Look for the soruce address representing out protected network
	# Look at the IP header, start at offset 12 and span 4 bytes for the
	# the source IP
	header ip[12:4] == 10.0.0.110/32
			
	# Look for the destination address 
	# Look at the IP header, start at offset 15 and span 4 bytes for the
	# the destination IP			
	# Consider anything going to none 10.0.0.0/32 destinations as something we don't own.
	header ip[16:4] != 10.0.0.110/32
			
	# Look for destination port 443
	header tcp[2:2] == 443
		
	# Look for the ASCII strings news or admin or login followed by php
	http /.*(news|admin|login).*\.php/
			
	# Ensure the 3-way handshake has completed and
	# The traffic is coming from an originator
	tcp-state established,originator 
			
	# Generate an event
	event "SUSPICIOUS POSSIBLE Powershell Empire Activity"
		
    }

Running Zeek

┌──(root💀securitynik)-[/tmp]
└─# zeek --readfile /home/securitynik/packets/empire-full-session.pcap --rulefile /home/securitynik/packets/powershell.sig --no-checksums

Verifying the files created 

┌──(root💀securitynik)-[/tmp]
└─# ls *.log
conn.log  dns.log  files.log  http.log  notice.log  packet_filter.log  pe.log  signatures.log  ssh.log  weird.log

Taking at look at the signature.log file, we see ...

┌──(root💀securitynik)-[/tmp]
└─# zeek-cut -d -m < signatures.log                                                                                                                            
ts      uid     src_addr        src_port        dst_addr        dst_port        note    sig_id  event_msg       sub_msg sig_count       host_count
2021-11-20T13:03:59-0500        CskRYg2jaNXRC0fLy8      10.0.0.110      1650    10.0.0.107      443     Signatures::Sensitive_Signature powershellEmpire      10.0.0.110: SUSPICIOUS POSSIBLE Powershell Empire Activity       /news.php       -       -
2021-11-20T16:03:25-0500        Ckr2TD12vYGon6LR58      10.0.0.110      1686    10.0.0.107      443     Signatures::Sensitive_Signature powershellEmpire      10.0.0.110: SUSPICIOUS POSSIBLE Powershell Empire Activity       /admin/get.php  -       -
2021-11-20T16:51:53-0500        CI3BQ34gmAYusuQ6Yf      10.0.0.110      1689    10.0.0.107      443     Signatures::Sensitive_Signature powershellEmpire      10.0.0.110: SUSPICIOUS POSSIBLE Powershell Empire Activity       /login/process.php      -       -
2021-11-20T17:12:48-0500        CT0hHi3BBoldu0NY3e      10.0.0.110      1690    10.0.0.107      443     Signatures::Sensitive_Signature 
...

Looking at the notice.log we see ...

┌──(root💀securitynik)-[/tmp]
└─# zeek-cut -d -m < notice.log | more
ts      uid     id.orig_h       id.orig_p       id.resp_h       id.resp_p       fuid    file_mime_type  file_desc       proto   note    msg     sub     src   dst      p       n       peer_descr      actions suppress_for    remote_location.country_code    remote_location.region  remote_location.city    remote_location
.latitude       remote_location.longitude
2021-11-20T13:03:59-0500        CskRYg2jaNXRC0fLy8      10.0.0.110      1650    10.0.0.107      443     -       -       -       tcp     Signatures::Sensitive_S
ignature        10.0.0.110: SUSPICIOUS POSSIBLE Powershell Empire Activity      /news.php       10.0.0.110      10.0.0.107      443     -       -       Notice:
:ACTION_LOG     3600.000000     -       -       -       -       -
...


Third, Zeek Scripting 

Now that we have used a signature to detect our Powershell Empire activity, let's now get to the more laborious but better way of using Zeek. Specifically it's scripting capabilities.

# Define the modules to use
@load base/files/extract
@load base/files/hash
@load base/frameworks/notice
@load base/frameworks/logging
@load base/protocols/conn
@load base/protocols/http


module PSEmpire;

export {
	global hit_count: int = 0;
	global compromised_psempire_hosts: set[addr] = { } &redef &write_expire=7 days;
	global empire_file_names: pattern = /news|admin|process/;		
		
	# Setup our notice capabilities
	redef enum Notice::Type += { suspicious_psempire_compromised_host };
	redef Notice::type_suppression_intervals += { [suspicious_psempire_compromised_host] = 1day };
		
	# Setup our own log
	redef enum Log::ID += { PSEmpire::LOG };
		
	option msg = "Say what you want to see";		
				
	# Define the log the fields for the PSEmpire log records
	type Info: record 
	    {
		ts: time &log;		# Add a timestamp
		src_host: addr &log;	# Add the source IP		
		src_port: port &log;	# Add the source port
		dst_host: addr &log;	# Add the destination IP
		dst_port: port &log;	# Add the destination port
		msg: string &log;	# Add the message info
	    };		
			
	}


# Execute this when zeek starts up
event zeek_init()
    {
	print "[*] Zeek has started ... ";
	print "[*] Initializing the log stream ... ";
	Log::create_stream(LOG, [$columns=Info, $path="PSEmpire"]);
	print "-----------------------------------------------------";		
	print "[*] Hunting for POWERSHELL EMPIRE Activity ... ";
	print "-----------------------------------------------------";
    }


# Check the HTTP Request
event http_request(c: connection, method: string, original_URI: string, unescaped_URI: string, version: string)
    {
	# print fmt("[*] HTTP Request structure: METHOD:%s | ORIGINAL_URI:%s | UNESCAPED_URI:%s", method, original_URI, unescaped_URI);
					
	if (c$id$orig_h == 10.0.0.110 && c$id$resp_p == 443/tcp && empire_file_names in original_URI)
            {
		PSEmpire::hit_count += 1;
		print fmt("[*] SUSPICIOUS POWERSHELL EMPIRE ACTIVITY %s:%s -> %s:%s", c$id$orig_h, c$id$orig_p, c$id$resp_h, c$id$resp_p);
		add compromised_psempire_hosts[c$id$orig_h];						
							
		# Write information to our log file
		    Log::write(LOG, Info(
		    $ts=network_time(),
		    $src_host = c$id$orig_h,
		    $src_port = c$id$orig_p,
		    $dst_host = c$id$resp_h,
		    $dst_port = c$id$resp_p,
		    $msg = "PowerShell Empire Activity Found!"
		));			

		# Raise our notice
		NOTICE([ 
		    $note=suspicious_psempire_compromised_host,
		    $msg = "SUSPICIOUS POWERSHELL EMPIRE ACTIVITY",
		    $sub = "A host is suspected to be infected with Powershell Empire CnC",
		    $conn = c,
		    $suppress_for = 60min,
		    $identifier = cat(c$id$orig_h,c$id$orig_p,c$id$resp_h,c$id$resp_p)																	
		    ]);
		}
	}

	
# Get the file Hashes and extract the files
event file_new(f: fa_file)		
    {
	if (f$source == "HTTP")
	{					
    	    for ( cid, cconns in f$conns )
	    {
		# print cconns$http$uri;
		if ( empire_file_names in cconns$http$uri  && cid$orig_h in compromised_psempire_hosts && cid$resp_h != 10.0.0.110 )
		    {
			local f_name = "EMPTY";
			local f_name_array = split_string(cconns$http$uri, /\//);   # Split the string by /
						
			if (|f_name_array| == 3)
			    f_name = f_name_array[2];
			else
			    f_name = f_name_array[1];

			local extracted_filename = fmt("%s-%s-%s", f$source, f$id, f_name );
			print fmt("   \\->  Extracted file: %s", extracted_filename );
					
			# Get the SHA256 Hashes of the files
			Files::add_analyzer(f, Files::ANALYZER_SHA256);
					
			# Extract the file
			Files::add_analyzer(f, Files::ANALYZER_EXTRACT, [$extract_filename=extracted_filename]);
						}
	    }
    }			`
}
	
	
	
# Finally some quick code to execute when Zeek ends
event zeek_done()
    {
	if ( |PSEmpire::hit_count| == 0 )
    	    {
		print "-----------------------------------------------------";
		print "[*] Lucky you! No PowerShell Empire IoCs found!";
		return;
	    }
		
	print "-----------------------------------------------------";
	print fmt("[*] We have %d hits for Powershell related IoCs", PSEmpire::hit_count );
	print fmt("[*] There is/are %d unique IP(s) across the %d hits", |compromised_psempire_hosts|, PSEmpire::hit_count);
	for ( ip in compromised_psempire_hosts )
	    {
		print fmt ("   \\- %s", ip);
	    }
		print "[*] Zeek has ended. :-( ";
	
        }

When the above code is run, get on the screen.

┌──(securitynik㉿securitynik)-[/tmp]
└─$ zeek --readfile /home/securitynik/packets/empire-full-session.pcap --rulefile /home/securitynik/packets/powershell.sig --no-checksums /home/securitynik/packets/psEmpire.zeek 

[*] Zeek has started ... 
[*] Initializing the log stream ... 
-----------------------------------------------------
[*] Hunting for POWERSHELL EMPIRE Activity ... 
-----------------------------------------------------
[*] SUSPICIOUS POWERSHELL EMPIRE ACTIVITY 10.0.0.110:1650/tcp -> 10.0.0.107:443/tcp
   \->  Extracted file: HTTP-FJAtZe4W1F6uRwvmM3-news.php
[*] SUSPICIOUS POWERSHELL EMPIRE ACTIVITY 10.0.0.110:1650/tcp -> 10.0.0.107:443/tcp
   \->  Extracted file: HTTP-FbbFCu4T8l288RA3R7-news.php
   \->  Extracted file: HTTP-FIWqGKoACo24S6dS8-news.php
[*] SUSPICIOUS POWERSHELL EMPIRE ACTIVITY 10.0.0.110:1650/tcp -> 10.0.0.107:443/tcp
   \->  Extracted file: HTTP-F4wiHG1bpwW03v71vh-news.php
   \->  Extracted file: HTTP-FghBlt1qKU0utYSHd7-news.php
...

-----------------------------------------------------
[*] We have 1807 hits for Powershell related IoCs
[*] There is/are 1 unique IP(s) across the 1807 hits
   \- 10.0.0.110
[*] Zeek has ended. :-( 
 
Notice above it says we extracted the files. Let's verify that by using ls on the extract_files folder

┌──(securitynik㉿securitynik)-[/tmp]
└─$ ls extract_files/
HTTP-F04ExA4j0mLQtMJfxd-get.php      HTTP-Fesf2Y3CmRhWycz7Pf-process.php  HTTP-Fp6Yax4DsWlpbttqR6-news.php
HTTP-F04Fi9X4phawyMEXl-news.php      HTTP-FeshuKrFfyOgA6uH5-process.php   HTTP-FPCjffBSpvM3iS48f-process.php
HTTP-F05oktdIUrPchmqg-news.php       HTTP-FEskgT2JF9cCFwnBcf-get.php      HTTP-FpcYVS1xKGD4yv2rCb-get.php
HTTP-F05vS52MRDIDb1BIyi-process.php  HTTP-Festcg1mUFuQygmVe-get.php       HTTP-FPeqrG4Ur8DlkjgYv4-process.php
....

Looks good!

┌──(securitynik㉿securitynik)-[/tmp]
└─$ zeek-cut -d ts fuid tx_hosts rx_hosts conn_uids source filename sha256 < files.log  | more
2021-11-20T13:03:59-0500        FJAtZe4W1F6uRwvmM3      10.0.0.107      10.0.0.110      CjvLUV2ZfpZGgNfiHj      HTTP    -     41ab2b2d10eb9fdcf1d667ec4f45db99ec1761c06a4e2d61a789710fb0c08a04
2021-11-20T13:04:02-0500        FbbFCu4T8l288RA3R7      10.0.0.110      10.0.0.107      CjvLUV2ZfpZGgNfiHj      HTTP    -     bf896ec20bad0ee772db7ea44b6bf45c2b7401687808bf8474591f0e1612b01c
2021-11-20T13:04:02-0500        FIWqGKoACo24S6dS8       10.0.0.107      10.0.0.110      CjvLUV2ZfpZGgNfiHj      HTTP    -     600df9d2f01f3388e2154bfacc557b6add77cddce147b1cdebc30ad188b57edb

Looking at our custom log file PSempire.log.

┌──(securitynik㉿securitynik)-[/tmp]
└─$ zeek-cut -d -m < PSEmpire.log | more                                                                                    
ts      src_host        src_port        dst_host        dst_port        msg
2021-11-20T13:03:59-0500        10.0.0.110      1650    10.0.0.107      443     PowerShell Empire Activity Found!
2021-11-20T13:04:02-0500        10.0.0.110      1650    10.0.0.107      443     PowerShell Empire Activity Found!
2021-11-20T13:04:03-0500        10.0.0.110      1650    10.0.0.107      443     PowerShell Empire Activity Found!
2021-11-20T13:05:06-0500        10.0.0.110      1650    10.0.0.107      443     PowerShell Empire Activity Found!
2021-11-20T13:06:06-0500        10.0.0.110      1650    10.0.0.107      443     PowerShell Empire Activity Found!
2021-11-20T13:07:06-0500        10.0.0.110      1650    10.0.0.107      443     PowerShell Empire Activity Found!
2021-11-20T13:08:07-0500        10.0.0.110      1650    10.0.0.107      443     PowerShell Empire Activity Found!
2021-11-20T13:09:07-0500        10.0.0.110      1650    10.0.0.107      443     PowerShell Empire Activity Found!


Well That's it for this post and this series.





No comments:

Post a Comment