mirror of
https://github.com/mandiant/capa.git
synced 2025-12-23 15:37:37 -08:00
pep8: scripts
This commit is contained in:
@@ -1,11 +1,11 @@
|
|||||||
'''
|
"""
|
||||||
Reformat the given capa rule into a consistent style.
|
Reformat the given capa rule into a consistent style.
|
||||||
Use the -i flag to update the rule in-place.
|
Use the -i flag to update the rule in-place.
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
|
|
||||||
$ python capafmt.py -i foo.yml
|
$ python capafmt.py -i foo.yml
|
||||||
'''
|
"""
|
||||||
import sys
|
import sys
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
@@ -14,22 +14,24 @@ import argparse
|
|||||||
import capa.rules
|
import capa.rules
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger('capafmt')
|
logger = logging.getLogger("capafmt")
|
||||||
|
|
||||||
|
|
||||||
def main(argv=None):
|
def main(argv=None):
|
||||||
if argv is None:
|
if argv is None:
|
||||||
argv = sys.argv[1:]
|
argv = sys.argv[1:]
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(description='Capa rule formatter.')
|
parser = argparse.ArgumentParser(description="Capa rule formatter.")
|
||||||
parser.add_argument('path', type=str,
|
parser.add_argument("path", type=str, help="Path to rule to format")
|
||||||
help='Path to rule to format')
|
parser.add_argument(
|
||||||
parser.add_argument('-i', '--in-place', action='store_true', dest='in_place',
|
"-i",
|
||||||
help='Format the rule in place, otherwise, write formatted rule to STDOUT')
|
"--in-place",
|
||||||
parser.add_argument('-v', '--verbose', action='store_true',
|
action="store_true",
|
||||||
help='Enable debug logging')
|
dest="in_place",
|
||||||
parser.add_argument('-q', '--quiet', action='store_true',
|
help="Format the rule in place, otherwise, write formatted rule to STDOUT",
|
||||||
help='Disable all output but errors')
|
)
|
||||||
|
parser.add_argument("-v", "--verbose", action="store_true", help="Enable debug logging")
|
||||||
|
parser.add_argument("-q", "--quiet", action="store_true", help="Disable all output but errors")
|
||||||
args = parser.parse_args(args=argv)
|
args = parser.parse_args(args=argv)
|
||||||
|
|
||||||
if args.verbose:
|
if args.verbose:
|
||||||
@@ -40,17 +42,17 @@ def main(argv=None):
|
|||||||
level = logging.INFO
|
level = logging.INFO
|
||||||
|
|
||||||
logging.basicConfig(level=level)
|
logging.basicConfig(level=level)
|
||||||
logging.getLogger('capafmt').setLevel(level)
|
logging.getLogger("capafmt").setLevel(level)
|
||||||
|
|
||||||
rule = capa.rules.Rule.from_yaml_file(args.path)
|
rule = capa.rules.Rule.from_yaml_file(args.path)
|
||||||
if args.in_place:
|
if args.in_place:
|
||||||
with open(args.path, 'wb') as f:
|
with open(args.path, "wb") as f:
|
||||||
f.write(rule.to_yaml().encode('utf-8'))
|
f.write(rule.to_yaml().encode("utf-8"))
|
||||||
else:
|
else:
|
||||||
print(rule.to_yaml().rstrip("\n"))
|
print(rule.to_yaml().rstrip("\n"))
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
sys.exit(main())
|
sys.exit(main())
|
||||||
|
|||||||
275
scripts/lint.py
275
scripts/lint.py
@@ -1,10 +1,10 @@
|
|||||||
'''
|
"""
|
||||||
Check the given capa rules for style issues.
|
Check the given capa rules for style issues.
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
|
|
||||||
$ python scripts/lint.py rules/
|
$ python scripts/lint.py rules/
|
||||||
'''
|
"""
|
||||||
import os
|
import os
|
||||||
import os.path
|
import os.path
|
||||||
import sys
|
import sys
|
||||||
@@ -22,41 +22,40 @@ import capa.engine
|
|||||||
import capa.features
|
import capa.features
|
||||||
import capa.features.insn
|
import capa.features.insn
|
||||||
|
|
||||||
logger = logging.getLogger('capa.lint')
|
logger = logging.getLogger("capa.lint")
|
||||||
|
|
||||||
|
|
||||||
class Lint(object):
|
class Lint(object):
|
||||||
name = 'lint'
|
name = "lint"
|
||||||
recommendation = ''
|
recommendation = ""
|
||||||
|
|
||||||
def check_rule(self, ctx, rule):
|
def check_rule(self, ctx, rule):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
class NameCasing(Lint):
|
class NameCasing(Lint):
|
||||||
name = 'rule name casing'
|
name = "rule name casing"
|
||||||
recommendation = 'Rename rule using to start with lower case letters'
|
recommendation = "Rename rule using to start with lower case letters"
|
||||||
|
|
||||||
def check_rule(self, ctx, rule):
|
def check_rule(self, ctx, rule):
|
||||||
return (rule.name[0] in string.ascii_uppercase and
|
return rule.name[0] in string.ascii_uppercase and rule.name[1] not in string.ascii_uppercase
|
||||||
rule.name[1] not in string.ascii_uppercase)
|
|
||||||
|
|
||||||
|
|
||||||
class FilenameDoesntMatchRuleName(Lint):
|
class FilenameDoesntMatchRuleName(Lint):
|
||||||
name = 'filename doesn\'t match the rule name'
|
name = "filename doesn't match the rule name"
|
||||||
recommendation = 'Rename rule file to match the rule name, expected: "{:s}", found: "{:s}"'
|
recommendation = 'Rename rule file to match the rule name, expected: "{:s}", found: "{:s}"'
|
||||||
|
|
||||||
def check_rule(self, ctx, rule):
|
def check_rule(self, ctx, rule):
|
||||||
expected = rule.name
|
expected = rule.name
|
||||||
expected = expected.lower()
|
expected = expected.lower()
|
||||||
expected = expected.replace(' ', '-')
|
expected = expected.replace(" ", "-")
|
||||||
expected = expected.replace('(', '')
|
expected = expected.replace("(", "")
|
||||||
expected = expected.replace(')', '')
|
expected = expected.replace(")", "")
|
||||||
expected = expected.replace('+', '')
|
expected = expected.replace("+", "")
|
||||||
expected = expected.replace('/', '')
|
expected = expected.replace("/", "")
|
||||||
expected = expected + '.yml'
|
expected = expected + ".yml"
|
||||||
|
|
||||||
found = os.path.basename(rule.meta['capa/path'])
|
found = os.path.basename(rule.meta["capa/path"])
|
||||||
|
|
||||||
self.recommendation = self.recommendation.format(expected, found)
|
self.recommendation = self.recommendation.format(expected, found)
|
||||||
|
|
||||||
@@ -64,95 +63,99 @@ class FilenameDoesntMatchRuleName(Lint):
|
|||||||
|
|
||||||
|
|
||||||
class MissingNamespace(Lint):
|
class MissingNamespace(Lint):
|
||||||
name = 'missing rule namespace'
|
name = "missing rule namespace"
|
||||||
recommendation = 'Add meta.namespace so that the rule is emitted correctly'
|
recommendation = "Add meta.namespace so that the rule is emitted correctly"
|
||||||
|
|
||||||
def check_rule(self, ctx, rule):
|
def check_rule(self, ctx, rule):
|
||||||
return ('namespace' not in rule.meta and
|
return (
|
||||||
not is_nursery_rule(rule) and
|
"namespace" not in rule.meta
|
||||||
'maec/malware-category' not in rule.meta and
|
and not is_nursery_rule(rule)
|
||||||
'lib' not in rule.meta)
|
and "maec/malware-category" not in rule.meta
|
||||||
|
and "lib" not in rule.meta
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class NamespaceDoesntMatchRulePath(Lint):
|
class NamespaceDoesntMatchRulePath(Lint):
|
||||||
name = 'file path doesn\'t match rule namespace'
|
name = "file path doesn't match rule namespace"
|
||||||
recommendation = 'Move rule to appropriate directory or update the namespace'
|
recommendation = "Move rule to appropriate directory or update the namespace"
|
||||||
|
|
||||||
def check_rule(self, ctx, rule):
|
def check_rule(self, ctx, rule):
|
||||||
# let the other lints catch namespace issues
|
# let the other lints catch namespace issues
|
||||||
if 'namespace' not in rule.meta:
|
if "namespace" not in rule.meta:
|
||||||
return False
|
return False
|
||||||
if is_nursery_rule(rule):
|
if is_nursery_rule(rule):
|
||||||
return False
|
return False
|
||||||
if 'maec/malware-category' in rule.meta:
|
if "maec/malware-category" in rule.meta:
|
||||||
return False
|
return False
|
||||||
if 'lib' in rule.meta:
|
if "lib" in rule.meta:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return rule.meta['namespace'] not in posixpath.normpath(rule.meta['capa/path'])
|
return rule.meta["namespace"] not in posixpath.normpath(rule.meta["capa/path"])
|
||||||
|
|
||||||
|
|
||||||
class MissingScope(Lint):
|
class MissingScope(Lint):
|
||||||
name = 'missing scope'
|
name = "missing scope"
|
||||||
recommendation = 'Add meta.scope so that the scope is explicit (defaults to `function`)'
|
recommendation = "Add meta.scope so that the scope is explicit (defaults to `function`)"
|
||||||
|
|
||||||
def check_rule(self, ctx, rule):
|
def check_rule(self, ctx, rule):
|
||||||
return 'scope' not in rule.meta
|
return "scope" not in rule.meta
|
||||||
|
|
||||||
|
|
||||||
class InvalidScope(Lint):
|
class InvalidScope(Lint):
|
||||||
name = 'invalid scope'
|
name = "invalid scope"
|
||||||
recommendation = 'Use only file, function, or basic block rule scopes'
|
recommendation = "Use only file, function, or basic block rule scopes"
|
||||||
|
|
||||||
def check_rule(self, ctx, rule):
|
def check_rule(self, ctx, rule):
|
||||||
return rule.meta.get('scope') not in ('file', 'function', 'basic block')
|
return rule.meta.get("scope") not in ("file", "function", "basic block")
|
||||||
|
|
||||||
|
|
||||||
class MissingAuthor(Lint):
|
class MissingAuthor(Lint):
|
||||||
name = 'missing author'
|
name = "missing author"
|
||||||
recommendation = 'Add meta.author so that users know who to contact with questions'
|
recommendation = "Add meta.author so that users know who to contact with questions"
|
||||||
|
|
||||||
def check_rule(self, ctx, rule):
|
def check_rule(self, ctx, rule):
|
||||||
return 'author' not in rule.meta
|
return "author" not in rule.meta
|
||||||
|
|
||||||
|
|
||||||
class MissingExamples(Lint):
|
class MissingExamples(Lint):
|
||||||
name = 'missing examples'
|
name = "missing examples"
|
||||||
recommendation = 'Add meta.examples so that the rule can be tested and verified'
|
recommendation = "Add meta.examples so that the rule can be tested and verified"
|
||||||
|
|
||||||
def check_rule(self, ctx, rule):
|
def check_rule(self, ctx, rule):
|
||||||
return ('examples' not in rule.meta or
|
return (
|
||||||
not isinstance(rule.meta['examples'], list) or
|
"examples" not in rule.meta
|
||||||
len(rule.meta['examples']) == 0 or
|
or not isinstance(rule.meta["examples"], list)
|
||||||
rule.meta['examples'] == [None])
|
or len(rule.meta["examples"]) == 0
|
||||||
|
or rule.meta["examples"] == [None]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class MissingExampleOffset(Lint):
|
class MissingExampleOffset(Lint):
|
||||||
name = 'missing example offset'
|
name = "missing example offset"
|
||||||
recommendation = 'Add offset of example function'
|
recommendation = "Add offset of example function"
|
||||||
|
|
||||||
def check_rule(self, ctx, rule):
|
def check_rule(self, ctx, rule):
|
||||||
if rule.meta.get('scope') in ('function', 'basic block'):
|
if rule.meta.get("scope") in ("function", "basic block"):
|
||||||
for example in rule.meta.get('examples', []):
|
for example in rule.meta.get("examples", []):
|
||||||
if example and ':' not in example:
|
if example and ":" not in example:
|
||||||
logger.debug('example: %s', example)
|
logger.debug("example: %s", example)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
class ExampleFileDNE(Lint):
|
class ExampleFileDNE(Lint):
|
||||||
name = 'referenced example doesn\'t exist'
|
name = "referenced example doesn't exist"
|
||||||
recommendation = 'Add the referenced example to samples directory ($capa-root/tests/data or supplied via --samples)'
|
recommendation = "Add the referenced example to samples directory ($capa-root/tests/data or supplied via --samples)"
|
||||||
|
|
||||||
def check_rule(self, ctx, rule):
|
def check_rule(self, ctx, rule):
|
||||||
if not rule.meta.get('examples'):
|
if not rule.meta.get("examples"):
|
||||||
# let the MissingExamples lint catch this case, don't double report.
|
# let the MissingExamples lint catch this case, don't double report.
|
||||||
return False
|
return False
|
||||||
|
|
||||||
found = False
|
found = False
|
||||||
for example in rule.meta.get('examples', []):
|
for example in rule.meta.get("examples", []):
|
||||||
if example:
|
if example:
|
||||||
example_id = example.partition(':')[0]
|
example_id = example.partition(":")[0]
|
||||||
if example_id in ctx['samples']:
|
if example_id in ctx["samples"]:
|
||||||
found = True
|
found = True
|
||||||
break
|
break
|
||||||
|
|
||||||
@@ -160,27 +163,27 @@ class ExampleFileDNE(Lint):
|
|||||||
|
|
||||||
|
|
||||||
class DoesntMatchExample(Lint):
|
class DoesntMatchExample(Lint):
|
||||||
name = 'doesn\'t match on referenced example'
|
name = "doesn't match on referenced example"
|
||||||
recommendation = 'Fix the rule logic or provide a different example'
|
recommendation = "Fix the rule logic or provide a different example"
|
||||||
|
|
||||||
def check_rule(self, ctx, rule):
|
def check_rule(self, ctx, rule):
|
||||||
if not ctx['is_thorough']:
|
if not ctx["is_thorough"]:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
for example in rule.meta.get('examples', []):
|
for example in rule.meta.get("examples", []):
|
||||||
example_id = example.partition(':')[0]
|
example_id = example.partition(":")[0]
|
||||||
try:
|
try:
|
||||||
path = ctx['samples'][example_id]
|
path = ctx["samples"][example_id]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
# lint ExampleFileDNE will catch this.
|
# lint ExampleFileDNE will catch this.
|
||||||
# don't double report.
|
# don't double report.
|
||||||
continue
|
continue
|
||||||
|
|
||||||
try:
|
try:
|
||||||
extractor = capa.main.get_extractor(path, 'auto')
|
extractor = capa.main.get_extractor(path, "auto")
|
||||||
capabilities = capa.main.find_capabilities(ctx['rules'], extractor, disable_progress=True)
|
capabilities = capa.main.find_capabilities(ctx["rules"], extractor, disable_progress=True)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error('failed to extract capabilities: %s %s %s', rule.name, path, e)
|
logger.error("failed to extract capabilities: %s %s %s", rule.name, path, e)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
if rule.name not in capabilities:
|
if rule.name not in capabilities:
|
||||||
@@ -188,7 +191,7 @@ class DoesntMatchExample(Lint):
|
|||||||
|
|
||||||
|
|
||||||
class UnusualMetaField(Lint):
|
class UnusualMetaField(Lint):
|
||||||
name = 'unusual meta field'
|
name = "unusual meta field"
|
||||||
recommendation = 'Remove the meta field: "{:s}"'
|
recommendation = 'Remove the meta field: "{:s}"'
|
||||||
|
|
||||||
def check_rule(self, ctx, rule):
|
def check_rule(self, ctx, rule):
|
||||||
@@ -204,32 +207,32 @@ class UnusualMetaField(Lint):
|
|||||||
|
|
||||||
|
|
||||||
class LibRuleNotInLibDirectory(Lint):
|
class LibRuleNotInLibDirectory(Lint):
|
||||||
name = 'lib rule not found in lib directory'
|
name = "lib rule not found in lib directory"
|
||||||
recommendation = 'Move the rule to the `lib` subdirectory of the rules path'
|
recommendation = "Move the rule to the `lib` subdirectory of the rules path"
|
||||||
|
|
||||||
def check_rule(self, ctx, rule):
|
def check_rule(self, ctx, rule):
|
||||||
if is_nursery_rule(rule):
|
if is_nursery_rule(rule):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if 'lib' not in rule.meta:
|
if "lib" not in rule.meta:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return '/lib/' not in posixpath.normpath(rule.meta['capa/path'])
|
return "/lib/" not in posixpath.normpath(rule.meta["capa/path"])
|
||||||
|
|
||||||
|
|
||||||
class LibRuleHasNamespace(Lint):
|
class LibRuleHasNamespace(Lint):
|
||||||
name = 'lib rule has a namespace'
|
name = "lib rule has a namespace"
|
||||||
recommendation = 'Remove the namespace from the rule'
|
recommendation = "Remove the namespace from the rule"
|
||||||
|
|
||||||
def check_rule(self, ctx, rule):
|
def check_rule(self, ctx, rule):
|
||||||
if 'lib' not in rule.meta:
|
if "lib" not in rule.meta:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return 'namespace' in rule.meta
|
return "namespace" in rule.meta
|
||||||
|
|
||||||
|
|
||||||
class FeatureStringTooShort(Lint):
|
class FeatureStringTooShort(Lint):
|
||||||
name = 'feature string too short'
|
name = "feature string too short"
|
||||||
recommendation = 'capa only extracts strings with length >= 4; will not match on "{:s}"'
|
recommendation = 'capa only extracts strings with length >= 4; will not match on "{:s}"'
|
||||||
|
|
||||||
def check_features(self, ctx, features):
|
def check_features(self, ctx, features):
|
||||||
@@ -242,9 +245,11 @@ class FeatureStringTooShort(Lint):
|
|||||||
|
|
||||||
|
|
||||||
class FeatureNegativeNumberOrOffset(Lint):
|
class FeatureNegativeNumberOrOffset(Lint):
|
||||||
name = 'feature value is negative'
|
name = "feature value is negative"
|
||||||
recommendation = 'capa treats all numbers as unsigned values; you may specify the number\'s two\'s complement ' \
|
recommendation = (
|
||||||
'representation; will not match on "{:d}"'
|
"capa treats all numbers as unsigned values; you may specify the number's two's complement "
|
||||||
|
'representation; will not match on "{:d}"'
|
||||||
|
)
|
||||||
|
|
||||||
def check_features(self, ctx, features):
|
def check_features(self, ctx, features):
|
||||||
for feature in features:
|
for feature in features:
|
||||||
@@ -318,7 +323,7 @@ def lint_features(ctx, rule):
|
|||||||
def get_features(ctx, rule):
|
def get_features(ctx, rule):
|
||||||
# get features from rule and all dependencies including subscopes and matched rules
|
# get features from rule and all dependencies including subscopes and matched rules
|
||||||
features = []
|
features = []
|
||||||
deps = [ctx['rules'].rules[dep] for dep in rule.get_dependencies()]
|
deps = [ctx["rules"].rules[dep] for dep in rule.get_dependencies()]
|
||||||
for r in [rule] + deps:
|
for r in [rule] + deps:
|
||||||
features.extend(get_rule_features(r))
|
features.extend(get_rule_features(r))
|
||||||
return features
|
return features
|
||||||
@@ -338,9 +343,7 @@ def get_rule_features(rule):
|
|||||||
return features
|
return features
|
||||||
|
|
||||||
|
|
||||||
LOGIC_LINTS = (
|
LOGIC_LINTS = (DoesntMatchExample(),)
|
||||||
DoesntMatchExample(),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def lint_logic(ctx, rule):
|
def lint_logic(ctx, rule):
|
||||||
@@ -348,53 +351,58 @@ def lint_logic(ctx, rule):
|
|||||||
|
|
||||||
|
|
||||||
def is_nursery_rule(rule):
|
def is_nursery_rule(rule):
|
||||||
'''
|
"""
|
||||||
The nursery is a spot for rules that have not yet been fully polished.
|
The nursery is a spot for rules that have not yet been fully polished.
|
||||||
For example, they may not have references to public example of a technique.
|
For example, they may not have references to public example of a technique.
|
||||||
Yet, we still want to capture and report on their matches.
|
Yet, we still want to capture and report on their matches.
|
||||||
'''
|
"""
|
||||||
return rule.meta.get('capa/nursery')
|
return rule.meta.get("capa/nursery")
|
||||||
|
|
||||||
|
|
||||||
def lint_rule(ctx, rule):
|
def lint_rule(ctx, rule):
|
||||||
logger.debug(rule.name)
|
logger.debug(rule.name)
|
||||||
|
|
||||||
violations = list(itertools.chain(
|
violations = list(
|
||||||
lint_name(ctx, rule),
|
itertools.chain(
|
||||||
lint_scope(ctx, rule),
|
lint_name(ctx, rule),
|
||||||
lint_meta(ctx, rule),
|
lint_scope(ctx, rule),
|
||||||
lint_logic(ctx, rule),
|
lint_meta(ctx, rule),
|
||||||
lint_features(ctx, rule),
|
lint_logic(ctx, rule),
|
||||||
))
|
lint_features(ctx, rule),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
if len(violations) > 0:
|
if len(violations) > 0:
|
||||||
category = rule.meta.get('rule-category')
|
category = rule.meta.get("rule-category")
|
||||||
|
|
||||||
print('')
|
print("")
|
||||||
print('%s%s %s' % (' (nursery) ' if is_nursery_rule(rule) else '',
|
print(
|
||||||
rule.name,
|
"%s%s %s"
|
||||||
('(%s)' % category) if category else ''))
|
% (" (nursery) " if is_nursery_rule(rule) else "", rule.name, ("(%s)" % category) if category else "",)
|
||||||
|
)
|
||||||
|
|
||||||
level = 'WARN' if is_nursery_rule(rule) else 'FAIL'
|
level = "WARN" if is_nursery_rule(rule) else "FAIL"
|
||||||
|
|
||||||
for violation in violations:
|
for violation in violations:
|
||||||
print('%s %s: %s: %s' % (
|
print(
|
||||||
' ' if is_nursery_rule(rule) else '', level, violation.name, violation.recommendation))
|
"%s %s: %s: %s"
|
||||||
|
% (" " if is_nursery_rule(rule) else "", level, violation.name, violation.recommendation,)
|
||||||
|
)
|
||||||
|
|
||||||
return len(violations) > 0 and not is_nursery_rule(rule)
|
return len(violations) > 0 and not is_nursery_rule(rule)
|
||||||
|
|
||||||
|
|
||||||
def lint(ctx, rules):
|
def lint(ctx, rules):
|
||||||
'''
|
"""
|
||||||
Args:
|
Args:
|
||||||
samples (Dict[string, string]): map from sample id to path.
|
samples (Dict[string, string]): map from sample id to path.
|
||||||
for each sample, record sample id of sha256, md5, and filename.
|
for each sample, record sample id of sha256, md5, and filename.
|
||||||
see `collect_samples(path)`.
|
see `collect_samples(path)`.
|
||||||
rules (List[Rule]): the rules to lint.
|
rules (List[Rule]): the rules to lint.
|
||||||
'''
|
"""
|
||||||
did_suggest_fix = False
|
did_suggest_fix = False
|
||||||
for rule in rules.rules.values():
|
for rule in rules.rules.values():
|
||||||
if rule.meta.get('capa/subscope-rule', False):
|
if rule.meta.get("capa/subscope-rule", False):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
did_suggest_fix = lint_rule(ctx, rule) or did_suggest_fix
|
did_suggest_fix = lint_rule(ctx, rule) or did_suggest_fix
|
||||||
@@ -403,27 +411,27 @@ def lint(ctx, rules):
|
|||||||
|
|
||||||
|
|
||||||
def collect_samples(path):
|
def collect_samples(path):
|
||||||
'''
|
"""
|
||||||
recurse through the given path, collecting all file paths, indexed by their content sha256, md5, and filename.
|
recurse through the given path, collecting all file paths, indexed by their content sha256, md5, and filename.
|
||||||
'''
|
"""
|
||||||
samples = {}
|
samples = {}
|
||||||
for root, dirs, files in os.walk(path):
|
for root, dirs, files in os.walk(path):
|
||||||
for name in files:
|
for name in files:
|
||||||
if name.endswith('.viv'):
|
if name.endswith(".viv"):
|
||||||
continue
|
continue
|
||||||
if name.endswith('.idb'):
|
if name.endswith(".idb"):
|
||||||
continue
|
continue
|
||||||
if name.endswith('.i64'):
|
if name.endswith(".i64"):
|
||||||
continue
|
continue
|
||||||
if name.endswith('.frz'):
|
if name.endswith(".frz"):
|
||||||
continue
|
continue
|
||||||
if name.endswith('.fnames'):
|
if name.endswith(".fnames"):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
path = os.path.join(root, name)
|
path = os.path.join(root, name)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with open(path, 'rb') as f:
|
with open(path, "rb") as f:
|
||||||
buf = f.read()
|
buf = f.read()
|
||||||
except IOError:
|
except IOError:
|
||||||
continue
|
continue
|
||||||
@@ -447,19 +455,16 @@ def main(argv=None):
|
|||||||
if argv is None:
|
if argv is None:
|
||||||
argv = sys.argv[1:]
|
argv = sys.argv[1:]
|
||||||
|
|
||||||
samples_path = os.path.join(os.path.dirname(__file__), '..', 'tests', 'data')
|
samples_path = os.path.join(os.path.dirname(__file__), "..", "tests", "data")
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(description='A program.')
|
parser = argparse.ArgumentParser(description="A program.")
|
||||||
parser.add_argument('rules', type=str,
|
parser.add_argument("rules", type=str, help="Path to rules")
|
||||||
help='Path to rules')
|
parser.add_argument("--samples", type=str, default=samples_path, help="Path to samples")
|
||||||
parser.add_argument('--samples', type=str, default=samples_path,
|
parser.add_argument(
|
||||||
help='Path to samples')
|
"--thorough", action="store_true", help="Enable thorough linting - takes more time, but does a better job",
|
||||||
parser.add_argument('--thorough', action='store_true',
|
)
|
||||||
help='Enable thorough linting - takes more time, but does a better job')
|
parser.add_argument("-v", "--verbose", action="store_true", help="Enable debug logging")
|
||||||
parser.add_argument('-v', '--verbose', action='store_true',
|
parser.add_argument("-q", "--quiet", action="store_true", help="Disable all output but errors")
|
||||||
help='Enable debug logging')
|
|
||||||
parser.add_argument('-q', '--quiet', action='store_true',
|
|
||||||
help='Disable all output but errors')
|
|
||||||
args = parser.parse_args(args=argv)
|
args = parser.parse_args(args=argv)
|
||||||
|
|
||||||
if args.verbose:
|
if args.verbose:
|
||||||
@@ -470,42 +475,42 @@ def main(argv=None):
|
|||||||
level = logging.INFO
|
level = logging.INFO
|
||||||
|
|
||||||
logging.basicConfig(level=level)
|
logging.basicConfig(level=level)
|
||||||
logging.getLogger('capa.lint').setLevel(level)
|
logging.getLogger("capa.lint").setLevel(level)
|
||||||
|
|
||||||
capa.main.set_vivisect_log_level(logging.CRITICAL)
|
capa.main.set_vivisect_log_level(logging.CRITICAL)
|
||||||
logging.getLogger('capa').setLevel(logging.CRITICAL)
|
logging.getLogger("capa").setLevel(logging.CRITICAL)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
rules = capa.main.get_rules(args.rules)
|
rules = capa.main.get_rules(args.rules)
|
||||||
rules = capa.rules.RuleSet(rules)
|
rules = capa.rules.RuleSet(rules)
|
||||||
logger.info('successfully loaded %s rules', len(rules))
|
logger.info("successfully loaded %s rules", len(rules))
|
||||||
except IOError as e:
|
except IOError as e:
|
||||||
logger.error('%s', str(e))
|
logger.error("%s", str(e))
|
||||||
return -1
|
return -1
|
||||||
except capa.rules.InvalidRule as e:
|
except capa.rules.InvalidRule as e:
|
||||||
logger.error('%s', str(e))
|
logger.error("%s", str(e))
|
||||||
return -1
|
return -1
|
||||||
|
|
||||||
logger.info('collecting potentially referenced samples')
|
logger.info("collecting potentially referenced samples")
|
||||||
if not os.path.exists(args.samples):
|
if not os.path.exists(args.samples):
|
||||||
logger.error('samples path %s does not exist', args.samples)
|
logger.error("samples path %s does not exist", args.samples)
|
||||||
return -1
|
return -1
|
||||||
|
|
||||||
samples = collect_samples(args.samples)
|
samples = collect_samples(args.samples)
|
||||||
|
|
||||||
ctx = {
|
ctx = {
|
||||||
'samples': samples,
|
"samples": samples,
|
||||||
'rules': rules,
|
"rules": rules,
|
||||||
'is_thorough': args.thorough,
|
"is_thorough": args.thorough,
|
||||||
}
|
}
|
||||||
|
|
||||||
did_violate = lint(ctx, rules)
|
did_violate = lint(ctx, rules)
|
||||||
if not did_violate:
|
if not did_violate:
|
||||||
logger.info('no suggestions, nice!')
|
logger.info("no suggestions, nice!")
|
||||||
return 0
|
return 0
|
||||||
else:
|
else:
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
sys.exit(main())
|
sys.exit(main())
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#!/usr/bin/env python2
|
#!/usr/bin/env python2
|
||||||
'''
|
"""
|
||||||
show the features extracted by capa.
|
show the features extracted by capa.
|
||||||
'''
|
"""
|
||||||
import sys
|
import sys
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
@@ -20,28 +20,27 @@ def main(argv=None):
|
|||||||
argv = sys.argv[1:]
|
argv = sys.argv[1:]
|
||||||
|
|
||||||
formats = [
|
formats = [
|
||||||
('auto', '(default) detect file type automatically'),
|
("auto", "(default) detect file type automatically"),
|
||||||
('pe', 'Windows PE file'),
|
("pe", "Windows PE file"),
|
||||||
('sc32', '32-bit shellcode'),
|
("sc32", "32-bit shellcode"),
|
||||||
('sc64', '64-bit shellcode'),
|
("sc64", "64-bit shellcode"),
|
||||||
('freeze', 'features previously frozen by capa'),
|
("freeze", "features previously frozen by capa"),
|
||||||
]
|
]
|
||||||
format_help = ', '.join(['%s: %s' % (f[0], f[1]) for f in formats])
|
format_help = ", ".join(["%s: %s" % (f[0], f[1]) for f in formats])
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(description='detect capabilities in programs.')
|
parser = argparse.ArgumentParser(description="detect capabilities in programs.")
|
||||||
parser.add_argument('sample', type=str,
|
parser.add_argument("sample", type=str, help="Path to sample to analyze")
|
||||||
help='Path to sample to analyze')
|
parser.add_argument(
|
||||||
parser.add_argument('-f', '--format', choices=[f[0] for f in formats], default='auto',
|
"-f", "--format", choices=[f[0] for f in formats], default="auto", help="Select sample format, %s" % format_help
|
||||||
help='Select sample format, %s' % format_help)
|
)
|
||||||
parser.add_argument('-F', '--function', type=lambda x: int(x, 0),
|
parser.add_argument("-F", "--function", type=lambda x: int(x, 0), help="Show features for specific function")
|
||||||
help='Show features for specific function')
|
|
||||||
args = parser.parse_args(args=argv)
|
args = parser.parse_args(args=argv)
|
||||||
|
|
||||||
logging.basicConfig(level=logging.INFO)
|
logging.basicConfig(level=logging.INFO)
|
||||||
logging.getLogger().setLevel(logging.INFO)
|
logging.getLogger().setLevel(logging.INFO)
|
||||||
|
|
||||||
if args.format == 'freeze':
|
if args.format == "freeze":
|
||||||
with open(args.sample, 'rb') as f:
|
with open(args.sample, "rb") as f:
|
||||||
extractor = capa.features.freeze.load(f.read())
|
extractor = capa.features.freeze.load(f.read())
|
||||||
else:
|
else:
|
||||||
vw = capa.main.get_workspace(args.sample, args.format)
|
vw = capa.main.get_workspace(args.sample, args.format)
|
||||||
@@ -50,32 +49,32 @@ def main(argv=None):
|
|||||||
if not args.function:
|
if not args.function:
|
||||||
for feature, va in extractor.extract_file_features():
|
for feature, va in extractor.extract_file_features():
|
||||||
if va:
|
if va:
|
||||||
print('file: 0x%08x: %s' % (va, feature))
|
print("file: 0x%08x: %s" % (va, feature))
|
||||||
else:
|
else:
|
||||||
print('file: 0x00000000: %s' % (feature))
|
print("file: 0x00000000: %s" % (feature))
|
||||||
|
|
||||||
functions = extractor.get_functions()
|
functions = extractor.get_functions()
|
||||||
|
|
||||||
if args.function:
|
if args.function:
|
||||||
if args.format == 'freeze':
|
if args.format == "freeze":
|
||||||
functions = filter(lambda f: f == args.function, functions)
|
functions = filter(lambda f: f == args.function, functions)
|
||||||
else:
|
else:
|
||||||
functions = filter(lambda f: f.va == args.function, functions)
|
functions = filter(lambda f: f.va == args.function, functions)
|
||||||
|
|
||||||
for f in functions:
|
for f in functions:
|
||||||
for feature, va in extractor.extract_function_features(f):
|
for feature, va in extractor.extract_function_features(f):
|
||||||
print('func: 0x%08x: %s' % (va, feature))
|
print("func: 0x%08x: %s" % (va, feature))
|
||||||
|
|
||||||
for bb in extractor.get_basic_blocks(f):
|
for bb in extractor.get_basic_blocks(f):
|
||||||
for feature, va in extractor.extract_basic_block_features(f, bb):
|
for feature, va in extractor.extract_basic_block_features(f, bb):
|
||||||
print('bb : 0x%08x: %s' % (va, feature))
|
print("bb : 0x%08x: %s" % (va, feature))
|
||||||
|
|
||||||
for insn in extractor.get_instructions(f, bb):
|
for insn in extractor.get_instructions(f, bb):
|
||||||
for feature, va in extractor.extract_insn_features(f, bb, insn):
|
for feature, va in extractor.extract_insn_features(f, bb, insn):
|
||||||
print('insn: 0x%08x: %s' % (va, feature))
|
print("insn: 0x%08x: %s" % (va, feature))
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
sys.exit(main())
|
sys.exit(main())
|
||||||
|
|||||||
Reference in New Issue
Block a user