def render_locations(ostream, match): # its possible to have an empty locations array here, # such as when we're in MODE_FAILURE and showing the logic # under a `not` statement (which will have no matched locations). locations = list(sorted(match.get("locations", []))) if len(locations) == 1: ostream.write(" @ ") ostream.write(rutils.hex(locations[0])) elif len(locations) > 1: ostream.write(" @ ") if len(locations) > 4: # don't display too many locations, because it becomes very noisy. # probably only the first handful of locations will be useful for inspection. ostream.write(", ".join(map(rutils.hex, locations[0:4]))) ostream.write(", and %d more..." % (len(locations) - 4)) else: ostream.write(", ".join(map(rutils.hex, locations)))
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"))
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 function @ 0x10004706 and: api: kernel32.SetLastError @ 0x100047C2 api: kernel32.GetLastError @ 0x10004A87 api: kernel32.OutputDebugString @ 0x10004767, 0x10004787, 0x10004816, 0x10004895 """ 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 had_match = False for (_, _, rule) in sorted( map( lambda rule: (rule["meta"].get("namespace", ""), rule["meta"][ "name"], rule), doc["rules"].values())): # default scope hides things like lib rules, malware-category rules, etc. # but in vverbose mode, we really want to show everything. # # still ignore subscope rules because they're stitched into the final document. if rule["meta"].get("capa/subscope"): 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) ostream.writeln(capability) had_match = True rows = [] for key in capa.rules.META_KEYS: if key == "name" or key not in rule["meta"]: continue if key == "examples": # I can't think of a reason that an analyst would pivot to the concrete example # directly from the capa output. # the more likely flow is to review the rule and go from there. # so, don't make the output messy by showing the examples. continue v = rule["meta"][key] if not v: continue if key in ("att&ck", "mbc"): v = [rutils.format_parts_id(vv) for vv in v] 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: %d" % (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.write(rutils.hex(location)) if rule["meta"]["scope"] == capa.rules.BASIC_BLOCK_SCOPE: ostream.write(" in function " + rutils.hex(functions_by_bb[location])) ostream.write("\n") render_match(ostream, match, indent=1) ostream.write("\n") if not had_match: ostream.writeln(rutils.bold("no capabilities found"))