Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Azure
GitHub Repository: Azure/Azure-Sentinel-Notebooks
Path: blob/master/tutorials-and-examples/example-notebooks/VirusTotal File Behavior Explorer - MS and Sysmon detonation.ipynb
3253 views
Kernel: Python 3.8 - AzureML

VirusTotal Behavior with Microsoft Sysmon Detonation

This notebook uses the VirusTotal API to retrieve detonation information for a file ID (the SHA256 hash of the file).

There are 5 main sections:

  • Setup

  • Retrieve basic information about the file ID

  • Retrieve and browse denotation data for the file

  • Build and view the process tree for the suspect behavior

  • Generate KQL filter clauses for subsets of the data to use in Microsoft Sentinel queries.

# If you don't have msticpy (with the vt3 option) installed, you'll need that %pip install --upgrade msticpy[vt3]
# initialize msticpy REQ_PYTHON_VER = "3.10" REQ_MSTICPY_VER = "2.12.0" from msticpy.nbtools import nbinit nbinit.init_notebook( namespace=globals(), );
from msticpy.sectools.vtlookupv3 import VTLookupV3 from msticpy.common.provider_settings import get_provider_settings import nest_asyncio nest_asyncio.apply() # Initialize VT Client vt_settings = get_provider_settings("TIProviders").get("VirusTotal") vt = VTLookupV3(vt_settings.args.get("AuthKey"))

VirusTotal Basic File Data

This section retrieves basic VT data for a file hash.

Running the following cell will show a form in which you can enter a file ID (the SHA256 hash of the file).

Clicking the Lookup button will search VirusTotal for a match and populate the summary details in the form. Selecting a result attribute (on the left) shows the data for that attribute.

The file ID entered here is used in subsequent parts of the notebook to download the behavior information.

import pprint import ipywidgets as widgets import pandas as pd vt_df: pd.DataFrame = None _NOT_FOUND = "Not found" border_layout = widgets.Layout( **{ "width": "90%", "border": "solid gray 1px", "margin": "1pt", "padding": "5pt", } ) def _ts_to_pydate(data): """Replace Unix timestamps in VT data with Py/pandas Timestamp.""" for date_col in (col for col in data.columns if col.endswith("_date")): data[date_col] = pd.to_datetime(data[date_col], unit="s", utc=True) return data def get_summary(data=None): data = data if data is not None else vt_df def_dict = {"sha256": "", "meaningful_name": "", "names": "", "magic": ""} if data is None: return def_dict if ( "first_submission_date" not in data.columns or data.iloc[0]["first_submission_date"] == _NOT_FOUND ): def_dict["sha256"] = _NOT_FOUND return def_dict return data[["sha256", "meaningful_name", "names", "magic"]].iloc[0].to_dict() def summary_html(title, summary): return f""" <h3>{title}</h3> <table> <tr> <td>ID</td><td>{summary.get('sha256')}</td> </tr> <tr> <td>Names</td><td>{summary.get('names')}</td> </tr> <tr> <td>File Type</td><td>{summary.get('magic')}</td> </tr> </table> """ def lookup_file_id(btn): del btn global vt_df vt_df = vt.get_object(txt_file_id.value, vt_type="file").T html_header.value = summary_html(basic_title, get_summary(vt_df)) if ( "first_submission_date" not in vt_df.columns or vt_df.iloc[0]["first_submission_date"] == _NOT_FOUND ): data_sel.options = [] return vt_df = _ts_to_pydate(vt_df) data_sel.options = vt_df.columns data_sel = widgets.Select( description="Attribute", layout=widgets.Layout(height="400px") ) data_view = widgets.Textarea( description="Value", layout=widgets.Layout(height="400px", width="60%") ) def _display_attribute(change): item = change.get("new") if item in vt_df.columns: data = vt_df.iloc[0][item] data_view.value = pprint.pformat(data) else: data_view.value = "" data_sel.observe(_display_attribute, names="value") txt_file_id = widgets.Text( description="Enter file ID (hash)", layout=widgets.Layout(width="70%"), style={"description_width": "150px"}, ) btn_lookup = widgets.Button(description="Lookup") btn_lookup.on_click(lookup_file_id) basic_title = "VirusTotal File hash lookup" html_header = widgets.HTML(summary_html( basic_title, get_summary() ), layout=border_layout) hb_file_lookup = widgets.HBox([txt_file_id, btn_lookup], layout=border_layout) hb_vt_attribs = widgets.HBox([data_sel, data_view], layout=border_layout) display(widgets.VBox([html_header, hb_file_lookup, hb_vt_attribs]))
VBox(children=(HTML(value='\n <h3>VirusTotal File hash lookup</h3>\n <table>\n <tr>\n <td>ID</…

Get Microsoft Detonation Details from Virus Total

This section tries to retrieve detonation behavior details from the file ID selected in the previous cell.

The output is split into groups of related data items such as file operations, registry operations, etc. Within each tabbed group, there are individual data sets in their own tabs.

The process tree data is included in this data set but is easier to view in the following section with the process tree viewer.

# Retrieve Behavior results import requests import re from pprint import pformat vt_root = "https://www.virustotal.com/api/v3" file_behavior_uri = f"{vt_root}/files/{{id}}/behaviour_summary" ms_file_behavior_uri = f"{vt_root}/file_behaviours/{{id}}_Microsoft Sysinternals" hdr = {"headers": {"X-Apikey": vt_settings.args.get("AuthKey")}} def get_vt_file_behavior(file_id): resp = requests.get(file_behavior_uri.format(id=file_id), **hdr) if resp.status_code == 200: return resp.json().get("data") def get_vt_file_behavior_mssi(file_id): resp = requests.get(ms_file_behavior_uri.format(id=file_id), **hdr) if resp.status_code == 200: return resp.json().get("data", {}) # Use the Microsoft/Sysmon data file_behavior = get_vt_file_behavior_mssi(txt_file_id.value) # Use the aggregated VT behavior # file_behavior = get_vt_file_behavior(txt_file_id.value) if "attributes" in file_behavior: categories = file_behavior.get("attributes") behavior_links = file_behavior.get("links") analysis_date = pd.Timestamp(categories["analysis_date"]) else: categories = file_behavior behavior_links = file_behavior.get("links") analysis_date = vt_df.iloc[0].last_analysis_date _CAT_PATTERNS = { "File": "file.*", "Process": "process.*|command.*|module.*", "Registry": "registry.*", "Network": ".*ips|dns.*|.*urls|ip.*|http.*|tls", "System": "mutex.*|calls.*|permissions.*|text.*", "Other": ".*" } def extract_subcats(pattern, categs): return {cat for cat in categs if re.match(pattern, cat)} def data_format(data_item): if not data_item: return "" if isinstance(data_item, list): if isinstance(data_item[0], dict): return pd.DataFrame(data_item).style.hide_index().render() if isinstance(data_item[0], str): return pd.DataFrame(pd.Series(data_item)).style.hide_index().render() return f"<pre>{pformat(data_item)}</pre>" groupings = {} remaining_categories = set(categories) for name, pattern in _CAT_PATTERNS.items(): groupings[name] = extract_subcats(pattern, remaining_categories) remaining_categories = remaining_categories - groupings[name] main_tab = widgets.Tab() child_tabs = {} for group, sub_cats in groupings.items(): sub_cat_tab = widgets.Tab() tab_content = { section: widgets.HTML(value=data_format(items)) for section, items in categories.items() if items and section in sub_cats } sub_cat_tab.children = list(tab_content.values()) for idx, section in enumerate(tab_content): sub_cat_tab.set_title(idx, section) child_tabs[group] = sub_cat_tab main_tab.children = list(child_tabs.values()) for idx, group_name in enumerate(child_tabs): main_tab.set_title(idx, group_name) html_title = widgets.HTML(summary_html( "VT Detonation Details", get_summary() ), layout=border_layout) display(widgets.VBox([html_title, main_tab]))
VBox(children=(HTML(value="\n <h3>VT Detonation Details</h3>\n <table>\n <tr>\n <td>ID</td><td…

Display Process Tree

The first cell extracts the behavior tree and builds the process tree dataframe.

from copy import deepcopy, copy import attr from pathlib import Path @attr.s(auto_attribs=True) class SIProcess: process_id: str name: str cmd_line: str parent_id: int = -1 proc_key: str = None parent_key: str = None path: str = None IsRoot: bool = False IsLeaf:bool = False IsBranch: bool = False children: list = [] # proc_children: list = [] time_offset: int = 0 def create_si_proc(raw_proc): """Return an SIProcess Object from a raw VT proc definition.""" # raw_proc = copy(raw_proc) name = raw_proc.get("name") raw_proc["cmd_line"] = name for proc in procs_created: if name.lower().endswith(proc): raw_proc["name"] = procs_created[proc] break raw_proc["proc_key"] = raw_proc["process_id"] + "|" + raw_proc["name"] # print(name, raw_proc.keys()) return SIProcess(**raw_proc) def extract_processes(process_data, parent=None): """Convert processes_tree attribute to SIProcessObjects.""" procs = [] for process in process_data: si_proc = create_si_proc(process) if parent: si_proc.parent_key = parent.proc_key si_proc.IsBranch = True else: si_proc.IsRoot = True child_procs_raw = process.get("children", []) if child_procs_raw: si_proc.children = extract_processes(child_procs_raw, parent=si_proc) else: si_proc.IsLeaf = True si_proc.IsBranch = False procs.append(si_proc) return procs # Convert to DF def procs_to_df(procs): """Convert the SIProcess objects to a list.""" df_list = [] for proc in procs: df_list.append(attr.asdict(proc)) if proc.children: df_list.extend(procs_to_df(proc.children)) return df_list proc_tree_raw = deepcopy(categories["processes_tree"]) procs_created = {Path(proc).parts[-1].lower(): proc for proc in categories["processes_created"]} si_procs = extract_processes(proc_tree_raw) proc_tree_df = pd.DataFrame(procs_to_df(si_procs)).drop(columns="children") proc_tree_df.head(4)
# Try to Match up 'command_executions' commandline data with # processes. def try_match_commandlines(command_executions, procs_cmds): procs_cmd = procs_cmds.copy() procs_cmd["cmd_line"] = np.nan weak_matches = 0 for cmd in command_executions: for idx, row in procs_cmd.iterrows(): # print(row["name"], cmd, row["cmd_line"], isinstance(row["cmd_line"], str)) if ( not isinstance(row["cmd_line"], str) and np.isnan(row["cmd_line"]) and row["name"] in cmd ): # print("Found match:", row["name"], "==", cmd) procs_cmd.loc[idx, "cmd_line"] = cmd break for cmd in command_executions: for idx, row in procs_cmd.iterrows(): # print(row["name"], cmd, row["cmd_line"], isinstance(row["cmd_line"], str)) if ( not isinstance(row["cmd_line"], str) and np.isnan(row["cmd_line"]) and Path(row["name"]).stem.lower() in cmd.lower() ): weak_matches += 1 # print("Found weak match:", row["name"], "~=", cmd) procs_cmd.loc[idx, "cmd_line"] = cmd break if weak_matches: print( f"WARNING: {weak_matches} of the {len(command_executions)} commandlines", "were weakly matched - some commandlines could be attributed", "to the wrong process.", end="\n" ) return procs_cmd proc_tree_cmd_df = try_match_commandlines(categories["command_executions"], proc_tree_df)
WARNING: 16 of the 17 commandlines were weakly matched - some commandlines could be attributed to the wrong process.

Display the tree

from msticpy.sectools.proc_tree_builder import ProcSchema, _build_proc_tree # Define a schema to map Df names on to internal ProcSchema proc_schema = { "process_name": "name", "process_id": "process_id", "parent_id": "parent_id", "cmd_line": "cmd_line", "time_stamp": "time_stamp", "logon_id": "logon_id", "path_separator": "\\", "user_name": "user_name", "host_name_column": "host", "event_id_column": "event_id", } processes = proc_tree_cmd_df processes["path"] = np.nan processes.loc[processes.IsRoot, "path"] = processes[processes.IsRoot].index.astype("str") # Fill in some required fields with placeholder data processes["time_stamp"] = analysis_date processes["host"] = "sandbox" processes["logon_id"] = "na" processes["event_id"] = "na" processes["source_index"] = processes.index.astype("str") proc_tree = processes.set_index("proc_key") first_unique = proc_tree.index.duplicated() proc_tree = proc_tree[~first_unique] # msticpy function to build the tree process_tree_df = _build_proc_tree(proc_tree) process_tree_df process_tree_df.mp_plot.process_tree(schema=ProcSchema(**proc_schema), legend_col="name", hide_legend=True)
MIME type unknown not supported
MIME type unknown not supported
(Figure(id='1457', ...), Row(id='1571', ...))

KQL Query Clause generator

Use this to generate filter clauses to search for the behaviors. For example, you can extract the IP addresses or command lines and build a query to search for matches in your organization.

Select the category of data that you want to use to generate the query.

sel_category = widgets.Select( description="Select a category", options=list(categories.keys()), layout=widgets.Layout(width="30%", height="200px"), style={"description_width": "150px"} ) txt_values = widgets.HTML(layout=widgets.Layout(width="60%", height="300px"),) vb_sel_category = widgets.HBox([sel_category, txt_values], layout=border_layout) def update_cat_values(change): cat = change.get("new") data = categories.get(cat) txt_values.value = data_format(data) sel_category.observe(update_cat_values, names="value") update_cat_values({"new": sel_category.value}) display(widgets.VBox([ widgets.HTML("<h3>Select a category to use in the query builder</h3>"), vb_sel_category ]))
VBox(children=(HTML(value='<h3>Select a category to use in the query builder</h3>'), HBox(children=(Select(des…

Connect to Microsoft Sentinel

We need to load a Microsoft Sentinel query provider and authenticate in order to retrieve the schema.

qry_prov = QueryProvider("AzureSentinel") qry_prov.connect(WorkspaceConfig())
Please wait. Loading Kqlmagic extension...done Connecting...
connected

Select table/column and the behavior data values for the query

import re sel_wgt_layout = widgets.Layout( height="300px", width="30%", ) sel_table = widgets.Select( description="Table", options=list(qry_prov.schema.keys()), layout=sel_wgt_layout ) sel_column = widgets.Select( description="Column", layout=sel_wgt_layout, ) def update_columns(change): table_name = change.get("new") table_schema = qry_prov.schema.get(table_name) sel_column.options = list(table_schema.keys()) sel_table.observe(update_columns, names="value") update_columns({"new": sel_table.value}) lbl_table_column = widgets.Label(value="Choose the table and column to use.") def get_cat_items(cat): """Return data items from the raw data.""" cat_items = categories.get(cat) if not isinstance(cat_items, list): return [cat_items] if cat == "command_executions": return [ item.strip()[1:-1] if (item.strip()[0] == '"' and item.strip()[-1] == '"') else item.strip() for item in cat_items ] if cat == "files_dropped": return [item["path"] for item in cat_items] if cat == "ip_traffic": return [item["destination_ip"] for item in cat_items] if cat == "dns_lookups": items = [item["hostname"] for item in cat_items] for item in cat_items: items.extend(item["resolved_ips"]) return items if not isinstance(cat_items[0], str): return [str(item) for item in cat_items] return cat_items sel_subset = nbwidgets.SelectSubset( source_items=get_cat_items(sel_category.value), default_selected=get_cat_items(sel_category.value), auto_display=False ) KQL_TEMPLATE = """ {table} | where TimeGenerated > ago(1d) | where {column} in (\n {values}\n) """ text_query = widgets.Textarea( description="KQL query", layout=widgets.Layout(width="90%", height="300px"), ) def create_query_filter(btn): del btn q_values = [re.sub(r"\\", r"\\\\", val) for val in sel_subset.selected_values] text_query.value = KQL_TEMPLATE.format( table=sel_table.value, column=sel_column.value, values=',\n '.join(f"'{item}'" for item in q_values) ) btn_query = widgets.Button(description="Gen KQL query") btn_query.on_click(create_query_filter) # Create the widget layout vb_table_column = widgets.VBox( [lbl_table_column, widgets.HBox([sel_table, sel_column])], layout=border_layout, ) lbl_select_items = widgets.Label(value="Select the file detonation values to query") vb_select_items = widgets.VBox([lbl_select_items, sel_subset.layout], layout=border_layout) lbl_query = widgets.Label(value="Click the 'Gen KQL query' to generate the template query.") vb_query = widgets.VBox([lbl_query, btn_query, text_query], layout=border_layout) hdr_html = widgets.HTML(f"<h3>Create query for '{sel_category.value}' detonation data</h3>") txt_html = widgets.HTML("To choose data from a different category, return to previous cell to select the category") display(widgets.VBox([hdr_html, txt_html, vb_table_column, vb_select_items, vb_query]))
VBox(children=(HTML(value="<h3>Create query for 'ip_traffic' detonation data</h3>"), HTML(value='To choose dat…
# paste the query between the quotes hunting_query = """ // replace this text with the query copied from the KQL query box """ results_df = qry_prov.exec_query(hunting_query) display(results_df)

TI Additional Lookups

You can use the MSTICPy TI provider to do additional lookups on values that you see in the behavior data.

By default, TILookup uses all configured providers, including VirusTotal

ioc_txt = widgets.Text( description="Enter IoC to lookup", layout=widgets.Layout(width="70%"), style={"description_width": "150px"}, ) ioc_txt
Text(value='', description='Enter IoC to lookup', layout=Layout(width='70%'), style=DescriptionStyle(descripti…
ti = TILookup() TILookup.browse(TILookup.result_to_df(ti.lookup_ioc(ioc_txt.value)))
Using Open PageRank. See https://www.domcop.com/openpagerank/what-is-openpagerank
VBox(children=(Text(value='', description='Filter:', style=DescriptionStyle(description_width='initial')), Sel…

Appendix - some supplementary VirusTotal lookups

vt_uri = "https://www.virustotal.com/api/v3/files/{id}/contacted_domains" hdr = {"headers": {"X-Apikey": vt_settings.args.get("AuthKey")}} resp = requests.get(vt_uri.format(id=txt_file_id.value), **hdr) display(HTML("<h2>Contacted domains</h2>")) for item in resp.json()["data"]: for attr in ("whois", "last_dns_records", "last_analysis_stats", "last_analysis_date"): print(attr, end=": ") if attr.endswith("_data"): pd.to_datetime(item["attributes"].get(attr)) else: pprint.pprint(item["attributes"].get(attr))
whois: ('Administrative city: REDACTED FOR PRIVACY\n' 'Administrative country: REDACTED FOR PRIVACY\n' 'Administrative state: REDACTED FOR PRIVACY\n' 'Create date: 2021-06-17\n' 'Domain name: sanctam.net\n' 'Domain registrar id: 69\n' 'Domain registrar url: http://domainhelp.opensrs.net\n' 'Expiry date: 2024-06-17\n' 'Query time: 2021-06-21 15:30:16\n' 'Registrant address: ff3a4678d9c7a906\n' 'Registrant city: ff3a4678d9c7a906\n' 'Registrant company: ff3a4678d9c7a906\n' 'Registrant country: Saint Kitts and Nevis\n' 'Registrant email: 3267309318f7846cs@\n' 'Registrant fax: ff3a4678d9c7a906\n' 'Registrant name: ff3a4678d9c7a906\n' 'Registrant phone: ff3a4678d9c7a906\n' 'Registrant state: 347e121c541b794d\n' 'Registrant zip: ff3a4678d9c7a906\n' 'Technical city: REDACTED FOR PRIVACY\n' 'Technical country: REDACTED FOR PRIVACY\n' 'Technical state: REDACTED FOR PRIVACY\n' 'Update date: 2021-06-17') last_dns_records: [{'ttl': 21600, 'type': 'NS', 'value': '2-nest.njalla.ma'}, {'ttl': 21600, 'type': 'NS', 'value': '3-pas.njalla.in'}, {'ttl': 21600, 'type': 'NS', 'value': '1-ceci.njalla.do'}, {'expire': 1814400, 'minimum': 86400, 'refresh': 21600, 'retry': 7200, 'rname': 'you.can-get-no.info', 'serial': 2110061957, 'ttl': 10800, 'type': 'SOA', 'value': '1-ceci.njalla.do'}] last_analysis_stats: {'harmless': 74, 'malicious': 3, 'suspicious': 1, 'timeout': 0, 'undetected': 10} last_analysis_date: None whois: ('Admin Country: AU\n' 'Admin Organization: Atlassian Pty Ltd\n' 'Admin State/Province: NSW\n' 'Creation Date: 1997-11-24T05:00:00+0000\n' 'Creation Date: 1997-11-24T05:00:00Z\n' 'DNSSEC: unsigned\n' 'Domain Name: BITBUCKET.ORG\n' 'Domain Name: bitbucket.org\n' 'Domain Status: clientDeleteProhibited ' '(https://www.icann.org/epp#clientDeleteProhibited)\n' 'Domain Status: clientDeleteProhibited ' 'https://icann.org/epp#clientDeleteProhibited\n' 'Domain Status: clientTransferProhibited ' '(https://www.icann.org/epp#clientTransferProhibited)\n' 'Domain Status: clientTransferProhibited ' 'https://icann.org/epp#clientTransferProhibited\n' 'Domain Status: clientUpdateProhibited ' '(https://www.icann.org/epp#clientUpdateProhibited)\n' 'Domain Status: clientUpdateProhibited ' 'https://icann.org/epp#clientUpdateProhibited\n' 'Name Server: NS-1305.AWSDNS-35.ORG\n' 'Name Server: NS-1746.AWSDNS-26.CO.UK\n' 'Name Server: NS-445.AWSDNS-55.COM\n' 'Name Server: NS-584.AWSDNS-09.NET\n' 'Name Server: ns-1305.awsdns-35.org\n' 'Name Server: ns-1746.awsdns-26.co.uk\n' 'Name Server: ns-445.awsdns-55.com\n' 'Name Server: ns-584.awsdns-09.net\n' 'Registrant Country: AU\n' 'Registrant Email: 8fa6414e6f7fe92ds@\n' 'Registrant Organization: e1511d3bfff6af34\n' 'Registrant State/Province: 487604ff64aa1e4b\n' 'Registrar Abuse Contact Email: [email protected]\n' 'Registrar Abuse Contact Phone: +1.2083895740\n' 'Registrar Abuse Contact Phone: +1.2083895770\n' 'Registrar IANA ID: 292\n' 'Registrar Registration Expiration Date: 2022-11-22T08:00:00+0000\n' 'Registrar URL: http://www.markmonitor.com\n' 'Registrar WHOIS Server: whois.markmonitor.com\n' 'Registrar: MarkMonitor Inc.\n' 'Registrar: MarkMonitor, Inc.\n' 'Registry Domain ID: D644926-LROR\n' 'Registry Expiry Date: 2022-11-23T05:00:00Z\n' 'Tech Country: AU\n' 'Tech Organization: Atlassian Pty Ltd\n' 'Tech State/Province: NSW\n' 'Updated Date: 2020-10-22T09:28:39+0000\n' 'Updated Date: 2020-10-22T09:28:39Z') last_dns_records: [{'ttl': 207, 'type': 'TXT', 'value': 'facebook-domain-verification=84vhrka086if54vvfvdk2ezuvfdwyq'}, {'flag': 0, 'tag': 'issue', 'ttl': 3600, 'type': 'CAA', 'value': 'letsencrypt.org'}, {'ttl': 207, 'type': 'TXT', 'value': 'v=spf1 ip4:167.89.90.3/32 ip4:167.89.37.79/32 ip4:167.89.38.16/32 ' 'ip4:167.89.38.188/32 ip4:167.89.38.218/32 include:sendgrid.net ' 'include:_spf.google.com include:stspg-customer.com ' 'include:atlassian.mail.e.sparkpost.com a:001.lax.mailroute.net ' '~all'}, {'ttl': 207, 'type': 'TXT', 'value': 'google-site-verification=X2bEMlmrK-ok6sdGW5j0jsTGCV7IqlqYQxVTV9MQngg'}, {'flag': 0, 'tag': 'issuewild', 'ttl': 3600, 'type': 'CAA', 'value': 'digicert.com'}, {'ttl': 2474, 'type': 'NS', 'value': 'ns-1305.awsdns-35.org'}, {'ttl': 207, 'type': 'TXT', 'value': 'status-page-domain-verification=3fbwzqm8vjhr'}, {'ttl': 207, 'type': 'TXT', 'value': 'google-site-verification=p99sO7SVcXanHfj-NuQZJKocj3_4Hkws2ZNbbieLW1M'}, {'expire': 1209600, 'minimum': 86400, 'refresh': 7200, 'retry': 900, 'rname': 'awsdns-hostmaster.amazon.com', 'serial': 1, 'ttl': 661, 'type': 'SOA', 'value': 'ns-584.awsdns-09.net'}, {'flag': 0, 'tag': 'issue', 'ttl': 3600, 'type': 'CAA', 'value': 'globalsign.com'}, {'priority': 10, 'ttl': 60, 'type': 'MX', 'value': 'mxb-001d9801.gslb.pphosted.com'}, {'ttl': 2474, 'type': 'NS', 'value': 'ns-1746.awsdns-26.co.uk'}, {'ttl': 2474, 'type': 'NS', 'value': 'ns-445.awsdns-55.com'}, {'ttl': 207, 'type': 'TXT', 'value': 'SFMC-hujcMHSUtdr25K46oa87SYTQ_gj4l2I-zbFGt0bY'}, {'ttl': 60, 'type': 'AAAA', 'value': '2406:da00:ff00::34cc:ea4a'}, {'ttl': 207, 'type': 'TXT', 'value': 'google-site-verification=oFmSUhmR77qRC-e8T1kL-eaO7pvT8XUmpw1G0p-lWqM'}, {'ttl': 207, 'type': 'TXT', 'value': 'SFMC-89c2pNWIGUgPI-CJy-7XLZpYvRb8I5pEy41O1PkG'}, {'flag': 0, 'tag': 'issue', 'ttl': 3600, 'type': 'CAA', 'value': 'digicert.com'}, {'ttl': 60, 'type': 'A', 'value': '104.192.141.1'}, {'ttl': 207, 'type': 'TXT', 'value': 'SFMC-PONHWFzQKgcV7iyHLZyLKk1HrqOwKnjXl2xryafq'}, {'ttl': 60, 'type': 'AAAA', 'value': '2406:da00:ff00::22c3:9b0a'}, {'flag': 0, 'tag': 'iodef', 'ttl': 3600, 'type': 'CAA', 'value': 'mailto:[email protected]'}, {'ttl': 60, 'type': 'AAAA', 'value': '2406:da00:ff00::22c2:513'}, {'ttl': 2474, 'type': 'NS', 'value': 'ns-584.awsdns-09.net'}, {'ttl': 207, 'type': 'TXT', 'value': 'SFMC-JPhMZcrkLREsn7ru3T0ROWWGnEuxBbg4_T_VNrZc'}, {'priority': 10, 'ttl': 60, 'type': 'MX', 'value': 'mxa-001d9801.gslb.pphosted.com'}] last_analysis_stats: {'harmless': 79, 'malicious': 1, 'suspicious': 0, 'timeout': 0, 'undetected': 8} last_analysis_date: None whois: ('Creation Date: 2015-08-18T08:30:17Z\n' 'DNSSEC: unsigned\n' 'Domain Name: NANOPOOL.ORG\n' 'Domain Status: clientTransferProhibited ' 'https://icann.org/epp#clientTransferProhibited\n' 'Domain Status: clientUpdateProhibited ' 'https://icann.org/epp#clientUpdateProhibited\n' 'Name Server: IGOR.NS.CLOUDFLARE.COM\n' 'Name Server: RITA.NS.CLOUDFLARE.COM\n' 'Registrant Country: US\n' 'Registrant Organization: a53695f8eedd6e9f\n' 'Registrant State/Province: 36e414cc8874c746\n' 'Registrar Abuse Contact Email: [email protected]\n' 'Registrar Abuse Contact Phone: +1.6022262389\n' 'Registrar IANA ID: 886\n' 'Registrar URL: www.domain.com\n' 'Registrar WHOIS Server: whois.domain.com\n' 'Registrar: Domain.com, LLC\n' 'Registry Domain ID: D177134984-LROR\n' 'Registry Expiry Date: 2023-08-18T08:30:17Z\n' 'Updated Date: 2020-12-30T19:28:01Z') last_dns_records: [{'ttl': 300, 'type': 'A', 'value': '51.255.34.118'}, {'ttl': 300, 'type': 'A', 'value': '135.125.238.108'}, {'ttl': 300, 'type': 'A', 'value': '51.15.54.102'}, {'ttl': 300, 'type': 'A', 'value': '51.15.78.68'}, {'ttl': 300, 'type': 'A', 'value': '51.15.58.224'}, {'ttl': 300, 'type': 'A', 'value': '51.83.33.228'}, {'ttl': 300, 'type': 'A', 'value': '217.182.169.148'}, {'ttl': 300, 'type': 'A', 'value': '51.68.143.81'}, {'ttl': 300, 'type': 'A', 'value': '51.15.65.182'}, {'ttl': 300, 'type': 'A', 'value': '51.15.69.136'}, {'ttl': 300, 'type': 'A', 'value': '46.105.31.147'}, {'ttl': 300, 'type': 'A', 'value': '185.71.66.31'}] last_analysis_stats: {'harmless': 73, 'malicious': 4, 'suspicious': 1, 'timeout': 0, 'undetected': 10} last_analysis_date: None
vt_uri = "https://www.virustotal.com/api/v3/files/{id}/contacted_urls" hdr = {"headers": {"X-Apikey": vt_settings.args.get("AuthKey")}} resp = requests.get(vt_uri.format(id=txt_file_id.value), **hdr) display(HTML("<h2>Contacted URLs</h2>")) for item in resp.json()["data"]: for attr in ("url", "threat_names", "last_final_url", "last_analysis_stats", "last_analysis_date"): print(attr, end=": ") if attr.endswith("_data"): pd.to_datetime(item["attributes"].get(attr)) else: pprint.pprint(item["attributes"].get(attr))
url: 'https://sanctam.net:58899/assets/txt/resource_url.php?type=xmrig' threat_names: [] last_final_url: 'https://sanctam.net:58899/assets/txt/resource_url.php?type=xmrig' last_analysis_stats: {'harmless': 74, 'malicious': 2, 'suspicious': 2, 'timeout': 0, 'undetected': 11} last_analysis_date: 1633430508