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:
manasghandat
2023-03-16 15:46:18 +05:30
committed by GitHub
parent 8cf74759a6
commit 1336796c0c
35 changed files with 201 additions and 227 deletions

View File

@@ -43,10 +43,12 @@ class Statement:
self.description = description self.description = description
def __str__(self): def __str__(self):
name = self.name.lower()
children = ",".join(map(str, self.get_children()))
if self.description: 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: else:
return "%s(%s)" % (self.name.lower(), ",".join(map(str, self.get_children()))) return f"{name}({children})"
def __repr__(self): def __repr__(self):
return str(self) return str(self)
@@ -232,9 +234,9 @@ class Range(Statement):
def __str__(self): def __str__(self):
if self.max == (1 << 64 - 1): 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: 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): class Subscope(Statement):

View File

@@ -149,11 +149,11 @@ class Feature(abc.ABC):
def __str__(self): def __str__(self):
if self.value is not None: if self.value is not None:
if self.description: 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: else:
return "%s(%s)" % (self.get_name_str(), self.get_value_str()) return f"{self.get_name_str()}({self.get_value_str()})"
else: else:
return "%s" % self.get_name_str() return f"{self.get_name_str()}"
def __repr__(self): def __repr__(self):
return str(self) return str(self)
@@ -242,7 +242,7 @@ class Substring(String):
def __str__(self): def __str__(self):
assert isinstance(self.value, str) assert isinstance(self.value, str)
return "substring(%s)" % escape_string(self.value) return f"substring({escape_string(self.value)})"
class _MatchedSubstring(Substring): class _MatchedSubstring(Substring):
@@ -267,11 +267,9 @@ class _MatchedSubstring(Substring):
self.matches = matches self.matches = matches
def __str__(self): def __str__(self):
matches = ", ".join(map(lambda s: '"' + s + '"', (self.matches or {}).keys()))
assert isinstance(self.value, str) assert isinstance(self.value, str)
return 'substring("%s", matches = %s)' % ( return f'substring("{self.value}", matches = {matches})'
self.value,
", ".join(map(lambda s: '"' + s + '"', (self.matches or {}).keys())),
)
class Regex(String): class Regex(String):
@@ -290,7 +288,7 @@ class Regex(String):
if value.endswith("/i"): if value.endswith("/i"):
value = value[: -len("i")] value = value[: -len("i")]
raise ValueError( 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 ) from exc
def evaluate(self, ctx, short_circuit=True): def evaluate(self, ctx, short_circuit=True):
@@ -336,7 +334,7 @@ class Regex(String):
def __str__(self): def __str__(self):
assert isinstance(self.value, str) assert isinstance(self.value, str)
return "regex(string =~ %s)" % self.value return f"regex(string =~ {self.value})"
class _MatchedRegex(Regex): class _MatchedRegex(Regex):
@@ -361,11 +359,9 @@ class _MatchedRegex(Regex):
self.matches = matches self.matches = matches
def __str__(self): def __str__(self):
matches = ", ".join(map(lambda s: '"' + s + '"', (self.matches or {}).keys()))
assert isinstance(self.value, str) assert isinstance(self.value, str)
return "regex(string =~ %s, matches = %s)" % ( return f"regex(string =~ {self.value}, matches = {matches})"
self.value,
", ".join(map(lambda s: '"' + s + '"', (self.matches or {}).keys())),
)
class StringFactory: class StringFactory:

View File

@@ -121,14 +121,14 @@ class ELF:
elif ei_class == 2: elif ei_class == 2:
self.bitness = 64 self.bitness = 64
else: else:
raise CorruptElfFile("invalid ei_class: 0x%02x" % ei_class) raise CorruptElfFile(f"invalid ei_class: 0x{ei_class:02x}")
if ei_data == 1: if ei_data == 1:
self.endian = "<" self.endian = "<"
elif ei_data == 2: elif ei_data == 2:
self.endian = ">" self.endian = ">"
else: 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: if self.bitness == 32:
e_phoff, e_shoff = struct.unpack_from(self.endian + "II", self.file_header, 0x1C) e_phoff, e_shoff = struct.unpack_from(self.endian + "II", self.file_header, 0x1C)

View File

@@ -55,7 +55,7 @@ def generate_symbols(dll: str, symbol: str) -> Iterator[str]:
dll = dll.lower() dll = dll.lower()
# kernel32.CreateFileA # kernel32.CreateFileA
yield "%s.%s" % (dll, symbol) yield f"{dll}.{symbol}"
if not is_ordinal(symbol): if not is_ordinal(symbol):
# CreateFileA # CreateFileA
@@ -63,7 +63,7 @@ def generate_symbols(dll: str, symbol: str) -> Iterator[str]:
if is_aw_function(symbol): if is_aw_function(symbol):
# kernel32.CreateFile # kernel32.CreateFile
yield "%s.%s" % (dll, symbol[:-1]) yield f"{dll}.{symbol[:-1]}"
if not is_ordinal(symbol): if not is_ordinal(symbol):
# CreateFile # CreateFile

View File

@@ -34,7 +34,7 @@ def get_printable_len(op: idaapi.op_t) -> int:
elif op.dtype == idaapi.dt_qword: elif op.dtype == idaapi.dt_qword:
chars = struct.pack("<Q", op_val) chars = struct.pack("<Q", op_val)
else: 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): def is_printable_ascii(chars_: bytes):
return all(c < 127 and chr(c) in string.printable for c in chars_) return all(c < 127 and chr(c) in string.printable for c in chars_)

View File

@@ -25,7 +25,7 @@ def find_byte_sequence(start: int, end: int, seq: bytes) -> Iterator[int]:
end: max virtual address end: max virtual address
seq: bytes to search e.g. b"\x01\x03" 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: while True:
# TODO find_binary: Deprecated. Please use ida_bytes.bin_search() instead. # TODO find_binary: Deprecated. Please use ida_bytes.bin_search() instead.
ea = idaapi.find_binary(start, end, seqstr, 0, idaapi.SEARCH_DOWN) ea = idaapi.find_binary(start, end, seqstr, 0, idaapi.SEARCH_DOWN)

View File

@@ -64,7 +64,7 @@ def extract_file_import_names(pe, **kwargs):
for imp in dll.imports: for imp in dll.imports:
if imp.import_by_ordinal: if imp.import_by_ordinal:
impname = "#%s" % imp.ordinal impname = f"#{imp.ordinal}"
else: else:
try: try:
impname = imp.name.partition(b"\x00")[0].decode("ascii") impname = imp.name.partition(b"\x00")[0].decode("ascii")

View File

@@ -121,7 +121,7 @@ def get_printable_len(oper: envi.archs.i386.disasm.i386ImmOper) -> int:
elif oper.tsize == 8: elif oper.tsize == 8:
chars = struct.pack("<Q", oper.imm) chars = struct.pack("<Q", oper.imm)
else: else:
raise ValueError("unexpected oper.tsize: %d" % (oper.tsize)) raise ValueError(f"unexpected oper.tsize: {oper.tsize}")
if is_printable_ascii(chars): if is_printable_ascii(chars):
return oper.tsize return oper.tsize

View File

@@ -44,7 +44,7 @@ def extract_file_import_names(vw, **kwargs) -> Iterator[Tuple[Feature, Address]]
modname, impname = tinfo.split(".", 1) modname, impname = tinfo.split(".", 1)
if is_viv_ord_impname(impname): if is_viv_ord_impname(impname):
# replace ord prefix with # # replace ord prefix with #
impname = "#%s" % impname[len("ord") :] impname = "#" + impname[len("ord") :]
addr = AbsoluteVirtualAddress(va) addr = AbsoluteVirtualAddress(va)
for name in capa.features.extractors.helpers.generate_symbols(modname, impname): for name in capa.features.extractors.helpers.generate_symbols(modname, impname):

View File

@@ -329,7 +329,7 @@ def loads(s: str) -> capa.features.extractors.base_extractor.FeatureExtractor:
freeze = Freeze.parse_raw(s) freeze = Freeze.parse_raw(s)
if freeze.version != 2: 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( return null.NullFeatureExtractor(
base_address=freeze.base_address.to_capa(), base_address=freeze.base_address.to_capa(),

View File

@@ -15,9 +15,9 @@ from capa.features.common import VALID_FEATURE_ACCESS, Feature
def hex(n: int) -> str: def hex(n: int) -> str:
"""render the given number using upper case hex, like: 0x123ABC""" """render the given number using upper case hex, like: 0x123ABC"""
if n < 0: if n < 0:
return "-0x%X" % (-n) return f"-0x{(-n):X}"
else: else:
return "0x%X" % n return f"0x{(n):X}"
class API(Feature): class API(Feature):
@@ -105,7 +105,7 @@ class _Operand(Feature, abc.ABC):
class OperandNumber(_Operand): class OperandNumber(_Operand):
# cached names so we don't do extra string formatting every ctor # 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 # operand[i].number: 0x12
def __init__(self, index: int, value: int, description=None): def __init__(self, index: int, value: int, description=None):
@@ -119,7 +119,7 @@ class OperandNumber(_Operand):
class OperandOffset(_Operand): class OperandOffset(_Operand):
# cached names so we don't do extra string formatting every ctor # 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 # operand[i].offset: 0x12
def __init__(self, index: int, value: int, description=None): def __init__(self, index: int, value: int, description=None):

View File

@@ -22,14 +22,14 @@ logger = logging.getLogger("capa")
def hex(n: int) -> str: def hex(n: int) -> str:
"""render the given number using upper case hex, like: 0x123ABC""" """render the given number using upper case hex, like: 0x123ABC"""
if n < 0: if n < 0:
return "-0x%X" % (-n) return f"-0x{(-n):X}"
else: else:
return "0x%X" % n return f"0x{(n):X}"
def get_file_taste(sample_path: str) -> bytes: def get_file_taste(sample_path: str) -> bytes:
if not os.path.exists(sample_path): 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: with open(sample_path, "rb") as f:
taste = f.read(8) taste = f.read(8)
return taste return taste

View File

@@ -45,7 +45,7 @@ NETNODE_RULES_CACHE_ID = "rules-cache-id"
def inform_user_ida_ui(message): 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(): def is_supported_ida_version():

View File

@@ -26,7 +26,7 @@ class CapaExplorerProgressIndicator(QtCore.QObject):
""" """
if ida_kernwin.user_cancelled(): if ida_kernwin.user_cancelled():
raise UserCancelledError("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): class CapaExplorerFeatureExtractor(IdaFeatureExtractor):
@@ -40,5 +40,5 @@ class CapaExplorerFeatureExtractor(IdaFeatureExtractor):
self.indicator = CapaExplorerProgressIndicator() self.indicator = CapaExplorerProgressIndicator()
def extract_function_features(self, fh: FunctionHandle): 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) return super().extract_function_features(fh)

View File

@@ -83,13 +83,13 @@ def trim_function_name(f, max_length=25):
""" """ """ """
n = idaapi.get_name(f.start_ea) n = idaapi.get_name(f.start_ea)
if len(n) > max_length: if len(n) > max_length:
n = "%s..." % n[:max_length] n = f"{n[:max_length]}..."
return n return n
def update_wait_box(text): def update_wait_box(text):
"""update the IDA wait box""" """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): class QLineEditClicked(QtWidgets.QLineEdit):
@@ -630,7 +630,7 @@ class CapaExplorerForm(idaapi.PluginForm):
try: try:
def on_load_rule(_, i, total): 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(): if ida_kernwin.user_cancelled():
raise UserCancelledError("user cancelled") raise UserCancelledError("user cancelled")
@@ -640,7 +640,7 @@ class CapaExplorerForm(idaapi.PluginForm):
return None return None
except Exception as e: except Exception as e:
capa.ida.helpers.inform_user_ida_ui( 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) 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") update_wait_box("verifying cached results")
view_status_rules: str = "%s (%d rules)" % ( count_source_rules = self.program_analysis_ruleset_cache.source_rule_count
settings.user[CAPA_SETTINGS_RULE_PATH], user_settings = settings.user[CAPA_SETTINGS_RULE_PATH]
self.program_analysis_ruleset_cache.source_rule_count, 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 # warn user about potentially outdated rules, depending on the use-case this may be expected
if ( if (
@@ -710,10 +709,8 @@ class CapaExplorerForm(idaapi.PluginForm):
) )
view_status_rules = "no rules matched for cache" view_status_rules = "no rules matched for cache"
new_view_status = "capa rules: %s, cached results (created %s)" % ( cached_results_time = self.resdoc_cache.meta.timestamp.strftime("%Y-%m-%d %H:%M:%S")
view_status_rules, new_view_status = f"capa rules: {view_status_rules}, cached results (created {cached_results_time})"
self.resdoc_cache.meta.timestamp.strftime("%Y-%m-%d %H:%M:%S"),
)
except Exception as e: except Exception as e:
logger.error("Failed to load cached capa results (error: %s).", e, exc_info=True) logger.error("Failed to load cached capa results (error: %s).", e, exc_info=True)
return False return False
@@ -725,7 +722,7 @@ class CapaExplorerForm(idaapi.PluginForm):
def slot_progress_feature_extraction(text): def slot_progress_feature_extraction(text):
"""slot function to handle feature extraction progress updates""" """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 self.process_count += 1
update_wait_box("initializing feature extractor") update_wait_box("initializing feature extractor")
@@ -843,12 +840,9 @@ class CapaExplorerForm(idaapi.PluginForm):
except Exception as e: except Exception as e:
logger.error("Failed to save results to database (error: %s)", e, exc_info=True) logger.error("Failed to save results to database (error: %s)", e, exc_info=True)
return False return False
user_settings = settings.user[CAPA_SETTINGS_RULE_PATH]
new_view_status = "capa rules: %s (%d rules)" % ( count_source_rules = self.program_analysis_ruleset_cache.source_rule_count
settings.user[CAPA_SETTINGS_RULE_PATH], new_view_status = f"capa rules: {user_settings} ({count_source_rules} rules)"
self.program_analysis_ruleset_cache.source_rule_count,
)
# regardless of new analysis, render results - e.g. we may only want to render results after checking # regardless of new analysis, render results - e.g. we may only want to render results after checking
# show results by function # 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.view_rulegen_features.load_features(all_file_features, all_function_features)
self.set_view_status_label( 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: except Exception as e:
logger.error("Failed to render views (error: %s)", e, exc_info=True) logger.error("Failed to render views (error: %s)", e, exc_info=True)

View File

@@ -30,7 +30,7 @@ def info_to_name(display):
def ea_to_hex(ea): def ea_to_hex(ea):
"""convert effective address (ea) to hex for display""" """convert effective address (ea) to hex for display"""
return "%08X" % ea return f"{hex(ea)}"
class CapaExplorerDataItem: class CapaExplorerDataItem:

View File

@@ -369,34 +369,34 @@ class CapaExplorerDataModel(QtCore.QAbstractItemModel):
if statement.type != rd.CompoundStatementType.NOT: if statement.type != rd.CompoundStatementType.NOT:
display = statement.type display = statement.type
if statement.description: if statement.description:
display += " (%s)" % statement.description display += f" ({statement.description})"
return CapaExplorerDefaultItem(parent, display) return CapaExplorerDefaultItem(parent, display)
elif isinstance(statement, rd.CompoundStatement) and statement.type == rd.CompoundStatementType.NOT: elif isinstance(statement, rd.CompoundStatement) and statement.type == rd.CompoundStatementType.NOT:
# TODO: do we display 'not' # TODO: do we display 'not'
pass pass
elif isinstance(statement, rd.SomeStatement): elif isinstance(statement, rd.SomeStatement):
display = "%d or more" % statement.count display = f"{statement.count} or more"
if statement.description: if statement.description:
display += " (%s)" % statement.description display += f" ({statement.description})"
return CapaExplorerDefaultItem(parent, display) return CapaExplorerDefaultItem(parent, display)
elif isinstance(statement, rd.RangeStatement): elif isinstance(statement, rd.RangeStatement):
# `range` is a weird node, its almost a hybrid of statement + feature. # `range` is a weird node, its almost a hybrid of statement + feature.
# it is a specific feature repeated multiple times. # it is a specific feature repeated multiple times.
# there's no additional logic in the feature part, just the existence of a feature. # 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. # 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: if statement.max == statement.min:
display += "%d" % (statement.min) display += f"{statement.min}"
elif statement.min == 0: elif statement.min == 0:
display += "%d or fewer" % (statement.max) display += f"{statement.max} or fewer"
elif statement.max == (1 << 64 - 1): elif statement.max == (1 << 64 - 1):
display += "%d or more" % (statement.min) display += f"{statement.min} or more"
else: else:
display += "between %d and %d" % (statement.min, statement.max) display += f"between {statement.min} and {statement.max}"
if statement.description: if statement.description:
display += " (%s)" % statement.description display += f" ({statement.description})"
parent2 = CapaExplorerFeatureItem(parent, display=display) parent2 = CapaExplorerFeatureItem(parent, display=display)
@@ -408,7 +408,7 @@ class CapaExplorerDataModel(QtCore.QAbstractItemModel):
elif isinstance(statement, rd.SubscopeStatement): elif isinstance(statement, rd.SubscopeStatement):
display = str(statement.scope) display = str(statement.scope)
if statement.description: if statement.description:
display += " (%s)" % statement.description display += f" ({statement.description})"
return CapaExplorerSubscopeItem(parent, display) return CapaExplorerSubscopeItem(parent, display)
else: else:
raise RuntimeError("unexpected match statement type: " + str(statement)) raise RuntimeError("unexpected match statement type: " + str(statement))
@@ -537,7 +537,7 @@ class CapaExplorerDataModel(QtCore.QAbstractItemModel):
if value: if value:
if isinstance(feature, frzf.StringFeature): 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: if isinstance(feature, frzf.PropertyFeature) and feature.access is not None:
key = f"property/{feature.access}" key = f"property/{feature.access}"
@@ -547,11 +547,11 @@ class CapaExplorerDataModel(QtCore.QAbstractItemModel):
key = f"operand[{feature.index}].offset" key = f"operand[{feature.index}].offset"
if feature.description: if feature.description:
return "%s(%s = %s)" % (key, value, feature.description) return f"{key}({value} = {feature.description})"
else: else:
return "%s(%s)" % (key, value) return f"{key}({value})"
else: else:
return "%s" % key return f"{key}"
def render_capa_doc_feature_node( def render_capa_doc_feature_node(
self, self,
@@ -669,7 +669,7 @@ class CapaExplorerDataModel(QtCore.QAbstractItemModel):
elif isinstance(feature, frzf.StringFeature): elif isinstance(feature, frzf.StringFeature):
# display string preview # display string preview
return CapaExplorerStringViewItem( 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( elif isinstance(

View File

@@ -58,7 +58,7 @@ def parse_yaml_line(feature):
if m: if m:
# reconstruct count without description # reconstruct count without description
feature, value, description, count = m.groups() feature, value, description, count = m.groups()
feature = "- count(%s(%s)): %s" % (feature, value, count) feature = f"- count({feature}({value})): {count}"
elif not feature.startswith("#"): elif not feature.startswith("#"):
feature, _, comment = feature.partition("#") feature, _, comment = feature.partition("#")
feature, _, description = feature.partition("=") feature, _, description = feature.partition("=")
@@ -72,18 +72,18 @@ def parse_node_for_feature(feature, description, comment, depth):
display = "" display = ""
if feature.startswith("#"): if feature.startswith("#"):
display += "%s%s\n" % (" " * depth, feature) display += f"{' '*depth}{feature}\n"
elif description: elif description:
if feature.startswith(("- and", "- or", "- optional", "- basic block", "- not", "- instruction:")): if feature.startswith(("- and", "- or", "- optional", "- basic block", "- not", "- instruction:")):
display += "%s%s" % (" " * depth, feature) display += f"{' '*depth}{feature}\n"
if comment: if comment:
display += " # %s" % comment display += f" # {comment}"
display += "\n%s- description: %s\n" % (" " * (depth + 2), description) display += f"\n{' '*(depth+2)}- description: {description}\n"
elif feature.startswith("- string"): elif feature.startswith("- string"):
display += "%s%s" % (" " * depth, feature) display += f"{' '*depth}{feature}\n"
if comment: if comment:
display += " # %s" % comment display += f" # {comment}"
display += "\n%sdescription: %s\n" % (" " * (depth + 2), description) display += f"\n{' '*(depth+2)}description: {description}\n"
elif feature.startswith("- count"): elif feature.startswith("- count"):
# count is weird, we need to format description based on feature type, so we parse with regex # 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> # assume format - count(<feature_name>(<feature_value>)): <count>
@@ -91,28 +91,22 @@ def parse_node_for_feature(feature, description, comment, depth):
if m: if m:
name, value, count = m.groups() name, value, count = m.groups()
if name in ("string",): if name in ("string",):
display += "%s%s" % (" " * depth, feature) display += f"{' '*depth}{feature}"
if comment: if comment:
display += " # %s" % comment display += " # %s" % comment
display += "\n%sdescription: %s\n" % (" " * (depth + 2), description) display += f"\n{' '*(depth+2)}description: {description}\n"
else: else:
display += "%s- count(%s(%s = %s)): %s" % ( display += f"{' '*depth}- count({name}({value} = {description})): {count}"
" " * depth,
name,
value,
description,
count,
)
if comment: if comment:
display += " # %s\n" % comment display += f" # {comment}\n"
else: else:
display += "%s%s = %s" % (" " * depth, feature, description) display += f"{' '*depth}{feature} = {description}"
if comment: if comment:
display += " # %s\n" % comment display += f" # {comment}\n"
else: else:
display += "%s%s" % (" " * depth, feature) display += f"{' '*depth}{feature}"
if comment: if comment:
display += " # %s\n" % comment display += f" # {comment}\n"
return display if display.endswith("\n") else display + "\n" return display if display.endswith("\n") else display + "\n"
@@ -198,14 +192,14 @@ class CapaExplorerRulegenPreview(QtWidgets.QTextEdit):
" name: <insert_name>", " name: <insert_name>",
" namespace: <insert_namespace>", " namespace: <insert_namespace>",
" authors:", " authors:",
" - %s" % author, f" - {author}",
" scope: %s" % scope, f" scope: {scope}",
" references:", " references:",
" - <insert_references>", " - <insert_references>",
" examples:", " examples:",
" - %s:0x%X" % (capa.ida.helpers.get_file_md5().upper(), ea) f" - {capa.ida.helpers.get_file_md5().upper()}:{hex(ea)}"
if ea if ea
else " - %s" % (capa.ida.helpers.get_file_md5().upper()), else f" - {capa.ida.helpers.get_file_md5().upper()}",
" features:", " features:",
] ]
self.setText("\n".join(metadata_default)) self.setText("\n".join(metadata_default))
@@ -539,7 +533,7 @@ class CapaExplorerRulegenEditor(QtWidgets.QTreeWidget):
# build submenu with modify actions # build submenu with modify actions
sub_menu = build_context_menu(self.parent(), sub_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 # build main menu with submenu + main actions
menu = build_context_menu(self.parent(), (sub_menu,) + actions) menu = build_context_menu(self.parent(), (sub_menu,) + actions)
@@ -654,21 +648,21 @@ class CapaExplorerRulegenEditor(QtWidgets.QTreeWidget):
# single features # single features
for k, v in filter(lambda t: t[1] == 1, counted): for k, v in filter(lambda t: t[1] == 1, counted):
if isinstance(k, (capa.features.common.String,)): 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: else:
value = k.get_value_str() 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 # n > 1 features
for k, v in filter(lambda t: t[1] > 1, counted): for k, v in filter(lambda t: t[1] > 1, counted):
if k.value: if k.value:
if isinstance(k, (capa.features.common.String,)): 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: else:
value = k.get_value_str() value = k.get_value_str()
display = "- count(%s(%s)): %d" % (k.name.lower(), value, v) display = f"- count({k.name.lower()}({value})): {v}"
else: else:
display = "- count(%s): %d" % (k.name.lower(), v) display = f"- count({k.name.lower()}): {v}"
self.new_feature_node(top_node, (display, "")) self.new_feature_node(top_node, (display, ""))
self.update_preview() self.update_preview()
@@ -880,7 +874,7 @@ class CapaExplorerRulegenFeatures(QtWidgets.QTreeWidget):
if isinstance(self.selectedItems()[0].data(0, 0x100), capa.features.common.Bytes): if isinstance(self.selectedItems()[0].data(0, 0x100), capa.features.common.Bytes):
actions.append(("Add n bytes...", (), self.slot_add_n_bytes_feature)) actions.append(("Add n bytes...", (), self.slot_add_n_bytes_feature))
else: 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)) actions.append((action_add_features_fmt, (), self.slot_add_selected_features))
@@ -1029,7 +1023,7 @@ class CapaExplorerRulegenFeatures(QtWidgets.QTreeWidget):
def format_address(e): def format_address(e):
if isinstance(e, AbsoluteVirtualAddress): if isinstance(e, AbsoluteVirtualAddress):
return "%X" % int(e) return f"{hex(int(e))}"
else: else:
return "" return ""
@@ -1038,8 +1032,8 @@ class CapaExplorerRulegenFeatures(QtWidgets.QTreeWidget):
name = feature.name.lower() name = feature.name.lower()
value = feature.get_value_str() value = feature.get_value_str()
if isinstance(feature, (capa.features.common.String,)): if isinstance(feature, (capa.features.common.String,)):
value = '"%s"' % capa.features.common.escape_string(value) value = f'"{capa.features.common.escape_string(value)}"'
return "%s(%s)" % (name, value) return f"{name}({value})"
for feature, addrs in sorted(features.items(), key=lambda k: sorted(k[1])): for feature, addrs in sorted(features.items(), key=lambda k: sorted(k[1])):
if isinstance(feature, capa.features.basicblock.BasicBlock): if isinstance(feature, capa.features.basicblock.BasicBlock):

View File

@@ -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) logger.debug("skipping library function 0x%x (%s)", f.address, function_name)
meta["library_functions"][f.address] = function_name meta["library_functions"][f.address] = function_name
n_libs = len(meta["library_functions"]) n_libs = len(meta["library_functions"])
percentage = 100 * (n_libs / n_funcs) percentage = round(100 * (n_libs / n_funcs))
if isinstance(pb, tqdm.tqdm): 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 continue
function_matches, bb_matches, insn_matches, feature_count = find_code_capabilities(ruleset, extractor, f) function_matches, bb_matches, insn_matches, feature_count = find_code_capabilities(ruleset, extractor, f)
@@ -397,8 +397,8 @@ def get_meta_str(vw):
meta = [] meta = []
for k in ["Format", "Platform", "Architecture"]: for k in ["Format", "Platform", "Architecture"]:
if k in vw.metadata: if k in vw.metadata:
meta.append("%s: %s" % (k.lower(), vw.metadata[k])) meta.append(f"{k.lower()}: {vw.metadata[k]}")
return "%s, number of functions: %d" % (", ".join(meta), len(vw.getFunctions())) return f"{', '.join(meta)}, number of functions: {len(vw.getFunctions())}"
def is_running_standalone() -> bool: def is_running_standalone() -> bool:
@@ -569,7 +569,7 @@ def collect_rule_file_paths(rule_paths: List[str]) -> List[str]:
rule_file_paths = [] rule_file_paths = []
for rule_path in rule_paths: for rule_path in rule_paths:
if not os.path.exists(rule_path): 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): if os.path.isfile(rule_path):
rule_file_paths.append(rule_path) rule_file_paths.append(rule_path)
@@ -660,7 +660,7 @@ def get_rules(
def get_signatures(sigs_path): def get_signatures(sigs_path):
if not os.path.exists(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 = [] paths = []
if os.path.isfile(sigs_path): if os.path.isfile(sigs_path):
@@ -844,13 +844,13 @@ def install_common_args(parser, wanted=None):
(FORMAT_SC64, "64-bit shellcode"), (FORMAT_SC64, "64-bit shellcode"),
(FORMAT_FREEZE, "features previously frozen by capa"), (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( parser.add_argument(
"-f", "-f",
"--format", "--format",
choices=[f[0] for f in formats], choices=[f[0] for f in formats],
default=FORMAT_AUTO, default=FORMAT_AUTO,
help="select sample format, %s" % format_help, help=f"select sample format, {format_help}",
) )
if "backend" in wanted: if "backend" in wanted:

View File

@@ -97,7 +97,7 @@ def render_capabilities(doc: rd.ResultDocument, ostream: StringIO):
if count == 1: if count == 1:
capability = rutils.bold(rule.meta.name) capability = rutils.bold(rule.meta.name)
else: 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)) rows.append((capability, rule.meta.namespace))
if rows: if rows:
@@ -135,9 +135,9 @@ def render_attack(doc: rd.ResultDocument, ostream: StringIO):
inner_rows = [] inner_rows = []
for technique, subtechnique, id in sorted(techniques): for technique, subtechnique, id in sorted(techniques):
if not subtechnique: if not subtechnique:
inner_rows.append("%s %s" % (rutils.bold(technique), id)) inner_rows.append(f"{rutils.bold(technique)} {id}")
else: else:
inner_rows.append("%s::%s %s" % (rutils.bold(technique), subtechnique, id)) inner_rows.append(f"{rutils.bold(technique)}::{subtechnique} {id}")
rows.append( rows.append(
( (
rutils.bold(tactic.upper()), rutils.bold(tactic.upper()),
@@ -178,9 +178,9 @@ def render_mbc(doc: rd.ResultDocument, ostream: StringIO):
inner_rows = [] inner_rows = []
for behavior, method, id in sorted(behaviors): for behavior, method, id in sorted(behaviors):
if not method: if not method:
inner_rows.append("%s [%s]" % (rutils.bold(behavior), id)) inner_rows.append(f"{rutils.bold(behavior)} [{id}]")
else: else:
inner_rows.append("%s::%s [%s]" % (rutils.bold(behavior), method, id)) inner_rows.append(f"{rutils.bold(behavior)}::{method} [{id}]")
rows.append( rows.append(
( (
rutils.bold(objective.upper()), rutils.bold(objective.upper()),

View File

@@ -32,7 +32,7 @@ def format_parts_id(data: Union[rd.AttackSpec, rd.MBCSpec]):
""" """
format canonical representation of ATT&CK/MBC parts and ID 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]: def capability_rules(doc: rd.ResultDocument) -> Iterator[rd.RuleMatches]:

View File

@@ -121,7 +121,7 @@ def render_rules(ostream, doc: rd.ResultDocument):
if count == 1: if count == 1:
capability = rutils.bold(rule.meta.name) capability = rutils.bold(rule.meta.name)
else: else:
capability = "%s (%d matches)" % (rutils.bold(rule.meta.name), count) capability = f"{rutils.bold(rule.meta.name)} ({count} matches)"
ostream.writeln(capability) ostream.writeln(capability)
had_match = True had_match = True

View File

@@ -43,7 +43,7 @@ def render_locations(ostream, locations: Iterable[frz.Address]):
# don't display too many locations, because it becomes very noisy. # don't display too many locations, because it becomes very noisy.
# probably only the first handful of locations will be useful for inspection. # probably only the first handful of locations will be useful for inspection.
ostream.write(", ".join(map(v.format_address, locations[0:4]))) 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: elif len(locations) > 1:
ostream.write(", ".join(map(v.format_address, locations))) 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(":") ostream.write(":")
if statement.description: if statement.description:
ostream.write(" = %s" % statement.description) ostream.write(f" = {statement.description}")
ostream.writeln("") ostream.writeln("")
elif isinstance(statement, (rd.CompoundStatement)): elif isinstance(statement, (rd.CompoundStatement)):
@@ -71,14 +71,14 @@ def render_statement(ostream, match: rd.Match, statement: rd.Statement, indent=0
ostream.write(":") ostream.write(":")
if statement.description: if statement.description:
ostream.write(" = %s" % statement.description) ostream.write(f" = {statement.description}")
ostream.writeln("") ostream.writeln("")
elif isinstance(statement, rd.SomeStatement): elif isinstance(statement, rd.SomeStatement):
ostream.write("%d or more:" % (statement.count)) ostream.write(f"{statement.count} or more:")
if statement.description: if statement.description:
ostream.write(" = %s" % statement.description) ostream.write(f" = {statement.description}")
ostream.writeln("") ostream.writeln("")
elif isinstance(statement, rd.RangeStatement): elif isinstance(statement, rd.RangeStatement):
@@ -92,28 +92,28 @@ def render_statement(ostream, match: rd.Match, statement: rd.Statement, indent=0
if value: if value:
if isinstance(child, frzf.StringFeature): 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) value = rutils.bold2(value)
if child.description: if child.description:
ostream.write("count(%s(%s = %s)): " % (child.type, value, child.description)) ostream.write(f"count({child.type}({value} = {child.description})): ")
else: else:
ostream.write("count(%s(%s)): " % (child.type, value)) ostream.write(f"count({child.type}({value})): ")
else: else:
ostream.write("count(%s): " % child.type) ostream.write(f"count({child.type}): ")
if statement.max == statement.min: if statement.max == statement.min:
ostream.write("%d" % (statement.min)) ostream.write(f"{statement.min}")
elif statement.min == 0: elif statement.min == 0:
ostream.write("%d or fewer" % (statement.max)) ostream.write(f"{statement.max} or fewer")
elif statement.max == (1 << 64 - 1): elif statement.max == (1 << 64 - 1):
ostream.write("%d or more" % (statement.min)) ostream.write(f"{statement.min} or more")
else: else:
ostream.write("between %d and %d" % (statement.min, statement.max)) ostream.write(f"between {statement.min} and {statement.max}")
if statement.description: if statement.description:
ostream.write(" = %s" % statement.description) ostream.write(f" = {statement.description}")
render_locations(ostream, match.locations) render_locations(ostream, match.locations)
ostream.writeln("") 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: 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): 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) value = feature.dict(by_alias=True).get(key, None)
if value is 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)): if not isinstance(feature, (frzf.RegexFeature, frzf.SubstringFeature)):
# like: # like:
@@ -290,11 +290,11 @@ def render_rules(ostream, doc: rd.ResultDocument):
if count == 1: if count == 1:
if rule.meta.lib: if rule.meta.lib:
lib_info = " (library rule)" lib_info = " (library rule)"
capability = "%s%s" % (rutils.bold(rule.meta.name), lib_info) capability = f"{rutils.bold(rule.meta.name)}{lib_info}"
else: else:
if rule.meta.lib: if rule.meta.lib:
lib_info = ", only showing first match of library rule" 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) ostream.writeln(capability)
had_match = True had_match = True
@@ -345,7 +345,7 @@ def render_rules(ostream, doc: rd.ResultDocument):
# because we do the file-scope evaluation a single time. # because we do the file-scope evaluation a single time.
# but i'm not 100% sure if this is/will always be true. # 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. # 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] first_address, first_match = matches[0]
render_match(ostream, first_match, indent=0) render_match(ostream, first_match, indent=0)
else: else:

View File

@@ -163,7 +163,7 @@ class InvalidRule(ValueError):
self.msg = msg self.msg = msg
def __str__(self): def __str__(self):
return "invalid rule: %s" % (self.msg) return f"invalid rule: {self.msg}"
def __repr__(self): def __repr__(self):
return str(self) return str(self)
@@ -177,7 +177,7 @@ class InvalidRuleWithPath(InvalidRule):
self.__cause__ = None self.__cause__ = None
def __str__(self): def __str__(self):
return "invalid rule: %s: %s" % (self.path, self.msg) return f"invalid rule: {self.path}: {self.msg}"
class InvalidRuleSet(ValueError): class InvalidRuleSet(ValueError):
@@ -186,7 +186,7 @@ class InvalidRuleSet(ValueError):
self.msg = msg self.msg = msg
def __str__(self): def __str__(self):
return "invalid rule set: %s" % (self.msg) return f"invalid rule set: {self.msg}"
def __repr__(self): def __repr__(self):
return str(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 isinstance(feature.value, str)
and capa.features.common.Characteristic(feature.value) not in SUPPORTED_FEATURES[scope] 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): if not isinstance(feature, capa.features.common.Characteristic):
# features of this scope that are not Characteristics will be Type instances. # features of this scope that are not Characteristics will be Type instances.
# check that the given feature is one of these types. # check that the given feature is one of these types.
types_for_scope = filter(lambda t: isinstance(t, type), SUPPORTED_FEATURES[scope]) types_for_scope = filter(lambda t: isinstance(t, type), SUPPORTED_FEATURES[scope])
if not isinstance(feature, tuple(types_for_scope)): # type: ignore 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: 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. # we want to use `{` characters, but this is a dict in yaml.
if not s.startswith("("): if not s.startswith("("):
raise InvalidRule("invalid range: %s" % (s)) raise InvalidRule(f"invalid range: {s}")
if not s.endswith(")"): if not s.endswith(")"):
raise InvalidRule("invalid range: %s" % (s)) raise InvalidRule(f"invalid range: {s}")
s = s[len("(") : -len(")")] s = s[len("(") : -len(")")]
min_spec, _, max_spec = s.partition(",") min_spec, _, max_spec = s.partition(",")
@@ -296,7 +296,7 @@ def parse_feature(key: str):
elif key == "property": elif key == "property":
return capa.features.insn.Property return capa.features.insn.Property
else: else:
raise InvalidRule("unexpected statement: %s" % key) raise InvalidRule(f"unexpected statement: {key}")
# this is the separator between a feature value and its description # this is the separator between a feature value and its description
@@ -310,11 +310,11 @@ def parse_bytes(s: str) -> bytes:
try: try:
b = codecs.decode(s.replace(" ", "").encode("ascii"), "hex") b = codecs.decode(s.replace(" ", "").encode("ascii"), "hex")
except binascii.Error: 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: if len(b) > MAX_BYTES_FEATURE_SIZE:
raise InvalidRule( 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 return b
@@ -337,15 +337,14 @@ def parse_description(s: Union[str, int, bytes], value_type: str, description=No
# - number: 10 = CONST_FOO # - number: 10 = CONST_FOO
# description: CONST_FOO # description: CONST_FOO
raise InvalidRule( raise InvalidRule(
'unexpected value: "%s", only one description allowed (inline description with `%s`)' f'unexpected value: "{s}", only one description allowed (inline description with `{DESCRIPTION_SEPARATOR}`)'
% (s, DESCRIPTION_SEPARATOR)
) )
value, _, description = s.partition(DESCRIPTION_SEPARATOR) value, _, description = s.partition(DESCRIPTION_SEPARATOR)
if description == "": if description == "":
# sanity check: # sanity check:
# there is an empty description, like `number: 10 =` # 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: else:
# this is a string, but there is no description, # this is a string, but there is no description,
# like: `api: CreateFileA` # like: `api: CreateFileA`
@@ -372,7 +371,7 @@ def parse_description(s: Union[str, int, bytes], value_type: str, description=No
try: try:
value = parse_int(value) value = parse_int(value)
except ValueError: 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: else:
# the value might be a number, like: `number: 10` # the value might be a number, like: `number: 10`
@@ -532,9 +531,9 @@ def build_statements(d, scope: str):
min, max = parse_range(count) min, max = parse_range(count)
return ceng.Range(feature, min=min, max=max, description=description) return ceng.Range(feature, min=min, max=max, description=description)
else: else:
raise InvalidRule("unexpected range: %s" % (count)) raise InvalidRule(f"unexpected range: {count}")
elif key == "string" and not isinstance(d[key], str): 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"): elif key.startswith("operand[") and key.endswith("].number"):
index = key[len("operand[") : -len("].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 == "format" and d[key] not in capa.features.common.VALID_FORMAT)
or (key == "arch" and d[key] not in capa.features.common.VALID_ARCH) 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/"): elif key.startswith("property/"):
access = key[len("property/") :] access = key[len("property/") :]
if access not in capa.features.common.VALID_FEATURE_ACCESS: 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")) value, description = parse_description(d[key], key, d.get("description"))
try: try:
@@ -617,10 +616,10 @@ class Rule:
self.definition = definition self.definition = definition
def __str__(self): def __str__(self):
return "Rule(name=%s)" % (self.name) return f"Rule(name={self.name})"
def __repr__(self): 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): 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 rule in rules_by_name.values():
for dep in rule.get_dependencies(namespaces): for dep in rule.get_dependencies(namespaces):
if dep not in rules_by_name: 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]]: def index_rules_by_namespace(rules: List[Rule]) -> Dict[str, List[Rule]]:

View File

@@ -112,7 +112,7 @@ def get_capa_results(args):
return { return {
"path": path, "path": path,
"status": "error", "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: except capa.main.UnsupportedRuntimeError:
return { return {
@@ -124,7 +124,7 @@ def get_capa_results(args):
return { return {
"path": path, "path": path,
"status": "error", "status": "error",
"error": "unexpected error: %s" % (e), "error": f"unexpected error: {e}",
} }
meta = capa.main.collect_metadata([], path, [], extractor) meta = capa.main.collect_metadata([], path, [], extractor)
@@ -202,7 +202,7 @@ def main(argv=None):
elif result["status"] == "ok": elif result["status"] == "ok":
results[result["path"]] = rd.ResultDocument.parse_obj(result["ok"]).json(exclude_none=True) results[result["path"]] = rd.ResultDocument.parse_obj(result["ok"]).json(exclude_none=True)
else: else:
raise ValueError("unexpected status: %s" % (result["status"])) raise ValueError(f"unexpected status: {result['status']}")
print(json.dumps(results)) print(json.dumps(results))

View File

@@ -77,7 +77,7 @@ def render_capabilities(doc: rd.ResultDocument, result):
if count == 1: if count == 1:
capability = rule.meta.name capability = rule.meta.name
else: 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"].setdefault(rule.meta.namespace, list())
result["CAPABILITY"][rule.meta.namespace].append(capability) result["CAPABILITY"][rule.meta.namespace].append(capability)
@@ -108,9 +108,9 @@ def render_attack(doc, result):
inner_rows = [] inner_rows = []
for technique, subtechnique, id in sorted(techniques): for technique, subtechnique, id in sorted(techniques):
if subtechnique is None: if subtechnique is None:
inner_rows.append("%s %s" % (technique, id)) inner_rows.append(f"{technique} {id}")
else: else:
inner_rows.append("%s::%s %s" % (technique, subtechnique, id)) inner_rows.append(f"{technique}::{subtechnique} {id}")
result["ATTCK"].setdefault(tactic.upper(), inner_rows) result["ATTCK"].setdefault(tactic.upper(), inner_rows)
@@ -142,9 +142,9 @@ def render_mbc(doc, result):
inner_rows = [] inner_rows = []
for behavior, method, id in sorted(behaviors): for behavior, method, id in sorted(behaviors):
if method is None: if method is None:
inner_rows.append("%s [%s]" % (behavior, id)) inner_rows.append(f"{behavior} [{id}]")
else: else:
inner_rows.append("%s::%s [%s]" % (behavior, method, id)) inner_rows.append(f"{behavior}::{method} [{id}]")
result["MBC"].setdefault(objective.upper(), inner_rows) result["MBC"].setdefault(objective.upper(), inner_rows)

View File

@@ -57,7 +57,7 @@ def load_analysis(bv):
if not path or not os.access(path, os.R_OK): if not path or not os.access(path, os.R_OK):
binaryninja.log_error("Invalid filename.") binaryninja.log_error("Invalid filename.")
return 0 return 0
binaryninja.log_info("Using capa file %s" % path) binaryninja.log_info(f"Using capa file {path}")
with open(path, "rb") as f: with open(path, "rb") as f:
doc = json.loads(f.read().decode("utf-8")) doc = json.loads(f.read().decode("utf-8"))
@@ -97,7 +97,7 @@ def load_analysis(bv):
else: else:
cmt = f"{name}" cmt = f"{name}"
binaryninja.log_info("0x%x: %s" % (va, cmt)) binaryninja.log_info(f"{hex(va)}: {cmt}")
try: try:
# message will look something like: # message will look something like:
# #

View File

@@ -101,9 +101,9 @@ def main():
rows = sorted(rows) rows = sorted(rows)
for ns, name, va in rows: for ns, name, va in rows:
if ns: if ns:
cmt = f"{name} ({ns})" cmt = name + f"({ns})"
else: else:
cmt = f"{name}" cmt = name
logger.info("0x%x: %s", va, cmt) logger.info("0x%x: %s", va, cmt)
try: try:

View File

@@ -19,17 +19,17 @@ def display_top(snapshot, key_type="lineno", limit=10):
print(f"Top {limit} lines") print(f"Top {limit} lines")
for index, stat in enumerate(top_stats[:limit], 1): for index, stat in enumerate(top_stats[:limit], 1):
frame = stat.traceback[0] 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() line = linecache.getline(frame.filename, frame.lineno).strip()
if line: if line:
print(" %s" % line) print(f" {line}")
other = top_stats[limit:] other = top_stats[limit:]
if other: if other:
size = sum(stat.size for stat in 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) 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(): def main():
@@ -49,7 +49,7 @@ def main():
print() print()
for i in range(count): 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_stdout(io.StringIO()):
with contextlib.redirect_stderr(io.StringIO()): with contextlib.redirect_stderr(io.StringIO()):
t0 = time.time() t0 = time.time()
@@ -59,9 +59,9 @@ def main():
gc.collect() gc.collect()
process = psutil.Process(os.getpid()) process = psutil.Process(os.getpid())
print(f" duration: {t1 - t0:.02f}s") print(f" duration: {(t1-t0):.2f}")
print(f" rss: {process.memory_info().rss / 1024 / 1024:.1f} MiB") print(f" rss: {(process.memory_info().rss / 1024 / 1024):.1f} MiB")
print(f" vms: {process.memory_info().vms / 1024 / 1024:.1f} MiB") print(f" vms: {(process.memory_info().vms / 1024 / 1024):.1f} MiB")
print("done.") print("done.")
gc.collect() gc.collect()

View File

@@ -133,9 +133,9 @@ def main(argv=None):
# so lets put that first. # so lets put that first.
# #
# https://docs.python.org/3/library/timeit.html#timeit.Timer.repeat # https://docs.python.org/3/library/timeit.html#timeit.Timer.repeat
"%0.2fs" % (min(samples) / float(args.number)), f"{(min(samples) / float(args.number)):.2f}s",
"%0.2fs" % (sum(samples) / float(args.repeat) / float(args.number)), f"{(sum(samples) / float(args.repeat) / float(args.number)):.2f}s",
"%0.2fs" % (max(samples) / float(args.number)), f"{(max(samples) / float(args.number)):.2f}s",
) )
], ],
headers=["label", "count(evaluations)", "min(time)", "avg(time)", "max(time)"], headers=["label", "count(evaluations)", "min(time)", "avg(time)", "max(time)"],

View File

@@ -118,7 +118,7 @@ def render_matches_by_function(doc: rd.ResultDocument):
for f in doc.meta.analysis.feature_counts.functions: for f in doc.meta.analysis.feature_counts.functions:
if not matches_by_function.get(f.address, {}): if not matches_by_function.get(f.address, {}):
continue 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]): for rule_name in sorted(matches_by_function[f.address]):
ostream.writeln(" - " + rule_name) ostream.writeln(" - " + rule_name)

View File

@@ -130,11 +130,11 @@ def main(argv=None):
return -1 return -1
for feature, addr in extractor.extract_global_features(): 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: if not args.function:
for feature, addr in extractor.extract_file_features(): 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()) 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)) 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]: 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 return -1
if len(function_handles) == 0: if len(function_handles) == 0:
print("%s not a function", args.function) print(f"{args.function} not a function")
return -1 return -1
print_features(function_handles, extractor) print_features(function_handles, extractor)
@@ -164,13 +164,13 @@ def ida_main():
import capa.features.extractors.ida.extractor import capa.features.extractors.ida.extractor
function = idc.get_func_attr(idc.here(), idc.FUNCATTR_START) 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() extractor = capa.features.extractors.ida.extractor.IdaFeatureExtractor()
if not function: if not function:
for feature, addr in extractor.extract_file_features(): for feature, addr in extractor.extract_file_features():
print("file: %s: %s" % (format_address(addr), feature)) print(f"file: {format_address(addr)}: {feature}")
return return
function_handles = tuple(extractor.get_functions()) 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)) function_handles = tuple(filter(lambda fh: fh.inner.start_ea == function, function_handles))
if len(function_handles) == 0: if len(function_handles) == 0:
print("0x%X not a function" % function) print(f"{hex(function)} not a function")
return -1 return -1
print_features(function_handles, extractor) 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) logger.debug("skipping library function %s (%s)", format_address(f.address), function_name)
continue continue
print("func: %s" % (format_address(f.address))) print(f"func: {format_address(f.address)}")
for feature, addr in extractor.extract_function_features(f): for feature, addr in extractor.extract_function_features(f):
if capa.features.common.is_global_feature(feature): if capa.features.common.is_global_feature(feature):
continue continue
if f.address != addr: 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: 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 bb in extractor.get_basic_blocks(f):
for feature, addr in extractor.extract_basic_block_features(f, bb): 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 continue
if bb.address != addr: 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: 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 insn in extractor.get_instructions(f, bb):
for feature, addr in extractor.extract_insn_features(f, bb, insn): 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: try:
if insn.address != addr: if insn.address != addr:
print( print(
" insn: %s: %s: %s -> %s" f" insn: {format_address(f.address)}: {format_address(insn.address)}: {feature} -> {format_address(addr)}"
% (
format_address(f.address),
format_address(insn.address),
feature,
format_address(addr),
)
) )
else: else:
print(" insn: %s: %s" % (format_address(insn.address), feature)) print(f" insn: {format_address(insn.address)}: {feature}")
except UnicodeEncodeError: except UnicodeEncodeError:
# may be an issue while piping to less and encountering non-ascii characters # may be an issue while piping to less and encountering non-ascii characters

View File

@@ -283,7 +283,7 @@ def get_data_path_by_name(name):
elif name.startswith("294b8d"): elif name.startswith("294b8d"):
return os.path.join(CD, "data", "294b8db1f2702b60fb2e42fdc50c2cee6a5046112da9a5703a548a4fa50477bc.elf_") return os.path.join(CD, "data", "294b8db1f2702b60fb2e42fdc50c2cee6a5046112da9a5703a548a4fa50477bc.elf_")
else: else:
raise ValueError("unexpected sample fixture: %s" % name) raise ValueError(f"unexpected sample fixture: {name}")
def get_sample_md5_by_name(name): def get_sample_md5_by_name(name):
@@ -341,7 +341,7 @@ def get_sample_md5_by_name(name):
# file name is SHA256 hash # file name is SHA256 hash
return "3db3e55b16a7b1b1afb970d5e77c5d98" return "3db3e55b16a7b1b1afb970d5e77c5d98"
else: else:
raise ValueError("unexpected sample fixture: %s" % name) raise ValueError(f"unexpected sample fixture: {name}")
def resolve_sample(sample): def resolve_sample(sample):
@@ -981,21 +981,16 @@ def do_test_feature_presence(get_extractor, sample, scope, feature, expected):
extractor = get_extractor(sample) extractor = get_extractor(sample)
features = scope(extractor) features = scope(extractor)
if expected: if expected:
msg = "%s should be found in %s" % (str(feature), scope.__name__) msg = f"{str(feature)} should be found in {scope.__name__}"
else: 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 assert feature.evaluate(features) == expected, msg
def do_test_feature_count(get_extractor, sample, scope, feature, expected): def do_test_feature_count(get_extractor, sample, scope, feature, expected):
extractor = get_extractor(sample) extractor = get_extractor(sample)
features = scope(extractor) features = scope(extractor)
msg = "%s should be found %d times in %s, found: %d" % ( msg = f"{str(feature)} should be found {expected} times in {scope.__name__}, found: {len(features[feature])}"
str(feature),
expected,
scope.__name__,
len(features[feature]),
)
assert len(features[feature]) == expected, msg assert len(features[feature]) == expected, msg

View File

@@ -31,7 +31,7 @@ def check_input_file(wanted):
found = binascii.hexlify(idautils.GetInputFileMD5()[:15]).decode("ascii").lower() found = binascii.hexlify(idautils.GetInputFileMD5()[:15]).decode("ascii").lower()
if not wanted.startswith(found): 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): def get_ida_extractor(_path):
@@ -51,7 +51,7 @@ def test_ida_features():
try: try:
check_input_file(fixtures.get_sample_md5_by_name(sample)) check_input_file(fixtures.get_sample_md5_by_name(sample))
except RuntimeError: except RuntimeError:
print("SKIP %s" % (id)) print(f"SKIP {id}")
continue continue
scope = fixtures.resolve_scope(scope) scope = fixtures.resolve_scope(scope)
@@ -60,10 +60,10 @@ def test_ida_features():
try: try:
fixtures.do_test_feature_presence(get_ida_extractor, sample, scope, feature, expected) fixtures.do_test_feature_presence(get_ida_extractor, sample, scope, feature, expected)
except Exception as e: except Exception as e:
print("FAIL %s" % (id)) print(f"FAIL {id}")
traceback.print_exc() traceback.print_exc()
else: else:
print("OK %s" % (id)) print(f"OK {id}")
@pytest.mark.skip(reason="IDA Pro tests must be run within IDA") @pytest.mark.skip(reason="IDA Pro tests must be run within IDA")
@@ -74,7 +74,7 @@ def test_ida_feature_counts():
try: try:
check_input_file(fixtures.get_sample_md5_by_name(sample)) check_input_file(fixtures.get_sample_md5_by_name(sample))
except RuntimeError: except RuntimeError:
print("SKIP %s" % (id)) print(f"SKIP {id}")
continue continue
scope = fixtures.resolve_scope(scope) scope = fixtures.resolve_scope(scope)
@@ -83,10 +83,10 @@ def test_ida_feature_counts():
try: try:
fixtures.do_test_feature_count(get_ida_extractor, sample, scope, feature, expected) fixtures.do_test_feature_count(get_ida_extractor, sample, scope, feature, expected)
except Exception as e: except Exception as e:
print("FAIL %s" % (id)) print(f"FAIL {id}")
traceback.print_exc() traceback.print_exc()
else: else:
print("OK %s" % (id)) print(f"OK {id}")
if __name__ == "__main__": if __name__ == "__main__":

View File

@@ -62,5 +62,5 @@ def test_bulk_process(tmpdir):
def run_program(script_path, args): def run_program(script_path, args):
args = [sys.executable] + [script_path] + args args = [sys.executable] + [script_path] + args
print("running: '%s'" % args) print(f"running: '{args}'")
return subprocess.run(args) return subprocess.run(args)