From 1336796c0cf369fc0bd27f2c7838a9466af16ed2 Mon Sep 17 00:00:00 2001 From: manasghandat <95558940+manasghandat@users.noreply.github.com> Date: Thu, 16 Mar 2023 15:46:18 +0530 Subject: [PATCH] code style : update remaining files (#1353) * code style: update string formatting using fstrings --------- Co-authored-by: Willi Ballenthin Co-authored-by: Moritz --- capa/engine.py | 10 ++-- capa/features/common.py | 24 ++++---- capa/features/extractors/elf.py | 4 +- capa/features/extractors/helpers.py | 4 +- capa/features/extractors/ida/basicblock.py | 2 +- capa/features/extractors/ida/helpers.py | 2 +- capa/features/extractors/pefile.py | 2 +- capa/features/extractors/viv/basicblock.py | 2 +- capa/features/extractors/viv/file.py | 2 +- capa/features/freeze/__init__.py | 2 +- capa/features/insn.py | 8 +-- capa/helpers.py | 6 +- capa/ida/helpers.py | 2 +- capa/ida/plugin/extractor.py | 4 +- capa/ida/plugin/form.py | 34 +++++------ capa/ida/plugin/item.py | 2 +- capa/ida/plugin/model.py | 30 +++++----- capa/ida/plugin/view.py | 66 ++++++++++------------ capa/main.py | 16 +++--- capa/render/default.py | 10 ++-- capa/render/utils.py | 2 +- capa/render/verbose.py | 2 +- capa/render/vverbose.py | 38 ++++++------- capa/rules/__init__.py | 41 +++++++------- scripts/bulk-process.py | 6 +- scripts/capa_as_library.py | 10 ++-- scripts/import-to-bn.py | 4 +- scripts/import-to-ida.py | 4 +- scripts/profile-memory.py | 16 +++--- scripts/profile-time.py | 6 +- scripts/show-capabilities-by-function.py | 2 +- scripts/show-features.py | 34 +++++------ tests/fixtures.py | 15 ++--- tests/test_ida_features.py | 14 ++--- tests/test_scripts.py | 2 +- 35 files changed, 201 insertions(+), 227 deletions(-) diff --git a/capa/engine.py b/capa/engine.py index dde1e7c8..4d9e0980 100644 --- a/capa/engine.py +++ b/capa/engine.py @@ -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): diff --git a/capa/features/common.py b/capa/features/common.py index c908d7bc..d2f1a4ff 100644 --- a/capa/features/common.py +++ b/capa/features/common.py @@ -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: diff --git a/capa/features/extractors/elf.py b/capa/features/extractors/elf.py index 08467481..ab79cc74 100644 --- a/capa/features/extractors/elf.py +++ b/capa/features/extractors/elf.py @@ -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) diff --git a/capa/features/extractors/helpers.py b/capa/features/extractors/helpers.py index 1e2ff2cb..d27b85b1 100644 --- a/capa/features/extractors/helpers.py +++ b/capa/features/extractors/helpers.py @@ -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 diff --git a/capa/features/extractors/ida/basicblock.py b/capa/features/extractors/ida/basicblock.py index 20633e8a..34f7d1ce 100644 --- a/capa/features/extractors/ida/basicblock.py +++ b/capa/features/extractors/ida/basicblock.py @@ -34,7 +34,7 @@ def get_printable_len(op: idaapi.op_t) -> int: elif op.dtype == idaapi.dt_qword: chars = struct.pack(" 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) diff --git a/capa/features/extractors/pefile.py b/capa/features/extractors/pefile.py index 038200b8..cf4f16c4 100644 --- a/capa/features/extractors/pefile.py +++ b/capa/features/extractors/pefile.py @@ -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") diff --git a/capa/features/extractors/viv/basicblock.py b/capa/features/extractors/viv/basicblock.py index 9848bec0..24a753ae 100644 --- a/capa/features/extractors/viv/basicblock.py +++ b/capa/features/extractors/viv/basicblock.py @@ -121,7 +121,7 @@ def get_printable_len(oper: envi.archs.i386.disasm.i386ImmOper) -> int: elif oper.tsize == 8: chars = struct.pack(" 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): diff --git a/capa/features/freeze/__init__.py b/capa/features/freeze/__init__.py index fd3dcdb9..e6911c5f 100644 --- a/capa/features/freeze/__init__.py +++ b/capa/features/freeze/__init__.py @@ -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(), diff --git a/capa/features/insn.py b/capa/features/insn.py index 030784fe..cc4d54e6 100644 --- a/capa/features/insn.py +++ b/capa/features/insn.py @@ -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): diff --git a/capa/helpers.py b/capa/helpers.py index a2edc812..d0b2123b 100644 --- a/capa/helpers.py +++ b/capa/helpers.py @@ -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 diff --git a/capa/ida/helpers.py b/capa/ida/helpers.py index 86584410..fbd502fe 100644 --- a/capa/ida/helpers.py +++ b/capa/ida/helpers.py @@ -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(): diff --git a/capa/ida/plugin/extractor.py b/capa/ida/plugin/extractor.py index a6464020..58bbc4ba 100644 --- a/capa/ida/plugin/extractor.py +++ b/capa/ida/plugin/extractor.py @@ -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) diff --git a/capa/ida/plugin/form.py b/capa/ida/plugin/form.py index 2f30928d..6084277d 100644 --- a/capa/ida/plugin/form.py +++ b/capa/ida/plugin/form.py @@ -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) diff --git a/capa/ida/plugin/item.py b/capa/ida/plugin/item.py index ac349424..6d0eee23 100644 --- a/capa/ida/plugin/item.py +++ b/capa/ida/plugin/item.py @@ -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: diff --git a/capa/ida/plugin/model.py b/capa/ida/plugin/model.py index 20d9be88..b53007ae 100644 --- a/capa/ida/plugin/model.py +++ b/capa/ida/plugin/model.py @@ -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( diff --git a/capa/ida/plugin/view.py b/capa/ida/plugin/view.py index 075d1ff3..93217028 100644 --- a/capa/ida/plugin/view.py +++ b/capa/ida/plugin/view.py @@ -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(()): @@ -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: ", " namespace: ", " authors:", - " - %s" % author, - " scope: %s" % scope, + f" - {author}", + f" scope: {scope}", " 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): diff --git a/capa/main.py b/capa/main.py index 617df71f..6d024778 100644 --- a/capa/main.py +++ b/capa/main.py @@ -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: diff --git a/capa/render/default.py b/capa/render/default.py index 66f765d6..76659252 100644 --- a/capa/render/default.py +++ b/capa/render/default.py @@ -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()), diff --git a/capa/render/utils.py b/capa/render/utils.py index 50e36cca..c65b705b 100644 --- a/capa/render/utils.py +++ b/capa/render/utils.py @@ -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]: diff --git a/capa/render/verbose.py b/capa/render/verbose.py index e494ff06..536e7242 100644 --- a/capa/render/verbose.py +++ b/capa/render/verbose.py @@ -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 diff --git a/capa/render/vverbose.py b/capa/render/vverbose.py index c65e28e4..0c782853 100644 --- a/capa/render/vverbose.py +++ b/capa/render/vverbose.py @@ -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: diff --git a/capa/rules/__init__.py b/capa/rules/__init__.py index 7d98e25a..64fd7e37 100644 --- a/capa/rules/__init__.py +++ b/capa/rules/__init__.py @@ -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]]: diff --git a/scripts/bulk-process.py b/scripts/bulk-process.py index 53880bcf..f22c55e0 100644 --- a/scripts/bulk-process.py +++ b/scripts/bulk-process.py @@ -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)) diff --git a/scripts/capa_as_library.py b/scripts/capa_as_library.py index a91ce133..884b2e94 100644 --- a/scripts/capa_as_library.py +++ b/scripts/capa_as_library.py @@ -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) diff --git a/scripts/import-to-bn.py b/scripts/import-to-bn.py index d157af44..1e5b4ca0 100644 --- a/scripts/import-to-bn.py +++ b/scripts/import-to-bn.py @@ -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: # diff --git a/scripts/import-to-ida.py b/scripts/import-to-ida.py index a32b45b9..058c2553 100644 --- a/scripts/import-to-ida.py +++ b/scripts/import-to-ida.py @@ -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: diff --git a/scripts/profile-memory.py b/scripts/profile-memory.py index c6e2df90..e5bc6515 100644 --- a/scripts/profile-memory.py +++ b/scripts/profile-memory.py @@ -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() diff --git a/scripts/profile-time.py b/scripts/profile-time.py index 09731f89..0c7f0783 100644 --- a/scripts/profile-time.py +++ b/scripts/profile-time.py @@ -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)"], diff --git a/scripts/show-capabilities-by-function.py b/scripts/show-capabilities-by-function.py index 8045e895..dbd47f8f 100644 --- a/scripts/show-capabilities-by-function.py +++ b/scripts/show-capabilities-by-function.py @@ -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) diff --git a/scripts/show-features.py b/scripts/show-features.py index d23a9a0a..297977d5 100644 --- a/scripts/show-features.py +++ b/scripts/show-features.py @@ -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 diff --git a/tests/fixtures.py b/tests/fixtures.py index bfbbbdb3..5602f096 100644 --- a/tests/fixtures.py +++ b/tests/fixtures.py @@ -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 diff --git a/tests/test_ida_features.py b/tests/test_ida_features.py index b408d632..b6917262 100644 --- a/tests/test_ida_features.py +++ b/tests/test_ida_features.py @@ -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__": diff --git a/tests/test_scripts.py b/tests/test_scripts.py index f5a9d701..f48a6f99 100644 --- a/tests/test_scripts.py +++ b/tests/test_scripts.py @@ -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)