mirror of
https://github.com/mandiant/capa.git
synced 2026-06-12 19:11:32 -07:00
code style : update remaining files (#1353)
* code style: update string formatting using fstrings --------- Co-authored-by: Willi Ballenthin <willi.ballenthin@gmail.com> Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
This commit is contained in:
+6
-4
@@ -43,10 +43,12 @@ class Statement:
|
||||
self.description = description
|
||||
|
||||
def __str__(self):
|
||||
name = self.name.lower()
|
||||
children = ",".join(map(str, self.get_children()))
|
||||
if self.description:
|
||||
return "%s(%s = %s)" % (self.name.lower(), ",".join(map(str, self.get_children())), self.description)
|
||||
return f"{name}({children} = {self.description})"
|
||||
else:
|
||||
return "%s(%s)" % (self.name.lower(), ",".join(map(str, self.get_children())))
|
||||
return f"{name}({children})"
|
||||
|
||||
def __repr__(self):
|
||||
return str(self)
|
||||
@@ -232,9 +234,9 @@ class Range(Statement):
|
||||
|
||||
def __str__(self):
|
||||
if self.max == (1 << 64 - 1):
|
||||
return "range(%s, min=%d, max=infinity)" % (str(self.child), self.min)
|
||||
return f"range({str(self.child)}, min={self.min}, max=infinity)"
|
||||
else:
|
||||
return "range(%s, min=%d, max=%d)" % (str(self.child), self.min, self.max)
|
||||
return f"range({str(self.child)}, min={self.min}, max={self.max})"
|
||||
|
||||
|
||||
class Subscope(Statement):
|
||||
|
||||
+10
-14
@@ -149,11 +149,11 @@ class Feature(abc.ABC):
|
||||
def __str__(self):
|
||||
if self.value is not None:
|
||||
if self.description:
|
||||
return "%s(%s = %s)" % (self.get_name_str(), self.get_value_str(), self.description)
|
||||
return f"{self.get_name_str()}({self.get_value_str()} = {self.description})"
|
||||
else:
|
||||
return "%s(%s)" % (self.get_name_str(), self.get_value_str())
|
||||
return f"{self.get_name_str()}({self.get_value_str()})"
|
||||
else:
|
||||
return "%s" % self.get_name_str()
|
||||
return f"{self.get_name_str()}"
|
||||
|
||||
def __repr__(self):
|
||||
return str(self)
|
||||
@@ -242,7 +242,7 @@ class Substring(String):
|
||||
|
||||
def __str__(self):
|
||||
assert isinstance(self.value, str)
|
||||
return "substring(%s)" % escape_string(self.value)
|
||||
return f"substring({escape_string(self.value)})"
|
||||
|
||||
|
||||
class _MatchedSubstring(Substring):
|
||||
@@ -267,11 +267,9 @@ class _MatchedSubstring(Substring):
|
||||
self.matches = matches
|
||||
|
||||
def __str__(self):
|
||||
matches = ", ".join(map(lambda s: '"' + s + '"', (self.matches or {}).keys()))
|
||||
assert isinstance(self.value, str)
|
||||
return 'substring("%s", matches = %s)' % (
|
||||
self.value,
|
||||
", ".join(map(lambda s: '"' + s + '"', (self.matches or {}).keys())),
|
||||
)
|
||||
return f'substring("{self.value}", matches = {matches})'
|
||||
|
||||
|
||||
class Regex(String):
|
||||
@@ -290,7 +288,7 @@ class Regex(String):
|
||||
if value.endswith("/i"):
|
||||
value = value[: -len("i")]
|
||||
raise ValueError(
|
||||
"invalid regular expression: %s it should use Python syntax, try it at https://pythex.org" % value
|
||||
f"invalid regular expression: {value} it should use Python syntax, try it at https://pythex.org"
|
||||
) from exc
|
||||
|
||||
def evaluate(self, ctx, short_circuit=True):
|
||||
@@ -336,7 +334,7 @@ class Regex(String):
|
||||
|
||||
def __str__(self):
|
||||
assert isinstance(self.value, str)
|
||||
return "regex(string =~ %s)" % self.value
|
||||
return f"regex(string =~ {self.value})"
|
||||
|
||||
|
||||
class _MatchedRegex(Regex):
|
||||
@@ -361,11 +359,9 @@ class _MatchedRegex(Regex):
|
||||
self.matches = matches
|
||||
|
||||
def __str__(self):
|
||||
matches = ", ".join(map(lambda s: '"' + s + '"', (self.matches or {}).keys()))
|
||||
assert isinstance(self.value, str)
|
||||
return "regex(string =~ %s, matches = %s)" % (
|
||||
self.value,
|
||||
", ".join(map(lambda s: '"' + s + '"', (self.matches or {}).keys())),
|
||||
)
|
||||
return f"regex(string =~ {self.value}, matches = {matches})"
|
||||
|
||||
|
||||
class StringFactory:
|
||||
|
||||
@@ -121,14 +121,14 @@ class ELF:
|
||||
elif ei_class == 2:
|
||||
self.bitness = 64
|
||||
else:
|
||||
raise CorruptElfFile("invalid ei_class: 0x%02x" % ei_class)
|
||||
raise CorruptElfFile(f"invalid ei_class: 0x{ei_class:02x}")
|
||||
|
||||
if ei_data == 1:
|
||||
self.endian = "<"
|
||||
elif ei_data == 2:
|
||||
self.endian = ">"
|
||||
else:
|
||||
raise CorruptElfFile("not an ELF file: invalid ei_data: 0x%02x" % ei_data)
|
||||
raise CorruptElfFile(f"not an ELF file: invalid ei_data: 0x{ei_data:02x}")
|
||||
|
||||
if self.bitness == 32:
|
||||
e_phoff, e_shoff = struct.unpack_from(self.endian + "II", self.file_header, 0x1C)
|
||||
|
||||
@@ -55,7 +55,7 @@ def generate_symbols(dll: str, symbol: str) -> Iterator[str]:
|
||||
dll = dll.lower()
|
||||
|
||||
# kernel32.CreateFileA
|
||||
yield "%s.%s" % (dll, symbol)
|
||||
yield f"{dll}.{symbol}"
|
||||
|
||||
if not is_ordinal(symbol):
|
||||
# CreateFileA
|
||||
@@ -63,7 +63,7 @@ def generate_symbols(dll: str, symbol: str) -> Iterator[str]:
|
||||
|
||||
if is_aw_function(symbol):
|
||||
# kernel32.CreateFile
|
||||
yield "%s.%s" % (dll, symbol[:-1])
|
||||
yield f"{dll}.{symbol[:-1]}"
|
||||
|
||||
if not is_ordinal(symbol):
|
||||
# CreateFile
|
||||
|
||||
@@ -34,7 +34,7 @@ def get_printable_len(op: idaapi.op_t) -> int:
|
||||
elif op.dtype == idaapi.dt_qword:
|
||||
chars = struct.pack("<Q", op_val)
|
||||
else:
|
||||
raise ValueError("Unhandled operand data type 0x%x." % op.dtype)
|
||||
raise ValueError(f"Unhandled operand data type 0x{op.dtype:x}.")
|
||||
|
||||
def is_printable_ascii(chars_: bytes):
|
||||
return all(c < 127 and chr(c) in string.printable for c in chars_)
|
||||
|
||||
@@ -25,7 +25,7 @@ def find_byte_sequence(start: int, end: int, seq: bytes) -> Iterator[int]:
|
||||
end: max virtual address
|
||||
seq: bytes to search e.g. b"\x01\x03"
|
||||
"""
|
||||
seqstr = " ".join(["%02x" % b for b in seq])
|
||||
seqstr = " ".join([f"{b:02x}" for b in seq])
|
||||
while True:
|
||||
# TODO find_binary: Deprecated. Please use ida_bytes.bin_search() instead.
|
||||
ea = idaapi.find_binary(start, end, seqstr, 0, idaapi.SEARCH_DOWN)
|
||||
|
||||
@@ -64,7 +64,7 @@ def extract_file_import_names(pe, **kwargs):
|
||||
|
||||
for imp in dll.imports:
|
||||
if imp.import_by_ordinal:
|
||||
impname = "#%s" % imp.ordinal
|
||||
impname = f"#{imp.ordinal}"
|
||||
else:
|
||||
try:
|
||||
impname = imp.name.partition(b"\x00")[0].decode("ascii")
|
||||
|
||||
@@ -121,7 +121,7 @@ def get_printable_len(oper: envi.archs.i386.disasm.i386ImmOper) -> int:
|
||||
elif oper.tsize == 8:
|
||||
chars = struct.pack("<Q", oper.imm)
|
||||
else:
|
||||
raise ValueError("unexpected oper.tsize: %d" % (oper.tsize))
|
||||
raise ValueError(f"unexpected oper.tsize: {oper.tsize}")
|
||||
|
||||
if is_printable_ascii(chars):
|
||||
return oper.tsize
|
||||
|
||||
@@ -44,7 +44,7 @@ def extract_file_import_names(vw, **kwargs) -> Iterator[Tuple[Feature, Address]]
|
||||
modname, impname = tinfo.split(".", 1)
|
||||
if is_viv_ord_impname(impname):
|
||||
# replace ord prefix with #
|
||||
impname = "#%s" % impname[len("ord") :]
|
||||
impname = "#" + impname[len("ord") :]
|
||||
|
||||
addr = AbsoluteVirtualAddress(va)
|
||||
for name in capa.features.extractors.helpers.generate_symbols(modname, impname):
|
||||
|
||||
@@ -329,7 +329,7 @@ def loads(s: str) -> capa.features.extractors.base_extractor.FeatureExtractor:
|
||||
|
||||
freeze = Freeze.parse_raw(s)
|
||||
if freeze.version != 2:
|
||||
raise ValueError("unsupported freeze format version: %d", freeze.version)
|
||||
raise ValueError(f"unsupported freeze format version: {freeze.version}")
|
||||
|
||||
return null.NullFeatureExtractor(
|
||||
base_address=freeze.base_address.to_capa(),
|
||||
|
||||
@@ -15,9 +15,9 @@ from capa.features.common import VALID_FEATURE_ACCESS, Feature
|
||||
def hex(n: int) -> str:
|
||||
"""render the given number using upper case hex, like: 0x123ABC"""
|
||||
if n < 0:
|
||||
return "-0x%X" % (-n)
|
||||
return f"-0x{(-n):X}"
|
||||
else:
|
||||
return "0x%X" % n
|
||||
return f"0x{(n):X}"
|
||||
|
||||
|
||||
class API(Feature):
|
||||
@@ -105,7 +105,7 @@ class _Operand(Feature, abc.ABC):
|
||||
|
||||
class OperandNumber(_Operand):
|
||||
# cached names so we don't do extra string formatting every ctor
|
||||
NAMES = ["operand[%d].number" % i for i in range(MAX_OPERAND_COUNT)]
|
||||
NAMES = [f"operand[{i}].number" for i in range(MAX_OPERAND_COUNT)]
|
||||
|
||||
# operand[i].number: 0x12
|
||||
def __init__(self, index: int, value: int, description=None):
|
||||
@@ -119,7 +119,7 @@ class OperandNumber(_Operand):
|
||||
|
||||
class OperandOffset(_Operand):
|
||||
# cached names so we don't do extra string formatting every ctor
|
||||
NAMES = ["operand[%d].offset" % i for i in range(MAX_OPERAND_COUNT)]
|
||||
NAMES = [f"operand[{i}].offset" for i in range(MAX_OPERAND_COUNT)]
|
||||
|
||||
# operand[i].offset: 0x12
|
||||
def __init__(self, index: int, value: int, description=None):
|
||||
|
||||
+3
-3
@@ -22,14 +22,14 @@ logger = logging.getLogger("capa")
|
||||
def hex(n: int) -> str:
|
||||
"""render the given number using upper case hex, like: 0x123ABC"""
|
||||
if n < 0:
|
||||
return "-0x%X" % (-n)
|
||||
return f"-0x{(-n):X}"
|
||||
else:
|
||||
return "0x%X" % n
|
||||
return f"0x{(n):X}"
|
||||
|
||||
|
||||
def get_file_taste(sample_path: str) -> bytes:
|
||||
if not os.path.exists(sample_path):
|
||||
raise IOError("sample path %s does not exist or cannot be accessed" % sample_path)
|
||||
raise IOError(f"sample path {sample_path} does not exist or cannot be accessed")
|
||||
with open(sample_path, "rb") as f:
|
||||
taste = f.read(8)
|
||||
return taste
|
||||
|
||||
+1
-1
@@ -45,7 +45,7 @@ NETNODE_RULES_CACHE_ID = "rules-cache-id"
|
||||
|
||||
|
||||
def inform_user_ida_ui(message):
|
||||
idaapi.info("%s. Please refer to IDA Output window for more information." % message)
|
||||
idaapi.info(f"{message}. Please refer to IDA Output window for more information.")
|
||||
|
||||
|
||||
def is_supported_ida_version():
|
||||
|
||||
@@ -26,7 +26,7 @@ class CapaExplorerProgressIndicator(QtCore.QObject):
|
||||
"""
|
||||
if ida_kernwin.user_cancelled():
|
||||
raise UserCancelledError("user cancelled")
|
||||
self.progress.emit("extracting features from %s" % text)
|
||||
self.progress.emit(f"extracting features from {text}")
|
||||
|
||||
|
||||
class CapaExplorerFeatureExtractor(IdaFeatureExtractor):
|
||||
@@ -40,5 +40,5 @@ class CapaExplorerFeatureExtractor(IdaFeatureExtractor):
|
||||
self.indicator = CapaExplorerProgressIndicator()
|
||||
|
||||
def extract_function_features(self, fh: FunctionHandle):
|
||||
self.indicator.update("function at 0x%X" % fh.inner.start_ea)
|
||||
self.indicator.update(f"function at {hex(fh.inner.start_ea)}")
|
||||
return super().extract_function_features(fh)
|
||||
|
||||
+14
-20
@@ -83,13 +83,13 @@ def trim_function_name(f, max_length=25):
|
||||
""" """
|
||||
n = idaapi.get_name(f.start_ea)
|
||||
if len(n) > max_length:
|
||||
n = "%s..." % n[:max_length]
|
||||
n = f"{n[:max_length]}..."
|
||||
return n
|
||||
|
||||
|
||||
def update_wait_box(text):
|
||||
"""update the IDA wait box"""
|
||||
ida_kernwin.replace_wait_box("capa explorer...%s" % text)
|
||||
ida_kernwin.replace_wait_box(f"capa explorer...{text}")
|
||||
|
||||
|
||||
class QLineEditClicked(QtWidgets.QLineEdit):
|
||||
@@ -630,7 +630,7 @@ class CapaExplorerForm(idaapi.PluginForm):
|
||||
try:
|
||||
|
||||
def on_load_rule(_, i, total):
|
||||
update_wait_box("loading capa rules from %s (%d of %d)" % (rule_path, i + 1, total))
|
||||
update_wait_box(f"loading capa rules from {rule_path} ({i+1} of {total})")
|
||||
if ida_kernwin.user_cancelled():
|
||||
raise UserCancelledError("user cancelled")
|
||||
|
||||
@@ -640,7 +640,7 @@ class CapaExplorerForm(idaapi.PluginForm):
|
||||
return None
|
||||
except Exception as e:
|
||||
capa.ida.helpers.inform_user_ida_ui(
|
||||
"Failed to load capa rules from %s" % settings.user[CAPA_SETTINGS_RULE_PATH]
|
||||
f"Failed to load capa rules from {settings.user[CAPA_SETTINGS_RULE_PATH]}"
|
||||
)
|
||||
|
||||
logger.error("Failed to load capa rules from %s (error: %s).", settings.user[CAPA_SETTINGS_RULE_PATH], e)
|
||||
@@ -691,10 +691,9 @@ class CapaExplorerForm(idaapi.PluginForm):
|
||||
|
||||
update_wait_box("verifying cached results")
|
||||
|
||||
view_status_rules: str = "%s (%d rules)" % (
|
||||
settings.user[CAPA_SETTINGS_RULE_PATH],
|
||||
self.program_analysis_ruleset_cache.source_rule_count,
|
||||
)
|
||||
count_source_rules = self.program_analysis_ruleset_cache.source_rule_count
|
||||
user_settings = settings.user[CAPA_SETTINGS_RULE_PATH]
|
||||
view_status_rules: str = f"{user_settings} ({count_source_rules} rules)"
|
||||
|
||||
# warn user about potentially outdated rules, depending on the use-case this may be expected
|
||||
if (
|
||||
@@ -710,10 +709,8 @@ class CapaExplorerForm(idaapi.PluginForm):
|
||||
)
|
||||
view_status_rules = "no rules matched for cache"
|
||||
|
||||
new_view_status = "capa rules: %s, cached results (created %s)" % (
|
||||
view_status_rules,
|
||||
self.resdoc_cache.meta.timestamp.strftime("%Y-%m-%d %H:%M:%S"),
|
||||
)
|
||||
cached_results_time = self.resdoc_cache.meta.timestamp.strftime("%Y-%m-%d %H:%M:%S")
|
||||
new_view_status = f"capa rules: {view_status_rules}, cached results (created {cached_results_time})"
|
||||
except Exception as e:
|
||||
logger.error("Failed to load cached capa results (error: %s).", e, exc_info=True)
|
||||
return False
|
||||
@@ -725,7 +722,7 @@ class CapaExplorerForm(idaapi.PluginForm):
|
||||
|
||||
def slot_progress_feature_extraction(text):
|
||||
"""slot function to handle feature extraction progress updates"""
|
||||
update_wait_box("%s (%d of %d)" % (text, self.process_count, self.process_total))
|
||||
update_wait_box(f"{text} ({self.process_count} of {self.process_total})")
|
||||
self.process_count += 1
|
||||
|
||||
update_wait_box("initializing feature extractor")
|
||||
@@ -843,12 +840,9 @@ class CapaExplorerForm(idaapi.PluginForm):
|
||||
except Exception as e:
|
||||
logger.error("Failed to save results to database (error: %s)", e, exc_info=True)
|
||||
return False
|
||||
|
||||
new_view_status = "capa rules: %s (%d rules)" % (
|
||||
settings.user[CAPA_SETTINGS_RULE_PATH],
|
||||
self.program_analysis_ruleset_cache.source_rule_count,
|
||||
)
|
||||
|
||||
user_settings = settings.user[CAPA_SETTINGS_RULE_PATH]
|
||||
count_source_rules = self.program_analysis_ruleset_cache.source_rule_count
|
||||
new_view_status = f"capa rules: {user_settings} ({count_source_rules} rules)"
|
||||
# regardless of new analysis, render results - e.g. we may only want to render results after checking
|
||||
# show results by function
|
||||
|
||||
@@ -1094,7 +1088,7 @@ class CapaExplorerForm(idaapi.PluginForm):
|
||||
self.view_rulegen_features.load_features(all_file_features, all_function_features)
|
||||
|
||||
self.set_view_status_label(
|
||||
"capa rules: %s (%d rules)" % (settings.user[CAPA_SETTINGS_RULE_PATH], ruleset.source_rule_count)
|
||||
f"capa rules: {settings.user[CAPA_SETTINGS_RULE_PATH]} ({settings.user[CAPA_SETTINGS_RULE_PATH]} rules)"
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error("Failed to render views (error: %s)", e, exc_info=True)
|
||||
|
||||
@@ -30,7 +30,7 @@ def info_to_name(display):
|
||||
|
||||
def ea_to_hex(ea):
|
||||
"""convert effective address (ea) to hex for display"""
|
||||
return "%08X" % ea
|
||||
return f"{hex(ea)}"
|
||||
|
||||
|
||||
class CapaExplorerDataItem:
|
||||
|
||||
+15
-15
@@ -369,34 +369,34 @@ class CapaExplorerDataModel(QtCore.QAbstractItemModel):
|
||||
if statement.type != rd.CompoundStatementType.NOT:
|
||||
display = statement.type
|
||||
if statement.description:
|
||||
display += " (%s)" % statement.description
|
||||
display += f" ({statement.description})"
|
||||
return CapaExplorerDefaultItem(parent, display)
|
||||
elif isinstance(statement, rd.CompoundStatement) and statement.type == rd.CompoundStatementType.NOT:
|
||||
# TODO: do we display 'not'
|
||||
pass
|
||||
elif isinstance(statement, rd.SomeStatement):
|
||||
display = "%d or more" % statement.count
|
||||
display = f"{statement.count} or more"
|
||||
if statement.description:
|
||||
display += " (%s)" % statement.description
|
||||
display += f" ({statement.description})"
|
||||
return CapaExplorerDefaultItem(parent, display)
|
||||
elif isinstance(statement, rd.RangeStatement):
|
||||
# `range` is a weird node, its almost a hybrid of statement + feature.
|
||||
# it is a specific feature repeated multiple times.
|
||||
# there's no additional logic in the feature part, just the existence of a feature.
|
||||
# so, we have to inline some of the feature rendering here.
|
||||
display = "count(%s): " % self.capa_doc_feature_to_display(statement.child)
|
||||
display = f"count({self.capa_doc_feature_to_display(statement.child)}): "
|
||||
|
||||
if statement.max == statement.min:
|
||||
display += "%d" % (statement.min)
|
||||
display += f"{statement.min}"
|
||||
elif statement.min == 0:
|
||||
display += "%d or fewer" % (statement.max)
|
||||
display += f"{statement.max} or fewer"
|
||||
elif statement.max == (1 << 64 - 1):
|
||||
display += "%d or more" % (statement.min)
|
||||
display += f"{statement.min} or more"
|
||||
else:
|
||||
display += "between %d and %d" % (statement.min, statement.max)
|
||||
display += f"between {statement.min} and {statement.max}"
|
||||
|
||||
if statement.description:
|
||||
display += " (%s)" % statement.description
|
||||
display += f" ({statement.description})"
|
||||
|
||||
parent2 = CapaExplorerFeatureItem(parent, display=display)
|
||||
|
||||
@@ -408,7 +408,7 @@ class CapaExplorerDataModel(QtCore.QAbstractItemModel):
|
||||
elif isinstance(statement, rd.SubscopeStatement):
|
||||
display = str(statement.scope)
|
||||
if statement.description:
|
||||
display += " (%s)" % statement.description
|
||||
display += f" ({statement.description})"
|
||||
return CapaExplorerSubscopeItem(parent, display)
|
||||
else:
|
||||
raise RuntimeError("unexpected match statement type: " + str(statement))
|
||||
@@ -537,7 +537,7 @@ class CapaExplorerDataModel(QtCore.QAbstractItemModel):
|
||||
|
||||
if value:
|
||||
if isinstance(feature, frzf.StringFeature):
|
||||
value = '"%s"' % capa.features.common.escape_string(value)
|
||||
value = f'"{capa.features.common.escape_string(value)}"'
|
||||
|
||||
if isinstance(feature, frzf.PropertyFeature) and feature.access is not None:
|
||||
key = f"property/{feature.access}"
|
||||
@@ -547,11 +547,11 @@ class CapaExplorerDataModel(QtCore.QAbstractItemModel):
|
||||
key = f"operand[{feature.index}].offset"
|
||||
|
||||
if feature.description:
|
||||
return "%s(%s = %s)" % (key, value, feature.description)
|
||||
return f"{key}({value} = {feature.description})"
|
||||
else:
|
||||
return "%s(%s)" % (key, value)
|
||||
return f"{key}({value})"
|
||||
else:
|
||||
return "%s" % key
|
||||
return f"{key}"
|
||||
|
||||
def render_capa_doc_feature_node(
|
||||
self,
|
||||
@@ -669,7 +669,7 @@ class CapaExplorerDataModel(QtCore.QAbstractItemModel):
|
||||
elif isinstance(feature, frzf.StringFeature):
|
||||
# display string preview
|
||||
return CapaExplorerStringViewItem(
|
||||
parent, display, location, '"%s"' % capa.features.common.escape_string(feature.string)
|
||||
parent, display, location, f'"{capa.features.common.escape_string(feature.string)}"'
|
||||
)
|
||||
|
||||
elif isinstance(
|
||||
|
||||
+30
-36
@@ -58,7 +58,7 @@ def parse_yaml_line(feature):
|
||||
if m:
|
||||
# reconstruct count without description
|
||||
feature, value, description, count = m.groups()
|
||||
feature = "- count(%s(%s)): %s" % (feature, value, count)
|
||||
feature = f"- count({feature}({value})): {count}"
|
||||
elif not feature.startswith("#"):
|
||||
feature, _, comment = feature.partition("#")
|
||||
feature, _, description = feature.partition("=")
|
||||
@@ -72,18 +72,18 @@ def parse_node_for_feature(feature, description, comment, depth):
|
||||
display = ""
|
||||
|
||||
if feature.startswith("#"):
|
||||
display += "%s%s\n" % (" " * depth, feature)
|
||||
display += f"{' '*depth}{feature}\n"
|
||||
elif description:
|
||||
if feature.startswith(("- and", "- or", "- optional", "- basic block", "- not", "- instruction:")):
|
||||
display += "%s%s" % (" " * depth, feature)
|
||||
display += f"{' '*depth}{feature}\n"
|
||||
if comment:
|
||||
display += " # %s" % comment
|
||||
display += "\n%s- description: %s\n" % (" " * (depth + 2), description)
|
||||
display += f" # {comment}"
|
||||
display += f"\n{' '*(depth+2)}- description: {description}\n"
|
||||
elif feature.startswith("- string"):
|
||||
display += "%s%s" % (" " * depth, feature)
|
||||
display += f"{' '*depth}{feature}\n"
|
||||
if comment:
|
||||
display += " # %s" % comment
|
||||
display += "\n%sdescription: %s\n" % (" " * (depth + 2), description)
|
||||
display += f" # {comment}"
|
||||
display += f"\n{' '*(depth+2)}description: {description}\n"
|
||||
elif feature.startswith("- count"):
|
||||
# count is weird, we need to format description based on feature type, so we parse with regex
|
||||
# assume format - count(<feature_name>(<feature_value>)): <count>
|
||||
@@ -91,28 +91,22 @@ def parse_node_for_feature(feature, description, comment, depth):
|
||||
if m:
|
||||
name, value, count = m.groups()
|
||||
if name in ("string",):
|
||||
display += "%s%s" % (" " * depth, feature)
|
||||
display += f"{' '*depth}{feature}"
|
||||
if comment:
|
||||
display += " # %s" % comment
|
||||
display += "\n%sdescription: %s\n" % (" " * (depth + 2), description)
|
||||
display += f"\n{' '*(depth+2)}description: {description}\n"
|
||||
else:
|
||||
display += "%s- count(%s(%s = %s)): %s" % (
|
||||
" " * depth,
|
||||
name,
|
||||
value,
|
||||
description,
|
||||
count,
|
||||
)
|
||||
display += f"{' '*depth}- count({name}({value} = {description})): {count}"
|
||||
if comment:
|
||||
display += " # %s\n" % comment
|
||||
display += f" # {comment}\n"
|
||||
else:
|
||||
display += "%s%s = %s" % (" " * depth, feature, description)
|
||||
display += f"{' '*depth}{feature} = {description}"
|
||||
if comment:
|
||||
display += " # %s\n" % comment
|
||||
display += f" # {comment}\n"
|
||||
else:
|
||||
display += "%s%s" % (" " * depth, feature)
|
||||
display += f"{' '*depth}{feature}"
|
||||
if comment:
|
||||
display += " # %s\n" % comment
|
||||
display += f" # {comment}\n"
|
||||
|
||||
return display if display.endswith("\n") else display + "\n"
|
||||
|
||||
@@ -198,14 +192,14 @@ class CapaExplorerRulegenPreview(QtWidgets.QTextEdit):
|
||||
" name: <insert_name>",
|
||||
" namespace: <insert_namespace>",
|
||||
" authors:",
|
||||
" - %s" % author,
|
||||
" scope: %s" % scope,
|
||||
f" - {author}",
|
||||
f" scope: {scope}",
|
||||
" references:",
|
||||
" - <insert_references>",
|
||||
" examples:",
|
||||
" - %s:0x%X" % (capa.ida.helpers.get_file_md5().upper(), ea)
|
||||
f" - {capa.ida.helpers.get_file_md5().upper()}:{hex(ea)}"
|
||||
if ea
|
||||
else " - %s" % (capa.ida.helpers.get_file_md5().upper()),
|
||||
else f" - {capa.ida.helpers.get_file_md5().upper()}",
|
||||
" features:",
|
||||
]
|
||||
self.setText("\n".join(metadata_default))
|
||||
@@ -539,7 +533,7 @@ class CapaExplorerRulegenEditor(QtWidgets.QTreeWidget):
|
||||
|
||||
# build submenu with modify actions
|
||||
sub_menu = build_context_menu(self.parent(), sub_actions)
|
||||
sub_menu.setTitle("Nest feature%s" % ("" if len(tuple(self.get_features(selected=True))) == 1 else "s"))
|
||||
sub_menu.setTitle(f"Nest feature{'' if len(tuple(self.get_features(selected=True))) == 1 else 's'}")
|
||||
|
||||
# build main menu with submenu + main actions
|
||||
menu = build_context_menu(self.parent(), (sub_menu,) + actions)
|
||||
@@ -654,21 +648,21 @@ class CapaExplorerRulegenEditor(QtWidgets.QTreeWidget):
|
||||
# single features
|
||||
for k, v in filter(lambda t: t[1] == 1, counted):
|
||||
if isinstance(k, (capa.features.common.String,)):
|
||||
value = '"%s"' % capa.features.common.escape_string(k.get_value_str())
|
||||
value = f'"{capa.features.common.escape_string(k.get_value_str())}"'
|
||||
else:
|
||||
value = k.get_value_str()
|
||||
self.new_feature_node(top_node, ("- %s: %s" % (k.name.lower(), value), ""))
|
||||
self.new_feature_node(top_node, (f"- {k.name.lower()}: {value}", ""))
|
||||
|
||||
# n > 1 features
|
||||
for k, v in filter(lambda t: t[1] > 1, counted):
|
||||
if k.value:
|
||||
if isinstance(k, (capa.features.common.String,)):
|
||||
value = '"%s"' % capa.features.common.escape_string(k.get_value_str())
|
||||
value = f'"{capa.features.common.escape_string(k.get_value_str())}"'
|
||||
else:
|
||||
value = k.get_value_str()
|
||||
display = "- count(%s(%s)): %d" % (k.name.lower(), value, v)
|
||||
display = f"- count({k.name.lower()}({value})): {v}"
|
||||
else:
|
||||
display = "- count(%s): %d" % (k.name.lower(), v)
|
||||
display = f"- count({k.name.lower()}): {v}"
|
||||
self.new_feature_node(top_node, (display, ""))
|
||||
|
||||
self.update_preview()
|
||||
@@ -880,7 +874,7 @@ class CapaExplorerRulegenFeatures(QtWidgets.QTreeWidget):
|
||||
if isinstance(self.selectedItems()[0].data(0, 0x100), capa.features.common.Bytes):
|
||||
actions.append(("Add n bytes...", (), self.slot_add_n_bytes_feature))
|
||||
else:
|
||||
action_add_features_fmt = "Add %d features" % selected_items_count
|
||||
action_add_features_fmt = f"Add {selected_items_count} features"
|
||||
|
||||
actions.append((action_add_features_fmt, (), self.slot_add_selected_features))
|
||||
|
||||
@@ -1029,7 +1023,7 @@ class CapaExplorerRulegenFeatures(QtWidgets.QTreeWidget):
|
||||
|
||||
def format_address(e):
|
||||
if isinstance(e, AbsoluteVirtualAddress):
|
||||
return "%X" % int(e)
|
||||
return f"{hex(int(e))}"
|
||||
else:
|
||||
return ""
|
||||
|
||||
@@ -1038,8 +1032,8 @@ class CapaExplorerRulegenFeatures(QtWidgets.QTreeWidget):
|
||||
name = feature.name.lower()
|
||||
value = feature.get_value_str()
|
||||
if isinstance(feature, (capa.features.common.String,)):
|
||||
value = '"%s"' % capa.features.common.escape_string(value)
|
||||
return "%s(%s)" % (name, value)
|
||||
value = f'"{capa.features.common.escape_string(value)}"'
|
||||
return f"{name}({value})"
|
||||
|
||||
for feature, addrs in sorted(features.items(), key=lambda k: sorted(k[1])):
|
||||
if isinstance(feature, capa.features.basicblock.BasicBlock):
|
||||
|
||||
+8
-8
@@ -261,9 +261,9 @@ def find_capabilities(ruleset: RuleSet, extractor: FeatureExtractor, disable_pro
|
||||
logger.debug("skipping library function 0x%x (%s)", f.address, function_name)
|
||||
meta["library_functions"][f.address] = function_name
|
||||
n_libs = len(meta["library_functions"])
|
||||
percentage = 100 * (n_libs / n_funcs)
|
||||
percentage = round(100 * (n_libs / n_funcs))
|
||||
if isinstance(pb, tqdm.tqdm):
|
||||
pb.set_postfix_str("skipped %d library functions (%d%%)" % (n_libs, percentage))
|
||||
pb.set_postfix_str(f"skipped {n_libs} library functions ({percentage}%)")
|
||||
continue
|
||||
|
||||
function_matches, bb_matches, insn_matches, feature_count = find_code_capabilities(ruleset, extractor, f)
|
||||
@@ -397,8 +397,8 @@ def get_meta_str(vw):
|
||||
meta = []
|
||||
for k in ["Format", "Platform", "Architecture"]:
|
||||
if k in vw.metadata:
|
||||
meta.append("%s: %s" % (k.lower(), vw.metadata[k]))
|
||||
return "%s, number of functions: %d" % (", ".join(meta), len(vw.getFunctions()))
|
||||
meta.append(f"{k.lower()}: {vw.metadata[k]}")
|
||||
return f"{', '.join(meta)}, number of functions: {len(vw.getFunctions())}"
|
||||
|
||||
|
||||
def is_running_standalone() -> bool:
|
||||
@@ -569,7 +569,7 @@ def collect_rule_file_paths(rule_paths: List[str]) -> List[str]:
|
||||
rule_file_paths = []
|
||||
for rule_path in rule_paths:
|
||||
if not os.path.exists(rule_path):
|
||||
raise IOError("rule path %s does not exist or cannot be accessed" % rule_path)
|
||||
raise IOError(f"rule path {rule_path} does not exist or cannot be accessed")
|
||||
|
||||
if os.path.isfile(rule_path):
|
||||
rule_file_paths.append(rule_path)
|
||||
@@ -660,7 +660,7 @@ def get_rules(
|
||||
|
||||
def get_signatures(sigs_path):
|
||||
if not os.path.exists(sigs_path):
|
||||
raise IOError("signatures path %s does not exist or cannot be accessed" % sigs_path)
|
||||
raise IOError(f"signatures path {sigs_path} does not exist or cannot be accessed")
|
||||
|
||||
paths = []
|
||||
if os.path.isfile(sigs_path):
|
||||
@@ -844,13 +844,13 @@ def install_common_args(parser, wanted=None):
|
||||
(FORMAT_SC64, "64-bit shellcode"),
|
||||
(FORMAT_FREEZE, "features previously frozen by capa"),
|
||||
]
|
||||
format_help = ", ".join(["%s: %s" % (f[0], f[1]) for f in formats])
|
||||
format_help = ", ".join([f"{f[0]}: {f[1]}" for f in formats])
|
||||
parser.add_argument(
|
||||
"-f",
|
||||
"--format",
|
||||
choices=[f[0] for f in formats],
|
||||
default=FORMAT_AUTO,
|
||||
help="select sample format, %s" % format_help,
|
||||
help=f"select sample format, {format_help}",
|
||||
)
|
||||
|
||||
if "backend" in wanted:
|
||||
|
||||
@@ -97,7 +97,7 @@ def render_capabilities(doc: rd.ResultDocument, ostream: StringIO):
|
||||
if count == 1:
|
||||
capability = rutils.bold(rule.meta.name)
|
||||
else:
|
||||
capability = "%s (%d matches)" % (rutils.bold(rule.meta.name), count)
|
||||
capability = f"{rutils.bold(rule.meta.name)} ({count} matches)"
|
||||
rows.append((capability, rule.meta.namespace))
|
||||
|
||||
if rows:
|
||||
@@ -135,9 +135,9 @@ def render_attack(doc: rd.ResultDocument, ostream: StringIO):
|
||||
inner_rows = []
|
||||
for technique, subtechnique, id in sorted(techniques):
|
||||
if not subtechnique:
|
||||
inner_rows.append("%s %s" % (rutils.bold(technique), id))
|
||||
inner_rows.append(f"{rutils.bold(technique)} {id}")
|
||||
else:
|
||||
inner_rows.append("%s::%s %s" % (rutils.bold(technique), subtechnique, id))
|
||||
inner_rows.append(f"{rutils.bold(technique)}::{subtechnique} {id}")
|
||||
rows.append(
|
||||
(
|
||||
rutils.bold(tactic.upper()),
|
||||
@@ -178,9 +178,9 @@ def render_mbc(doc: rd.ResultDocument, ostream: StringIO):
|
||||
inner_rows = []
|
||||
for behavior, method, id in sorted(behaviors):
|
||||
if not method:
|
||||
inner_rows.append("%s [%s]" % (rutils.bold(behavior), id))
|
||||
inner_rows.append(f"{rutils.bold(behavior)} [{id}]")
|
||||
else:
|
||||
inner_rows.append("%s::%s [%s]" % (rutils.bold(behavior), method, id))
|
||||
inner_rows.append(f"{rutils.bold(behavior)}::{method} [{id}]")
|
||||
rows.append(
|
||||
(
|
||||
rutils.bold(objective.upper()),
|
||||
|
||||
@@ -32,7 +32,7 @@ def format_parts_id(data: Union[rd.AttackSpec, rd.MBCSpec]):
|
||||
"""
|
||||
format canonical representation of ATT&CK/MBC parts and ID
|
||||
"""
|
||||
return "%s [%s]" % ("::".join(data.parts), data.id)
|
||||
return f"{'::'.join(data.parts)} [{data.id}]"
|
||||
|
||||
|
||||
def capability_rules(doc: rd.ResultDocument) -> Iterator[rd.RuleMatches]:
|
||||
|
||||
@@ -121,7 +121,7 @@ def render_rules(ostream, doc: rd.ResultDocument):
|
||||
if count == 1:
|
||||
capability = rutils.bold(rule.meta.name)
|
||||
else:
|
||||
capability = "%s (%d matches)" % (rutils.bold(rule.meta.name), count)
|
||||
capability = f"{rutils.bold(rule.meta.name)} ({count} matches)"
|
||||
|
||||
ostream.writeln(capability)
|
||||
had_match = True
|
||||
|
||||
+19
-19
@@ -43,7 +43,7 @@ def render_locations(ostream, locations: Iterable[frz.Address]):
|
||||
# don't display too many locations, because it becomes very noisy.
|
||||
# probably only the first handful of locations will be useful for inspection.
|
||||
ostream.write(", ".join(map(v.format_address, locations[0:4])))
|
||||
ostream.write(", and %d more..." % (len(locations) - 4))
|
||||
ostream.write(f", and {(len(locations) - 4)} more...")
|
||||
|
||||
elif len(locations) > 1:
|
||||
ostream.write(", ".join(map(v.format_address, locations)))
|
||||
@@ -62,7 +62,7 @@ def render_statement(ostream, match: rd.Match, statement: rd.Statement, indent=0
|
||||
|
||||
ostream.write(":")
|
||||
if statement.description:
|
||||
ostream.write(" = %s" % statement.description)
|
||||
ostream.write(f" = {statement.description}")
|
||||
ostream.writeln("")
|
||||
|
||||
elif isinstance(statement, (rd.CompoundStatement)):
|
||||
@@ -71,14 +71,14 @@ def render_statement(ostream, match: rd.Match, statement: rd.Statement, indent=0
|
||||
|
||||
ostream.write(":")
|
||||
if statement.description:
|
||||
ostream.write(" = %s" % statement.description)
|
||||
ostream.write(f" = {statement.description}")
|
||||
ostream.writeln("")
|
||||
|
||||
elif isinstance(statement, rd.SomeStatement):
|
||||
ostream.write("%d or more:" % (statement.count))
|
||||
ostream.write(f"{statement.count} or more:")
|
||||
|
||||
if statement.description:
|
||||
ostream.write(" = %s" % statement.description)
|
||||
ostream.write(f" = {statement.description}")
|
||||
ostream.writeln("")
|
||||
|
||||
elif isinstance(statement, rd.RangeStatement):
|
||||
@@ -92,28 +92,28 @@ def render_statement(ostream, match: rd.Match, statement: rd.Statement, indent=0
|
||||
|
||||
if value:
|
||||
if isinstance(child, frzf.StringFeature):
|
||||
value = '"%s"' % capa.features.common.escape_string(value)
|
||||
value = f'"{capa.features.common.escape_string(value)}"'
|
||||
|
||||
value = rutils.bold2(value)
|
||||
|
||||
if child.description:
|
||||
ostream.write("count(%s(%s = %s)): " % (child.type, value, child.description))
|
||||
ostream.write(f"count({child.type}({value} = {child.description})): ")
|
||||
else:
|
||||
ostream.write("count(%s(%s)): " % (child.type, value))
|
||||
ostream.write(f"count({child.type}({value})): ")
|
||||
else:
|
||||
ostream.write("count(%s): " % child.type)
|
||||
ostream.write(f"count({child.type}): ")
|
||||
|
||||
if statement.max == statement.min:
|
||||
ostream.write("%d" % (statement.min))
|
||||
ostream.write(f"{statement.min}")
|
||||
elif statement.min == 0:
|
||||
ostream.write("%d or fewer" % (statement.max))
|
||||
ostream.write(f"{statement.max} or fewer")
|
||||
elif statement.max == (1 << 64 - 1):
|
||||
ostream.write("%d or more" % (statement.min))
|
||||
ostream.write(f"{statement.min} or more")
|
||||
else:
|
||||
ostream.write("between %d and %d" % (statement.min, statement.max))
|
||||
ostream.write(f"between {statement.min} and {statement.max}")
|
||||
|
||||
if statement.description:
|
||||
ostream.write(" = %s" % statement.description)
|
||||
ostream.write(f" = {statement.description}")
|
||||
render_locations(ostream, match.locations)
|
||||
ostream.writeln("")
|
||||
|
||||
@@ -122,7 +122,7 @@ def render_statement(ostream, match: rd.Match, statement: rd.Statement, indent=0
|
||||
|
||||
|
||||
def render_string_value(s: str) -> str:
|
||||
return '"%s"' % capa.features.common.escape_string(s)
|
||||
return f'"{capa.features.common.escape_string(s)}"'
|
||||
|
||||
|
||||
def render_feature(ostream, match: rd.Match, feature: frzf.Feature, indent=0):
|
||||
@@ -143,7 +143,7 @@ def render_feature(ostream, match: rd.Match, feature: frzf.Feature, indent=0):
|
||||
value = feature.dict(by_alias=True).get(key, None)
|
||||
|
||||
if value is None:
|
||||
raise ValueError("%s contains None" % key)
|
||||
raise ValueError(f"{key} contains None")
|
||||
|
||||
if not isinstance(feature, (frzf.RegexFeature, frzf.SubstringFeature)):
|
||||
# like:
|
||||
@@ -290,11 +290,11 @@ def render_rules(ostream, doc: rd.ResultDocument):
|
||||
if count == 1:
|
||||
if rule.meta.lib:
|
||||
lib_info = " (library rule)"
|
||||
capability = "%s%s" % (rutils.bold(rule.meta.name), lib_info)
|
||||
capability = f"{rutils.bold(rule.meta.name)}{lib_info}"
|
||||
else:
|
||||
if rule.meta.lib:
|
||||
lib_info = ", only showing first match of library rule"
|
||||
capability = "%s (%d matches%s)" % (rutils.bold(rule.meta.name), count, lib_info)
|
||||
capability = f"{rutils.bold(rule.meta.name)} ({count} matches{lib_info})"
|
||||
|
||||
ostream.writeln(capability)
|
||||
had_match = True
|
||||
@@ -345,7 +345,7 @@ def render_rules(ostream, doc: rd.ResultDocument):
|
||||
# because we do the file-scope evaluation a single time.
|
||||
# but i'm not 100% sure if this is/will always be true.
|
||||
# so, lets be explicit about our assumptions and raise an exception if they fail.
|
||||
raise RuntimeError("unexpected file scope match count: %d" % (len(matches)))
|
||||
raise RuntimeError(f"unexpected file scope match count: {len(matches)}")
|
||||
first_address, first_match = matches[0]
|
||||
render_match(ostream, first_match, indent=0)
|
||||
else:
|
||||
|
||||
+20
-21
@@ -163,7 +163,7 @@ class InvalidRule(ValueError):
|
||||
self.msg = msg
|
||||
|
||||
def __str__(self):
|
||||
return "invalid rule: %s" % (self.msg)
|
||||
return f"invalid rule: {self.msg}"
|
||||
|
||||
def __repr__(self):
|
||||
return str(self)
|
||||
@@ -177,7 +177,7 @@ class InvalidRuleWithPath(InvalidRule):
|
||||
self.__cause__ = None
|
||||
|
||||
def __str__(self):
|
||||
return "invalid rule: %s: %s" % (self.path, self.msg)
|
||||
return f"invalid rule: {self.path}: {self.msg}"
|
||||
|
||||
|
||||
class InvalidRuleSet(ValueError):
|
||||
@@ -186,7 +186,7 @@ class InvalidRuleSet(ValueError):
|
||||
self.msg = msg
|
||||
|
||||
def __str__(self):
|
||||
return "invalid rule set: %s" % (self.msg)
|
||||
return f"invalid rule set: {self.msg}"
|
||||
|
||||
def __repr__(self):
|
||||
return str(self)
|
||||
@@ -200,14 +200,14 @@ def ensure_feature_valid_for_scope(scope: str, feature: Union[Feature, Statement
|
||||
and isinstance(feature.value, str)
|
||||
and capa.features.common.Characteristic(feature.value) not in SUPPORTED_FEATURES[scope]
|
||||
):
|
||||
raise InvalidRule("feature %s not supported for scope %s" % (feature, scope))
|
||||
raise InvalidRule(f"feature {feature} not supported for scope {scope}")
|
||||
|
||||
if not isinstance(feature, capa.features.common.Characteristic):
|
||||
# features of this scope that are not Characteristics will be Type instances.
|
||||
# check that the given feature is one of these types.
|
||||
types_for_scope = filter(lambda t: isinstance(t, type), SUPPORTED_FEATURES[scope])
|
||||
if not isinstance(feature, tuple(types_for_scope)): # type: ignore
|
||||
raise InvalidRule("feature %s not supported for scope %s" % (feature, scope))
|
||||
raise InvalidRule(f"feature {feature} not supported for scope {scope}")
|
||||
|
||||
|
||||
def parse_int(s: str) -> int:
|
||||
@@ -224,10 +224,10 @@ def parse_range(s: str):
|
||||
"""
|
||||
# we want to use `{` characters, but this is a dict in yaml.
|
||||
if not s.startswith("("):
|
||||
raise InvalidRule("invalid range: %s" % (s))
|
||||
raise InvalidRule(f"invalid range: {s}")
|
||||
|
||||
if not s.endswith(")"):
|
||||
raise InvalidRule("invalid range: %s" % (s))
|
||||
raise InvalidRule(f"invalid range: {s}")
|
||||
|
||||
s = s[len("(") : -len(")")]
|
||||
min_spec, _, max_spec = s.partition(",")
|
||||
@@ -296,7 +296,7 @@ def parse_feature(key: str):
|
||||
elif key == "property":
|
||||
return capa.features.insn.Property
|
||||
else:
|
||||
raise InvalidRule("unexpected statement: %s" % key)
|
||||
raise InvalidRule(f"unexpected statement: {key}")
|
||||
|
||||
|
||||
# this is the separator between a feature value and its description
|
||||
@@ -310,11 +310,11 @@ def parse_bytes(s: str) -> bytes:
|
||||
try:
|
||||
b = codecs.decode(s.replace(" ", "").encode("ascii"), "hex")
|
||||
except binascii.Error:
|
||||
raise InvalidRule('unexpected bytes value: must be a valid hex sequence: "%s"' % s)
|
||||
raise InvalidRule(f'unexpected bytes value: must be a valid hex sequence: "{s}"')
|
||||
|
||||
if len(b) > MAX_BYTES_FEATURE_SIZE:
|
||||
raise InvalidRule(
|
||||
"unexpected bytes value: byte sequences must be no larger than %s bytes" % MAX_BYTES_FEATURE_SIZE
|
||||
f"unexpected bytes value: byte sequences must be no larger than {MAX_BYTES_FEATURE_SIZE} bytes"
|
||||
)
|
||||
|
||||
return b
|
||||
@@ -337,15 +337,14 @@ def parse_description(s: Union[str, int, bytes], value_type: str, description=No
|
||||
# - number: 10 = CONST_FOO
|
||||
# description: CONST_FOO
|
||||
raise InvalidRule(
|
||||
'unexpected value: "%s", only one description allowed (inline description with `%s`)'
|
||||
% (s, DESCRIPTION_SEPARATOR)
|
||||
f'unexpected value: "{s}", only one description allowed (inline description with `{DESCRIPTION_SEPARATOR}`)'
|
||||
)
|
||||
|
||||
value, _, description = s.partition(DESCRIPTION_SEPARATOR)
|
||||
if description == "":
|
||||
# sanity check:
|
||||
# there is an empty description, like `number: 10 =`
|
||||
raise InvalidRule('unexpected value: "%s", description cannot be empty' % s)
|
||||
raise InvalidRule(f'unexpected value: "{s}", description cannot be empty')
|
||||
else:
|
||||
# this is a string, but there is no description,
|
||||
# like: `api: CreateFileA`
|
||||
@@ -372,7 +371,7 @@ def parse_description(s: Union[str, int, bytes], value_type: str, description=No
|
||||
try:
|
||||
value = parse_int(value)
|
||||
except ValueError:
|
||||
raise InvalidRule('unexpected value: "%s", must begin with numerical value' % value)
|
||||
raise InvalidRule(f'unexpected value: "{value}", must begin with numerical value')
|
||||
|
||||
else:
|
||||
# the value might be a number, like: `number: 10`
|
||||
@@ -532,9 +531,9 @@ def build_statements(d, scope: str):
|
||||
min, max = parse_range(count)
|
||||
return ceng.Range(feature, min=min, max=max, description=description)
|
||||
else:
|
||||
raise InvalidRule("unexpected range: %s" % (count))
|
||||
raise InvalidRule(f"unexpected range: {count}")
|
||||
elif key == "string" and not isinstance(d[key], str):
|
||||
raise InvalidRule("ambiguous string value %s, must be defined as explicit string" % d[key])
|
||||
raise InvalidRule(f"ambiguous string value {d[key]}, must be defined as explicit string")
|
||||
|
||||
elif key.startswith("operand[") and key.endswith("].number"):
|
||||
index = key[len("operand[") : -len("].number")]
|
||||
@@ -573,12 +572,12 @@ def build_statements(d, scope: str):
|
||||
or (key == "format" and d[key] not in capa.features.common.VALID_FORMAT)
|
||||
or (key == "arch" and d[key] not in capa.features.common.VALID_ARCH)
|
||||
):
|
||||
raise InvalidRule("unexpected %s value %s" % (key, d[key]))
|
||||
raise InvalidRule(f"unexpected {key} value {d[key]}")
|
||||
|
||||
elif key.startswith("property/"):
|
||||
access = key[len("property/") :]
|
||||
if access not in capa.features.common.VALID_FEATURE_ACCESS:
|
||||
raise InvalidRule("unexpected %s access %s" % (key, access))
|
||||
raise InvalidRule(f"unexpected {key} access {access}")
|
||||
|
||||
value, description = parse_description(d[key], key, d.get("description"))
|
||||
try:
|
||||
@@ -617,10 +616,10 @@ class Rule:
|
||||
self.definition = definition
|
||||
|
||||
def __str__(self):
|
||||
return "Rule(name=%s)" % (self.name)
|
||||
return f"Rule(name={self.name})"
|
||||
|
||||
def __repr__(self):
|
||||
return "Rule(scope=%s, name=%s)" % (self.scope, self.name)
|
||||
return f"Rule(scope={self.scope}, name={self.name})"
|
||||
|
||||
def get_dependencies(self, namespaces):
|
||||
"""
|
||||
@@ -998,7 +997,7 @@ def ensure_rule_dependencies_are_met(rules: List[Rule]) -> None:
|
||||
for rule in rules_by_name.values():
|
||||
for dep in rule.get_dependencies(namespaces):
|
||||
if dep not in rules_by_name:
|
||||
raise InvalidRule('rule "%s" depends on missing rule "%s"' % (rule.name, dep))
|
||||
raise InvalidRule(f'rule "{rule.name}" depends on missing rule "{dep}"')
|
||||
|
||||
|
||||
def index_rules_by_namespace(rules: List[Rule]) -> Dict[str, List[Rule]]:
|
||||
|
||||
@@ -112,7 +112,7 @@ def get_capa_results(args):
|
||||
return {
|
||||
"path": path,
|
||||
"status": "error",
|
||||
"error": "input file does not appear to be a PE file: %s" % path,
|
||||
"error": f"input file does not appear to be a PE file: {path}",
|
||||
}
|
||||
except capa.main.UnsupportedRuntimeError:
|
||||
return {
|
||||
@@ -124,7 +124,7 @@ def get_capa_results(args):
|
||||
return {
|
||||
"path": path,
|
||||
"status": "error",
|
||||
"error": "unexpected error: %s" % (e),
|
||||
"error": f"unexpected error: {e}",
|
||||
}
|
||||
|
||||
meta = capa.main.collect_metadata([], path, [], extractor)
|
||||
@@ -202,7 +202,7 @@ def main(argv=None):
|
||||
elif result["status"] == "ok":
|
||||
results[result["path"]] = rd.ResultDocument.parse_obj(result["ok"]).json(exclude_none=True)
|
||||
else:
|
||||
raise ValueError("unexpected status: %s" % (result["status"]))
|
||||
raise ValueError(f"unexpected status: {result['status']}")
|
||||
|
||||
print(json.dumps(results))
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@ def render_capabilities(doc: rd.ResultDocument, result):
|
||||
if count == 1:
|
||||
capability = rule.meta.name
|
||||
else:
|
||||
capability = "%s (%d matches)" % (rule.meta.name, count)
|
||||
capability = f"{rule.meta.name} ({count} matches)"
|
||||
|
||||
result["CAPABILITY"].setdefault(rule.meta.namespace, list())
|
||||
result["CAPABILITY"][rule.meta.namespace].append(capability)
|
||||
@@ -108,9 +108,9 @@ def render_attack(doc, result):
|
||||
inner_rows = []
|
||||
for technique, subtechnique, id in sorted(techniques):
|
||||
if subtechnique is None:
|
||||
inner_rows.append("%s %s" % (technique, id))
|
||||
inner_rows.append(f"{technique} {id}")
|
||||
else:
|
||||
inner_rows.append("%s::%s %s" % (technique, subtechnique, id))
|
||||
inner_rows.append(f"{technique}::{subtechnique} {id}")
|
||||
result["ATTCK"].setdefault(tactic.upper(), inner_rows)
|
||||
|
||||
|
||||
@@ -142,9 +142,9 @@ def render_mbc(doc, result):
|
||||
inner_rows = []
|
||||
for behavior, method, id in sorted(behaviors):
|
||||
if method is None:
|
||||
inner_rows.append("%s [%s]" % (behavior, id))
|
||||
inner_rows.append(f"{behavior} [{id}]")
|
||||
else:
|
||||
inner_rows.append("%s::%s [%s]" % (behavior, method, id))
|
||||
inner_rows.append(f"{behavior}::{method} [{id}]")
|
||||
result["MBC"].setdefault(objective.upper(), inner_rows)
|
||||
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ def load_analysis(bv):
|
||||
if not path or not os.access(path, os.R_OK):
|
||||
binaryninja.log_error("Invalid filename.")
|
||||
return 0
|
||||
binaryninja.log_info("Using capa file %s" % path)
|
||||
binaryninja.log_info(f"Using capa file {path}")
|
||||
|
||||
with open(path, "rb") as f:
|
||||
doc = json.loads(f.read().decode("utf-8"))
|
||||
@@ -97,7 +97,7 @@ def load_analysis(bv):
|
||||
else:
|
||||
cmt = f"{name}"
|
||||
|
||||
binaryninja.log_info("0x%x: %s" % (va, cmt))
|
||||
binaryninja.log_info(f"{hex(va)}: {cmt}")
|
||||
try:
|
||||
# message will look something like:
|
||||
#
|
||||
|
||||
@@ -101,9 +101,9 @@ def main():
|
||||
rows = sorted(rows)
|
||||
for ns, name, va in rows:
|
||||
if ns:
|
||||
cmt = f"{name} ({ns})"
|
||||
cmt = name + f"({ns})"
|
||||
else:
|
||||
cmt = f"{name}"
|
||||
cmt = name
|
||||
|
||||
logger.info("0x%x: %s", va, cmt)
|
||||
try:
|
||||
|
||||
@@ -19,17 +19,17 @@ def display_top(snapshot, key_type="lineno", limit=10):
|
||||
print(f"Top {limit} lines")
|
||||
for index, stat in enumerate(top_stats[:limit], 1):
|
||||
frame = stat.traceback[0]
|
||||
print(f"#{index}: {frame.filename}:{frame.lineno}: {stat.size / 1024:.1f} KiB")
|
||||
print(f"#{index}: {frame.filename}:{frame.lineno}: {(stat.size/1024):.1f} KiB")
|
||||
line = linecache.getline(frame.filename, frame.lineno).strip()
|
||||
if line:
|
||||
print(" %s" % line)
|
||||
print(f" {line}")
|
||||
|
||||
other = top_stats[limit:]
|
||||
if other:
|
||||
size = sum(stat.size for stat in other)
|
||||
print(f"{len(other)} other: {size / 1024:.1f} KiB")
|
||||
print(f"{len(other)} other: {(size/1024):.1f} KiB")
|
||||
total = sum(stat.size for stat in top_stats)
|
||||
print(f"Total allocated size: {total / 1024:.1f} KiB")
|
||||
print(f"Total allocated size: {(total/1024):.1f} KiB")
|
||||
|
||||
|
||||
def main():
|
||||
@@ -49,7 +49,7 @@ def main():
|
||||
print()
|
||||
|
||||
for i in range(count):
|
||||
print(f"iteration {i + 1}/{count}...")
|
||||
print(f"iteration {i+1}/{count}...")
|
||||
with contextlib.redirect_stdout(io.StringIO()):
|
||||
with contextlib.redirect_stderr(io.StringIO()):
|
||||
t0 = time.time()
|
||||
@@ -59,9 +59,9 @@ def main():
|
||||
gc.collect()
|
||||
|
||||
process = psutil.Process(os.getpid())
|
||||
print(f" duration: {t1 - t0:.02f}s")
|
||||
print(f" rss: {process.memory_info().rss / 1024 / 1024:.1f} MiB")
|
||||
print(f" vms: {process.memory_info().vms / 1024 / 1024:.1f} MiB")
|
||||
print(f" duration: {(t1-t0):.2f}")
|
||||
print(f" rss: {(process.memory_info().rss / 1024 / 1024):.1f} MiB")
|
||||
print(f" vms: {(process.memory_info().vms / 1024 / 1024):.1f} MiB")
|
||||
|
||||
print("done.")
|
||||
gc.collect()
|
||||
|
||||
@@ -133,9 +133,9 @@ def main(argv=None):
|
||||
# so lets put that first.
|
||||
#
|
||||
# https://docs.python.org/3/library/timeit.html#timeit.Timer.repeat
|
||||
"%0.2fs" % (min(samples) / float(args.number)),
|
||||
"%0.2fs" % (sum(samples) / float(args.repeat) / float(args.number)),
|
||||
"%0.2fs" % (max(samples) / float(args.number)),
|
||||
f"{(min(samples) / float(args.number)):.2f}s",
|
||||
f"{(sum(samples) / float(args.repeat) / float(args.number)):.2f}s",
|
||||
f"{(max(samples) / float(args.number)):.2f}s",
|
||||
)
|
||||
],
|
||||
headers=["label", "count(evaluations)", "min(time)", "avg(time)", "max(time)"],
|
||||
|
||||
@@ -118,7 +118,7 @@ def render_matches_by_function(doc: rd.ResultDocument):
|
||||
for f in doc.meta.analysis.feature_counts.functions:
|
||||
if not matches_by_function.get(f.address, {}):
|
||||
continue
|
||||
ostream.writeln("function at %s with %d features: " % (capa.render.verbose.format_address(addr), f.count))
|
||||
ostream.writeln(f"function at {capa.render.verbose.format_address(addr)} with {f.count} features: ")
|
||||
for rule_name in sorted(matches_by_function[f.address]):
|
||||
ostream.writeln(" - " + rule_name)
|
||||
|
||||
|
||||
+14
-20
@@ -130,11 +130,11 @@ def main(argv=None):
|
||||
return -1
|
||||
|
||||
for feature, addr in extractor.extract_global_features():
|
||||
print("global: %s: %s" % (format_address(addr), feature))
|
||||
print(f"global: {format_address(addr)}: {feature}")
|
||||
|
||||
if not args.function:
|
||||
for feature, addr in extractor.extract_file_features():
|
||||
print("file: %s: %s" % (format_address(addr), feature))
|
||||
print(f"file: {format_address(addr)}: {feature}")
|
||||
|
||||
function_handles = tuple(extractor.get_functions())
|
||||
|
||||
@@ -146,11 +146,11 @@ def main(argv=None):
|
||||
function_handles = tuple(filter(lambda fh: format_address(fh.address) == args.function, function_handles))
|
||||
|
||||
if args.function not in [format_address(fh.address) for fh in function_handles]:
|
||||
print("%s not a function" % args.function)
|
||||
print(f"{args.function} not a function")
|
||||
return -1
|
||||
|
||||
if len(function_handles) == 0:
|
||||
print("%s not a function", args.function)
|
||||
print(f"{args.function} not a function")
|
||||
return -1
|
||||
|
||||
print_features(function_handles, extractor)
|
||||
@@ -164,13 +164,13 @@ def ida_main():
|
||||
import capa.features.extractors.ida.extractor
|
||||
|
||||
function = idc.get_func_attr(idc.here(), idc.FUNCATTR_START)
|
||||
print("getting features for current function 0x%X" % function)
|
||||
print(f"getting features for current function {hex(function)}")
|
||||
|
||||
extractor = capa.features.extractors.ida.extractor.IdaFeatureExtractor()
|
||||
|
||||
if not function:
|
||||
for feature, addr in extractor.extract_file_features():
|
||||
print("file: %s: %s" % (format_address(addr), feature))
|
||||
print(f"file: {format_address(addr)}: {feature}")
|
||||
return
|
||||
|
||||
function_handles = tuple(extractor.get_functions())
|
||||
@@ -179,7 +179,7 @@ def ida_main():
|
||||
function_handles = tuple(filter(lambda fh: fh.inner.start_ea == function, function_handles))
|
||||
|
||||
if len(function_handles) == 0:
|
||||
print("0x%X not a function" % function)
|
||||
print(f"{hex(function)} not a function")
|
||||
return -1
|
||||
|
||||
print_features(function_handles, extractor)
|
||||
@@ -194,16 +194,16 @@ def print_features(functions, extractor: capa.features.extractors.base_extractor
|
||||
logger.debug("skipping library function %s (%s)", format_address(f.address), function_name)
|
||||
continue
|
||||
|
||||
print("func: %s" % (format_address(f.address)))
|
||||
print(f"func: {format_address(f.address)}")
|
||||
|
||||
for feature, addr in extractor.extract_function_features(f):
|
||||
if capa.features.common.is_global_feature(feature):
|
||||
continue
|
||||
|
||||
if f.address != addr:
|
||||
print(" func: %s: %s -> %s" % (format_address(f.address), feature, format_address(addr)))
|
||||
print(f" func: {format_address(f.address)}: {feature} -> {format_address(addr)}")
|
||||
else:
|
||||
print(" func: %s: %s" % (format_address(f.address), feature))
|
||||
print(f" func: {format_address(f.address)}: {feature}")
|
||||
|
||||
for bb in extractor.get_basic_blocks(f):
|
||||
for feature, addr in extractor.extract_basic_block_features(f, bb):
|
||||
@@ -211,9 +211,9 @@ def print_features(functions, extractor: capa.features.extractors.base_extractor
|
||||
continue
|
||||
|
||||
if bb.address != addr:
|
||||
print(" bb: %s: %s -> %s" % (format_address(bb.address), feature, format_address(addr)))
|
||||
print(f" bb: {format_address(bb.address)}: {feature} -> {format_address(addr)}")
|
||||
else:
|
||||
print(" bb: %s: %s" % (format_address(bb.address), feature))
|
||||
print(f" bb: {format_address(bb.address)}: {feature}")
|
||||
|
||||
for insn in extractor.get_instructions(f, bb):
|
||||
for feature, addr in extractor.extract_insn_features(f, bb, insn):
|
||||
@@ -223,16 +223,10 @@ def print_features(functions, extractor: capa.features.extractors.base_extractor
|
||||
try:
|
||||
if insn.address != addr:
|
||||
print(
|
||||
" insn: %s: %s: %s -> %s"
|
||||
% (
|
||||
format_address(f.address),
|
||||
format_address(insn.address),
|
||||
feature,
|
||||
format_address(addr),
|
||||
)
|
||||
f" insn: {format_address(f.address)}: {format_address(insn.address)}: {feature} -> {format_address(addr)}"
|
||||
)
|
||||
else:
|
||||
print(" insn: %s: %s" % (format_address(insn.address), feature))
|
||||
print(f" insn: {format_address(insn.address)}: {feature}")
|
||||
|
||||
except UnicodeEncodeError:
|
||||
# may be an issue while piping to less and encountering non-ascii characters
|
||||
|
||||
+5
-10
@@ -283,7 +283,7 @@ def get_data_path_by_name(name):
|
||||
elif name.startswith("294b8d"):
|
||||
return os.path.join(CD, "data", "294b8db1f2702b60fb2e42fdc50c2cee6a5046112da9a5703a548a4fa50477bc.elf_")
|
||||
else:
|
||||
raise ValueError("unexpected sample fixture: %s" % name)
|
||||
raise ValueError(f"unexpected sample fixture: {name}")
|
||||
|
||||
|
||||
def get_sample_md5_by_name(name):
|
||||
@@ -341,7 +341,7 @@ def get_sample_md5_by_name(name):
|
||||
# file name is SHA256 hash
|
||||
return "3db3e55b16a7b1b1afb970d5e77c5d98"
|
||||
else:
|
||||
raise ValueError("unexpected sample fixture: %s" % name)
|
||||
raise ValueError(f"unexpected sample fixture: {name}")
|
||||
|
||||
|
||||
def resolve_sample(sample):
|
||||
@@ -981,21 +981,16 @@ def do_test_feature_presence(get_extractor, sample, scope, feature, expected):
|
||||
extractor = get_extractor(sample)
|
||||
features = scope(extractor)
|
||||
if expected:
|
||||
msg = "%s should be found in %s" % (str(feature), scope.__name__)
|
||||
msg = f"{str(feature)} should be found in {scope.__name__}"
|
||||
else:
|
||||
msg = "%s should not be found in %s" % (str(feature), scope.__name__)
|
||||
msg = f"{str(feature)} should not be found in {scope.__name__}"
|
||||
assert feature.evaluate(features) == expected, msg
|
||||
|
||||
|
||||
def do_test_feature_count(get_extractor, sample, scope, feature, expected):
|
||||
extractor = get_extractor(sample)
|
||||
features = scope(extractor)
|
||||
msg = "%s should be found %d times in %s, found: %d" % (
|
||||
str(feature),
|
||||
expected,
|
||||
scope.__name__,
|
||||
len(features[feature]),
|
||||
)
|
||||
msg = f"{str(feature)} should be found {expected} times in {scope.__name__}, found: {len(features[feature])}"
|
||||
assert len(features[feature]) == expected, msg
|
||||
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ def check_input_file(wanted):
|
||||
found = binascii.hexlify(idautils.GetInputFileMD5()[:15]).decode("ascii").lower()
|
||||
|
||||
if not wanted.startswith(found):
|
||||
raise RuntimeError("please run the tests against sample with MD5: `%s`" % (wanted))
|
||||
raise RuntimeError(f"please run the tests against sample with MD5: `{wanted}`")
|
||||
|
||||
|
||||
def get_ida_extractor(_path):
|
||||
@@ -51,7 +51,7 @@ def test_ida_features():
|
||||
try:
|
||||
check_input_file(fixtures.get_sample_md5_by_name(sample))
|
||||
except RuntimeError:
|
||||
print("SKIP %s" % (id))
|
||||
print(f"SKIP {id}")
|
||||
continue
|
||||
|
||||
scope = fixtures.resolve_scope(scope)
|
||||
@@ -60,10 +60,10 @@ def test_ida_features():
|
||||
try:
|
||||
fixtures.do_test_feature_presence(get_ida_extractor, sample, scope, feature, expected)
|
||||
except Exception as e:
|
||||
print("FAIL %s" % (id))
|
||||
print(f"FAIL {id}")
|
||||
traceback.print_exc()
|
||||
else:
|
||||
print("OK %s" % (id))
|
||||
print(f"OK {id}")
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="IDA Pro tests must be run within IDA")
|
||||
@@ -74,7 +74,7 @@ def test_ida_feature_counts():
|
||||
try:
|
||||
check_input_file(fixtures.get_sample_md5_by_name(sample))
|
||||
except RuntimeError:
|
||||
print("SKIP %s" % (id))
|
||||
print(f"SKIP {id}")
|
||||
continue
|
||||
|
||||
scope = fixtures.resolve_scope(scope)
|
||||
@@ -83,10 +83,10 @@ def test_ida_feature_counts():
|
||||
try:
|
||||
fixtures.do_test_feature_count(get_ida_extractor, sample, scope, feature, expected)
|
||||
except Exception as e:
|
||||
print("FAIL %s" % (id))
|
||||
print(f"FAIL {id}")
|
||||
traceback.print_exc()
|
||||
else:
|
||||
print("OK %s" % (id))
|
||||
print(f"OK {id}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -62,5 +62,5 @@ def test_bulk_process(tmpdir):
|
||||
|
||||
def run_program(script_path, args):
|
||||
args = [sys.executable] + [script_path] + args
|
||||
print("running: '%s'" % args)
|
||||
print(f"running: '{args}'")
|
||||
return subprocess.run(args)
|
||||
|
||||
Reference in New Issue
Block a user