mirror of
https://github.com/mandiant/capa.git
synced 2025-12-22 23:26:21 -08:00
*: formatting
This commit is contained in:
@@ -21,12 +21,28 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
# these are the standard metadata fields, in the preferred order.
|
# these are the standard metadata fields, in the preferred order.
|
||||||
# when reformatted, any custom keys will come after these.
|
# when reformatted, any custom keys will come after these.
|
||||||
META_KEYS = ("name", "namespace", "rule-category", "maec/malware-category", "author", "description", "lib", "scope", "att&ck", "mbc", "references", "examples")
|
META_KEYS = (
|
||||||
|
'name',
|
||||||
|
'namespace',
|
||||||
|
'rule-category',
|
||||||
|
'maec/analysis-conclusion',
|
||||||
|
'maec/analysis-conclusion-ov',
|
||||||
|
'maec/malware-category',
|
||||||
|
'maec/malware-category-ov',
|
||||||
|
'author',
|
||||||
|
'description',
|
||||||
|
'lib',
|
||||||
|
'scope',
|
||||||
|
'att&ck',
|
||||||
|
'mbc',
|
||||||
|
'references',
|
||||||
|
'examples'
|
||||||
|
)
|
||||||
# these are meta fields that are internal to capa,
|
# these are meta fields that are internal to capa,
|
||||||
# and added during rule reading/construction.
|
# and added during rule reading/construction.
|
||||||
# they may help use manipulate or index rules,
|
# they may help use manipulate or index rules,
|
||||||
# but should not be exposed to clients.
|
# but should not be exposed to clients.
|
||||||
HIDDEN_META_KEYS = ("capa/nursery", "capa/path")
|
HIDDEN_META_KEYS = ('capa/nursery', 'capa/path')
|
||||||
|
|
||||||
|
|
||||||
FILE_SCOPE = 'file'
|
FILE_SCOPE = 'file'
|
||||||
@@ -545,11 +561,11 @@ class Rule(object):
|
|||||||
definition = yaml.load(self.definition)
|
definition = yaml.load(self.definition)
|
||||||
# definition retains a reference to `meta`,
|
# definition retains a reference to `meta`,
|
||||||
# so we're updating that in place.
|
# so we're updating that in place.
|
||||||
definition["rule"]["meta"] = self.meta
|
definition['rule']['meta'] = self.meta
|
||||||
meta = self.meta
|
meta = self.meta
|
||||||
|
|
||||||
meta["name"] = self.name
|
meta['name'] = self.name
|
||||||
meta["scope"] = self.scope
|
meta['scope'] = self.scope
|
||||||
|
|
||||||
def move_to_end(m, k):
|
def move_to_end(m, k):
|
||||||
# ruamel.yaml uses an ordereddict-like structure to track maps (CommentedMap).
|
# ruamel.yaml uses an ordereddict-like structure to track maps (CommentedMap).
|
||||||
@@ -559,8 +575,8 @@ class Rule(object):
|
|||||||
del m[k]
|
del m[k]
|
||||||
m[k] = v
|
m[k] = v
|
||||||
|
|
||||||
move_to_end(definition["rule"], "meta")
|
move_to_end(definition['rule'], 'meta')
|
||||||
move_to_end(definition["rule"], "features")
|
move_to_end(definition['rule'], 'features')
|
||||||
|
|
||||||
for key in META_KEYS:
|
for key in META_KEYS:
|
||||||
if key in meta:
|
if key in meta:
|
||||||
@@ -590,7 +606,7 @@ class Rule(object):
|
|||||||
continue
|
continue
|
||||||
meta[key] = value
|
meta[key] = value
|
||||||
|
|
||||||
return ostream.getvalue().decode('utf-8').rstrip("\n") + "\n"
|
return ostream.getvalue().decode('utf-8').rstrip('\n') + '\n'
|
||||||
|
|
||||||
|
|
||||||
def get_rules_with_scope(rules, scope):
|
def get_rules_with_scope(rules, scope):
|
||||||
|
|||||||
@@ -47,12 +47,12 @@ class FilenameDoesntMatchRuleName(Lint):
|
|||||||
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'])
|
||||||
|
|
||||||
@@ -85,7 +85,7 @@ class NamespaceDoesntMatchRulePath(Lint):
|
|||||||
if 'lib' in rule.meta:
|
if 'lib' in rule.meta:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return rule.meta["namespace"] not in rule.meta['capa/path'].replace('\\', '/')
|
return rule.meta['namespace'] not in rule.meta['capa/path'].replace('\\', '/')
|
||||||
|
|
||||||
|
|
||||||
class MissingScope(Lint):
|
class MissingScope(Lint):
|
||||||
@@ -185,7 +185,7 @@ class DoesntMatchExample(Lint):
|
|||||||
|
|
||||||
class UnusualMetaField(Lint):
|
class UnusualMetaField(Lint):
|
||||||
name = 'unusual meta field'
|
name = 'unusual meta field'
|
||||||
recommendation = 'Remove the unusual meta field'
|
recommendation = 'Remove the meta field: "{:s}"'
|
||||||
|
|
||||||
def check_rule(self, ctx, rule):
|
def check_rule(self, ctx, rule):
|
||||||
for key in rule.meta.keys():
|
for key in rule.meta.keys():
|
||||||
@@ -193,7 +193,7 @@ class UnusualMetaField(Lint):
|
|||||||
continue
|
continue
|
||||||
if key in capa.rules.HIDDEN_META_KEYS:
|
if key in capa.rules.HIDDEN_META_KEYS:
|
||||||
continue
|
continue
|
||||||
logger.debug("unusual meta field: %s", key)
|
self.recommendation = self.recommendation.format(key)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|||||||
@@ -24,15 +24,15 @@ logger = logging.getLogger('migrate-rules')
|
|||||||
|
|
||||||
def read_plan(plan_path):
|
def read_plan(plan_path):
|
||||||
with open(plan_path, 'rb') as f:
|
with open(plan_path, 'rb') as f:
|
||||||
return list(csv.DictReader(f, restkey="other", fieldnames=(
|
return list(csv.DictReader(f, restkey='other', fieldnames=(
|
||||||
"existing path",
|
'existing path',
|
||||||
"existing name",
|
'existing name',
|
||||||
"existing rule-category",
|
'existing rule-category',
|
||||||
"proposed name",
|
'proposed name',
|
||||||
"proposed namespace",
|
'proposed namespace',
|
||||||
"ATT&CK",
|
'ATT&CK',
|
||||||
"MBC",
|
'MBC',
|
||||||
"comment1",
|
'comment1',
|
||||||
)))
|
)))
|
||||||
|
|
||||||
|
|
||||||
@@ -48,8 +48,8 @@ def read_rules(rule_directory):
|
|||||||
rule = capa.rules.Rule.from_yaml_file(path)
|
rule = capa.rules.Rule.from_yaml_file(path)
|
||||||
rules[rule.name] = rule
|
rules[rule.name] = rule
|
||||||
|
|
||||||
if "nursery" in path:
|
if 'nursery' in path:
|
||||||
rule.meta["capa/nursery"] = True
|
rule.meta['capa/nursery'] = True
|
||||||
return rules
|
return rules
|
||||||
|
|
||||||
|
|
||||||
@@ -70,89 +70,89 @@ def main(argv=None):
|
|||||||
logging.getLogger().setLevel(logging.INFO)
|
logging.getLogger().setLevel(logging.INFO)
|
||||||
|
|
||||||
plan = read_plan(args.plan)
|
plan = read_plan(args.plan)
|
||||||
logger.info("read %d plan entries", len(plan))
|
logger.info('read %d plan entries', len(plan))
|
||||||
|
|
||||||
rules = read_rules(args.source)
|
rules = read_rules(args.source)
|
||||||
logger.info("read %d rules", len(rules))
|
logger.info('read %d rules', len(rules))
|
||||||
|
|
||||||
planned_rules = set([row["existing name"] for row in plan])
|
planned_rules = set([row['existing name'] for row in plan])
|
||||||
unplanned_rules = [rule for (name, rule) in rules.items() if name not in planned_rules]
|
unplanned_rules = [rule for (name, rule) in rules.items() if name not in planned_rules]
|
||||||
|
|
||||||
if unplanned_rules:
|
if unplanned_rules:
|
||||||
logger.error("plan does not account for %d rules:" % (len(unplanned_rules)))
|
logger.error('plan does not account for %d rules:' % (len(unplanned_rules)))
|
||||||
for rule in unplanned_rules:
|
for rule in unplanned_rules:
|
||||||
logger.error(" " + rule.name)
|
logger.error(' ' + rule.name)
|
||||||
return -1
|
return -1
|
||||||
|
|
||||||
# pairs of strings (needle, replacement)
|
# pairs of strings (needle, replacement)
|
||||||
match_translations = []
|
match_translations = []
|
||||||
|
|
||||||
for row in plan:
|
for row in plan:
|
||||||
if not row["existing name"]:
|
if not row['existing name']:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
rule = rules[row["existing name"]]
|
rule = rules[row['existing name']]
|
||||||
|
|
||||||
if rule.meta["name"] != row["proposed name"]:
|
if rule.meta['name'] != row['proposed name']:
|
||||||
logger.info("renaming rule '%s' -> '%s'", rule.meta["name"], row["proposed name"])
|
logger.info("renaming rule '%s' -> '%s'", rule.meta['name'], row['proposed name'])
|
||||||
|
|
||||||
# assume the yaml is formatted like `- match: $rule-name`.
|
# assume the yaml is formatted like `- match: $rule-name`.
|
||||||
# but since its been linted, this should be ok.
|
# but since its been linted, this should be ok.
|
||||||
match_translations.append(
|
match_translations.append(
|
||||||
("- match: " + rule.meta["name"],
|
('- match: ' + rule.meta['name'],
|
||||||
"- match: " + row["proposed name"]))
|
'- match: ' + row['proposed name']))
|
||||||
|
|
||||||
rule.meta["name"] = row["proposed name"]
|
rule.meta['name'] = row['proposed name']
|
||||||
rule.name = row["proposed name"]
|
rule.name = row['proposed name']
|
||||||
|
|
||||||
if "rule-category" in rule.meta:
|
if 'rule-category' in rule.meta:
|
||||||
logger.info("deleting rule category '%s'", rule.meta["rule-category"])
|
logger.info("deleting rule category '%s'", rule.meta['rule-category'])
|
||||||
del rule.meta["rule-category"]
|
del rule.meta['rule-category']
|
||||||
|
|
||||||
rule.meta["namespace"] = row["proposed namespace"]
|
rule.meta['namespace'] = row['proposed namespace']
|
||||||
|
|
||||||
if row["ATT&CK"] != 'n/a' and row["ATT&CK"] != "":
|
if row['ATT&CK'] != 'n/a' and row['ATT&CK'] != '':
|
||||||
tag = row["ATT&CK"]
|
tag = row['ATT&CK']
|
||||||
name, _, id = tag.rpartition(" ")
|
name, _, id = tag.rpartition(' ')
|
||||||
tag = "%s [%s]" % (name, id)
|
tag = '%s [%s]' % (name, id)
|
||||||
rule.meta["att&ck"] = [tag]
|
rule.meta['att&ck'] = [tag]
|
||||||
|
|
||||||
if row["MBC"] != 'n/a' and row["MBC"] != "":
|
if row['MBC'] != 'n/a' and row['MBC'] != '':
|
||||||
tag = row["MBC"]
|
tag = row['MBC']
|
||||||
rule.meta["mbc"] = [tag]
|
rule.meta['mbc'] = [tag]
|
||||||
|
|
||||||
for rule in rules.values():
|
for rule in rules.values():
|
||||||
filename = rule.name
|
filename = rule.name
|
||||||
filename = filename.lower()
|
filename = filename.lower()
|
||||||
filename = filename.replace(" ", "-")
|
filename = filename.replace(' ', '-')
|
||||||
filename = filename.replace("(", "")
|
filename = filename.replace('(', '')
|
||||||
filename = filename.replace(")", "")
|
filename = filename.replace(')', '')
|
||||||
filename = filename.replace("+", "")
|
filename = filename.replace('+', '')
|
||||||
filename = filename.replace("/", "")
|
filename = filename.replace('/', '')
|
||||||
filename = filename + ".yml"
|
filename = filename + '.yml'
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if rule.meta.get("capa/nursery"):
|
if rule.meta.get('capa/nursery'):
|
||||||
directory = os.path.join(args.destination, "nursery")
|
directory = os.path.join(args.destination, 'nursery')
|
||||||
elif rule.meta.get("lib"):
|
elif rule.meta.get('lib'):
|
||||||
directory = os.path.join(args.destination, "lib")
|
directory = os.path.join(args.destination, 'lib')
|
||||||
else:
|
else:
|
||||||
directory = os.path.join(args.destination, rule.meta.get("namespace"))
|
directory = os.path.join(args.destination, rule.meta.get('namespace'))
|
||||||
os.makedirs(directory)
|
os.makedirs(directory)
|
||||||
except OSError:
|
except OSError:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
logger.info("created namespace: %s", directory)
|
logger.info('created namespace: %s', directory)
|
||||||
|
|
||||||
path = os.path.join(directory, filename)
|
path = os.path.join(directory, filename)
|
||||||
logger.info("writing rule %s", path)
|
logger.info('writing rule %s', path)
|
||||||
|
|
||||||
doc = rule.to_yaml().decode("utf-8")
|
doc = rule.to_yaml().decode('utf-8')
|
||||||
for (needle, replacement) in match_translations:
|
for (needle, replacement) in match_translations:
|
||||||
doc = doc.replace(needle, replacement)
|
doc = doc.replace(needle, replacement)
|
||||||
|
|
||||||
with open(path, "wb") as f:
|
with open(path, 'wb') as f:
|
||||||
f.write(doc.encode("utf-8"))
|
f.write(doc.encode('utf-8'))
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user