def render_capabilities(doc, ostream): """ example:: +-------------------------------------------------------+-------------------------------------------------+ | CAPABILITY | NAMESPACE | |-------------------------------------------------------+-------------------------------------------------| | check for OutputDebugString error (2 matches) | anti-analysis/anti-debugging/debugger-detection | | read and send data from client to server | c2/file-transfer | | ... | ... | +-------------------------------------------------------+-------------------------------------------------+ """ rows = [] for rule in rutils.capability_rules(doc): count = len(rule["matches"]) if count == 1: capability = rutils.bold(rule["meta"]["name"]) else: capability = "%s (%d matches)" % (rutils.bold( rule["meta"]["name"]), count) rows.append((capability, rule["meta"]["namespace"])) if rows: ostream.write( tabulate.tabulate( rows, headers=[width("CAPABILITY", 50), width("NAMESPACE", 50)], tablefmt="psql")) ostream.write("\n") else: ostream.writeln(rutils.bold("no capabilities found"))
def render_mbc(doc, ostream): """ example:: {'MBC': {'ANTI-BEHAVIORAL ANALYSIS': ['Debugger Detection::Timing/Delay Check ' 'GetTickCount [B0001.032]', 'Emulator Detection [B0004]', 'Virtual Machine Detection::Instruction ' 'Testing [B0009.029]', 'Virtual Machine Detection [B0009]'], 'COLLECTION': ['Keylogging::Polling [F0002.002]'], 'CRYPTOGRAPHY': ['Encrypt Data::RC4 [C0027.009]', 'Generate Pseudo-random Sequence::RC4 PRGA ' '[C0021.004]']} } """ ostream["MBC"] = dict() objectives = collections.defaultdict(set) for rule in rutils.capability_rules(doc): if not rule["meta"].get("mbc"): continue for mbc in rule["meta"]["mbc"]: objectives[mbc["objective"]].add((mbc["behavior"], mbc.get("method"), mbc["id"])) for objective, behaviors in sorted(objectives.items()): inner_rows = [] for (behavior, method, id) in sorted(behaviors): if method is None: inner_rows.append("%s [%s]" % (behavior, id)) else: inner_rows.append("%s::%s [%s]" % (behavior, method, id)) ostream["MBC"].setdefault(objective.upper(), inner_rows)
def each(self, target): self.results = {} try: rules = capa.rules.RuleSet(capa.main.get_rules(self.rules, disable_progress=True)) extractor = capa.main.get_extractor(target, "auto", capa.main.BACKEND_VIV, [], False, disable_progress=True) capabilities, counts = capa.main.find_capabilities(rules, extractor, disable_progress=True) except Exception as error: raise ModuleExecutionError(self, 'Could not run capa on target with error: ' + str(error)) meta = capa.main.collect_metadata('', target, self.rules, extractor) meta['analysis'].update(counts) doc = capa.render.result_document.convert_capabilities_to_result_document(meta, rules, capabilities) # extract all MBS behaviors # taken from https://github.com/mandiant/capa/blob/master/scripts/capa_as_library.py if doc: for rule in rutils.capability_rules(doc): if not rule['meta'].get('mbc'): continue for mbc in rule['meta']['mbc']: if mbc['objective'] not in self.results: self.results[mbc['objective']] = [] self.results[mbc['objective']].append(f"{mbc['id']}: {mbc['behavior']}::{mbc.get('method')}") return len(self.results) > 0
def render_mbc(doc, ostream): """ example:: +--------------------------+------------------------------------------------------------+ | MBC Objective | MBC Behavior | |--------------------------+------------------------------------------------------------| | ANTI-BEHAVIORAL ANALYSIS | Virtual Machine Detection::Instruction Testing [B0009.029] | | COLLECTION | Keylogging::Polling [F0002.002] | | COMMUNICATION | Interprocess Communication::Create Pipe [C0003.001] | | | Interprocess Communication::Write Pipe [C0003.004] | | IMPACT | Remote Access::Reverse Shell [B0022.001] | +--------------------------+------------------------------------------------------------+ """ objectives = collections.defaultdict(set) for rule in rutils.capability_rules(doc): if not rule["meta"].get("mbc"): continue mbcs = rule["meta"]["mbc"] if not isinstance(mbcs, list): raise ValueError("invalid rule: MBC mapping is not a list") for mbc in mbcs: objective, _, rest = mbc.partition("::") if "::" in rest: behavior, _, rest = rest.partition("::") method, _, id = rest.rpartition(" ") objectives[objective].add((behavior, method, id)) else: behavior, _, id = rest.rpartition(" ") objectives[objective].add((behavior, id)) rows = [] for objective, behaviors in sorted(objectives.items()): inner_rows = [] for spec in sorted(behaviors): if len(spec) == 2: behavior, id = spec inner_rows.append("%s %s" % (rutils.bold(behavior), id)) elif len(spec) == 3: behavior, method, id = spec inner_rows.append("%s::%s %s" % (rutils.bold(behavior), method, id)) else: raise RuntimeError("unexpected MBC spec format") rows.append(( rutils.bold(objective.upper()), "\n".join(inner_rows), )) if rows: ostream.write( tabulate.tabulate(rows, headers=[ width("MBC Objective", 25), width("MBC Behavior", 75) ], tablefmt="psql")) ostream.write("\n")
def find_subrule_matches(doc): """ collect the rule names that have been matched as a subrule match. this way we can avoid displaying entries for things that are too specific. """ matches = set([]) def rec(node): if not node["success"]: # there's probably a bug here for rules that do `not: match: ...` # but we don't have any examples of this yet return elif node["node"]["type"] == "statement": for child in node["children"]: rec(child) elif node["node"]["type"] == "feature": if node["node"]["feature"]["type"] == "match": matches.add(node["node"]["feature"]["match"]) for rule in rutils.capability_rules(doc): for node in rule["matches"].values(): rec(node) return matches
def render_attack(doc, ostream): """ example:: {'ATT&CK': {'COLLECTION': ['Input Capture::Keylogging [T1056.001]'], 'DEFENSE EVASION': ['Obfuscated Files or Information [T1027]', 'Virtualization/Sandbox Evasion::System Checks ' '[T1497.001]'], 'DISCOVERY': ['File and Directory Discovery [T1083]', 'Query Registry [T1012]', 'System Information Discovery [T1082]'], 'EXECUTION': ['Shared Modules [T1129]']} } """ ostream["ATTCK"] = dict() tactics = collections.defaultdict(set) for rule in rutils.capability_rules(doc): if not rule["meta"].get("att&ck"): continue for attack in rule["meta"]["att&ck"]: tactics[attack["tactic"]].add((attack["technique"], attack.get("subtechnique"), attack["id"])) for tactic, techniques in sorted(tactics.items()): inner_rows = [] for (technique, subtechnique, id) in sorted(techniques): if subtechnique is None: inner_rows.append("%s %s" % (technique, id)) else: inner_rows.append("%s::%s %s" % (technique, subtechnique, id)) ostream["ATTCK"].setdefault(tactic.upper(), inner_rows)
def capa_render_mbc(doc, ostream): ostream["MBC"] = dict() objectives = collections.defaultdict(set) for rule in rutils.capability_rules(doc): if not rule["meta"].get("mbc"): continue mbcs = rule["meta"]["mbc"] if not isinstance(mbcs, list): raise ValueError("invalid rule: MBC mapping is not a list") for mbc in mbcs: objective, _, rest = mbc.partition("::") if "::" in rest: behavior, _, rest = rest.partition("::") method, _, id = rest.rpartition(" ") objectives[objective].add((behavior, method, id)) else: behavior, _, id = rest.rpartition(" ") objectives[objective].add((behavior, id)) for objective, behaviors in sorted(objectives.items()): inner_rows = [] for spec in sorted(behaviors): if len(spec) == 2: behavior, id = spec inner_rows.append("%s %s" % (behavior, id)) elif len(spec) == 3: behavior, method, id = spec inner_rows.append("%s::%s %s" % (behavior, method, id)) else: raise RuntimeError("unexpected MBC spec format") ostream["MBC"].setdefault(objective.upper(), inner_rows)
def render_capa_doc_by_function(self, doc): """ """ matches_by_function = {} for rule in rutils.capability_rules(doc): for ea in rule["matches"].keys(): ea = capa.ida.helpers.get_func_start_ea(ea) if ea is None: # file scope, skip rendering in this mode continue if not matches_by_function.get(ea, ()): # new function root matches_by_function[ea] = (CapaExplorerFunctionItem( self.root_node, ea, can_check=False), []) function_root, match_cache = matches_by_function[ea] if rule["meta"]["name"] in match_cache: # rule match already rendered for this function root, skip it continue match_cache.append(rule["meta"]["name"]) CapaExplorerRuleItem( function_root, rule["meta"]["name"], rule["meta"].get("namespace"), len(rule["matches"]), rule["source"], can_check=False, )
def render_capa_doc(self, doc): """render capa features specified in doc @param doc: capa result doc """ # inform model that changes are about to occur self.beginResetModel() for rule in rutils.capability_rules(doc): rule_name = rule["meta"]["name"] rule_namespace = rule["meta"].get("namespace") parent = CapaExplorerRuleItem(self.root_node, rule_name, rule_namespace, len(rule["matches"]), rule["source"]) for (location, match ) in doc["rules"][rule["meta"]["name"]]["matches"].items(): if rule["meta"]["scope"] == capa.rules.FILE_SCOPE: parent2 = parent elif rule["meta"]["scope"] == capa.rules.FUNCTION_SCOPE: parent2 = CapaExplorerFunctionItem(parent, location) elif rule["meta"]["scope"] == capa.rules.BASIC_BLOCK_SCOPE: parent2 = CapaExplorerBlockItem(parent, location) else: raise RuntimeError("unexpected rule scope: " + str(rule["meta"]["scope"])) self.render_capa_doc_match(parent2, match, doc) # inform model changes have ended self.endResetModel()
def render_capabilities(doc, ostream): """ example:: {'CAPABILITY': {'accept command line arguments': 'host-interaction/cli', 'allocate thread local storage (2 matches)': 'host-interaction/process', 'check for time delay via GetTickCount': 'anti-analysis/anti-debugging/debugger-detection', 'check if process is running under wine': 'anti-analysis/anti-emulation/wine', 'contain a resource (.rsrc) section': 'executable/pe/section/rsrc', 'write file (3 matches)': 'host-interaction/file-system/write'} } """ subrule_matches = find_subrule_matches(doc) ostream["CAPABILITY"] = dict() for rule in rutils.capability_rules(doc): if rule["meta"]["name"] in subrule_matches: # rules that are also matched by other rules should not get rendered by default. # this cuts down on the amount of output while giving approx the same detail. # see #224 continue count = len(rule["matches"]) if count == 1: capability = rule["meta"]["name"] else: capability = "%s (%d matches)" % (rule["meta"]["name"], count) ostream["CAPABILITY"].setdefault(rule["meta"]["namespace"], list()) ostream["CAPABILITY"][rule["meta"]["namespace"]].append(capability)
def find_subrule_matches(doc: rd.ResultDocument) -> Set[str]: """ collect the rule names that have been matched as a subrule match. this way we can avoid displaying entries for things that are too specific. """ matches = set([]) def rec(node: rd.Match): if not node.success: # there's probably a bug here for rules that do `not: match: ...` # but we don't have any examples of this yet return elif isinstance(node.node, rd.StatementNode): for child in node.children: rec(child) elif isinstance(node.node, rd.FeatureNode): if isinstance(node.node.feature, frzf.MatchFeature): matches.add(node.node.feature.match) for rule in rutils.capability_rules(doc): for _, node in rule.matches: rec(node) return matches
def render_attack(doc, ostream): """ example:: +------------------------+----------------------------------------------------------------------+ | ATT&CK Tactic | ATT&CK Technique | |------------------------+----------------------------------------------------------------------| | DEFENSE EVASION | Obfuscated Files or Information [T1027] | | DISCOVERY | Query Registry [T1012] | | | System Information Discovery [T1082] | | EXECUTION | Command and Scripting Interpreter::Windows Command Shell [T1059.003] | | | Shared Modules [T1129] | | EXFILTRATION | Exfiltration Over C2 Channel [T1041] | | PERSISTENCE | Create or Modify System Process::Windows Service [T1543.003] | +------------------------+----------------------------------------------------------------------+ """ tactics = collections.defaultdict(set) for rule in rutils.capability_rules(doc): if not rule["meta"].get("att&ck"): continue for attack in rule["meta"]["att&ck"]: tactic, _, rest = attack.partition("::") if "::" in rest: technique, _, rest = rest.partition("::") subtechnique, _, id = rest.rpartition(" ") tactics[tactic].add((technique, subtechnique, id)) else: technique, _, id = rest.rpartition(" ") tactics[tactic].add((technique, id)) rows = [] for tactic, techniques in sorted(tactics.items()): inner_rows = [] for spec in sorted(techniques): if len(spec) == 2: technique, id = spec inner_rows.append("%s %s" % (rutils.bold(technique), id)) elif len(spec) == 3: technique, subtechnique, id = spec inner_rows.append("%s::%s %s" % (rutils.bold(technique), subtechnique, id)) else: raise RuntimeError("unexpected ATT&CK spec format") rows.append(( rutils.bold(tactic.upper()), "\n".join(inner_rows), )) if rows: ostream.write( tabulate.tabulate(rows, headers=[ width("ATT&CK Tactic", 20), width("ATT&CK Technique", 80) ], tablefmt="psql")) ostream.write("\n")
def render_matches_by_function(doc): """ like: function at 0x1000321a with 33 features: - get hostname - initialize Winsock library function at 0x10003286 with 63 features: - create thread - terminate thread function at 0x10003415 with 116 features: - write file - send data - link function at runtime - create HTTP request - get common file path - send HTTP request - connect to HTTP server """ functions_by_bb = {} for function, info in doc["meta"]["analysis"]["layout"]["functions"].items( ): for bb in info["matched_basic_blocks"]: functions_by_bb[bb] = function ostream = rutils.StringIO() matches_by_function = collections.defaultdict(set) for rule in rutils.capability_rules(doc): if rule["meta"]["scope"] == capa.rules.FUNCTION_SCOPE: for va in rule["matches"].keys(): matches_by_function[va].add(rule["meta"]["name"]) elif rule["meta"]["scope"] == capa.rules.BASIC_BLOCK_SCOPE: for va in rule["matches"].keys(): function = functions_by_bb[va] matches_by_function[function].add(rule["meta"]["name"]) else: # file scope pass for va, feature_count in sorted( doc["meta"]["analysis"]["feature_counts"]["functions"].items()): va = int(va) if not matches_by_function.get(va, {}): continue ostream.writeln("function at 0x%X with %d features: " % (va, feature_count)) for rule_name in sorted(matches_by_function[va]): ostream.writeln(" - " + rule_name) return ostream.getvalue()
def render_attack(doc, ostream: StringIO): """ example:: +------------------------+----------------------------------------------------------------------+ | ATT&CK Tactic | ATT&CK Technique | |------------------------+----------------------------------------------------------------------| | DEFENSE EVASION | Obfuscated Files or Information [T1027] | | DISCOVERY | Query Registry [T1012] | | | System Information Discovery [T1082] | | EXECUTION | Command and Scripting Interpreter::Windows Command Shell [T1059.003] | | | Shared Modules [T1129] | | EXFILTRATION | Exfiltration Over C2 Channel [T1041] | | PERSISTENCE | Create or Modify System Process::Windows Service [T1543.003] | +------------------------+----------------------------------------------------------------------+ """ tactics = collections.defaultdict(set) for rule in rutils.capability_rules(doc): if not rule["meta"].get("att&ck"): continue for attack in rule["meta"]["att&ck"]: tactics[attack["tactic"]].add( (attack["technique"], attack.get("subtechnique"), attack["id"])) rows = [] for tactic, techniques in sorted(tactics.items()): inner_rows = [] for (technique, subtechnique, id) in sorted(techniques): if subtechnique is None: inner_rows.append("%s %s" % (rutils.bold(technique), id)) else: inner_rows.append("%s::%s %s" % (rutils.bold(technique), subtechnique, id)) rows.append(( rutils.bold(tactic.upper()), "\n".join(inner_rows), )) if rows: ostream.write( tabulate.tabulate(rows, headers=[ width("ATT&CK Tactic", 20), width("ATT&CK Technique", 80) ], tablefmt="psql")) ostream.write("\n")
def render_capa_doc_mitre_summary(self): """ render capa MITRE ATT&CK results """ tactics = collections.defaultdict(set) for rule in rutils.capability_rules(self.doc): if not rule["meta"].get("att&ck"): continue for attack in rule["meta"]["att&ck"]: tactic, _, rest = attack.partition("::") if "::" in rest: technique, _, rest = rest.partition("::") subtechnique, _, id = rest.rpartition(" ") tactics[tactic].add((technique, subtechnique, id)) else: technique, _, id = rest.rpartition(" ") tactics[tactic].add((technique, id)) column_one = [] column_two = [] for (tactic, techniques) in sorted(tactics.items()): column_one.append(tactic.upper()) # add extra space when more than one technique column_one.extend(["" for i in range(len(techniques) - 1)]) for spec in sorted(techniques): if len(spec) == 2: technique, id = spec column_two.append("%s %s" % (technique, id)) elif len(spec) == 3: technique, subtechnique, id = spec column_two.append("%s::%s %s" % (technique, subtechnique, id)) else: raise RuntimeError("unexpected ATT&CK spec format") self.view_attack.setRowCount(max(len(column_one), len(column_two))) for row, value in enumerate(column_one): self.view_attack.setItem(row, 0, self.render_new_table_header_item(value)) for row, value in enumerate(column_two): self.view_attack.setItem(row, 1, QtWidgets.QTableWidgetItem(value)) # resize columns to content self.view_attack.resizeColumnsToContents()
def render_mbc(doc, ostream): """ example:: {'MBC': {'ANTI-BEHAVIORAL ANALYSIS': ['Debugger Detection::Timing/Delay Check ' 'GetTickCount [B0001.032]', 'Emulator Detection [B0004]', 'Virtual Machine Detection::Instruction ' 'Testing [B0009.029]', 'Virtual Machine Detection [B0009]'], 'COLLECTION': ['Keylogging::Polling [F0002.002]'], 'CRYPTOGRAPHY': ['Encrypt Data::RC4 [C0027.009]', 'Generate Pseudo-random Sequence::RC4 PRGA ' '[C0021.004]']} } """ ostream["MBC"] = dict() objectives = collections.defaultdict(set) for rule in rutils.capability_rules(doc): if not rule["meta"].get("mbc"): continue mbcs = rule["meta"]["mbc"] if not isinstance(mbcs, list): raise ValueError("invalid rule: MBC mapping is not a list") for mbc in mbcs: objective, _, rest = mbc.partition("::") if "::" in rest: behavior, _, rest = rest.partition("::") method, _, id = rest.rpartition(" ") objectives[objective].add((behavior, method, id)) else: behavior, _, id = rest.rpartition(" ") objectives[objective].add((behavior, id)) for objective, behaviors in sorted(objectives.items()): inner_rows = [] for spec in sorted(behaviors): if len(spec) == 2: behavior, id = spec inner_rows.append("%s %s" % (behavior, id)) elif len(spec) == 3: behavior, method, id = spec inner_rows.append("%s::%s %s" % (behavior, method, id)) else: raise RuntimeError("unexpected MBC spec format") ostream["MBC"].setdefault(objective.upper(), inner_rows)
def render_capa_doc_summary(self): """ render capa summary results """ for (row, rule) in enumerate(rutils.capability_rules(self.doc)): count = len(rule["matches"]) if count == 1: capability = rule["meta"]["name"] else: capability = "%s (%d matches)" % (rule["meta"]["name"], count) self.view_summary.setRowCount(row + 1) self.view_summary.setItem(row, 0, self.render_new_table_header_item(capability)) self.view_summary.setItem(row, 1, QtWidgets.QTableWidgetItem(rule["meta"]["namespace"])) # resize columns to content self.view_summary.resizeColumnsToContents()
def render_mbc(doc, ostream: StringIO): """ example:: +--------------------------+------------------------------------------------------------+ | MBC Objective | MBC Behavior | |--------------------------+------------------------------------------------------------| | ANTI-BEHAVIORAL ANALYSIS | Virtual Machine Detection::Instruction Testing [B0009.029] | | COLLECTION | Keylogging::Polling [F0002.002] | | COMMUNICATION | Interprocess Communication::Create Pipe [C0003.001] | | | Interprocess Communication::Write Pipe [C0003.004] | | IMPACT | Remote Access::Reverse Shell [B0022.001] | +--------------------------+------------------------------------------------------------+ """ objectives = collections.defaultdict(set) for rule in rutils.capability_rules(doc): if not rule["meta"].get("mbc"): continue for mbc in rule["meta"]["mbc"]: objectives[mbc["objective"]].add( (mbc["behavior"], mbc.get("method"), mbc["id"])) rows = [] for objective, behaviors in sorted(objectives.items()): inner_rows = [] for (behavior, method, id) in sorted(behaviors): if method is None: inner_rows.append("%s [%s]" % (rutils.bold(behavior), id)) else: inner_rows.append("%s::%s [%s]" % (rutils.bold(behavior), method, id)) rows.append(( rutils.bold(objective.upper()), "\n".join(inner_rows), )) if rows: ostream.write( tabulate.tabulate(rows, headers=[ width("MBC Objective", 25), width("MBC Behavior", 75) ], tablefmt="psql")) ostream.write("\n")
def render_capa_doc_by_function(self, doc): """ """ matches_by_function = {} for rule in rutils.capability_rules(doc): for ea in rule["matches"].keys(): ea = capa.ida.helpers.get_func_start_ea(ea) if ea is None: # file scope, skip for rendering in this mode continue if None is matches_by_function.get(ea, None): matches_by_function[ea] = CapaExplorerFunctionItem(self.root_node, ea, can_check=False) CapaExplorerRuleItem( matches_by_function[ea], rule["meta"]["name"], rule["meta"].get("namespace"), len(rule["matches"]), rule["source"], can_check=False, )
def render_rules(ostream, doc): """ like: receive data (2 matches) namespace communication description all known techniques for receiving data from a potential C2 server scope function matches 0x10003A13 0x10003797 """ had_match = False for rule in rutils.capability_rules(doc): count = len(rule["matches"]) if count == 1: capability = rutils.bold(rule["meta"]["name"]) else: capability = "%s (%d matches)" % (rutils.bold( rule["meta"]["name"]), count) ostream.writeln(capability) had_match = True rows = [] for key in ("namespace", "description", "scope"): if key == "name" or key not in rule["meta"]: continue v = rule["meta"][key] if isinstance(v, list) and len(v) == 1: v = v[0] rows.append((key, v)) if rule["meta"]["scope"] != capa.rules.FILE_SCOPE: locations = doc["rules"][rule["meta"]["name"]]["matches"].keys() rows.append(("matches", "\n".join(map(rutils.hex, locations)))) ostream.writeln(tabulate.tabulate(rows, tablefmt="plain")) ostream.write("\n") if not had_match: ostream.writeln(rutils.bold("no capabilities found"))
def render_capa_doc_by_program(self, doc): """ """ for rule in rutils.capability_rules(doc): rule_name = rule["meta"]["name"] rule_namespace = rule["meta"].get("namespace") parent = CapaExplorerRuleItem( self.root_node, rule_name, rule_namespace, len(rule["matches"]), rule["source"] ) for (location, match) in doc["rules"][rule["meta"]["name"]]["matches"].items(): if rule["meta"]["scope"] == capa.rules.FILE_SCOPE: parent2 = parent elif rule["meta"]["scope"] == capa.rules.FUNCTION_SCOPE: parent2 = CapaExplorerFunctionItem(parent, location) elif rule["meta"]["scope"] == capa.rules.BASIC_BLOCK_SCOPE: parent2 = CapaExplorerBlockItem(parent, location) else: raise RuntimeError("unexpected rule scope: " + str(rule["meta"]["scope"])) self.render_capa_doc_match(parent2, match, doc)
def render_attack(doc, ostream): """ example:: {'ATT&CK': {'COLLECTION': ['Input Capture::Keylogging [T1056.001]'], 'DEFENSE EVASION': ['Obfuscated Files or Information [T1027]', 'Virtualization/Sandbox Evasion::System Checks ' '[T1497.001]'], 'DISCOVERY': ['File and Directory Discovery [T1083]', 'Query Registry [T1012]', 'System Information Discovery [T1082]'], 'EXECUTION': ['Shared Modules [T1129]']} } """ ostream["ATTCK"] = dict() tactics = collections.defaultdict(set) for rule in rutils.capability_rules(doc): if not rule["meta"].get("att&ck"): continue for attack in rule["meta"]["att&ck"]: tactic, _, rest = attack.partition("::") if "::" in rest: technique, _, rest = rest.partition("::") subtechnique, _, id = rest.rpartition(" ") tactics[tactic].add((technique, subtechnique, id)) else: technique, _, id = rest.rpartition(" ") tactics[tactic].add((technique, id)) for tactic, techniques in sorted(tactics.items()): inner_rows = [] for spec in sorted(techniques): if len(spec) == 2: technique, id = spec inner_rows.append("%s %s" % (technique, id)) elif len(spec) == 3: technique, subtechnique, id = spec inner_rows.append("%s::%s %s" % (technique, subtechnique, id)) else: raise RuntimeError("unexpected ATT&CK spec format") ostream["ATTCK"].setdefault(tactic.upper(), inner_rows)
def render_capabilities(doc, ostream): """ example:: +-------------------------------------------------------+-------------------------------------------------+ | CAPABILITY | NAMESPACE | |-------------------------------------------------------+-------------------------------------------------| | check for OutputDebugString error (2 matches) | anti-analysis/anti-debugging/debugger-detection | | read and send data from client to server | c2/file-transfer | | ... | ... | +-------------------------------------------------------+-------------------------------------------------+ """ subrule_matches = find_subrule_matches(doc) rows = [] for rule in rutils.capability_rules(doc): if rule["meta"]["name"] in subrule_matches: # rules that are also matched by other rules should not get rendered by default. # this cuts down on the amount of output while giving approx the same detail. # see #224 continue count = len(rule["matches"]) if count == 1: capability = rutils.bold(rule["meta"]["name"]) else: capability = "%s (%d matches)" % (rutils.bold( rule["meta"]["name"]), count) rows.append((capability, rule["meta"]["namespace"])) if rows: ostream.write( tabulate.tabulate( rows, headers=[width("CAPABILITY", 50), width("NAMESPACE", 50)], tablefmt="psql")) ostream.write("\n") else: ostream.writeln(rutils.bold("no capabilities found"))
def render_matches_by_function(doc): """ like: function at 0x1000321a with 33 features: - get hostname - initialize Winsock library function at 0x10003286 with 63 features: - create thread - terminate thread function at 0x10003415 with 116 features: - write file - send data - link function at runtime - create HTTP request - get common file path - send HTTP request - connect to HTTP server """ ostream = rutils.StringIO() matches_by_function = collections.defaultdict(set) for rule in rutils.capability_rules(doc): for va in rule["matches"].keys(): matches_by_function[va].add(rule["meta"]["name"]) for va, feature_count in sorted( doc["meta"]["analysis"]["feature_counts"]["functions"].items()): va = int(va) if not matches_by_function.get(va, {}): continue ostream.writeln("function at 0x%X with %d features: " % (va, feature_count)) for rule_name in matches_by_function[va]: ostream.writeln(" - " + rule_name) ostream.write("\n") return ostream.getvalue()
def render_rules(ostream, doc): """ like: ## rules check for OutputDebugString error namespace anti-analysis/anti-debugging/debugger-detection author [email protected] scope function mbc Anti-Behavioral Analysis::Detect Debugger::OutputDebugString examples Practical Malware Analysis Lab 16-02.exe_:0x401020 function @ 0x10004706 and: api: kernel32.SetLastError @ 0x100047C2 api: kernel32.GetLastError @ 0x10004A87 api: kernel32.OutputDebugString @ 0x10004767, 0x10004787, 0x10004816, 0x10004895 """ had_match = False for rule in rutils.capability_rules(doc): count = len(rule["matches"]) if count == 1: capability = rutils.bold(rule["meta"]["name"]) else: capability = "%s (%d matches)" % (rutils.bold(rule["meta"]["name"]), count) ostream.writeln(capability) had_match = True rows = [] for key in capa.rules.META_KEYS: if key == "name" or key not in rule["meta"]: continue v = rule["meta"][key] if isinstance(v, list) and len(v) == 1: v = v[0] elif isinstance(v, list) and len(v) > 1: v = ", ".join(v) rows.append((key, v)) ostream.writeln(tabulate.tabulate(rows, tablefmt="plain")) if rule["meta"]["scope"] == capa.rules.FILE_SCOPE: matches = list(doc["rules"][rule["meta"]["name"]]["matches"].values()) if len(matches) != 1: # i think there should only ever be one match per file-scope rule, # because we do the file-scope evaluation a single time. # but i'm not 100% sure if this is/will always be true. # so, lets be explicit about our assumptions and raise an exception if they fail. raise RuntimeError("unexpected file scope match count: " + len(matches)) render_match(ostream, matches[0], indent=0) else: for location, match in sorted(doc["rules"][rule["meta"]["name"]]["matches"].items()): ostream.write(rule["meta"]["scope"]) ostream.write(" @ ") ostream.writeln(rutils.hex(location)) render_match(ostream, match, indent=1) ostream.write("\n") if not had_match: ostream.writeln(rutils.bold("no capabilities found"))