*: formatting

This commit is contained in:
William Ballenthin
2020-06-26 18:44:19 -06:00
parent f82e453440
commit 26fef7c615
3 changed files with 85 additions and 69 deletions

View File

@@ -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):

View File

@@ -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

View File

@@ -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