mirror of
https://github.com/mandiant/capa.git
synced 2026-01-07 02:04:11 -08:00
Merge pull request #105 from fireeye/output-feature-count
Output feature count
This commit is contained in:
@@ -339,7 +339,13 @@ class CapaExplorerForm(idaapi.PluginForm):
|
||||
rules_path = os.path.join(os.path.dirname(self.file_loc), "../..", "rules")
|
||||
rules = capa.main.get_rules(rules_path)
|
||||
rules = capa.rules.RuleSet(rules)
|
||||
capabilities = capa.main.find_capabilities(rules, capa.features.extractors.ida.IdaFeatureExtractor(), True)
|
||||
|
||||
meta = capa.ida.helpers.collect_metadata()
|
||||
|
||||
capabilities, counts = capa.main.find_capabilities(
|
||||
rules, capa.features.extractors.ida.IdaFeatureExtractor(), True
|
||||
)
|
||||
meta["analysis"].update(counts)
|
||||
|
||||
# support binary files specifically for x86/AMD64 shellcode
|
||||
# warn user binary file is loaded but still allow capa to process it
|
||||
@@ -364,7 +370,6 @@ class CapaExplorerForm(idaapi.PluginForm):
|
||||
|
||||
logger.info("analysis completed.")
|
||||
|
||||
meta = capa.ida.helpers.collect_metadata()
|
||||
doc = capa.render.convert_capabilities_to_result_document(meta, rules, capabilities)
|
||||
|
||||
self.model_data.render_capa_doc(doc)
|
||||
|
||||
32
capa/main.py
32
capa/main.py
@@ -68,7 +68,7 @@ def find_function_capabilities(ruleset, extractor, f):
|
||||
function_features[capa.features.MatchedRule(rule_name)].add(va)
|
||||
|
||||
_, function_matches = capa.engine.match(ruleset.function_rules, function_features, oint(f))
|
||||
return function_matches, bb_matches
|
||||
return function_matches, bb_matches, len(function_features)
|
||||
|
||||
|
||||
def find_file_capabilities(ruleset, extractor, function_features):
|
||||
@@ -84,20 +84,25 @@ def find_file_capabilities(ruleset, extractor, function_features):
|
||||
if feature not in file_features:
|
||||
file_features[feature] = set()
|
||||
|
||||
logger.info("analyzed file and extracted %d features", len(file_features))
|
||||
logger.debug("analyzed file and extracted %d features", len(file_features))
|
||||
|
||||
file_features.update(function_features)
|
||||
|
||||
_, matches = capa.engine.match(ruleset.file_rules, file_features, 0x0)
|
||||
return matches
|
||||
return matches, len(file_features)
|
||||
|
||||
|
||||
def find_capabilities(ruleset, extractor, disable_progress=None):
|
||||
all_function_matches = collections.defaultdict(list)
|
||||
all_bb_matches = collections.defaultdict(list)
|
||||
|
||||
meta = {"feature_counts": {"file": 0, "functions": {},}}
|
||||
|
||||
for f in tqdm.tqdm(extractor.get_functions(), disable=disable_progress, unit=" functions"):
|
||||
function_matches, bb_matches = find_function_capabilities(ruleset, extractor, f)
|
||||
function_matches, bb_matches, feature_count = find_function_capabilities(ruleset, extractor, f)
|
||||
meta["feature_counts"]["functions"][f.__int__()] = feature_count
|
||||
logger.debug("analyzed function 0x%x and extracted %d features", f.__int__(), feature_count)
|
||||
|
||||
for rule_name, res in function_matches.items():
|
||||
all_function_matches[rule_name].extend(res)
|
||||
for rule_name, res in bb_matches.items():
|
||||
@@ -110,14 +115,15 @@ def find_capabilities(ruleset, extractor, disable_progress=None):
|
||||
for rule_name, results in all_function_matches.items()
|
||||
}
|
||||
|
||||
all_file_matches = find_file_capabilities(ruleset, extractor, function_features)
|
||||
all_file_matches, feature_count = find_file_capabilities(ruleset, extractor, function_features)
|
||||
meta["feature_counts"]["file"] = feature_count
|
||||
|
||||
matches = {}
|
||||
matches.update(all_bb_matches)
|
||||
matches.update(all_function_matches)
|
||||
matches.update(all_file_matches)
|
||||
|
||||
return matches
|
||||
return matches, meta
|
||||
|
||||
|
||||
def has_rule_with_namespace(rules, capabilities, rule_cat):
|
||||
@@ -301,7 +307,10 @@ def get_rules(rule_path):
|
||||
for root, dirs, files in os.walk(rule_path):
|
||||
for file in files:
|
||||
if not file.endswith(".yml"):
|
||||
logger.warning("skipping non-.yml file: %s", file)
|
||||
if not (file.endswith(".md") or file.endswith(".git")):
|
||||
# expect to see readme.md, format.md, and maybe a .git directory
|
||||
# other things maybe are rules, but are mis-named.
|
||||
logger.warning("skipping non-.yml file: %s", file)
|
||||
continue
|
||||
|
||||
rule_path = os.path.join(root, file)
|
||||
@@ -485,7 +494,8 @@ def main(argv=None):
|
||||
|
||||
meta = collect_metadata(argv, args.sample, format, extractor)
|
||||
|
||||
capabilities = find_capabilities(rules, extractor)
|
||||
capabilities, counts = find_capabilities(rules, extractor)
|
||||
meta["analysis"].update(counts)
|
||||
|
||||
if has_file_limitation(rules, capabilities):
|
||||
# bail if capa encountered file limitation e.g. a packed binary
|
||||
@@ -542,12 +552,14 @@ def ida_main():
|
||||
rules = get_rules(rules_path)
|
||||
rules = capa.rules.RuleSet(rules)
|
||||
|
||||
capabilities = find_capabilities(rules, capa.features.extractors.ida.IdaFeatureExtractor())
|
||||
meta = capa.ida.helpers.collect_metadata()
|
||||
|
||||
capabilities, counts = find_capabilities(rules, capa.features.extractors.ida.IdaFeatureExtractor())
|
||||
meta["analysis"].update(counts)
|
||||
|
||||
if has_file_limitation(rules, capabilities, is_standalone=False):
|
||||
capa.ida.helpers.inform_user_ida_ui("capa encountered warnings during analysis")
|
||||
|
||||
meta = capa.ida.helpers.collect_metadata()
|
||||
print(capa.render.render_default(meta, rules, capabilities))
|
||||
|
||||
|
||||
|
||||
@@ -17,11 +17,10 @@ def width(s, character_count):
|
||||
|
||||
|
||||
def render_meta(doc, ostream):
|
||||
rows = []
|
||||
rows.append((width("md5", 22), width(doc["meta"]["sample"]["md5"], 82)))
|
||||
rows.append(("path", doc["meta"]["sample"]["path"]))
|
||||
rows.append(("timestamp", doc["meta"]["timestamp"]))
|
||||
rows.append(("capa version", doc["meta"]["version"]))
|
||||
rows = [
|
||||
(width("md5", 22), width(doc["meta"]["sample"]["md5"], 82)),
|
||||
("path", doc["meta"]["sample"]["path"]),
|
||||
]
|
||||
|
||||
ostream.write(tabulate.tabulate(rows, tablefmt="psql"))
|
||||
ostream.write("\n")
|
||||
@@ -48,7 +47,7 @@ def render_capabilities(doc, ostream):
|
||||
capability = "%s (%d matches)" % (rutils.bold(rule["meta"]["name"]), count)
|
||||
rows.append((capability, rule["meta"]["namespace"]))
|
||||
|
||||
ostream.write(tabulate.tabulate(rows, headers=[width("CAPABILITY", 40), width("NAMESPACE", 40)], tablefmt="psql"))
|
||||
ostream.write(tabulate.tabulate(rows, headers=[width("CAPABILITY", 50), width("NAMESPACE", 50)], tablefmt="psql"))
|
||||
ostream.write("\n")
|
||||
|
||||
|
||||
|
||||
@@ -20,22 +20,53 @@ import capa.rules
|
||||
import capa.render.utils as rutils
|
||||
|
||||
|
||||
def render_verbose(doc):
|
||||
ostream = rutils.StringIO()
|
||||
def render_meta(ostream, doc):
|
||||
"""
|
||||
like:
|
||||
|
||||
rows = []
|
||||
rows.append(("md5", doc["meta"]["sample"]["md5"]))
|
||||
rows.append(("sha1", doc["meta"]["sample"]["sha1"]))
|
||||
rows.append(("sha256", doc["meta"]["sample"]["sha256"]))
|
||||
rows.append(("path", doc["meta"]["sample"]["path"]))
|
||||
rows.append(("timestamp", doc["meta"]["timestamp"]))
|
||||
rows.append(("capa version", doc["meta"]["version"]))
|
||||
rows.append(("format", doc["meta"]["analysis"]["format"]))
|
||||
rows.append(("extractor", doc["meta"]["analysis"]["extractor"]))
|
||||
rows.append(("base address", hex(doc["meta"]["analysis"]["base_address"])))
|
||||
md5 84882c9d43e23d63b82004fae74ebb61
|
||||
sha1 c6fb3b50d946bec6f391aefa4e54478cf8607211
|
||||
sha256 5eced7367ed63354b4ed5c556e2363514293f614c2c2eb187273381b2ef5f0f9
|
||||
path /tmp/suspicious.dll_
|
||||
timestamp 2020-07-03T10:17:05.796933
|
||||
capa version 0.0.0
|
||||
format auto
|
||||
extractor VivisectFeatureExtractor
|
||||
base address 0x10000000
|
||||
function count 42
|
||||
total feature count 1918
|
||||
"""
|
||||
rows = [
|
||||
("md5", doc["meta"]["sample"]["md5"]),
|
||||
("sha1", doc["meta"]["sample"]["sha1"]),
|
||||
("sha256", doc["meta"]["sample"]["sha256"]),
|
||||
("path", doc["meta"]["sample"]["path"]),
|
||||
("timestamp", doc["meta"]["timestamp"]),
|
||||
("capa version", doc["meta"]["version"]),
|
||||
("format", doc["meta"]["analysis"]["format"]),
|
||||
("extractor", doc["meta"]["analysis"]["extractor"]),
|
||||
("base address", hex(doc["meta"]["analysis"]["base_address"])),
|
||||
("function count", len(doc["meta"]["analysis"]["feature_counts"]["functions"])),
|
||||
(
|
||||
"total feature count",
|
||||
doc["meta"]["analysis"]["feature_counts"]["file"]
|
||||
+ sum(doc["meta"]["analysis"]["feature_counts"]["functions"].values()),
|
||||
),
|
||||
]
|
||||
ostream.writeln(tabulate.tabulate(rows, tablefmt="plain"))
|
||||
ostream.write("\n")
|
||||
|
||||
|
||||
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
|
||||
"""
|
||||
for rule in rutils.capability_rules(doc):
|
||||
count = len(rule["matches"])
|
||||
if count == 1:
|
||||
@@ -62,4 +93,14 @@ def render_verbose(doc):
|
||||
ostream.writeln(tabulate.tabulate(rows, tablefmt="plain"))
|
||||
ostream.write("\n")
|
||||
|
||||
|
||||
def render_verbose(doc):
|
||||
ostream = rutils.StringIO()
|
||||
|
||||
render_meta(ostream, doc)
|
||||
ostream.write("\n")
|
||||
|
||||
render_rules(ostream, doc)
|
||||
ostream.write("\n")
|
||||
|
||||
return ostream.getvalue()
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import collections
|
||||
|
||||
import tabulate
|
||||
|
||||
import capa.rules
|
||||
import capa.render.utils as rutils
|
||||
import capa.render.verbose
|
||||
|
||||
|
||||
def render_locations(ostream, match):
|
||||
@@ -138,22 +141,23 @@ def render_match(ostream, match, indent=0, mode=MODE_SUCCESS):
|
||||
render_match(ostream, child, indent=indent + 1, mode=child_mode)
|
||||
|
||||
|
||||
def render_vverbose(doc):
|
||||
ostream = rutils.StringIO()
|
||||
|
||||
rows = []
|
||||
rows.append(("md5", doc["meta"]["sample"]["md5"]))
|
||||
rows.append(("sha1", doc["meta"]["sample"]["sha1"]))
|
||||
rows.append(("sha256", doc["meta"]["sample"]["sha256"]))
|
||||
rows.append(("path", doc["meta"]["sample"]["path"]))
|
||||
rows.append(("timestamp", doc["meta"]["timestamp"]))
|
||||
rows.append(("capa version", doc["meta"]["version"]))
|
||||
rows.append(("format", doc["meta"]["analysis"]["format"]))
|
||||
rows.append(("extractor", doc["meta"]["analysis"]["extractor"]))
|
||||
rows.append(("base address", hex(doc["meta"]["analysis"]["base_address"])))
|
||||
ostream.writeln(tabulate.tabulate(rows, tablefmt="plain"))
|
||||
ostream.write("\n")
|
||||
def render_rules(ostream, doc):
|
||||
"""
|
||||
like:
|
||||
|
||||
## rules
|
||||
check for OutputDebugString error
|
||||
namespace anti-analysis/anti-debugging/debugger-detection
|
||||
author michael.hunhoff@fireeye.com
|
||||
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
|
||||
"""
|
||||
for rule in rutils.capability_rules(doc):
|
||||
count = len(rule["matches"])
|
||||
if count == 1:
|
||||
@@ -192,7 +196,16 @@ def render_vverbose(doc):
|
||||
ostream.write(" @ ")
|
||||
ostream.writeln(rutils.hex(location))
|
||||
render_match(ostream, match, indent=1)
|
||||
|
||||
ostream.write("\n")
|
||||
|
||||
|
||||
def render_vverbose(doc):
|
||||
ostream = rutils.StringIO()
|
||||
|
||||
capa.render.verbose.render_meta(ostream, doc)
|
||||
ostream.write("\n")
|
||||
|
||||
render_rules(ostream, doc)
|
||||
ostream.write("\n")
|
||||
|
||||
return ostream.getvalue()
|
||||
|
||||
@@ -180,7 +180,7 @@ class DoesntMatchExample(Lint):
|
||||
|
||||
try:
|
||||
extractor = capa.main.get_extractor(path, "auto")
|
||||
capabilities = capa.main.find_capabilities(ctx["rules"], extractor, disable_progress=True)
|
||||
capabilities, meta = capa.main.find_capabilities(ctx["rules"], extractor, disable_progress=True)
|
||||
except Exception as e:
|
||||
logger.error("failed to extract capabilities: %s %s %s", rule.name, path, e)
|
||||
return True
|
||||
|
||||
242
scripts/show-capabilities-by-function.py
Normal file
242
scripts/show-capabilities-by-function.py
Normal file
@@ -0,0 +1,242 @@
|
||||
#!/usr/bin/env python2
|
||||
"""
|
||||
show-capabilities-by-function
|
||||
|
||||
Invoke capa to extract the capabilities of the given sample
|
||||
and emit the results grouped by function.
|
||||
|
||||
This is useful to identify "complex functions" - that is,
|
||||
functions that implement a lot of different types of logic.
|
||||
|
||||
Example::
|
||||
|
||||
$ python scripts/show-capabilities-by-function.py /tmp/suspicious.dll_
|
||||
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
|
||||
function at 0x10003797 with 81 features:
|
||||
- get socket status
|
||||
- send data
|
||||
- receive data
|
||||
- create TCP socket
|
||||
- send data on socket
|
||||
- receive data on socket
|
||||
- act as TCP client
|
||||
- resolve DNS
|
||||
- create UDP socket
|
||||
- initialize Winsock library
|
||||
- set socket configuration
|
||||
- connect TCP socket
|
||||
...
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
import collections
|
||||
|
||||
import argparse
|
||||
import colorama
|
||||
|
||||
import capa.main
|
||||
import capa.rules
|
||||
import capa.engine
|
||||
import capa.render
|
||||
import capa.features
|
||||
import capa.render.utils as rutils
|
||||
import capa.features.freeze
|
||||
import capa.features.extractors.viv
|
||||
|
||||
logger = logging.getLogger("capa.show-capabilities-by-function")
|
||||
|
||||
|
||||
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 main(argv=None):
|
||||
if argv is None:
|
||||
argv = sys.argv[1:]
|
||||
|
||||
formats = [
|
||||
("auto", "(default) detect file type automatically"),
|
||||
("pe", "Windows PE file"),
|
||||
("sc32", "32-bit shellcode"),
|
||||
("sc64", "64-bit shellcode"),
|
||||
("freeze", "features previously frozen by capa"),
|
||||
]
|
||||
format_help = ", ".join(["%s: %s" % (f[0], f[1]) for f in formats])
|
||||
|
||||
parser = argparse.ArgumentParser(description="detect capabilities in programs.")
|
||||
parser.add_argument("sample", type=str, help="Path to sample to analyze")
|
||||
parser.add_argument(
|
||||
"-r",
|
||||
"--rules",
|
||||
type=str,
|
||||
default="(embedded rules)",
|
||||
help="Path to rule file or directory, use embedded rules by default",
|
||||
)
|
||||
parser.add_argument("-t", "--tag", type=str, help="Filter on rule meta field values")
|
||||
parser.add_argument("-d", "--debug", action="store_true", help="Enable debugging output on STDERR")
|
||||
parser.add_argument("-q", "--quiet", action="store_true", help="Disable all output but errors")
|
||||
parser.add_argument(
|
||||
"-f",
|
||||
"--format",
|
||||
choices=[f[0] for f in formats],
|
||||
default="auto",
|
||||
help="Select sample format, %s" % format_help,
|
||||
)
|
||||
args = parser.parse_args(args=argv)
|
||||
|
||||
if args.quiet:
|
||||
logging.basicConfig(level=logging.ERROR)
|
||||
logging.getLogger().setLevel(logging.ERROR)
|
||||
elif args.debug:
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
logging.getLogger().setLevel(logging.DEBUG)
|
||||
else:
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logging.getLogger().setLevel(logging.INFO)
|
||||
|
||||
# disable vivisect-related logging, it's verbose and not relevant for capa users
|
||||
capa.main.set_vivisect_log_level(logging.CRITICAL)
|
||||
|
||||
# py2 doesn't know about cp65001, which is a variant of utf-8 on windows
|
||||
# tqdm bails when trying to render the progress bar in this setup.
|
||||
# because cp65001 is utf-8, we just map that codepage to the utf-8 codec.
|
||||
# see #380 and: https://stackoverflow.com/a/3259271/87207
|
||||
import codecs
|
||||
|
||||
codecs.register(lambda name: codecs.lookup("utf-8") if name == "cp65001" else None)
|
||||
|
||||
if args.rules == "(embedded rules)":
|
||||
logger.info("-" * 80)
|
||||
logger.info(" Using default embedded rules.")
|
||||
logger.info(" To provide your own rules, use the form `capa.exe ./path/to/rules/ /path/to/mal.exe`.")
|
||||
logger.info(" You can see the current default rule set here:")
|
||||
logger.info(" https://github.com/fireeye/capa-rules")
|
||||
logger.info("-" * 80)
|
||||
|
||||
logger.debug("detected running from source")
|
||||
args.rules = os.path.join(os.path.dirname(__file__), "..", "rules")
|
||||
logger.debug("default rule path (source method): %s", args.rules)
|
||||
else:
|
||||
logger.info("using rules path: %s", args.rules)
|
||||
|
||||
try:
|
||||
rules = capa.main.get_rules(args.rules)
|
||||
rules = capa.rules.RuleSet(rules)
|
||||
logger.info("successfully loaded %s rules", len(rules))
|
||||
if args.tag:
|
||||
rules = rules.filter_rules_by_meta(args.tag)
|
||||
logger.info("selected %s rules", len(rules))
|
||||
except (IOError, capa.rules.InvalidRule, capa.rules.InvalidRuleSet) as e:
|
||||
logger.error("%s", str(e))
|
||||
return -1
|
||||
|
||||
with open(args.sample, "rb") as f:
|
||||
taste = f.read(8)
|
||||
|
||||
if (args.format == "freeze") or (args.format == "auto" and capa.features.freeze.is_freeze(taste)):
|
||||
format = "freeze"
|
||||
with open(args.sample, "rb") as f:
|
||||
extractor = capa.features.freeze.load(f.read())
|
||||
else:
|
||||
format = args.format
|
||||
try:
|
||||
extractor = capa.main.get_extractor(args.sample, args.format)
|
||||
except capa.main.UnsupportedFormatError:
|
||||
logger.error("-" * 80)
|
||||
logger.error(" Input file does not appear to be a PE file.")
|
||||
logger.error(" ")
|
||||
logger.error(
|
||||
" capa currently only supports analyzing PE files (or shellcode, when using --format sc32|sc64)."
|
||||
)
|
||||
logger.error(
|
||||
" If you don't know the input file type, you can try using the `file` utility to guess it."
|
||||
)
|
||||
logger.error("-" * 80)
|
||||
return -1
|
||||
except capa.main.UnsupportedRuntimeError:
|
||||
logger.error("-" * 80)
|
||||
logger.error(" Unsupported runtime or Python interpreter.")
|
||||
logger.error(" ")
|
||||
logger.error(" capa supports running under Python 2.7 using Vivisect for binary analysis.")
|
||||
logger.error(" It can also run within IDA Pro, using either Python 2.7 or 3.5+.")
|
||||
logger.error(" ")
|
||||
logger.error(
|
||||
" If you're seeing this message on the command line, please ensure you're running Python 2.7."
|
||||
)
|
||||
logger.error("-" * 80)
|
||||
return -1
|
||||
|
||||
meta = capa.main.collect_metadata(argv, args.sample, format, extractor)
|
||||
capabilities, counts = capa.main.find_capabilities(rules, extractor)
|
||||
meta["analysis"].update(counts)
|
||||
|
||||
if capa.main.has_file_limitation(rules, capabilities):
|
||||
# bail if capa encountered file limitation e.g. a packed binary
|
||||
# do show the output in verbose mode, though.
|
||||
if not (args.verbose or args.vverbose or args.json):
|
||||
return -1
|
||||
|
||||
# colorama will detect:
|
||||
# - when on Windows console, and fixup coloring, and
|
||||
# - when not an interactive session, and disable coloring
|
||||
# renderers should use coloring and assume it will be stripped out if necessary.
|
||||
colorama.init()
|
||||
doc = capa.render.convert_capabilities_to_result_document(meta, rules, capabilities)
|
||||
print(render_matches_by_function(doc))
|
||||
colorama.deinit()
|
||||
|
||||
logger.info("done.")
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -1,6 +1,60 @@
|
||||
#!/usr/bin/env python2
|
||||
"""
|
||||
show the features extracted by capa.
|
||||
show-features
|
||||
|
||||
Show the features that capa extracts from the given sample,
|
||||
to assist with the development of rules.
|
||||
|
||||
If you have a function with a capability that you'd like to detect,
|
||||
you can run this tool and grep for the function/basic block/instruction addresses
|
||||
to see what capa picks up.
|
||||
This way, you can verify that capa successfully notices the features you'd reference.
|
||||
|
||||
Example::
|
||||
|
||||
$ python scripts/show-features.py /tmp/suspicious.dll_
|
||||
...
|
||||
file: 0x10004e4d: export(__entry)
|
||||
file: 0x10004706: export(Install)
|
||||
file: 0x10004c2b: export(uninstallA)
|
||||
file: 0x10005034: import(kernel32.GetStartupInfoA)
|
||||
file: 0x10005034: import(GetStartupInfoA)
|
||||
file: 0x10005048: import(kernel32.SetLastError)
|
||||
file: 0x00004e10: string(Y29ubmVjdA==)
|
||||
file: 0x00004e28: string(practicalmalwareanalysis.com)
|
||||
file: 0x00004e68: string(serve.html)
|
||||
file: 0x00004eb8: string(dW5zdXBwb3J0)
|
||||
file: 0x00004ec8: string(c2xlZXA=)
|
||||
func: 0x100012c2: characteristic(calls to)
|
||||
func: 0x10001000: characteristic(loop)
|
||||
bb : 0x10001000: basic block
|
||||
insn: 0x10001000: mnemonic(push)
|
||||
insn: 0x10001001: mnemonic(push)
|
||||
insn: 0x10001002: mnemonic(push)
|
||||
insn: 0x10001003: mnemonic(push)
|
||||
insn: 0x10001004: mnemonic(push)
|
||||
insn: 0x10001005: mnemonic(push)
|
||||
insn: 0x10001006: mnemonic(xor)
|
||||
insn: 0x10001008: number(0x1)
|
||||
insn: 0x10001008: mnemonic(mov)
|
||||
bb : 0x1000100a: basic block
|
||||
bb : 0x1000100a: characteristic(tight loop)
|
||||
insn: 0x1000100a: mnemonic(movzx)
|
||||
insn: 0x1000100d: mnemonic(mov)
|
||||
insn: 0x1000100f: offset(0x1000A7C8)
|
||||
insn: 0x1000100f: mnemonic(mov)
|
||||
insn: 0x10001015: offset(0x100075C8)
|
||||
insn: 0x10001015: mnemonic(mov)
|
||||
insn: 0x1000101b: mnemonic(mov)
|
||||
insn: 0x1000101d: number(0x80)
|
||||
insn: 0x1000101d: mnemonic(and)
|
||||
insn: 0x10001020: mnemonic(neg)
|
||||
insn: 0x10001022: mnemonic(sbb)
|
||||
insn: 0x10001024: number(0x1B)
|
||||
insn: 0x10001024: mnemonic(and)
|
||||
insn: 0x10001027: number(0x1)
|
||||
insn: 0x10001027: mnemonic(shl)
|
||||
...
|
||||
"""
|
||||
import sys
|
||||
import logging
|
||||
|
||||
@@ -93,7 +93,8 @@ def get_capabilities(path, rules):
|
||||
logger.debug("matching rules in %s", path)
|
||||
with open(path, "rb") as f:
|
||||
extractor = capa.features.freeze.load(f.read())
|
||||
return capa.main.find_capabilities(rules, extractor, disable_progress=True)
|
||||
capabilities, meta = capa.main.find_capabilities(rules, extractor, disable_progress=True)
|
||||
return capabilities
|
||||
|
||||
|
||||
def get_function_hits(capabilities, rule_name):
|
||||
|
||||
16
setup.py
16
setup.py
@@ -41,13 +41,15 @@ setuptools.setup(
|
||||
include_package_data=True,
|
||||
install_requires=requirements,
|
||||
extras_require={
|
||||
"dev": ["pytest",
|
||||
"pytest-sugar",
|
||||
"pytest-instafail",
|
||||
"pytest-cov",
|
||||
"pycodestyle",
|
||||
"black ; python_version>'3.0'",
|
||||
"isort"]
|
||||
"dev": [
|
||||
"pytest",
|
||||
"pytest-sugar",
|
||||
"pytest-instafail",
|
||||
"pytest-cov",
|
||||
"pycodestyle",
|
||||
"black ; python_version>'3.0'",
|
||||
"isort",
|
||||
]
|
||||
},
|
||||
zip_safe=False,
|
||||
keywords="capa",
|
||||
|
||||
@@ -59,7 +59,7 @@ def test_null_feature_extractor():
|
||||
),
|
||||
]
|
||||
)
|
||||
capabilities = capa.main.find_capabilities(rules, EXTRACTOR)
|
||||
capabilities, meta = capa.main.find_capabilities(rules, EXTRACTOR)
|
||||
assert "xor loop" in capabilities
|
||||
|
||||
|
||||
|
||||
@@ -147,7 +147,7 @@ def test_match_across_scopes_file_function(sample_9324d1a8ae37a36ae560c37448c970
|
||||
extractor = capa.features.extractors.viv.VivisectFeatureExtractor(
|
||||
sample_9324d1a8ae37a36ae560c37448c9705a.vw, sample_9324d1a8ae37a36ae560c37448c9705a.path,
|
||||
)
|
||||
capabilities = capa.main.find_capabilities(rules, extractor)
|
||||
capabilities, meta = capa.main.find_capabilities(rules, extractor)
|
||||
assert "install service" in capabilities
|
||||
assert ".text section" in capabilities
|
||||
assert ".text section and install service" in capabilities
|
||||
@@ -212,7 +212,7 @@ def test_match_across_scopes(sample_9324d1a8ae37a36ae560c37448c9705a):
|
||||
extractor = capa.features.extractors.viv.VivisectFeatureExtractor(
|
||||
sample_9324d1a8ae37a36ae560c37448c9705a.vw, sample_9324d1a8ae37a36ae560c37448c9705a.path
|
||||
)
|
||||
capabilities = capa.main.find_capabilities(rules, extractor)
|
||||
capabilities, meta = capa.main.find_capabilities(rules, extractor)
|
||||
assert "tight loop" in capabilities
|
||||
assert "kill thread loop" in capabilities
|
||||
assert "kill thread program" in capabilities
|
||||
@@ -241,7 +241,7 @@ def test_subscope_bb_rules(sample_9324d1a8ae37a36ae560c37448c9705a):
|
||||
extractor = capa.features.extractors.viv.VivisectFeatureExtractor(
|
||||
sample_9324d1a8ae37a36ae560c37448c9705a.vw, sample_9324d1a8ae37a36ae560c37448c9705a.path,
|
||||
)
|
||||
capabilities = capa.main.find_capabilities(rules, extractor)
|
||||
capabilities, meta = capa.main.find_capabilities(rules, extractor)
|
||||
assert "test rule" in capabilities
|
||||
|
||||
|
||||
@@ -267,7 +267,7 @@ def test_byte_matching(sample_9324d1a8ae37a36ae560c37448c9705a):
|
||||
extractor = capa.features.extractors.viv.VivisectFeatureExtractor(
|
||||
sample_9324d1a8ae37a36ae560c37448c9705a.vw, sample_9324d1a8ae37a36ae560c37448c9705a.path,
|
||||
)
|
||||
capabilities = capa.main.find_capabilities(rules, extractor)
|
||||
capabilities, meta = capa.main.find_capabilities(rules, extractor)
|
||||
assert "byte match test" in capabilities
|
||||
|
||||
|
||||
@@ -294,5 +294,5 @@ def test_count_bb(sample_9324d1a8ae37a36ae560c37448c9705a):
|
||||
extractor = capa.features.extractors.viv.VivisectFeatureExtractor(
|
||||
sample_9324d1a8ae37a36ae560c37448c9705a.vw, sample_9324d1a8ae37a36ae560c37448c9705a.path,
|
||||
)
|
||||
capabilities = capa.main.find_capabilities(rules, extractor)
|
||||
capabilities, meta = capa.main.find_capabilities(rules, extractor)
|
||||
assert "count bb" in capabilities
|
||||
|
||||
Reference in New Issue
Block a user