mirror of
https://github.com/mandiant/capa.git
synced 2026-07-10 14:12:29 -07:00
Merge remote-tracking branch 'parentrepo/dynamic-feature-extraction' into analysis-flavor
This commit is contained in:
@@ -169,15 +169,17 @@ def main(argv=None):
|
||||
return -1
|
||||
|
||||
samples = []
|
||||
for base, directories, files in os.walk(args.input):
|
||||
for base, _, files in os.walk(args.input):
|
||||
for file in files:
|
||||
samples.append(os.path.join(base, file))
|
||||
|
||||
def pmap(f, args, parallelism=multiprocessing.cpu_count()):
|
||||
cpu_count = multiprocessing.cpu_count()
|
||||
|
||||
def pmap(f, args, parallelism=cpu_count):
|
||||
"""apply the given function f to the given args using subprocesses"""
|
||||
return multiprocessing.Pool(parallelism).imap(f, args)
|
||||
|
||||
def tmap(f, args, parallelism=multiprocessing.cpu_count()):
|
||||
def tmap(f, args, parallelism=cpu_count):
|
||||
"""apply the given function f to the given args using threads"""
|
||||
return multiprocessing.pool.ThreadPool(parallelism).imap(f, args)
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""
|
||||
Create a cache of the given rules.
|
||||
This is only really intended to be used by CI to pre-cache rulesets
|
||||
This is only really intended to be used by CI to pre-cache rulesets
|
||||
that will be distributed within PyInstaller binaries.
|
||||
|
||||
Usage:
|
||||
@@ -17,7 +17,6 @@ See the License for the specific language governing permissions and limitations
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import logging
|
||||
import argparse
|
||||
|
||||
|
||||
+77
-74
@@ -54,7 +54,7 @@ var_names = ["".join(letters) for letters in itertools.product(string.ascii_lowe
|
||||
|
||||
# this have to be the internal names used by capa.py which are sometimes different to the ones written out in the rules, e.g. "2 or more" is "Some", count is Range
|
||||
unsupported = ["characteristic", "mnemonic", "offset", "subscope", "Range"]
|
||||
# TODO shorten this list, possible stuff:
|
||||
# further idea: shorten this list, possible stuff:
|
||||
# - 2 or more strings: e.g.
|
||||
# -- https://github.com/mandiant/capa-rules/blob/master/collection/file-managers/gather-direct-ftp-information.yml
|
||||
# -- https://github.com/mandiant/capa-rules/blob/master/collection/browser/gather-firefox-profile-information.yml
|
||||
@@ -94,7 +94,7 @@ private rule capa_pe_file : CAPA {
|
||||
|
||||
def check_feature(statement, rulename):
|
||||
if statement in unsupported:
|
||||
logger.info("unsupported: " + statement + " in rule: " + rulename)
|
||||
logger.info("unsupported: %s in rule: %s", statement, rulename)
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
@@ -112,7 +112,7 @@ def convert_capa_number_to_yara_bytes(number):
|
||||
sys.exit()
|
||||
|
||||
number = re.sub(r"^0[xX]", "", number)
|
||||
logger.info("number ok: " + repr(number))
|
||||
logger.info("number ok: %r", number)
|
||||
|
||||
# include spaces every 2 hex
|
||||
bytesv = re.sub(r"(..)", r"\1 ", number)
|
||||
@@ -142,9 +142,9 @@ def convert_description(statement):
|
||||
desc = statement.description
|
||||
if desc:
|
||||
yara_desc = " // " + desc
|
||||
logger.info("using desc: " + repr(yara_desc))
|
||||
logger.info("using desc: %r", yara_desc)
|
||||
return yara_desc
|
||||
except:
|
||||
except Exception:
|
||||
# no description
|
||||
pass
|
||||
|
||||
@@ -153,7 +153,7 @@ def convert_description(statement):
|
||||
|
||||
def convert_rule(rule, rulename, cround, depth):
|
||||
depth += 1
|
||||
logger.info("recursion depth: " + str(depth))
|
||||
logger.info("recursion depth: %d", depth)
|
||||
|
||||
global var_names
|
||||
|
||||
@@ -164,7 +164,7 @@ def convert_rule(rule, rulename, cround, depth):
|
||||
return "BREAK", s_type
|
||||
elif s_type == "string":
|
||||
string = kid.value
|
||||
logger.info("doing string: " + repr(string))
|
||||
logger.info("doing string: %r", string)
|
||||
string = string.replace("\\", "\\\\")
|
||||
string = string.replace("\n", "\\n")
|
||||
string = string.replace("\t", "\\t")
|
||||
@@ -172,14 +172,16 @@ def convert_rule(rule, rulename, cround, depth):
|
||||
yara_strings += "\t$" + var_name + ' = "' + string + '" ascii wide' + convert_description(kid) + "\n"
|
||||
yara_condition += "\t$" + var_name + " "
|
||||
elif s_type == "api" or s_type == "import":
|
||||
# TODO: is it possible in YARA to make a difference between api & import?
|
||||
# research needed to decide if its possible in YARA to make a difference between api & import?
|
||||
|
||||
# https://github.com/mandiant/capa-rules/blob/master/doc/format.md#api
|
||||
api = kid.value
|
||||
logger.info("doing api: " + repr(api))
|
||||
logger.info("doing api: %r", api)
|
||||
|
||||
# e.g. kernel32.CreateNamedPipe => look for kernel32.dll and CreateNamedPipe
|
||||
# TODO: improve .NET API call handling
|
||||
#
|
||||
# note: the handling of .NET API calls could be improved here.
|
||||
# once we have a motivation and some examples, lets do that.
|
||||
if "::" in api:
|
||||
mod, api = api.split("::")
|
||||
|
||||
@@ -204,20 +206,20 @@ def convert_rule(rule, rulename, cround, depth):
|
||||
var_name = "api_" + var_names.pop(0)
|
||||
|
||||
# limit regex with word boundary \b but also search for appended A and W
|
||||
# TODO: better use something like /(\\x00|\\x01|\\x02|\\x03|\\x04)' + api + '(A|W)?\\x00/ ???
|
||||
# alternatively: use something like /(\\x00|\\x01|\\x02|\\x03|\\x04)' + api + '(A|W)?\\x00/ ???
|
||||
yara_strings += "\t$" + var_name + " = /\\b" + api + "(A|W)?\\b/ ascii wide\n"
|
||||
yara_condition += "\t$" + var_name + " "
|
||||
|
||||
elif s_type == "export":
|
||||
export = kid.value
|
||||
logger.info("doing export: " + repr(export))
|
||||
logger.info("doing export: %r", export)
|
||||
|
||||
yara_condition += '\tpe.exports("' + export + '") '
|
||||
|
||||
elif s_type == "section":
|
||||
# https://github.com/mandiant/capa-rules/blob/master/doc/format.md#section
|
||||
section = kid.value
|
||||
logger.info("doing section: " + repr(section))
|
||||
logger.info("doing section: %r", section)
|
||||
|
||||
# e.g. - section: .rsrc
|
||||
var_name_sec = var_names.pop(0)
|
||||
@@ -229,14 +231,14 @@ def convert_rule(rule, rulename, cround, depth):
|
||||
elif s_type == "match":
|
||||
# https://github.com/mandiant/capa-rules/blob/master/doc/format.md#matching-prior-rule-matches-and-namespaces
|
||||
match = kid.value
|
||||
logger.info("doing match: " + repr(match))
|
||||
logger.info("doing match: %r", match)
|
||||
|
||||
# e.g. - match: create process
|
||||
# - match: host-interaction/file-system/write
|
||||
match_rule_name = convert_rule_name(match)
|
||||
|
||||
if match.startswith(rulename + "/"):
|
||||
logger.info("Depending on myself = basic block: " + match)
|
||||
logger.info("Depending on myself = basic block: %s", match)
|
||||
return "BREAK", "Depending on myself = basic block"
|
||||
|
||||
if match_rule_name in converted_rules:
|
||||
@@ -244,14 +246,14 @@ def convert_rule(rule, rulename, cround, depth):
|
||||
else:
|
||||
# don't complain in the early rounds as there should be 3+ rounds (if all rules are converted)
|
||||
if cround > min_rounds - 2:
|
||||
logger.info("needed sub-rule not converted (yet, maybe in next round): " + repr(match))
|
||||
logger.info("needed sub-rule not converted (yet, maybe in next round): %r", match)
|
||||
return "BREAK", "needed sub-rule not converted"
|
||||
else:
|
||||
return "BREAK", "NOLOG"
|
||||
|
||||
elif s_type == "bytes":
|
||||
bytesv = kid.get_value_str()
|
||||
logger.info("doing bytes: " + repr(bytesv))
|
||||
logger.info("doing bytes: %r", bytesv)
|
||||
var_name = var_names.pop(0)
|
||||
|
||||
yara_strings += "\t$" + var_name + " = { " + bytesv + " }" + convert_description(kid) + "\n"
|
||||
@@ -259,19 +261,19 @@ def convert_rule(rule, rulename, cround, depth):
|
||||
|
||||
elif s_type == "number":
|
||||
number = kid.get_value_str()
|
||||
logger.info("doing number: " + repr(number))
|
||||
logger.info("doing number: %r", number)
|
||||
|
||||
if len(number) < 10:
|
||||
logger.info("too short for byte search (until I figure out how to do it properly)" + repr(number))
|
||||
logger.info("too short for byte search (until I figure out how to do it properly): %r", number)
|
||||
return "BREAK", "Number too short"
|
||||
|
||||
# there's just one rule which contains 0xFFFFFFF but yara gives a warning if if used
|
||||
if number == "0xFFFFFFFF":
|
||||
return "BREAK", "slow byte pattern for YARA search"
|
||||
|
||||
logger.info("number ok: " + repr(number))
|
||||
logger.info("number ok: %r", number)
|
||||
number = convert_capa_number_to_yara_bytes(number)
|
||||
logger.info("number ok: " + repr(number))
|
||||
logger.info("number ok: %r", number)
|
||||
|
||||
var_name = "num_" + var_names.pop(0)
|
||||
yara_strings += "\t$" + var_name + " = { " + number + "}" + convert_description(kid) + "\n"
|
||||
@@ -279,7 +281,7 @@ def convert_rule(rule, rulename, cround, depth):
|
||||
|
||||
elif s_type == "regex":
|
||||
regex = kid.get_value_str()
|
||||
logger.info("doing regex: " + repr(regex))
|
||||
logger.info("doing regex: %r", regex)
|
||||
|
||||
# change capas /xxx/i to yaras /xxx/ nocase, count will be used later to decide appending 'nocase'
|
||||
regex, count = re.subn(r"/i$", "/", regex)
|
||||
@@ -315,7 +317,7 @@ def convert_rule(rule, rulename, cround, depth):
|
||||
elif s_type == "Not" or s_type == "And" or s_type == "Or":
|
||||
pass
|
||||
else:
|
||||
logger.info("something unhandled: " + repr(s_type))
|
||||
logger.info("something unhandled: %r", s_type)
|
||||
sys.exit()
|
||||
|
||||
return yara_strings, yara_condition
|
||||
@@ -329,7 +331,7 @@ def convert_rule(rule, rulename, cround, depth):
|
||||
|
||||
statement = rule.name
|
||||
|
||||
logger.info("doing statement: " + statement)
|
||||
logger.info("doing statement: %s", statement)
|
||||
|
||||
if check_feature(statement, rulename):
|
||||
return "BREAK", statement, rule_comment, incomplete
|
||||
@@ -337,18 +339,18 @@ def convert_rule(rule, rulename, cround, depth):
|
||||
if statement == "And" or statement == "Or":
|
||||
desc = convert_description(rule)
|
||||
if desc:
|
||||
logger.info("description of bool statement: " + repr(desc))
|
||||
logger.info("description of bool statement: %r", desc)
|
||||
yara_strings_list.append("\t" * depth + desc + "\n")
|
||||
elif statement == "Not":
|
||||
logger.info("one of those seldom nots: " + rule.name)
|
||||
logger.info("one of those seldom nots: %s", rule.name)
|
||||
|
||||
# check for nested statements
|
||||
try:
|
||||
kids = rule.children
|
||||
num_kids = len(kids)
|
||||
logger.info("kids: " + kids)
|
||||
except:
|
||||
logger.info("no kids in rule: " + rule.name)
|
||||
logger.info("kids: %s", kids)
|
||||
except Exception:
|
||||
logger.info("no kids in rule: %s", rule.name)
|
||||
|
||||
try:
|
||||
# maybe it's "Not" = only one child:
|
||||
@@ -356,31 +358,31 @@ def convert_rule(rule, rulename, cround, depth):
|
||||
kids = [kid]
|
||||
num_kids = 1
|
||||
logger.info("kid: %s", kids)
|
||||
except:
|
||||
except Exception:
|
||||
logger.info("no kid in rule: %s", rule.name)
|
||||
|
||||
# just a single statement without 'and' or 'or' before it in this rule
|
||||
if "kids" not in locals().keys():
|
||||
logger.info("no kids: " + rule.name)
|
||||
logger.info("no kids: %s", rule.name)
|
||||
|
||||
yara_strings_sub, yara_condition_sub = do_statement(statement, rule)
|
||||
|
||||
if yara_strings_sub == "BREAK":
|
||||
logger.info("Unknown feature at1: " + rule.name)
|
||||
logger.info("Unknown feature at1: %s", rule.name)
|
||||
return "BREAK", yara_condition_sub, rule_comment, incomplete
|
||||
yara_strings_list.append(yara_strings_sub)
|
||||
yara_condition_list.append(yara_condition_sub)
|
||||
|
||||
else:
|
||||
x = 0
|
||||
logger.info("doing kids: %r - len: %s", kids, num_kids)
|
||||
logger.info("doing kids: %r - len: %d", kids, num_kids)
|
||||
for kid in kids:
|
||||
s_type = kid.name
|
||||
logger.info("doing type: " + s_type + " kidnum: " + str(x))
|
||||
logger.info("doing type: %s kidnum: %d", s_type, x)
|
||||
|
||||
if s_type == "Some":
|
||||
cmin = kid.count
|
||||
logger.info("Some type with minimum: " + str(cmin))
|
||||
logger.info("Some type with minimum: %d", cmin)
|
||||
|
||||
if not cmin:
|
||||
logger.info("this is optional: which means, we can just ignore it")
|
||||
@@ -395,8 +397,8 @@ def convert_rule(rule, rulename, cround, depth):
|
||||
return "BREAK", "Some aka x or more (TODO)", rule_comment, incomplete
|
||||
|
||||
if s_type == "And" or s_type == "Or" or s_type == "Not" and not kid.name == "Some":
|
||||
logger.info("doing bool with recursion: " + repr(kid))
|
||||
logger.info("kid coming: " + repr(kid.name))
|
||||
logger.info("doing bool with recursion: %r", kid)
|
||||
logger.info("kid coming: %r", kid.name)
|
||||
# logger.info("grandchildren: " + repr(kid.children))
|
||||
|
||||
#
|
||||
@@ -406,22 +408,24 @@ def convert_rule(rule, rulename, cround, depth):
|
||||
kid, rulename, cround, depth
|
||||
)
|
||||
|
||||
logger.info("coming out of this recursion, depth: " + repr(depth) + " s_type: " + s_type)
|
||||
logger.info("coming out of this recursion, depth: %d s_type: %s", depth, s_type)
|
||||
|
||||
if yara_strings_sub == "BREAK":
|
||||
logger.info(
|
||||
"Unknown feature at2: " + rule.name + " - s_type: " + s_type + " - depth: " + str(depth)
|
||||
"Unknown feature at2: %s - s_type: %s - depth: %d",
|
||||
rule.name,
|
||||
s_type,
|
||||
depth,
|
||||
)
|
||||
|
||||
# luckily this is only a killer, if we're inside an 'And', inside 'Or' we're just missing some coverage
|
||||
# only accept incomplete rules in rounds > 3 because the reason might be a reference to another rule not converted yet because of missing dependencies
|
||||
logger.info("rule.name, depth, cround: " + rule.name + ", " + str(depth) + ", " + str(cround))
|
||||
logger.info("rule.name, depth, cround: %s, %d, %d", rule.name, depth, cround)
|
||||
if rule.name == "Or" and depth == 1 and cround > min_rounds - 1:
|
||||
logger.info(
|
||||
"Unknown feature, just ignore this branch and keep the rest bec we're in Or (1): "
|
||||
+ s_type
|
||||
+ " - depth: "
|
||||
+ str(depth)
|
||||
"Unknown feature, just ignore this branch and keep the rest bec we're in Or (1): %s - depth: %s",
|
||||
s_type,
|
||||
depth,
|
||||
)
|
||||
# remove last 'or'
|
||||
# yara_condition = re.sub(r'\sor $', ' ', yara_condition)
|
||||
@@ -442,14 +446,13 @@ def convert_rule(rule, rulename, cround, depth):
|
||||
yara_strings_sub, yara_condition_sub = do_statement(s_type, kid)
|
||||
|
||||
if yara_strings_sub == "BREAK":
|
||||
logger.info("Unknown feature at3: " + rule.name)
|
||||
logger.info("rule.name, depth, cround: " + rule.name + ", " + str(depth) + ", " + str(cround))
|
||||
logger.info("Unknown feature at3: %s", rule.name)
|
||||
logger.info("rule.name, depth, cround: %s, %d, %d", rule.name, depth, cround)
|
||||
if rule.name == "Or" and depth == 1 and cround > min_rounds - 1:
|
||||
logger.info(
|
||||
"Unknown feature, just ignore this branch and keep the rest bec we're in Or (2): "
|
||||
+ s_type
|
||||
+ " - depth: "
|
||||
+ str(depth)
|
||||
"Unknown feature, just ignore this branch and keep the rest bec we're in Or (2): %s - depth: %d",
|
||||
s_type,
|
||||
depth,
|
||||
)
|
||||
|
||||
rule_comment += "This rule is incomplete because a branch inside an Or-statement had an unsupported feature and was skipped"
|
||||
@@ -487,7 +490,7 @@ def convert_rule(rule, rulename, cround, depth):
|
||||
|
||||
elif statement == "Some":
|
||||
cmin = rule.count
|
||||
logger.info("Some type with minimum at2: " + str(cmin))
|
||||
logger.info("Some type with minimum at2: %d", cmin)
|
||||
|
||||
if not cmin:
|
||||
logger.info("this is optional: which means, we can just ignore it")
|
||||
@@ -500,7 +503,7 @@ def convert_rule(rule, rulename, cround, depth):
|
||||
yara_condition = "not " + "".join(yara_condition_list) + " "
|
||||
else:
|
||||
if len(yara_condition_list) != 1:
|
||||
logger.info("something wrong around here" + repr(yara_condition_list) + " - " + statement)
|
||||
logger.info("something wrong around here %r - %s", yara_condition_list, statement)
|
||||
sys.exit()
|
||||
|
||||
# strings might be empty with only conditions
|
||||
@@ -509,8 +512,10 @@ def convert_rule(rule, rulename, cround, depth):
|
||||
|
||||
yara_condition = "\n\t" + yara_condition_list[0]
|
||||
|
||||
logger.info(f"# end of convert_rule() #strings: {len(yara_strings_list)} #conditions: {len(yara_condition_list)}")
|
||||
logger.info(f"strings: {yara_strings} conditions: {yara_condition}")
|
||||
logger.info(
|
||||
"# end of convert_rule() #strings: %d #conditions: %d", len(yara_strings_list), len(yara_condition_list)
|
||||
)
|
||||
logger.info("strings: %s conditions: %s", yara_strings, yara_condition)
|
||||
|
||||
return yara_strings, yara_condition, rule_comment, incomplete
|
||||
|
||||
@@ -522,7 +527,7 @@ def output_yar(yara):
|
||||
def output_unsupported_capa_rules(yaml, capa_rulename, url, reason):
|
||||
if reason != "NOLOG":
|
||||
if capa_rulename not in unsupported_capa_rules_list:
|
||||
logger.info("unsupported: " + capa_rulename + " - reason: " + reason + " - url: " + url)
|
||||
logger.info("unsupported: %s - reason: %s, - url: %s", capa_rulename, reason, url)
|
||||
|
||||
unsupported_capa_rules_list.append(capa_rulename)
|
||||
unsupported_capa_rules.write(yaml.encode("utf-8") + b"\n")
|
||||
@@ -546,32 +551,32 @@ def convert_rules(rules, namespaces, cround, make_priv):
|
||||
rule_name = convert_rule_name(rule.name)
|
||||
|
||||
if rule.is_subscope_rule():
|
||||
logger.info("skipping sub scope rule capa: " + rule.name)
|
||||
logger.info("skipping sub scope rule capa: %s", rule.name)
|
||||
continue
|
||||
|
||||
if rule_name in converted_rules:
|
||||
logger.info("skipping already converted rule capa: " + rule.name + " - yara rule: " + rule_name)
|
||||
logger.info("skipping already converted rule capa: %s - yara rule: %s", rule.name, rule_name)
|
||||
continue
|
||||
|
||||
logger.info("-------------------------- DOING RULE CAPA: " + rule.name + " - yara rule: " + rule_name)
|
||||
logger.info("-------------------------- DOING RULE CAPA: %s - yara rule: ", rule.name, rule_name)
|
||||
if "capa/path" in rule.meta:
|
||||
url = get_rule_url(rule.meta["capa/path"])
|
||||
else:
|
||||
url = "no url"
|
||||
|
||||
logger.info("URL: " + url)
|
||||
logger.info("statements: " + repr(rule.statement))
|
||||
logger.info("URL: %s", url)
|
||||
logger.info("statements: %r", rule.statement)
|
||||
|
||||
# don't really know what that passed empty string is good for :)
|
||||
dependencies = rule.get_dependencies(namespaces)
|
||||
|
||||
if len(dependencies):
|
||||
logger.info("Dependencies at4: " + rule.name + " - dep: " + str(dependencies))
|
||||
logger.info("Dependencies at4: %s - dep: %s", rule.name, dependencies)
|
||||
|
||||
for dep in dependencies:
|
||||
logger.info("Dependencies at44: " + dep)
|
||||
logger.info("Dependencies at44: %s", dep)
|
||||
if not dep.startswith(rule.name + "/"):
|
||||
logger.info("Depending on another rule: " + dep)
|
||||
logger.info("Depending on another rule: %s", dep)
|
||||
continue
|
||||
|
||||
yara_strings, yara_condition, rule_comment, incomplete = convert_rule(rule.statement, rule.name, cround, 0)
|
||||
@@ -580,7 +585,7 @@ def convert_rules(rules, namespaces, cround, make_priv):
|
||||
# only give up if in final extra round #9000
|
||||
if cround == 9000:
|
||||
output_unsupported_capa_rules(rule.to_yaml(), rule.name, url, yara_condition)
|
||||
logger.info("Unknown feature at5: " + rule.name)
|
||||
logger.info("Unknown feature at5: %s", rule.name)
|
||||
else:
|
||||
yara_meta = ""
|
||||
metas = rule.meta
|
||||
@@ -596,24 +601,24 @@ def convert_rules(rules, namespaces, cround, make_priv):
|
||||
if meta_name == "att&ck":
|
||||
meta_name = "attack"
|
||||
for attack in list(metas[meta]):
|
||||
logger.info("attack:" + attack)
|
||||
logger.info("attack: %s", attack)
|
||||
# cut out tag in square brackets, e.g. Defense Evasion::Obfuscated Files or Information [T1027] => T1027
|
||||
r = re.search(r"\[(T[^\]]*)", attack)
|
||||
if r:
|
||||
tag = r.group(1)
|
||||
logger.info("attack tag:" + tag)
|
||||
logger.info("attack tag: %s", tag)
|
||||
tag = re.sub(r"\W", "_", tag)
|
||||
rule_tags += tag + " "
|
||||
# also add a line "attack = ..." to yaras 'meta:' to keep the long description:
|
||||
yara_meta += '\tattack = "' + attack + '"\n'
|
||||
elif meta_name == "mbc":
|
||||
for mbc in list(metas[meta]):
|
||||
logger.info("mbc:" + mbc)
|
||||
logger.info("mbc: %s", mbc)
|
||||
# cut out tag in square brackets, e.g. Cryptography::Encrypt Data::RC6 [C0027.010] => C0027.010
|
||||
r = re.search(r"\[(.[^\]]*)", mbc)
|
||||
if r:
|
||||
tag = r.group(1)
|
||||
logger.info("mbc tag:" + tag)
|
||||
logger.info("mbc tag: %s", tag)
|
||||
tag = re.sub(r"\W", "_", tag)
|
||||
rule_tags += tag + " "
|
||||
|
||||
@@ -676,8 +681,6 @@ def convert_rules(rules, namespaces, cround, make_priv):
|
||||
|
||||
yara += " condition:" + condition_header + yara_condition + "\n}"
|
||||
|
||||
# TODO: now the rule is finished and could be automatically checked with the capa-testfile(s) named in meta
|
||||
# (doing it for all of them using yara-ci upload at the moment)
|
||||
output_yar(yara)
|
||||
converted_rules.append(rule_name)
|
||||
count_incomplete += incomplete
|
||||
@@ -713,10 +716,10 @@ def main(argv=None):
|
||||
try:
|
||||
rules = capa.main.get_rules([args.rules])
|
||||
namespaces = capa.rules.index_rules_by_namespace(list(rules.rules.values()))
|
||||
logger.info("successfully loaded %s rules (including subscope rules which will be ignored)", len(rules))
|
||||
logger.info("successfully loaded %d rules (including subscope rules which will be ignored)", len(rules))
|
||||
if args.tag:
|
||||
rules = rules.filter_rules_by_meta(args.tag)
|
||||
logger.debug("selected %s rules", len(rules))
|
||||
logger.debug("selected %d rules", len(rules))
|
||||
for i, r in enumerate(rules.rules, 1):
|
||||
logger.debug(" %d. %s", i, r)
|
||||
except (IOError, capa.rules.InvalidRule, capa.rules.InvalidRuleSet) as e:
|
||||
@@ -748,7 +751,7 @@ def main(argv=None):
|
||||
count_incomplete = 0
|
||||
while num_rules != len(converted_rules) or cround < min_rounds:
|
||||
cround += 1
|
||||
logger.info("doing convert_rules(), round: " + str(cround))
|
||||
logger.info("doing convert_rules(), round: %d", cround)
|
||||
num_rules = len(converted_rules)
|
||||
count_incomplete += convert_rules(rules, namespaces, cround, make_priv)
|
||||
|
||||
@@ -758,7 +761,7 @@ def main(argv=None):
|
||||
stats = "\n// converted rules : " + str(len(converted_rules))
|
||||
stats += "\n// among those are incomplete : " + str(count_incomplete)
|
||||
stats += "\n// unconverted rules : " + str(len(unsupported_capa_rules_list)) + "\n"
|
||||
logger.info(stats)
|
||||
logger.info("%s", stats)
|
||||
output_yar(stats)
|
||||
|
||||
return 0
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import json
|
||||
import collections
|
||||
from typing import Any, Dict
|
||||
from typing import Any, Set, Dict
|
||||
|
||||
import capa.main
|
||||
import capa.rules
|
||||
@@ -13,7 +13,6 @@ import capa.render.utils as rutils
|
||||
import capa.render.default
|
||||
import capa.render.result_document as rd
|
||||
import capa.features.freeze.features as frzf
|
||||
from capa.engine import *
|
||||
from capa.features.common import OS_AUTO, FORMAT_AUTO
|
||||
|
||||
|
||||
@@ -30,7 +29,7 @@ def find_subrule_matches(doc: rd.ResultDocument) -> Set[str]:
|
||||
collect the rule names that have been matched as a subrule match.
|
||||
this way we can avoid displaying entries for things that are too specific.
|
||||
"""
|
||||
matches = set([])
|
||||
matches = set()
|
||||
|
||||
def rec(node: rd.Match):
|
||||
if not node.success:
|
||||
@@ -66,7 +65,7 @@ def render_capabilities(doc: rd.ResultDocument, result):
|
||||
"""
|
||||
subrule_matches = find_subrule_matches(doc)
|
||||
|
||||
result["CAPABILITY"] = dict()
|
||||
result["CAPABILITY"] = {}
|
||||
for rule in rutils.capability_rules(doc):
|
||||
if rule.meta.name in subrule_matches:
|
||||
# rules that are also matched by other rules should not get rendered by default.
|
||||
@@ -80,7 +79,7 @@ def render_capabilities(doc: rd.ResultDocument, result):
|
||||
else:
|
||||
capability = f"{rule.meta.name} ({count} matches)"
|
||||
|
||||
result["CAPABILITY"].setdefault(rule.meta.namespace, list())
|
||||
result["CAPABILITY"].setdefault(rule.meta.namespace, [])
|
||||
result["CAPABILITY"][rule.meta.namespace].append(capability)
|
||||
|
||||
|
||||
@@ -97,7 +96,7 @@ def render_attack(doc, result):
|
||||
'EXECUTION': ['Shared Modules [T1129]']}
|
||||
}
|
||||
"""
|
||||
result["ATTCK"] = dict()
|
||||
result["ATTCK"] = {}
|
||||
tactics = collections.defaultdict(set)
|
||||
for rule in rutils.capability_rules(doc):
|
||||
if not rule.meta.attack:
|
||||
@@ -130,7 +129,7 @@ def render_mbc(doc, result):
|
||||
'[C0021.004]']}
|
||||
}
|
||||
"""
|
||||
result["MBC"] = dict()
|
||||
result["MBC"] = {}
|
||||
objectives = collections.defaultdict(set)
|
||||
for rule in rutils.capability_rules(doc):
|
||||
if not rule.meta.mbc:
|
||||
@@ -150,7 +149,7 @@ def render_mbc(doc, result):
|
||||
|
||||
|
||||
def render_dictionary(doc: rd.ResultDocument) -> Dict[str, Any]:
|
||||
result: Dict[str, Any] = dict()
|
||||
result: Dict[str, Any] = {}
|
||||
render_meta(doc, result)
|
||||
render_attack(doc, result)
|
||||
render_mbc(doc, result)
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright (C) 2020 Mandiant, Inc. All Rights Reserved.
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at: [package root]/LICENSE.txt
|
||||
# Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
# is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and limitations under the License.
|
||||
|
||||
# Use a console with emojis support for a better experience
|
||||
# Use venv to ensure that `python` calls the correct python version
|
||||
|
||||
# Stash uncommitted changes
|
||||
MSG="pre-push-$(date +%s)";
|
||||
git stash push -kum "$MSG" &>/dev/null ;
|
||||
STASH_LIST=$(git stash list);
|
||||
if [[ "$STASH_LIST" == *"$MSG"* ]]; then
|
||||
echo "Uncommitted changes stashed with message '$MSG', if you abort before they are restored run \`git stash pop\`";
|
||||
fi
|
||||
|
||||
restore_stashed() {
|
||||
if [[ "$STASH_LIST" == *"$MSG"* ]]; then
|
||||
git stash pop --index &>/dev/null ;
|
||||
echo "Stashed changes '$MSG' restored";
|
||||
fi
|
||||
}
|
||||
|
||||
# Run isort and print state
|
||||
python -m isort --profile black --length-sort --line-width 120 -c . > isort-output.log 2>&1;
|
||||
if [ $? == 0 ]; then
|
||||
echo 'isort succeeded!! 💖';
|
||||
else
|
||||
echo 'isort FAILED! 😭';
|
||||
echo 'Check isort-output.log for details';
|
||||
restore_stashed;
|
||||
exit 1;
|
||||
fi
|
||||
|
||||
# Run black and print state
|
||||
python -m black -l 120 --check . > black-output.log 2>&1;
|
||||
if [ $? == 0 ]; then
|
||||
echo 'black succeeded!! 💝';
|
||||
else
|
||||
echo 'black FAILED! 😭';
|
||||
echo 'Check black-output.log for details';
|
||||
restore_stashed;
|
||||
exit 2;
|
||||
fi
|
||||
|
||||
# Run rule linter and print state
|
||||
python ./scripts/lint.py ./rules/ > rule-linter-output.log 2>&1;
|
||||
if [ $? == 0 ]; then
|
||||
echo 'Rule linter succeeded!! 💘';
|
||||
else
|
||||
echo 'Rule linter FAILED! 😭';
|
||||
echo 'Check rule-linter-output.log for details';
|
||||
restore_stashed;
|
||||
exit 3;
|
||||
fi
|
||||
|
||||
# Run tests except if first argument is no_tests
|
||||
if [ "$1" != 'no_tests' ]; then
|
||||
echo 'Running tests, please wait ⌛';
|
||||
python -m pytest tests/ --maxfail=1;
|
||||
if [ $? == 0 ]; then
|
||||
echo 'Tests succeed!! 🎉';
|
||||
else
|
||||
echo 'Tests FAILED! 😓';
|
||||
echo 'Run `pytest -v --cov=capa test/` if you need more details';
|
||||
restore_stashed;
|
||||
exit 4;
|
||||
fi
|
||||
fi
|
||||
|
||||
restore_stashed;
|
||||
echo 'SUCCEEDED 🎉🎉';
|
||||
|
||||
@@ -42,12 +42,12 @@ def get_features(rule_path: str) -> list:
|
||||
list: A list of all feature statements contained within the rule file.
|
||||
"""
|
||||
feature_list = []
|
||||
with open(rule_path, "r") as f:
|
||||
with open(rule_path, "r", encoding="utf-8") as f:
|
||||
try:
|
||||
new_rule = capa.rules.Rule.from_yaml(f.read())
|
||||
feature_list = get_child_features(new_rule.statement)
|
||||
except Exception as e:
|
||||
logger.error("Error: New rule " + rule_path + " " + str(type(e)) + " " + str(e))
|
||||
logger.error("Error: New rule %s %s %s", rule_path, str(type(e)), str(e))
|
||||
sys.exit(-1)
|
||||
return feature_list
|
||||
|
||||
@@ -73,7 +73,7 @@ def find_overlapping_rules(new_rule_path, rules_path):
|
||||
continue
|
||||
count += 1
|
||||
# Checks if any features match between existing and new rule.
|
||||
if any([feature in rule_features for feature in new_rule_features]):
|
||||
if any(feature in rule_features for feature in new_rule_features):
|
||||
overlapping_rules.append(rule_name)
|
||||
|
||||
result = {"overlapping_rules": overlapping_rules, "count": count}
|
||||
|
||||
+21
-17
@@ -28,13 +28,17 @@ Unless required by applicable law or agreed to in writing, software distributed
|
||||
is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and limitations under the License.
|
||||
"""
|
||||
import json
|
||||
import logging
|
||||
import binascii
|
||||
|
||||
import ida_nalt
|
||||
import ida_funcs
|
||||
import ida_kernwin
|
||||
|
||||
import capa.rules
|
||||
import capa.features.freeze
|
||||
import capa.render.result_document
|
||||
|
||||
logger = logging.getLogger("capa")
|
||||
|
||||
|
||||
@@ -64,37 +68,37 @@ def main():
|
||||
if not path:
|
||||
return 0
|
||||
|
||||
with open(path, "rb") as f:
|
||||
doc = json.loads(f.read().decode("utf-8"))
|
||||
|
||||
if "meta" not in doc or "rules" not in doc:
|
||||
logger.error("doesn't appear to be a capa report")
|
||||
return -1
|
||||
result_doc = capa.render.result_document.ResultDocument.parse_file(path)
|
||||
meta, capabilities = result_doc.to_capa()
|
||||
|
||||
# in IDA 7.4, the MD5 hash may be truncated, for example:
|
||||
# wanted: 84882c9d43e23d63b82004fae74ebb61
|
||||
# found: b'84882C9D43E23D63B82004FAE74EBB6\x00'
|
||||
#
|
||||
# see: https://github.com/idapython/bin/issues/11
|
||||
a = doc["meta"]["sample"]["md5"].lower()
|
||||
b = ida_nalt.retrieve_input_file_md5().lower()
|
||||
a = meta.sample.md5.lower()
|
||||
b = binascii.hexlify(ida_nalt.retrieve_input_file_md5()).decode("ascii").lower()
|
||||
if not a.startswith(b):
|
||||
logger.error("sample mismatch")
|
||||
return -2
|
||||
|
||||
rows = []
|
||||
for rule in doc["rules"].values():
|
||||
if rule["meta"].get("lib"):
|
||||
for name in capabilities.keys():
|
||||
rule = result_doc.rules[name]
|
||||
if rule.meta.lib:
|
||||
continue
|
||||
if rule["meta"].get("capa/subscope"):
|
||||
if rule.meta.is_subscope_rule:
|
||||
continue
|
||||
if rule["meta"]["scope"] != "function":
|
||||
if rule.meta.scope != capa.rules.Scope.FUNCTION:
|
||||
continue
|
||||
|
||||
name = rule["meta"]["name"]
|
||||
ns = rule["meta"].get("namespace", "")
|
||||
for va in rule["matches"].keys():
|
||||
va = int(va)
|
||||
ns = rule.meta.namespace
|
||||
|
||||
for address, _ in rule.matches:
|
||||
if address.type != capa.features.freeze.AddressType.ABSOLUTE:
|
||||
continue
|
||||
|
||||
va = address.value
|
||||
rows.append((ns, name, va))
|
||||
|
||||
# order by (namespace, name) so that like things show up together
|
||||
|
||||
+7
-7
@@ -43,7 +43,7 @@ import capa.engine
|
||||
import capa.helpers
|
||||
import capa.features.insn
|
||||
from capa.rules import Rule, RuleSet
|
||||
from capa.features.common import OS_AUTO, FORMAT_PE, FORMAT_DOTNET, String, Feature, Substring
|
||||
from capa.features.common import OS_AUTO, String, Feature, Substring
|
||||
from capa.render.result_document import RuleMetadata
|
||||
|
||||
logger = logging.getLogger("lint")
|
||||
@@ -355,7 +355,7 @@ class DoesntMatchExample(Lint):
|
||||
try:
|
||||
capabilities = get_sample_capabilities(ctx, path)
|
||||
except Exception as e:
|
||||
logger.error("failed to extract capabilities: %s %s %s", rule.name, str(path), e, exc_info=True)
|
||||
logger.exception("failed to extract capabilities: %s %s %s", rule.name, str(path), e)
|
||||
return True
|
||||
|
||||
if rule.name not in capabilities:
|
||||
@@ -516,7 +516,7 @@ class FeatureNegativeNumber(Lint):
|
||||
recommendation = "specify the number's two's complement representation"
|
||||
recommendation_template = (
|
||||
"capa treats number features as unsigned values; you may specify the number's two's complement "
|
||||
'representation; will not match on "{:d}"'
|
||||
+ 'representation; will not match on "{:d}"'
|
||||
)
|
||||
|
||||
def check_features(self, ctx: Context, features: List[Feature]):
|
||||
@@ -534,7 +534,7 @@ class FeatureNtdllNtoskrnlApi(Lint):
|
||||
level = Lint.WARN
|
||||
recommendation_template = (
|
||||
"check if {:s} is exported by both ntdll and ntoskrnl; if true, consider removing {:s} "
|
||||
"module requirement to improve detection"
|
||||
+ "module requirement to improve detection"
|
||||
)
|
||||
|
||||
def check_features(self, ctx: Context, features: List[Feature]):
|
||||
@@ -825,7 +825,7 @@ def lint_rule(ctx: Context, rule: Rule):
|
||||
print("")
|
||||
|
||||
if is_nursery_rule(rule):
|
||||
has_examples = not any(map(lambda v: v.level == Lint.FAIL and v.name == "missing examples", violations))
|
||||
has_examples = not any(v.level == Lint.FAIL and v.name == "missing examples" for v in violations)
|
||||
lints_failed = len(
|
||||
tuple(
|
||||
filter(
|
||||
@@ -873,7 +873,7 @@ def lint(ctx: Context):
|
||||
ret = {}
|
||||
|
||||
source_rules = [rule for rule in ctx.rules.rules.values() if not rule.is_subscope_rule()]
|
||||
with tqdm.contrib.logging.tqdm_logging_redirect(source_rules, unit="rule") as pbar:
|
||||
with tqdm.contrib.logging.tqdm_logging_redirect(source_rules, unit="rule", leave=False) as pbar:
|
||||
with capa.helpers.redirecting_print_to_tqdm(False):
|
||||
for rule in pbar:
|
||||
name = rule.name
|
||||
@@ -888,7 +888,7 @@ def collect_samples(path) -> Dict[str, Path]:
|
||||
recurse through the given path, collecting all file paths, indexed by their content sha256, md5, and filename.
|
||||
"""
|
||||
samples = {}
|
||||
for root, dirs, files in os.walk(path):
|
||||
for root, _, files in os.walk(path):
|
||||
for name in files:
|
||||
if name.endswith(".viv"):
|
||||
continue
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
"T1583.005": "Acquire Infrastructure::Botnet",
|
||||
"T1583.006": "Acquire Infrastructure::Web Services",
|
||||
"T1583.007": "Acquire Infrastructure::Serverless",
|
||||
"T1583.008": "Acquire Infrastructure::Malvertising",
|
||||
"T1584": "Compromise Infrastructure",
|
||||
"T1584.001": "Compromise Infrastructure::Domains",
|
||||
"T1584.002": "Compromise Infrastructure::DNS Server",
|
||||
@@ -88,7 +89,8 @@
|
||||
"T1608.003": "Stage Capabilities::Install Digital Certificate",
|
||||
"T1608.004": "Stage Capabilities::Drive-by Target",
|
||||
"T1608.005": "Stage Capabilities::Link Target",
|
||||
"T1608.006": "Stage Capabilities::SEO Poisoning"
|
||||
"T1608.006": "Stage Capabilities::SEO Poisoning",
|
||||
"T1650": "Acquire Access"
|
||||
},
|
||||
"Initial Access": {
|
||||
"T1078": "Valid Accounts",
|
||||
@@ -128,6 +130,7 @@
|
||||
"T1059.006": "Command and Scripting Interpreter::Python",
|
||||
"T1059.007": "Command and Scripting Interpreter::JavaScript",
|
||||
"T1059.008": "Command and Scripting Interpreter::Network Device CLI",
|
||||
"T1059.009": "Command and Scripting Interpreter::Cloud API",
|
||||
"T1072": "Software Deployment Tools",
|
||||
"T1106": "Native API",
|
||||
"T1129": "Shared Modules",
|
||||
@@ -145,7 +148,8 @@
|
||||
"T1569.002": "System Services::Service Execution",
|
||||
"T1609": "Container Administration Command",
|
||||
"T1610": "Deploy Container",
|
||||
"T1648": "Serverless Execution"
|
||||
"T1648": "Serverless Execution",
|
||||
"T1651": "Cloud Administration Command"
|
||||
},
|
||||
"Persistence": {
|
||||
"T1037": "Boot or Logon Initialization Scripts",
|
||||
@@ -247,6 +251,7 @@
|
||||
"T1556.005": "Modify Authentication Process::Reversible Encryption",
|
||||
"T1556.006": "Modify Authentication Process::Multi-Factor Authentication",
|
||||
"T1556.007": "Modify Authentication Process::Hybrid Identity",
|
||||
"T1556.008": "Modify Authentication Process::Network Provider DLL",
|
||||
"T1574": "Hijack Execution Flow",
|
||||
"T1574.001": "Hijack Execution Flow::DLL Search Order Hijacking",
|
||||
"T1574.002": "Hijack Execution Flow::DLL Side-Loading",
|
||||
@@ -372,6 +377,8 @@
|
||||
"T1027.007": "Obfuscated Files or Information::Dynamic API Resolution",
|
||||
"T1027.008": "Obfuscated Files or Information::Stripped Payloads",
|
||||
"T1027.009": "Obfuscated Files or Information::Embedded Payloads",
|
||||
"T1027.010": "Obfuscated Files or Information::Command Obfuscation",
|
||||
"T1027.011": "Obfuscated Files or Information::Fileless Storage",
|
||||
"T1036": "Masquerading",
|
||||
"T1036.001": "Masquerading::Invalid Code Signature",
|
||||
"T1036.002": "Masquerading::Right-to-Left Override",
|
||||
@@ -380,6 +387,7 @@
|
||||
"T1036.005": "Masquerading::Match Legitimate Name or Location",
|
||||
"T1036.006": "Masquerading::Space after Filename",
|
||||
"T1036.007": "Masquerading::Double File Extension",
|
||||
"T1036.008": "Masquerading::Masquerade File Type",
|
||||
"T1055": "Process Injection",
|
||||
"T1055.001": "Process Injection::Dynamic-link Library Injection",
|
||||
"T1055.002": "Process Injection::Portable Executable Injection",
|
||||
@@ -487,6 +495,7 @@
|
||||
"T1556.005": "Modify Authentication Process::Reversible Encryption",
|
||||
"T1556.006": "Modify Authentication Process::Multi-Factor Authentication",
|
||||
"T1556.007": "Modify Authentication Process::Hybrid Identity",
|
||||
"T1556.008": "Modify Authentication Process::Network Provider DLL",
|
||||
"T1562": "Impair Defenses",
|
||||
"T1562.001": "Impair Defenses::Disable or Modify Tools",
|
||||
"T1562.002": "Impair Defenses::Disable Windows Event Logging",
|
||||
@@ -497,6 +506,7 @@
|
||||
"T1562.008": "Impair Defenses::Disable Cloud Logs",
|
||||
"T1562.009": "Impair Defenses::Safe Mode Boot",
|
||||
"T1562.010": "Impair Defenses::Downgrade Attack",
|
||||
"T1562.011": "Impair Defenses::Spoof Security Alerting",
|
||||
"T1564": "Hide Artifacts",
|
||||
"T1564.001": "Hide Artifacts::Hidden Files and Directories",
|
||||
"T1564.002": "Hide Artifacts::Hidden Users",
|
||||
@@ -574,6 +584,7 @@
|
||||
"T1552.005": "Unsecured Credentials::Cloud Instance Metadata API",
|
||||
"T1552.006": "Unsecured Credentials::Group Policy Preferences",
|
||||
"T1552.007": "Unsecured Credentials::Container API",
|
||||
"T1552.008": "Unsecured Credentials::Chat Messages",
|
||||
"T1555": "Credentials from Password Stores",
|
||||
"T1555.001": "Credentials from Password Stores::Keychain",
|
||||
"T1555.002": "Credentials from Password Stores::Securityd Memory",
|
||||
@@ -588,6 +599,7 @@
|
||||
"T1556.005": "Modify Authentication Process::Reversible Encryption",
|
||||
"T1556.006": "Modify Authentication Process::Multi-Factor Authentication",
|
||||
"T1556.007": "Modify Authentication Process::Hybrid Identity",
|
||||
"T1556.008": "Modify Authentication Process::Network Provider DLL",
|
||||
"T1557": "Adversary-in-the-Middle",
|
||||
"T1557.001": "Adversary-in-the-Middle::LLMNR/NBT-NS Poisoning and SMB Relay",
|
||||
"T1557.002": "Adversary-in-the-Middle::ARP Cache Poisoning",
|
||||
@@ -630,7 +642,7 @@
|
||||
"T1124": "System Time Discovery",
|
||||
"T1135": "Network Share Discovery",
|
||||
"T1201": "Password Policy Discovery",
|
||||
"T1217": "Browser Bookmark Discovery",
|
||||
"T1217": "Browser Information Discovery",
|
||||
"T1482": "Domain Trust Discovery",
|
||||
"T1497": "Virtualization/Sandbox Evasion",
|
||||
"T1497.001": "Virtualization/Sandbox Evasion::System Checks",
|
||||
@@ -646,7 +658,8 @@
|
||||
"T1614.001": "System Location Discovery::System Language Discovery",
|
||||
"T1615": "Group Policy Discovery",
|
||||
"T1619": "Cloud Storage Object Discovery",
|
||||
"T1622": "Debugger Evasion"
|
||||
"T1622": "Debugger Evasion",
|
||||
"T1652": "Device Driver Discovery"
|
||||
},
|
||||
"Lateral Movement": {
|
||||
"T1021": "Remote Services",
|
||||
@@ -656,6 +669,7 @@
|
||||
"T1021.004": "Remote Services::SSH",
|
||||
"T1021.005": "Remote Services::VNC",
|
||||
"T1021.006": "Remote Services::Windows Remote Management",
|
||||
"T1021.007": "Remote Services::Cloud Services",
|
||||
"T1072": "Software Deployment Tools",
|
||||
"T1080": "Taint Shared Content",
|
||||
"T1091": "Replication Through Removable Media",
|
||||
@@ -768,7 +782,8 @@
|
||||
"T1537": "Transfer Data to Cloud Account",
|
||||
"T1567": "Exfiltration Over Web Service",
|
||||
"T1567.001": "Exfiltration Over Web Service::Exfiltration to Code Repository",
|
||||
"T1567.002": "Exfiltration Over Web Service::Exfiltration to Cloud Storage"
|
||||
"T1567.002": "Exfiltration Over Web Service::Exfiltration to Cloud Storage",
|
||||
"T1567.003": "Exfiltration Over Web Service::Exfiltration to Text Storage Sites"
|
||||
},
|
||||
"Impact": {
|
||||
"T1485": "Data Destruction",
|
||||
|
||||
@@ -27,7 +27,7 @@ example:
|
||||
|--------------------------------------|----------------------|-------------|-------------|-------------|
|
||||
| 18c30e4 main: remove perf debug msgs | 66,561,622 | 132.13s | 125.14s | 139.12s |
|
||||
|
||||
^^^ --label or git hash
|
||||
^^^ --label or git hash
|
||||
"""
|
||||
import sys
|
||||
import timeit
|
||||
@@ -112,7 +112,7 @@ def main(argv=None):
|
||||
)
|
||||
|
||||
assert isinstance(extractor, StaticFeatureExtractor)
|
||||
with tqdm.tqdm(total=args.number * args.repeat) as pbar:
|
||||
with tqdm.tqdm(total=args.number * args.repeat, leave=False) as pbar:
|
||||
|
||||
def do_iteration():
|
||||
capa.perf.reset()
|
||||
@@ -121,12 +121,12 @@ def main(argv=None):
|
||||
|
||||
samples = timeit.repeat(do_iteration, number=args.number, repeat=args.repeat)
|
||||
|
||||
logger.debug("perf: find capabilities: min: %0.2fs" % (min(samples) / float(args.number)))
|
||||
logger.debug("perf: find capabilities: avg: %0.2fs" % (sum(samples) / float(args.repeat) / float(args.number)))
|
||||
logger.debug("perf: find capabilities: max: %0.2fs" % (max(samples) / float(args.number)))
|
||||
logger.debug("perf: find capabilities: min: %0.2fs", (min(samples) / float(args.number)))
|
||||
logger.debug("perf: find capabilities: avg: %0.2fs", (sum(samples) / float(args.repeat) / float(args.number)))
|
||||
logger.debug("perf: find capabilities: max: %0.2fs", (max(samples) / float(args.number)))
|
||||
|
||||
for counter, count in capa.perf.counters.most_common():
|
||||
logger.debug("perf: counter: {:}: {:,}".format(counter, count))
|
||||
logger.debug("perf: counter: %s: %s", counter, count)
|
||||
|
||||
print(
|
||||
tabulate.tabulate(
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# Copyright (C) 2020 Mandiant, Inc. All Rights Reserved.
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at: [package root]/LICENSE.txt
|
||||
# Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
# is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and limitations under the License.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
GIT_DIR=$(git rev-parse --show-toplevel);
|
||||
cd "$GIT_DIR";
|
||||
|
||||
# hooks may exist already (e.g. git-lfs configuration)
|
||||
# If the `.git/hooks/$arg` file doesn't exist it, initialize with `#!/usr/bin/env bash`
|
||||
# After that append `scripts/hooks/$arg` and ensure they can be run
|
||||
create_hook() {
|
||||
if [[ ! -e .git/hooks/$1 ]]; then
|
||||
echo "#!/usr/bin/env bash" > ".git/hooks/$1";
|
||||
fi
|
||||
echo "scripts/ci.sh ${2:-}" >> ".git/hooks/$1";
|
||||
chmod +x .git/hooks/"$1";
|
||||
}
|
||||
|
||||
printf 'Adding scripts/ci.sh to .git/hooks/';
|
||||
create_hook 'pre-commit' 'no_tests';
|
||||
create_hook 'pre-push';
|
||||
@@ -65,7 +65,7 @@ class MitreExtractor:
|
||||
if self.url == "":
|
||||
raise ValueError(f"URL not specified in class {self.__class__.__name__}")
|
||||
|
||||
logging.info(f"Downloading STIX data at: {self.url}")
|
||||
logging.info("Downloading STIX data at: %s", self.url)
|
||||
stix_json = requests.get(self.url).json()
|
||||
self._memory_store = MemoryStore(stix_data=stix_json["objects"])
|
||||
|
||||
@@ -170,12 +170,12 @@ def main(args: argparse.Namespace) -> None:
|
||||
logging.info("Extracting MBC behaviors...")
|
||||
data["mbc"] = MbcExtractor().run()
|
||||
|
||||
logging.info(f"Writing results to {args.output}")
|
||||
logging.info("Writing results to %s", args.output)
|
||||
try:
|
||||
with open(args.output, "w") as jf:
|
||||
with open(args.output, "w", encoding="utf-8") as jf:
|
||||
json.dump(data, jf, indent=2)
|
||||
except BaseException as e:
|
||||
logging.error(f"Exception encountered when writing results: {e}")
|
||||
logging.error("Exception encountered when writing results: %s", e)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -69,7 +69,6 @@ import sys
|
||||
import logging
|
||||
import os.path
|
||||
import argparse
|
||||
from typing import cast
|
||||
|
||||
import capa.main
|
||||
import capa.rules
|
||||
@@ -104,7 +103,7 @@ def main(argv=None):
|
||||
capa.main.handle_common_args(args)
|
||||
|
||||
try:
|
||||
taste = capa.helpers.get_file_taste(args.sample)
|
||||
_ = capa.helpers.get_file_taste(args.sample)
|
||||
except IOError as e:
|
||||
logger.error("%s", str(e))
|
||||
return -1
|
||||
@@ -156,7 +155,6 @@ def print_static_analysis(extractor: StaticFeatureExtractor, args):
|
||||
|
||||
if args.function:
|
||||
if args.format == "freeze":
|
||||
# TODO fix
|
||||
function_handles = tuple(filter(lambda fh: fh.address == args.function, function_handles))
|
||||
else:
|
||||
function_handles = tuple(filter(lambda fh: format_address(fh.address) == args.function, function_handles))
|
||||
@@ -285,7 +283,7 @@ def ida_main():
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if capa.main.is_runtime_ida():
|
||||
if capa.helpers.is_runtime_ida():
|
||||
ida_main()
|
||||
else:
|
||||
sys.exit(main())
|
||||
|
||||
Reference in New Issue
Block a user