11import argparse
22import os
3+ from typing import Dict , List
34
45import pandas as pd
56import requests
7+ from loguru import logger
68from stix2 import MemoryStore
79
810# import mitreattack.attackToExcel.stixToDf as stixToDf
1214SUB_CHARACTERS = ["\\ " , "/" ]
1315
1416
15- def get_stix_data (domain , version = None , remote = None ):
17+ def get_stix_data (domain : str , version : str = None , remote : str = None ) -> MemoryStore :
1618 """Download the ATT&CK STIX data for the given domain and version from MITRE/CTI (or just domain if a remote workbench is specified).
1719
18- :param domain: the domain of ATT&CK to fetch data from, e.g "enterprise-attack"
19- :param version: the version of attack to fetch data from, e.g "v8.1". If omitted, returns the latest version
20- (not used for invocations that use remote)
21- :param remote: optional url to a ATT&CK workbench instance. If specified, data will be retrieved from the target
22- Workbench instead of MITRE/CTI
23- :returns: a MemoryStore containing the domain data
20+ Parameters
21+ ----------
22+ domain : str
23+ The domain of ATT&CK to fetch data from, e.g "enterprise-attack"
24+ version : str, optional
25+ The version of attack to fetch data from, e.g "v8.1".
26+ If omitted, returns the latest version (not used for invocations that use remote), by default None
27+ remote : str, optional
28+ Optional url to a ATT&CK workbench instance.
29+ If specified, data will be retrieved from the target Workbench instead of MITRE/CTI, by default None
30+
31+ Returns
32+ -------
33+ MemoryStore
34+ A stix2.MemoryStore object containing the domain data
2435 """
2536 if remote : # Using Workbench Instance
2637 if ":" not in remote [6 :]:
@@ -29,23 +40,35 @@ def get_stix_data(domain, version=None, remote=None):
2940 remote = "http://" + remote
3041 url = f"{ remote } /api/stix-bundles?domain={ domain } &includeRevoked=true&includeDeprecated=true"
3142 stix_json = requests .get (url ).json ()
32- return MemoryStore (stix_json )
43+ ms = MemoryStore (stix_json )
3344 else : # Using MITRE/CTI
3445 if version :
3546 url = f"https://raw.githubusercontent.com/mitre/cti/ATT%26CK-{ version } /{ domain } /{ domain } .json"
3647 else :
3748 url = f"https://raw.githubusercontent.com/mitre/cti/master/{ domain } /{ domain } .json"
3849
3950 stix_json = requests .get (url ).json ()
40- return MemoryStore (stix_data = stix_json ["objects" ])
51+ ms = MemoryStore (stix_data = stix_json ["objects" ])
52+
53+ return ms
4154
4255
43- def build_dataframes (src , domain ) :
56+ def build_dataframes (src : MemoryStore , domain : str ) -> Dict :
4457 """Build pandas dataframes for each attack type, and return a dictionary lookup for each type to the relevant dataframe.
4558
46- :param src: MemoryStore or other stix2 DataSource object
47- :param domain: domain of ATT&CK src corresponds to, e.g "enterprise-attack"
48- :returns: a dict lookup of each ATT&CK type to dataframes for the given type to be ingested by write_excel
59+ :returns:
60+
61+ Parameters
62+ ----------
63+ src : MemoryStore
64+ MemoryStore or other stix2 DataSource object
65+ domain : str
66+ domain of ATT&CK src corresponds to, e.g "enterprise-attack"
67+
68+ Returns
69+ -------
70+ dict
71+ A dict lookup of each ATT&CK type to dataframes for the given type to be ingested by write_excel
4972 """
5073 df = {
5174 "techniques" : stixToDf .techniquesToDf (src , domain ),
@@ -62,16 +85,26 @@ def build_dataframes(src, domain):
6285 return df
6386
6487
65- def write_excel (dataframes , domain , version = None , outputDir = "." ):
88+ def write_excel (dataframes : Dict , domain : str , version : str = None , outputDir : str = "." ) -> List :
6689 """Given a set of dataframes from build_dataframes, write the ATT&CK dataset to output directory.
6790
68- :param dataframes: pandas dataframes as built by build_dataframes
69- :param domain: domain of ATT&CK the dataframes correspond to, e.g "enterprise-attack"
70- :param version: optional, the version of ATT&CK the dataframes correspond to, e.g "v8.1".
71- If omitted, the output files will not be labelled with the version number
72- :param outputDir: optional, the directory to write the excel files to. If omitted writes to a
73- subfolder of the current directory depending on specified domain and version
74- :returns: a list of filepaths corresponding to the files written by the function
91+ Parameters
92+ ----------
93+ dataframes : dict
94+ A dictionary of pandas dataframes as built by build_dataframes()
95+ domain : str
96+ Domain of ATT&CK the dataframes correspond to, e.g "enterprise-attack"
97+ version : str, optional
98+ The version of ATT&CK the dataframes correspond to, e.g "v8.1".
99+ If omitted, the output files will not be labelled with the version number, by default None
100+ outputDir : str, optional
101+ The directory to write the excel files to.
102+ If omitted writes to a subfolder of the current directory depending on specified domain and version, by default "."
103+
104+ Returns
105+ -------
106+ list
107+ A list of filepaths corresponding to the files written by the function
75108 """
76109 print ("writing formatted files... " , end = "" , flush = True )
77110 # master list of files that have been written
@@ -184,18 +217,55 @@ def write_excel(dataframes, domain, version=None, outputDir="."):
184217 return written_files
185218
186219
187- def export (domain = "enterprise-attack" , version = None , outputDir = "." , remote = None ):
188- """Download ATT&CK data from MITRE/CTI and convert it to excel spreadsheets.
220+ def export (
221+ domain : str = "enterprise-attack" ,
222+ version : str = None ,
223+ outputDir : str = "." ,
224+ remote : str = None ,
225+ stix_file : str = None ,
226+ ):
227+ """Download ATT&CK data from MITRE/CTI and convert it to Excel spreadsheets.
189228
190- :param domain: the domain of ATT&CK to download, e.g "enterprise-attack"
191- :param version: optional, the version of ATT&CK to download, e.g "v8.1". If omitted will build the current version
192- of ATT&CK
193- :param outputDir: optional, the directory to write the excel files to. If omitted writes to a
194- subfolder of the current directory depending on specified domain and version
195- :param remote: optional, the URL of a remote ATT&CK Workbench instance to connect to for stix data
229+ Parameters
230+ ----------
231+ domain : str, optional
232+ The domain of ATT&CK to download, e.g "enterprise-attack", by default "enterprise-attack"
233+ version : str, optional
234+ The version of ATT&CK to download, e.g "v8.1".
235+ If omitted will build the current version of ATT&CK, by default None
236+ outputDir : str, optional
237+ The directory to write the excel files to.
238+ If omitted writes to a subfolder of the current directory depending on specified domain and version, by default "."
239+ remote : str, optional
240+ The URL of a remote ATT&CK Workbench instance to connect to for stix data.
241+ Mutually exclusive with stix_file.
242+ by default None
243+ stix_file : str, optional
244+ Path to a local STIX file containing ATT&CK data for a domain, by default None
245+
246+ Raises
247+ ------
248+ ValueError
249+ Raised if both `remote` and `stix_file` are passed
250+ FileNotFoundError
251+ Raised if `stix_file` not found
196252 """
253+ if remote and stix_file :
254+ raise ValueError ("remote and stix_file are mutually exclusive. Please only use one or the other" )
255+
256+ mem_store = None
257+ if remote :
258+ mem_store = get_stix_data (domain , version , remote )
259+ elif stix_file :
260+ if os .path .exists (stix_file ):
261+ logger .info (f"Loading STIX file from: { stix_file } " )
262+ mem_store = MemoryStore ()
263+ mem_store .load_from_file (stix_file )
264+ else :
265+ raise FileNotFoundError (f"{ stix_file } file does not exist." )
266+
197267 # build dataframes
198- dataframes = build_dataframes (get_stix_data ( domain , version , remote ), domain )
268+ dataframes = build_dataframes (src = mem_store , domain = domain )
199269 write_excel (dataframes , domain , version , outputDir )
200270
201271
0 commit comments