From 788251ba2b00c5417e34c3ee24062cce744a06bc Mon Sep 17 00:00:00 2001 From: Willi Ballenthin Date: Fri, 20 Oct 2023 11:37:42 +0000 Subject: [PATCH 01/17] vverbose: render scope for humans --- capa/render/vverbose.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/capa/render/vverbose.py b/capa/render/vverbose.py index ba2328846..af0268c87 100644 --- a/capa/render/vverbose.py +++ b/capa/render/vverbose.py @@ -331,10 +331,12 @@ def render_rules(ostream, doc: rd.ResultDocument): rows.append(("author", ", ".join(rule.meta.authors))) if doc.meta.flavor == rd.Flavor.STATIC: - rows.append(("scope", f"{rule.meta.scopes.static}")) + assert rule.meta.scopes.static is not None + rows.append(("scope", rule.meta.scopes.static.value)) if doc.meta.flavor == rd.Flavor.DYNAMIC: - rows.append(("scope", f"{rule.meta.scopes.dynamic}")) + assert rule.meta.scopes.dynamic is not None + rows.append(("scope", rule.meta.scopes.dynamic.value)) if rule.meta.attack: rows.append(("att&ck", ", ".join([rutils.format_parts_id(v) for v in rule.meta.attack]))) @@ -363,9 +365,11 @@ def render_rules(ostream, doc: rd.ResultDocument): else: for location, match in sorted(doc.rules[rule.meta.name].matches): if doc.meta.flavor == rd.Flavor.STATIC: - ostream.write(f"{rule.meta.scopes.static}") + assert rule.meta.scopes.static is not None + ostream.write(rule.meta.scopes.static.value) elif doc.meta.flavor == rd.Flavor.DYNAMIC: - ostream.write(f"{rule.meta.scopes.dynamic}") + assert rule.meta.scopes.dynamic is not None + ostream.write(rule.meta.scopes.dynamic.value) else: capa.helpers.assert_never(doc.meta.flavor) From ee4f02908c76c6f716482c42ccaf9a486f5d5161 Mon Sep 17 00:00:00 2001 From: Willi Ballenthin Date: Fri, 20 Oct 2023 12:38:35 +0000 Subject: [PATCH 02/17] layout: capture process name --- capa/features/extractors/base_extractor.py | 8 ++ capa/features/extractors/cape/extractor.py | 6 +- capa/features/extractors/null.py | 4 + capa/features/freeze/__init__.py | 4 + capa/main.py | 3 + capa/render/proto/__init__.py | 2 + capa/render/proto/capa.proto | 1 + capa/render/proto/capa_pb2.py | 124 ++++++++++----------- capa/render/proto/capa_pb2.pyi | 5 +- capa/render/vverbose.py | 114 ++++++++++++++----- tests/test_freeze_dynamic.py | 1 + 11 files changed, 182 insertions(+), 90 deletions(-) diff --git a/capa/features/extractors/base_extractor.py b/capa/features/extractors/base_extractor.py index fbf4b0f37..e78feab35 100644 --- a/capa/features/extractors/base_extractor.py +++ b/capa/features/extractors/base_extractor.py @@ -415,6 +415,14 @@ def extract_process_features(self, ph: ProcessHandle) -> Iterator[Tuple[Feature, """ raise NotImplementedError() + @abc.abstractmethod + def get_process_name(self, ph: ProcessHandle) -> str: + """ + Returns the human-readable name for the given process, + such as the filename. + """ + raise NotImplementedError() + @abc.abstractmethod def get_threads(self, ph: ProcessHandle) -> Iterator[ThreadHandle]: """ diff --git a/capa/features/extractors/cape/extractor.py b/capa/features/extractors/cape/extractor.py index 1c8cfd2a0..d670d6d82 100644 --- a/capa/features/extractors/cape/extractor.py +++ b/capa/features/extractors/cape/extractor.py @@ -17,7 +17,7 @@ from capa.exceptions import EmptyReportError, UnsupportedFormatError from capa.features.common import Feature, Characteristic from capa.features.address import NO_ADDRESS, Address, AbsoluteVirtualAddress, _NoAddress -from capa.features.extractors.cape.models import Static, CapeReport +from capa.features.extractors.cape.models import Static, Process, CapeReport from capa.features.extractors.base_extractor import ( CallHandle, SampleHashes, @@ -60,6 +60,10 @@ def get_processes(self) -> Iterator[ProcessHandle]: def extract_process_features(self, ph: ProcessHandle) -> Iterator[Tuple[Feature, Address]]: yield from capa.features.extractors.cape.process.extract_features(ph) + def get_process_name(self, ph) -> str: + process: Process = ph.inner + return process.process_name + def get_threads(self, ph: ProcessHandle) -> Iterator[ThreadHandle]: yield from capa.features.extractors.cape.process.get_threads(ph) diff --git a/capa/features/extractors/null.py b/capa/features/extractors/null.py index 89358a595..82d5c3721 100644 --- a/capa/features/extractors/null.py +++ b/capa/features/extractors/null.py @@ -110,6 +110,7 @@ class ThreadFeatures: class ProcessFeatures: features: List[Tuple[Address, Feature]] threads: Dict[Address, ThreadFeatures] + name: str @dataclass @@ -140,6 +141,9 @@ def extract_process_features(self, ph): for addr, feature in self.processes[ph.address].features: yield feature, addr + def get_process_name(self, ph) -> str: + return self.processes[ph.address].name + def get_threads(self, ph): for address in sorted(self.processes[ph.address].threads.keys()): assert isinstance(address, ThreadAddress) diff --git a/capa/features/freeze/__init__.py b/capa/features/freeze/__init__.py index 84969c868..230b8c3b4 100644 --- a/capa/features/freeze/__init__.py +++ b/capa/features/freeze/__init__.py @@ -300,6 +300,7 @@ class ThreadFeatures(BaseModel): class ProcessFeatures(BaseModel): address: Address + name: str features: Tuple[ProcessFeature, ...] threads: Tuple[ThreadFeatures, ...] @@ -463,6 +464,7 @@ def dumps_dynamic(extractor: DynamicFeatureExtractor) -> str: process_features: List[ProcessFeatures] = [] for p in extractor.get_processes(): paddr = Address.from_capa(p.address) + pname = extractor.get_process_name(p) pfeatures = [ ProcessFeature( process=paddr, @@ -515,6 +517,7 @@ def dumps_dynamic(extractor: DynamicFeatureExtractor) -> str: process_features.append( ProcessFeatures( address=paddr, + name=pname, features=tuple(pfeatures), threads=tuple(threads), ) @@ -595,6 +598,7 @@ def loads_dynamic(s: str) -> DynamicFeatureExtractor: file_features=[(f.address.to_capa(), f.feature.to_capa()) for f in freeze.features.file], processes={ p.address.to_capa(): null.ProcessFeatures( + name=p.name, features=[(fe.address.to_capa(), fe.feature.to_capa()) for fe in p.features], threads={ t.address.to_capa(): null.ThreadFeatures( diff --git a/capa/main.py b/capa/main.py index 642778877..2e2ed6336 100644 --- a/capa/main.py +++ b/capa/main.py @@ -1062,8 +1062,10 @@ def compute_dynamic_layout(rules, extractor: DynamicFeatureExtractor, capabiliti assert isinstance(extractor, DynamicFeatureExtractor) processes_by_thread: Dict[Address, Address] = {} threads_by_processes: Dict[Address, List[Address]] = {} + names_by_process: Dict[Address, str] = {} for p in extractor.get_processes(): threads_by_processes[p.address] = [] + names_by_process[p.address] = extractor.get_process_name(p) for t in extractor.get_threads(p): processes_by_thread[t.address] = p.address threads_by_processes[p.address].append(t.address) @@ -1080,6 +1082,7 @@ def compute_dynamic_layout(rules, extractor: DynamicFeatureExtractor, capabiliti processes=tuple( rdoc.ProcessLayout( address=frz.Address.from_capa(p), + name=names_by_process[p], matched_threads=tuple( rdoc.ThreadLayout(address=frz.Address.from_capa(t)) for t in threads if t in matched_threads ) # this object is open to extension in the future, diff --git a/capa/render/proto/__init__.py b/capa/render/proto/__init__.py index ad81ced57..ea3e2e11f 100644 --- a/capa/render/proto/__init__.py +++ b/capa/render/proto/__init__.py @@ -224,6 +224,7 @@ def dynamic_analysis_to_pb2(analysis: rd.DynamicAnalysis) -> capa_pb2.DynamicAna processes=[ capa_pb2.ProcessLayout( address=addr_to_pb2(p.address), + name=p.name, matched_threads=[capa_pb2.ThreadLayout(address=addr_to_pb2(t.address)) for t in p.matched_threads], ) for p in analysis.layout.processes @@ -705,6 +706,7 @@ def dynamic_analysis_from_pb2(analysis: capa_pb2.DynamicAnalysis) -> rd.DynamicA [ rd.ProcessLayout( address=addr_from_pb2(p.address), + name=p.name, matched_threads=tuple( [rd.ThreadLayout(address=addr_from_pb2(t.address)) for t in p.matched_threads] ), diff --git a/capa/render/proto/capa.proto b/capa/render/proto/capa.proto index 7cd6a3529..41f2c5cb0 100644 --- a/capa/render/proto/capa.proto +++ b/capa/render/proto/capa.proto @@ -291,6 +291,7 @@ message ProcessFeatureCount { message ProcessLayout { Address address = 1; repeated ThreadLayout matched_threads = 2; + string name = 3; } message PropertyFeature { diff --git a/capa/render/proto/capa_pb2.py b/capa/render/proto/capa_pb2.py index e855c863f..bd422bb3d 100644 --- a/capa/render/proto/capa_pb2.py +++ b/capa/render/proto/capa_pb2.py @@ -13,7 +13,7 @@ -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1c\x63\x61pa/render/proto/capa.proto\"Q\n\nAPIFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x0b\n\x03\x61pi\x18\x02 \x01(\t\x12\x18\n\x0b\x64\x65scription\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"\xdf\x01\n\x07\x41\x64\x64ress\x12\x1a\n\x04type\x18\x01 \x01(\x0e\x32\x0c.AddressType\x12\x15\n\x01v\x18\x02 \x01(\x0b\x32\x08.IntegerH\x00\x12%\n\x0ctoken_offset\x18\x03 \x01(\x0b\x32\r.Token_OffsetH\x00\x12\x1d\n\x08ppid_pid\x18\x04 \x01(\x0b\x32\t.Ppid_PidH\x00\x12%\n\x0cppid_pid_tid\x18\x05 \x01(\x0b\x32\r.Ppid_Pid_TidH\x00\x12+\n\x0fppid_pid_tid_id\x18\x06 \x01(\x0b\x32\x10.Ppid_Pid_Tid_IdH\x00\x42\x07\n\x05value\"\xe4\x01\n\x08\x41nalysis\x12\x0e\n\x06\x66ormat\x18\x01 \x01(\t\x12\x0c\n\x04\x61rch\x18\x02 \x01(\t\x12\n\n\x02os\x18\x03 \x01(\t\x12\x11\n\textractor\x18\x04 \x01(\t\x12\r\n\x05rules\x18\x05 \x03(\t\x12\x1e\n\x0c\x62\x61se_address\x18\x06 \x01(\x0b\x32\x08.Address\x12\x17\n\x06layout\x18\x07 \x01(\x0b\x32\x07.Layout\x12&\n\x0e\x66\x65\x61ture_counts\x18\x08 \x01(\x0b\x32\x0e.FeatureCounts\x12+\n\x11library_functions\x18\t \x03(\x0b\x32\x10.LibraryFunction\"S\n\x0b\x41rchFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x0c\n\x04\x61rch\x18\x02 \x01(\t\x12\x18\n\x0b\x64\x65scription\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"`\n\nAttackSpec\x12\r\n\x05parts\x18\x01 \x03(\t\x12\x0e\n\x06tactic\x18\x02 \x01(\t\x12\x11\n\ttechnique\x18\x03 \x01(\t\x12\x14\n\x0csubtechnique\x18\x04 \x01(\t\x12\n\n\x02id\x18\x05 \x01(\t\"K\n\x11\x42\x61sicBlockFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x18\n\x0b\x64\x65scription\x18\x02 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"-\n\x10\x42\x61sicBlockLayout\x12\x19\n\x07\x61\x64\x64ress\x18\x01 \x01(\x0b\x32\x08.Address\"U\n\x0c\x42ytesFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\r\n\x05\x62ytes\x18\x02 \x01(\t\x12\x18\n\x0b\x64\x65scription\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"g\n\x15\x43haracteristicFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x16\n\x0e\x63haracteristic\x18\x02 \x01(\t\x12\x18\n\x0b\x64\x65scription\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"V\n\x0c\x43lassFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x0e\n\x06\x63lass_\x18\x02 \x01(\t\x12\x18\n\x0b\x64\x65scription\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"K\n\x11\x43ompoundStatement\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x18\n\x0b\x64\x65scription\x18\x02 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"\xac\x01\n\x0f\x44ynamicAnalysis\x12\x0e\n\x06\x66ormat\x18\x01 \x01(\t\x12\x0c\n\x04\x61rch\x18\x02 \x01(\t\x12\n\n\x02os\x18\x03 \x01(\t\x12\x11\n\textractor\x18\x04 \x01(\t\x12\r\n\x05rules\x18\x05 \x03(\t\x12\x1e\n\x06layout\x18\x06 \x01(\x0b\x32\x0e.DynamicLayout\x12-\n\x0e\x66\x65\x61ture_counts\x18\x07 \x01(\x0b\x32\x15.DynamicFeatureCounts\"M\n\x14\x44ynamicFeatureCounts\x12\x0c\n\x04\x66ile\x18\x01 \x01(\x04\x12\'\n\tprocesses\x18\x02 \x03(\x0b\x32\x14.ProcessFeatureCount\"2\n\rDynamicLayout\x12!\n\tprocesses\x18\x01 \x03(\x0b\x32\x0e.ProcessLayout\"W\n\rExportFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x0e\n\x06\x65xport\x18\x02 \x01(\t\x12\x18\n\x0b\x64\x65scription\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"G\n\rFeatureCounts\x12\x0c\n\x04\x66ile\x18\x01 \x01(\x04\x12(\n\tfunctions\x18\x02 \x03(\x0b\x32\x15.FunctionFeatureCount\"\xf7\x06\n\x0b\x46\x65\x61tureNode\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x18\n\x02os\x18\x02 \x01(\x0b\x32\n.OSFeatureH\x00\x12\x1c\n\x04\x61rch\x18\x03 \x01(\x0b\x32\x0c.ArchFeatureH\x00\x12 \n\x06\x66ormat\x18\x04 \x01(\x0b\x32\x0e.FormatFeatureH\x00\x12\x1e\n\x05match\x18\x05 \x01(\x0b\x32\r.MatchFeatureH\x00\x12\x30\n\x0e\x63haracteristic\x18\x06 \x01(\x0b\x32\x16.CharacteristicFeatureH\x00\x12 \n\x06\x65xport\x18\x07 \x01(\x0b\x32\x0e.ExportFeatureH\x00\x12!\n\x07import_\x18\x08 \x01(\x0b\x32\x0e.ImportFeatureH\x00\x12\"\n\x07section\x18\t \x01(\x0b\x32\x0f.SectionFeatureH\x00\x12-\n\rfunction_name\x18\n \x01(\x0b\x32\x14.FunctionNameFeatureH\x00\x12&\n\tsubstring\x18\x0b \x01(\x0b\x32\x11.SubstringFeatureH\x00\x12\x1e\n\x05regex\x18\x0c \x01(\x0b\x32\r.RegexFeatureH\x00\x12 \n\x06string\x18\r \x01(\x0b\x32\x0e.StringFeatureH\x00\x12\x1f\n\x06\x63lass_\x18\x0e \x01(\x0b\x32\r.ClassFeatureH\x00\x12&\n\tnamespace\x18\x0f \x01(\x0b\x32\x11.NamespaceFeatureH\x00\x12\x1a\n\x03\x61pi\x18\x10 \x01(\x0b\x32\x0b.APIFeatureH\x00\x12%\n\tproperty_\x18\x11 \x01(\x0b\x32\x10.PropertyFeatureH\x00\x12 \n\x06number\x18\x12 \x01(\x0b\x32\x0e.NumberFeatureH\x00\x12\x1e\n\x05\x62ytes\x18\x13 \x01(\x0b\x32\r.BytesFeatureH\x00\x12 \n\x06offset\x18\x14 \x01(\x0b\x32\x0e.OffsetFeatureH\x00\x12$\n\x08mnemonic\x18\x15 \x01(\x0b\x32\x10.MnemonicFeatureH\x00\x12/\n\x0eoperand_number\x18\x16 \x01(\x0b\x32\x15.OperandNumberFeatureH\x00\x12/\n\x0eoperand_offset\x18\x17 \x01(\x0b\x32\x15.OperandOffsetFeatureH\x00\x12)\n\x0b\x62\x61sic_block\x18\x18 \x01(\x0b\x32\x12.BasicBlockFeatureH\x00\x42\t\n\x07\x66\x65\x61ture\"W\n\rFormatFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x0e\n\x06\x66ormat\x18\x02 \x01(\t\x12\x18\n\x0b\x64\x65scription\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"@\n\x14\x46unctionFeatureCount\x12\x19\n\x07\x61\x64\x64ress\x18\x01 \x01(\x0b\x32\x08.Address\x12\r\n\x05\x63ount\x18\x02 \x01(\x04\"\\\n\x0e\x46unctionLayout\x12\x19\n\x07\x61\x64\x64ress\x18\x01 \x01(\x0b\x32\x08.Address\x12/\n\x14matched_basic_blocks\x18\x02 \x03(\x0b\x32\x11.BasicBlockLayout\"d\n\x13\x46unctionNameFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x15\n\rfunction_name\x18\x02 \x01(\t\x12\x18\n\x0b\x64\x65scription\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"X\n\rImportFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x0f\n\x07import_\x18\x02 \x01(\t\x12\x18\n\x0b\x64\x65scription\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\",\n\x06Layout\x12\"\n\tfunctions\x18\x01 \x03(\x0b\x32\x0f.FunctionLayout\":\n\x0fLibraryFunction\x12\x19\n\x07\x61\x64\x64ress\x18\x01 \x01(\x0b\x32\x08.Address\x12\x0c\n\x04name\x18\x02 \x01(\t\"Y\n\x07MBCSpec\x12\r\n\x05parts\x18\x01 \x03(\t\x12\x11\n\tobjective\x18\x02 \x01(\t\x12\x10\n\x08\x62\x65havior\x18\x03 \x01(\t\x12\x0e\n\x06method\x18\x04 \x01(\t\x12\n\n\x02id\x18\x05 \x01(\t\"\x9a\x01\n\x0cMaecMetadata\x12\x1b\n\x13\x61nalysis_conclusion\x18\x01 \x01(\t\x12\x1e\n\x16\x61nalysis_conclusion_ov\x18\x02 \x01(\t\x12\x16\n\x0emalware_family\x18\x03 \x01(\t\x12\x18\n\x10malware_category\x18\x04 \x01(\t\x12\x1b\n\x13malware_category_ov\x18\x05 \x01(\t\"\x82\x02\n\x05Match\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12#\n\tstatement\x18\x02 \x01(\x0b\x32\x0e.StatementNodeH\x00\x12\x1f\n\x07\x66\x65\x61ture\x18\x03 \x01(\x0b\x32\x0c.FeatureNodeH\x00\x12\x18\n\x08\x63hildren\x18\x05 \x03(\x0b\x32\x06.Match\x12\x1b\n\tlocations\x18\x06 \x03(\x0b\x32\x08.Address\x12&\n\x08\x63\x61ptures\x18\x07 \x03(\x0b\x32\x14.Match.CapturesEntry\x1a;\n\rCapturesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\x19\n\x05value\x18\x02 \x01(\x0b\x32\n.Addresses:\x02\x38\x01\x42\x06\n\x04node\"U\n\x0cMatchFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\r\n\x05match\x18\x02 \x01(\t\x12\x18\n\x0b\x64\x65scription\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"\xf6\x01\n\x08Metadata\x12\x11\n\ttimestamp\x18\x01 \x01(\t\x12\x0f\n\x07version\x18\x02 \x01(\t\x12\x0c\n\x04\x61rgv\x18\x03 \x03(\t\x12\x17\n\x06sample\x18\x04 \x01(\x0b\x32\x07.Sample\x12\x1f\n\x08\x61nalysis\x18\x05 \x01(\x0b\x32\t.AnalysisB\x02\x18\x01\x12\x17\n\x06\x66lavor\x18\x06 \x01(\x0e\x32\x07.Flavor\x12*\n\x0fstatic_analysis\x18\x07 \x01(\x0b\x32\x0f.StaticAnalysisH\x00\x12,\n\x10\x64ynamic_analysis\x18\x08 \x01(\x0b\x32\x10.DynamicAnalysisH\x00\x42\x0b\n\tanalysis2\"[\n\x0fMnemonicFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x10\n\x08mnemonic\x18\x02 \x01(\t\x12\x18\n\x0b\x64\x65scription\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"]\n\x10NamespaceFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x11\n\tnamespace\x18\x02 \x01(\t\x12\x18\n\x0b\x64\x65scription\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"`\n\rNumberFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x17\n\x06number\x18\x02 \x01(\x0b\x32\x07.Number\x12\x18\n\x0b\x64\x65scription\x18\x05 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"O\n\tOSFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\n\n\x02os\x18\x02 \x01(\t\x12\x18\n\x0b\x64\x65scription\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"a\n\rOffsetFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x18\n\x06offset\x18\x02 \x01(\x0b\x32\x08.Integer\x12\x18\n\x0b\x64\x65scription\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"\x7f\n\x14OperandNumberFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\r\n\x05index\x18\x02 \x01(\r\x12 \n\x0eoperand_number\x18\x03 \x01(\x0b\x32\x08.Integer\x12\x18\n\x0b\x64\x65scription\x18\x04 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"\x7f\n\x14OperandOffsetFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\r\n\x05index\x18\x02 \x01(\r\x12 \n\x0eoperand_offset\x18\x03 \x01(\x0b\x32\x08.Integer\x12\x18\n\x0b\x64\x65scription\x18\x04 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"?\n\x13ProcessFeatureCount\x12\x19\n\x07\x61\x64\x64ress\x18\x01 \x01(\x0b\x32\x08.Address\x12\r\n\x05\x63ount\x18\x02 \x01(\x04\"R\n\rProcessLayout\x12\x19\n\x07\x61\x64\x64ress\x18\x01 \x01(\x0b\x32\x08.Address\x12&\n\x0fmatched_threads\x18\x02 \x03(\x0b\x32\r.ThreadLayout\"|\n\x0fPropertyFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x11\n\tproperty_\x18\x02 \x01(\t\x12\x13\n\x06\x61\x63\x63\x65ss\x18\x03 \x01(\tH\x00\x88\x01\x01\x12\x18\n\x0b\x64\x65scription\x18\x04 \x01(\tH\x01\x88\x01\x01\x42\t\n\x07_accessB\x0e\n\x0c_description\"\x7f\n\x0eRangeStatement\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x0b\n\x03min\x18\x02 \x01(\x04\x12\x0b\n\x03max\x18\x03 \x01(\x04\x12\x1b\n\x05\x63hild\x18\x04 \x01(\x0b\x32\x0c.FeatureNode\x12\x18\n\x0b\x64\x65scription\x18\x05 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"U\n\x0cRegexFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\r\n\x05regex\x18\x02 \x01(\t\x12\x18\n\x0b\x64\x65scription\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"\x90\x01\n\x0eResultDocument\x12\x17\n\x04meta\x18\x01 \x01(\x0b\x32\t.Metadata\x12)\n\x05rules\x18\x02 \x03(\x0b\x32\x1a.ResultDocument.RulesEntry\x1a:\n\nRulesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\x1b\n\x05value\x18\x02 \x01(\x0b\x32\x0c.RuleMatches:\x02\x38\x01\"`\n\x0bRuleMatches\x12\x1b\n\x04meta\x18\x01 \x01(\x0b\x32\r.RuleMetadata\x12\x0e\n\x06source\x18\x02 \x01(\t\x12$\n\x07matches\x18\x03 \x03(\x0b\x32\x13.Pair_Address_Match\"\xa7\x02\n\x0cRuleMetadata\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x11\n\tnamespace\x18\x02 \x01(\t\x12\x0f\n\x07\x61uthors\x18\x03 \x03(\t\x12\x19\n\x05scope\x18\x04 \x01(\x0e\x32\x06.ScopeB\x02\x18\x01\x12\x1b\n\x06\x61ttack\x18\x05 \x03(\x0b\x32\x0b.AttackSpec\x12\x15\n\x03mbc\x18\x06 \x03(\x0b\x32\x08.MBCSpec\x12\x12\n\nreferences\x18\x07 \x03(\t\x12\x10\n\x08\x65xamples\x18\x08 \x03(\t\x12\x13\n\x0b\x64\x65scription\x18\t \x01(\t\x12\x0b\n\x03lib\x18\n \x01(\x08\x12\x1b\n\x04maec\x18\x0b \x01(\x0b\x32\r.MaecMetadata\x12\x18\n\x10is_subscope_rule\x18\x0c \x01(\x08\x12\x17\n\x06scopes\x18\r \x01(\x0b\x32\x07.Scopes\"A\n\x06Sample\x12\x0b\n\x03md5\x18\x01 \x01(\t\x12\x0c\n\x04sha1\x18\x02 \x01(\t\x12\x0e\n\x06sha256\x18\x03 \x01(\t\x12\x0c\n\x04path\x18\x04 \x01(\t\"Z\n\x06Scopes\x12\x1b\n\x06static\x18\x01 \x01(\x0e\x32\x06.ScopeH\x00\x88\x01\x01\x12\x1c\n\x07\x64ynamic\x18\x02 \x01(\x0e\x32\x06.ScopeH\x01\x88\x01\x01\x42\t\n\x07_staticB\n\n\x08_dynamic\"Y\n\x0eSectionFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x0f\n\x07section\x18\x02 \x01(\t\x12\x18\n\x0b\x64\x65scription\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"V\n\rSomeStatement\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\r\n\x05\x63ount\x18\x02 \x01(\r\x12\x18\n\x0b\x64\x65scription\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"\xbc\x01\n\rStatementNode\x12\x0c\n\x04type\x18\x01 \x01(\t\x12 \n\x05range\x18\x02 \x01(\x0b\x32\x0f.RangeStatementH\x00\x12\x1e\n\x04some\x18\x03 \x01(\x0b\x32\x0e.SomeStatementH\x00\x12&\n\x08subscope\x18\x04 \x01(\x0b\x32\x12.SubscopeStatementH\x00\x12&\n\x08\x63ompound\x18\x05 \x01(\x0b\x32\x12.CompoundStatementH\x00\x42\x0b\n\tstatement\"\xf6\x01\n\x0eStaticAnalysis\x12\x0e\n\x06\x66ormat\x18\x01 \x01(\t\x12\x0c\n\x04\x61rch\x18\x02 \x01(\t\x12\n\n\x02os\x18\x03 \x01(\t\x12\x11\n\textractor\x18\x04 \x01(\t\x12\r\n\x05rules\x18\x05 \x03(\t\x12\x1e\n\x0c\x62\x61se_address\x18\x06 \x01(\x0b\x32\x08.Address\x12\x1d\n\x06layout\x18\x07 \x01(\x0b\x32\r.StaticLayout\x12,\n\x0e\x66\x65\x61ture_counts\x18\x08 \x01(\x0b\x32\x14.StaticFeatureCounts\x12+\n\x11library_functions\x18\t \x03(\x0b\x32\x10.LibraryFunction\"M\n\x13StaticFeatureCounts\x12\x0c\n\x04\x66ile\x18\x01 \x01(\x04\x12(\n\tfunctions\x18\x02 \x03(\x0b\x32\x15.FunctionFeatureCount\"2\n\x0cStaticLayout\x12\"\n\tfunctions\x18\x01 \x03(\x0b\x32\x0f.FunctionLayout\"W\n\rStringFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x0e\n\x06string\x18\x02 \x01(\t\x12\x18\n\x0b\x64\x65scription\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"b\n\x11SubscopeStatement\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x15\n\x05scope\x18\x02 \x01(\x0e\x32\x06.Scope\x12\x18\n\x0b\x64\x65scription\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"]\n\x10SubstringFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x11\n\tsubstring\x18\x02 \x01(\t\x12\x18\n\x0b\x64\x65scription\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\")\n\x0cThreadLayout\x12\x19\n\x07\x61\x64\x64ress\x18\x01 \x01(\x0b\x32\x08.Address\"&\n\tAddresses\x12\x19\n\x07\x61\x64\x64ress\x18\x01 \x03(\x0b\x32\x08.Address\"F\n\x12Pair_Address_Match\x12\x19\n\x07\x61\x64\x64ress\x18\x01 \x01(\x0b\x32\x08.Address\x12\x15\n\x05match\x18\x02 \x01(\x0b\x32\x06.Match\"7\n\x0cToken_Offset\x12\x17\n\x05token\x18\x01 \x01(\x0b\x32\x08.Integer\x12\x0e\n\x06offset\x18\x02 \x01(\x04\"9\n\x08Ppid_Pid\x12\x16\n\x04ppid\x18\x01 \x01(\x0b\x32\x08.Integer\x12\x15\n\x03pid\x18\x02 \x01(\x0b\x32\x08.Integer\"T\n\x0cPpid_Pid_Tid\x12\x16\n\x04ppid\x18\x01 \x01(\x0b\x32\x08.Integer\x12\x15\n\x03pid\x18\x02 \x01(\x0b\x32\x08.Integer\x12\x15\n\x03tid\x18\x03 \x01(\x0b\x32\x08.Integer\"m\n\x0fPpid_Pid_Tid_Id\x12\x16\n\x04ppid\x18\x01 \x01(\x0b\x32\x08.Integer\x12\x15\n\x03pid\x18\x02 \x01(\x0b\x32\x08.Integer\x12\x15\n\x03tid\x18\x03 \x01(\x0b\x32\x08.Integer\x12\x14\n\x02id\x18\x04 \x01(\x0b\x32\x08.Integer\",\n\x07Integer\x12\x0b\n\x01u\x18\x01 \x01(\x04H\x00\x12\x0b\n\x01i\x18\x02 \x01(\x12H\x00\x42\x07\n\x05value\"8\n\x06Number\x12\x0b\n\x01u\x18\x01 \x01(\x04H\x00\x12\x0b\n\x01i\x18\x02 \x01(\x12H\x00\x12\x0b\n\x01\x66\x18\x03 \x01(\x01H\x00\x42\x07\n\x05value*\x92\x02\n\x0b\x41\x64\x64ressType\x12\x1b\n\x17\x41\x44\x44RESSTYPE_UNSPECIFIED\x10\x00\x12\x18\n\x14\x41\x44\x44RESSTYPE_ABSOLUTE\x10\x01\x12\x18\n\x14\x41\x44\x44RESSTYPE_RELATIVE\x10\x02\x12\x14\n\x10\x41\x44\x44RESSTYPE_FILE\x10\x03\x12\x18\n\x14\x41\x44\x44RESSTYPE_DN_TOKEN\x10\x04\x12\x1f\n\x1b\x41\x44\x44RESSTYPE_DN_TOKEN_OFFSET\x10\x05\x12\x1a\n\x16\x41\x44\x44RESSTYPE_NO_ADDRESS\x10\x06\x12\x17\n\x13\x41\x44\x44RESSTYPE_PROCESS\x10\x07\x12\x16\n\x12\x41\x44\x44RESSTYPE_THREAD\x10\x08\x12\x14\n\x10\x41\x44\x44RESSTYPE_CALL\x10\t*G\n\x06\x46lavor\x12\x16\n\x12\x46LAVOR_UNSPECIFIED\x10\x00\x12\x11\n\rFLAVOR_STATIC\x10\x01\x12\x12\n\x0e\x46LAVOR_DYNAMIC\x10\x02*\xa5\x01\n\x05Scope\x12\x15\n\x11SCOPE_UNSPECIFIED\x10\x00\x12\x0e\n\nSCOPE_FILE\x10\x01\x12\x12\n\x0eSCOPE_FUNCTION\x10\x02\x12\x15\n\x11SCOPE_BASIC_BLOCK\x10\x03\x12\x15\n\x11SCOPE_INSTRUCTION\x10\x04\x12\x11\n\rSCOPE_PROCESS\x10\x05\x12\x10\n\x0cSCOPE_THREAD\x10\x06\x12\x0e\n\nSCOPE_CALL\x10\x07\x62\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1c\x63\x61pa/render/proto/capa.proto\"Q\n\nAPIFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x0b\n\x03\x61pi\x18\x02 \x01(\t\x12\x18\n\x0b\x64\x65scription\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"\xdf\x01\n\x07\x41\x64\x64ress\x12\x1a\n\x04type\x18\x01 \x01(\x0e\x32\x0c.AddressType\x12\x15\n\x01v\x18\x02 \x01(\x0b\x32\x08.IntegerH\x00\x12%\n\x0ctoken_offset\x18\x03 \x01(\x0b\x32\r.Token_OffsetH\x00\x12\x1d\n\x08ppid_pid\x18\x04 \x01(\x0b\x32\t.Ppid_PidH\x00\x12%\n\x0cppid_pid_tid\x18\x05 \x01(\x0b\x32\r.Ppid_Pid_TidH\x00\x12+\n\x0fppid_pid_tid_id\x18\x06 \x01(\x0b\x32\x10.Ppid_Pid_Tid_IdH\x00\x42\x07\n\x05value\"\xe4\x01\n\x08\x41nalysis\x12\x0e\n\x06\x66ormat\x18\x01 \x01(\t\x12\x0c\n\x04\x61rch\x18\x02 \x01(\t\x12\n\n\x02os\x18\x03 \x01(\t\x12\x11\n\textractor\x18\x04 \x01(\t\x12\r\n\x05rules\x18\x05 \x03(\t\x12\x1e\n\x0c\x62\x61se_address\x18\x06 \x01(\x0b\x32\x08.Address\x12\x17\n\x06layout\x18\x07 \x01(\x0b\x32\x07.Layout\x12&\n\x0e\x66\x65\x61ture_counts\x18\x08 \x01(\x0b\x32\x0e.FeatureCounts\x12+\n\x11library_functions\x18\t \x03(\x0b\x32\x10.LibraryFunction\"S\n\x0b\x41rchFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x0c\n\x04\x61rch\x18\x02 \x01(\t\x12\x18\n\x0b\x64\x65scription\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"`\n\nAttackSpec\x12\r\n\x05parts\x18\x01 \x03(\t\x12\x0e\n\x06tactic\x18\x02 \x01(\t\x12\x11\n\ttechnique\x18\x03 \x01(\t\x12\x14\n\x0csubtechnique\x18\x04 \x01(\t\x12\n\n\x02id\x18\x05 \x01(\t\"K\n\x11\x42\x61sicBlockFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x18\n\x0b\x64\x65scription\x18\x02 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"-\n\x10\x42\x61sicBlockLayout\x12\x19\n\x07\x61\x64\x64ress\x18\x01 \x01(\x0b\x32\x08.Address\"U\n\x0c\x42ytesFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\r\n\x05\x62ytes\x18\x02 \x01(\t\x12\x18\n\x0b\x64\x65scription\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"g\n\x15\x43haracteristicFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x16\n\x0e\x63haracteristic\x18\x02 \x01(\t\x12\x18\n\x0b\x64\x65scription\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"V\n\x0c\x43lassFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x0e\n\x06\x63lass_\x18\x02 \x01(\t\x12\x18\n\x0b\x64\x65scription\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"K\n\x11\x43ompoundStatement\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x18\n\x0b\x64\x65scription\x18\x02 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"\xac\x01\n\x0f\x44ynamicAnalysis\x12\x0e\n\x06\x66ormat\x18\x01 \x01(\t\x12\x0c\n\x04\x61rch\x18\x02 \x01(\t\x12\n\n\x02os\x18\x03 \x01(\t\x12\x11\n\textractor\x18\x04 \x01(\t\x12\r\n\x05rules\x18\x05 \x03(\t\x12\x1e\n\x06layout\x18\x06 \x01(\x0b\x32\x0e.DynamicLayout\x12-\n\x0e\x66\x65\x61ture_counts\x18\x07 \x01(\x0b\x32\x15.DynamicFeatureCounts\"M\n\x14\x44ynamicFeatureCounts\x12\x0c\n\x04\x66ile\x18\x01 \x01(\x04\x12\'\n\tprocesses\x18\x02 \x03(\x0b\x32\x14.ProcessFeatureCount\"2\n\rDynamicLayout\x12!\n\tprocesses\x18\x01 \x03(\x0b\x32\x0e.ProcessLayout\"W\n\rExportFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x0e\n\x06\x65xport\x18\x02 \x01(\t\x12\x18\n\x0b\x64\x65scription\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"G\n\rFeatureCounts\x12\x0c\n\x04\x66ile\x18\x01 \x01(\x04\x12(\n\tfunctions\x18\x02 \x03(\x0b\x32\x15.FunctionFeatureCount\"\xf7\x06\n\x0b\x46\x65\x61tureNode\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x18\n\x02os\x18\x02 \x01(\x0b\x32\n.OSFeatureH\x00\x12\x1c\n\x04\x61rch\x18\x03 \x01(\x0b\x32\x0c.ArchFeatureH\x00\x12 \n\x06\x66ormat\x18\x04 \x01(\x0b\x32\x0e.FormatFeatureH\x00\x12\x1e\n\x05match\x18\x05 \x01(\x0b\x32\r.MatchFeatureH\x00\x12\x30\n\x0e\x63haracteristic\x18\x06 \x01(\x0b\x32\x16.CharacteristicFeatureH\x00\x12 \n\x06\x65xport\x18\x07 \x01(\x0b\x32\x0e.ExportFeatureH\x00\x12!\n\x07import_\x18\x08 \x01(\x0b\x32\x0e.ImportFeatureH\x00\x12\"\n\x07section\x18\t \x01(\x0b\x32\x0f.SectionFeatureH\x00\x12-\n\rfunction_name\x18\n \x01(\x0b\x32\x14.FunctionNameFeatureH\x00\x12&\n\tsubstring\x18\x0b \x01(\x0b\x32\x11.SubstringFeatureH\x00\x12\x1e\n\x05regex\x18\x0c \x01(\x0b\x32\r.RegexFeatureH\x00\x12 \n\x06string\x18\r \x01(\x0b\x32\x0e.StringFeatureH\x00\x12\x1f\n\x06\x63lass_\x18\x0e \x01(\x0b\x32\r.ClassFeatureH\x00\x12&\n\tnamespace\x18\x0f \x01(\x0b\x32\x11.NamespaceFeatureH\x00\x12\x1a\n\x03\x61pi\x18\x10 \x01(\x0b\x32\x0b.APIFeatureH\x00\x12%\n\tproperty_\x18\x11 \x01(\x0b\x32\x10.PropertyFeatureH\x00\x12 \n\x06number\x18\x12 \x01(\x0b\x32\x0e.NumberFeatureH\x00\x12\x1e\n\x05\x62ytes\x18\x13 \x01(\x0b\x32\r.BytesFeatureH\x00\x12 \n\x06offset\x18\x14 \x01(\x0b\x32\x0e.OffsetFeatureH\x00\x12$\n\x08mnemonic\x18\x15 \x01(\x0b\x32\x10.MnemonicFeatureH\x00\x12/\n\x0eoperand_number\x18\x16 \x01(\x0b\x32\x15.OperandNumberFeatureH\x00\x12/\n\x0eoperand_offset\x18\x17 \x01(\x0b\x32\x15.OperandOffsetFeatureH\x00\x12)\n\x0b\x62\x61sic_block\x18\x18 \x01(\x0b\x32\x12.BasicBlockFeatureH\x00\x42\t\n\x07\x66\x65\x61ture\"W\n\rFormatFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x0e\n\x06\x66ormat\x18\x02 \x01(\t\x12\x18\n\x0b\x64\x65scription\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"@\n\x14\x46unctionFeatureCount\x12\x19\n\x07\x61\x64\x64ress\x18\x01 \x01(\x0b\x32\x08.Address\x12\r\n\x05\x63ount\x18\x02 \x01(\x04\"\\\n\x0e\x46unctionLayout\x12\x19\n\x07\x61\x64\x64ress\x18\x01 \x01(\x0b\x32\x08.Address\x12/\n\x14matched_basic_blocks\x18\x02 \x03(\x0b\x32\x11.BasicBlockLayout\"d\n\x13\x46unctionNameFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x15\n\rfunction_name\x18\x02 \x01(\t\x12\x18\n\x0b\x64\x65scription\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"X\n\rImportFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x0f\n\x07import_\x18\x02 \x01(\t\x12\x18\n\x0b\x64\x65scription\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\",\n\x06Layout\x12\"\n\tfunctions\x18\x01 \x03(\x0b\x32\x0f.FunctionLayout\":\n\x0fLibraryFunction\x12\x19\n\x07\x61\x64\x64ress\x18\x01 \x01(\x0b\x32\x08.Address\x12\x0c\n\x04name\x18\x02 \x01(\t\"Y\n\x07MBCSpec\x12\r\n\x05parts\x18\x01 \x03(\t\x12\x11\n\tobjective\x18\x02 \x01(\t\x12\x10\n\x08\x62\x65havior\x18\x03 \x01(\t\x12\x0e\n\x06method\x18\x04 \x01(\t\x12\n\n\x02id\x18\x05 \x01(\t\"\x9a\x01\n\x0cMaecMetadata\x12\x1b\n\x13\x61nalysis_conclusion\x18\x01 \x01(\t\x12\x1e\n\x16\x61nalysis_conclusion_ov\x18\x02 \x01(\t\x12\x16\n\x0emalware_family\x18\x03 \x01(\t\x12\x18\n\x10malware_category\x18\x04 \x01(\t\x12\x1b\n\x13malware_category_ov\x18\x05 \x01(\t\"\x82\x02\n\x05Match\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12#\n\tstatement\x18\x02 \x01(\x0b\x32\x0e.StatementNodeH\x00\x12\x1f\n\x07\x66\x65\x61ture\x18\x03 \x01(\x0b\x32\x0c.FeatureNodeH\x00\x12\x18\n\x08\x63hildren\x18\x05 \x03(\x0b\x32\x06.Match\x12\x1b\n\tlocations\x18\x06 \x03(\x0b\x32\x08.Address\x12&\n\x08\x63\x61ptures\x18\x07 \x03(\x0b\x32\x14.Match.CapturesEntry\x1a;\n\rCapturesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\x19\n\x05value\x18\x02 \x01(\x0b\x32\n.Addresses:\x02\x38\x01\x42\x06\n\x04node\"U\n\x0cMatchFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\r\n\x05match\x18\x02 \x01(\t\x12\x18\n\x0b\x64\x65scription\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"\xf6\x01\n\x08Metadata\x12\x11\n\ttimestamp\x18\x01 \x01(\t\x12\x0f\n\x07version\x18\x02 \x01(\t\x12\x0c\n\x04\x61rgv\x18\x03 \x03(\t\x12\x17\n\x06sample\x18\x04 \x01(\x0b\x32\x07.Sample\x12\x1f\n\x08\x61nalysis\x18\x05 \x01(\x0b\x32\t.AnalysisB\x02\x18\x01\x12\x17\n\x06\x66lavor\x18\x06 \x01(\x0e\x32\x07.Flavor\x12*\n\x0fstatic_analysis\x18\x07 \x01(\x0b\x32\x0f.StaticAnalysisH\x00\x12,\n\x10\x64ynamic_analysis\x18\x08 \x01(\x0b\x32\x10.DynamicAnalysisH\x00\x42\x0b\n\tanalysis2\"[\n\x0fMnemonicFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x10\n\x08mnemonic\x18\x02 \x01(\t\x12\x18\n\x0b\x64\x65scription\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"]\n\x10NamespaceFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x11\n\tnamespace\x18\x02 \x01(\t\x12\x18\n\x0b\x64\x65scription\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"`\n\rNumberFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x17\n\x06number\x18\x02 \x01(\x0b\x32\x07.Number\x12\x18\n\x0b\x64\x65scription\x18\x05 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"O\n\tOSFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\n\n\x02os\x18\x02 \x01(\t\x12\x18\n\x0b\x64\x65scription\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"a\n\rOffsetFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x18\n\x06offset\x18\x02 \x01(\x0b\x32\x08.Integer\x12\x18\n\x0b\x64\x65scription\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"\x7f\n\x14OperandNumberFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\r\n\x05index\x18\x02 \x01(\r\x12 \n\x0eoperand_number\x18\x03 \x01(\x0b\x32\x08.Integer\x12\x18\n\x0b\x64\x65scription\x18\x04 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"\x7f\n\x14OperandOffsetFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\r\n\x05index\x18\x02 \x01(\r\x12 \n\x0eoperand_offset\x18\x03 \x01(\x0b\x32\x08.Integer\x12\x18\n\x0b\x64\x65scription\x18\x04 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"?\n\x13ProcessFeatureCount\x12\x19\n\x07\x61\x64\x64ress\x18\x01 \x01(\x0b\x32\x08.Address\x12\r\n\x05\x63ount\x18\x02 \x01(\x04\"`\n\rProcessLayout\x12\x19\n\x07\x61\x64\x64ress\x18\x01 \x01(\x0b\x32\x08.Address\x12&\n\x0fmatched_threads\x18\x02 \x03(\x0b\x32\r.ThreadLayout\x12\x0c\n\x04name\x18\x03 \x01(\t\"|\n\x0fPropertyFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x11\n\tproperty_\x18\x02 \x01(\t\x12\x13\n\x06\x61\x63\x63\x65ss\x18\x03 \x01(\tH\x00\x88\x01\x01\x12\x18\n\x0b\x64\x65scription\x18\x04 \x01(\tH\x01\x88\x01\x01\x42\t\n\x07_accessB\x0e\n\x0c_description\"\x7f\n\x0eRangeStatement\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x0b\n\x03min\x18\x02 \x01(\x04\x12\x0b\n\x03max\x18\x03 \x01(\x04\x12\x1b\n\x05\x63hild\x18\x04 \x01(\x0b\x32\x0c.FeatureNode\x12\x18\n\x0b\x64\x65scription\x18\x05 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"U\n\x0cRegexFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\r\n\x05regex\x18\x02 \x01(\t\x12\x18\n\x0b\x64\x65scription\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"\x90\x01\n\x0eResultDocument\x12\x17\n\x04meta\x18\x01 \x01(\x0b\x32\t.Metadata\x12)\n\x05rules\x18\x02 \x03(\x0b\x32\x1a.ResultDocument.RulesEntry\x1a:\n\nRulesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\x1b\n\x05value\x18\x02 \x01(\x0b\x32\x0c.RuleMatches:\x02\x38\x01\"`\n\x0bRuleMatches\x12\x1b\n\x04meta\x18\x01 \x01(\x0b\x32\r.RuleMetadata\x12\x0e\n\x06source\x18\x02 \x01(\t\x12$\n\x07matches\x18\x03 \x03(\x0b\x32\x13.Pair_Address_Match\"\xa7\x02\n\x0cRuleMetadata\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x11\n\tnamespace\x18\x02 \x01(\t\x12\x0f\n\x07\x61uthors\x18\x03 \x03(\t\x12\x19\n\x05scope\x18\x04 \x01(\x0e\x32\x06.ScopeB\x02\x18\x01\x12\x1b\n\x06\x61ttack\x18\x05 \x03(\x0b\x32\x0b.AttackSpec\x12\x15\n\x03mbc\x18\x06 \x03(\x0b\x32\x08.MBCSpec\x12\x12\n\nreferences\x18\x07 \x03(\t\x12\x10\n\x08\x65xamples\x18\x08 \x03(\t\x12\x13\n\x0b\x64\x65scription\x18\t \x01(\t\x12\x0b\n\x03lib\x18\n \x01(\x08\x12\x1b\n\x04maec\x18\x0b \x01(\x0b\x32\r.MaecMetadata\x12\x18\n\x10is_subscope_rule\x18\x0c \x01(\x08\x12\x17\n\x06scopes\x18\r \x01(\x0b\x32\x07.Scopes\"A\n\x06Sample\x12\x0b\n\x03md5\x18\x01 \x01(\t\x12\x0c\n\x04sha1\x18\x02 \x01(\t\x12\x0e\n\x06sha256\x18\x03 \x01(\t\x12\x0c\n\x04path\x18\x04 \x01(\t\"Z\n\x06Scopes\x12\x1b\n\x06static\x18\x01 \x01(\x0e\x32\x06.ScopeH\x00\x88\x01\x01\x12\x1c\n\x07\x64ynamic\x18\x02 \x01(\x0e\x32\x06.ScopeH\x01\x88\x01\x01\x42\t\n\x07_staticB\n\n\x08_dynamic\"Y\n\x0eSectionFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x0f\n\x07section\x18\x02 \x01(\t\x12\x18\n\x0b\x64\x65scription\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"V\n\rSomeStatement\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\r\n\x05\x63ount\x18\x02 \x01(\r\x12\x18\n\x0b\x64\x65scription\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"\xbc\x01\n\rStatementNode\x12\x0c\n\x04type\x18\x01 \x01(\t\x12 \n\x05range\x18\x02 \x01(\x0b\x32\x0f.RangeStatementH\x00\x12\x1e\n\x04some\x18\x03 \x01(\x0b\x32\x0e.SomeStatementH\x00\x12&\n\x08subscope\x18\x04 \x01(\x0b\x32\x12.SubscopeStatementH\x00\x12&\n\x08\x63ompound\x18\x05 \x01(\x0b\x32\x12.CompoundStatementH\x00\x42\x0b\n\tstatement\"\xf6\x01\n\x0eStaticAnalysis\x12\x0e\n\x06\x66ormat\x18\x01 \x01(\t\x12\x0c\n\x04\x61rch\x18\x02 \x01(\t\x12\n\n\x02os\x18\x03 \x01(\t\x12\x11\n\textractor\x18\x04 \x01(\t\x12\r\n\x05rules\x18\x05 \x03(\t\x12\x1e\n\x0c\x62\x61se_address\x18\x06 \x01(\x0b\x32\x08.Address\x12\x1d\n\x06layout\x18\x07 \x01(\x0b\x32\r.StaticLayout\x12,\n\x0e\x66\x65\x61ture_counts\x18\x08 \x01(\x0b\x32\x14.StaticFeatureCounts\x12+\n\x11library_functions\x18\t \x03(\x0b\x32\x10.LibraryFunction\"M\n\x13StaticFeatureCounts\x12\x0c\n\x04\x66ile\x18\x01 \x01(\x04\x12(\n\tfunctions\x18\x02 \x03(\x0b\x32\x15.FunctionFeatureCount\"2\n\x0cStaticLayout\x12\"\n\tfunctions\x18\x01 \x03(\x0b\x32\x0f.FunctionLayout\"W\n\rStringFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x0e\n\x06string\x18\x02 \x01(\t\x12\x18\n\x0b\x64\x65scription\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"b\n\x11SubscopeStatement\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x15\n\x05scope\x18\x02 \x01(\x0e\x32\x06.Scope\x12\x18\n\x0b\x64\x65scription\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"]\n\x10SubstringFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x11\n\tsubstring\x18\x02 \x01(\t\x12\x18\n\x0b\x64\x65scription\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\")\n\x0cThreadLayout\x12\x19\n\x07\x61\x64\x64ress\x18\x01 \x01(\x0b\x32\x08.Address\"&\n\tAddresses\x12\x19\n\x07\x61\x64\x64ress\x18\x01 \x03(\x0b\x32\x08.Address\"F\n\x12Pair_Address_Match\x12\x19\n\x07\x61\x64\x64ress\x18\x01 \x01(\x0b\x32\x08.Address\x12\x15\n\x05match\x18\x02 \x01(\x0b\x32\x06.Match\"7\n\x0cToken_Offset\x12\x17\n\x05token\x18\x01 \x01(\x0b\x32\x08.Integer\x12\x0e\n\x06offset\x18\x02 \x01(\x04\"9\n\x08Ppid_Pid\x12\x16\n\x04ppid\x18\x01 \x01(\x0b\x32\x08.Integer\x12\x15\n\x03pid\x18\x02 \x01(\x0b\x32\x08.Integer\"T\n\x0cPpid_Pid_Tid\x12\x16\n\x04ppid\x18\x01 \x01(\x0b\x32\x08.Integer\x12\x15\n\x03pid\x18\x02 \x01(\x0b\x32\x08.Integer\x12\x15\n\x03tid\x18\x03 \x01(\x0b\x32\x08.Integer\"m\n\x0fPpid_Pid_Tid_Id\x12\x16\n\x04ppid\x18\x01 \x01(\x0b\x32\x08.Integer\x12\x15\n\x03pid\x18\x02 \x01(\x0b\x32\x08.Integer\x12\x15\n\x03tid\x18\x03 \x01(\x0b\x32\x08.Integer\x12\x14\n\x02id\x18\x04 \x01(\x0b\x32\x08.Integer\",\n\x07Integer\x12\x0b\n\x01u\x18\x01 \x01(\x04H\x00\x12\x0b\n\x01i\x18\x02 \x01(\x12H\x00\x42\x07\n\x05value\"8\n\x06Number\x12\x0b\n\x01u\x18\x01 \x01(\x04H\x00\x12\x0b\n\x01i\x18\x02 \x01(\x12H\x00\x12\x0b\n\x01\x66\x18\x03 \x01(\x01H\x00\x42\x07\n\x05value*\x92\x02\n\x0b\x41\x64\x64ressType\x12\x1b\n\x17\x41\x44\x44RESSTYPE_UNSPECIFIED\x10\x00\x12\x18\n\x14\x41\x44\x44RESSTYPE_ABSOLUTE\x10\x01\x12\x18\n\x14\x41\x44\x44RESSTYPE_RELATIVE\x10\x02\x12\x14\n\x10\x41\x44\x44RESSTYPE_FILE\x10\x03\x12\x18\n\x14\x41\x44\x44RESSTYPE_DN_TOKEN\x10\x04\x12\x1f\n\x1b\x41\x44\x44RESSTYPE_DN_TOKEN_OFFSET\x10\x05\x12\x1a\n\x16\x41\x44\x44RESSTYPE_NO_ADDRESS\x10\x06\x12\x17\n\x13\x41\x44\x44RESSTYPE_PROCESS\x10\x07\x12\x16\n\x12\x41\x44\x44RESSTYPE_THREAD\x10\x08\x12\x14\n\x10\x41\x44\x44RESSTYPE_CALL\x10\t*G\n\x06\x46lavor\x12\x16\n\x12\x46LAVOR_UNSPECIFIED\x10\x00\x12\x11\n\rFLAVOR_STATIC\x10\x01\x12\x12\n\x0e\x46LAVOR_DYNAMIC\x10\x02*\xa5\x01\n\x05Scope\x12\x15\n\x11SCOPE_UNSPECIFIED\x10\x00\x12\x0e\n\nSCOPE_FILE\x10\x01\x12\x12\n\x0eSCOPE_FUNCTION\x10\x02\x12\x15\n\x11SCOPE_BASIC_BLOCK\x10\x03\x12\x15\n\x11SCOPE_INSTRUCTION\x10\x04\x12\x11\n\rSCOPE_PROCESS\x10\x05\x12\x10\n\x0cSCOPE_THREAD\x10\x06\x12\x0e\n\nSCOPE_CALL\x10\x07\x62\x06proto3') _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'capa.render.proto.capa_pb2', globals()) @@ -28,12 +28,12 @@ _RESULTDOCUMENT_RULESENTRY._serialized_options = b'8\001' _RULEMETADATA.fields_by_name['scope']._options = None _RULEMETADATA.fields_by_name['scope']._serialized_options = b'\030\001' - _ADDRESSTYPE._serialized_start=7510 - _ADDRESSTYPE._serialized_end=7784 - _FLAVOR._serialized_start=7786 - _FLAVOR._serialized_end=7857 - _SCOPE._serialized_start=7860 - _SCOPE._serialized_end=8025 + _ADDRESSTYPE._serialized_start=7524 + _ADDRESSTYPE._serialized_end=7798 + _FLAVOR._serialized_start=7800 + _FLAVOR._serialized_end=7871 + _SCOPE._serialized_start=7874 + _SCOPE._serialized_end=8039 _APIFEATURE._serialized_start=32 _APIFEATURE._serialized_end=113 _ADDRESS._serialized_start=116 @@ -111,59 +111,59 @@ _PROCESSFEATURECOUNT._serialized_start=4710 _PROCESSFEATURECOUNT._serialized_end=4773 _PROCESSLAYOUT._serialized_start=4775 - _PROCESSLAYOUT._serialized_end=4857 - _PROPERTYFEATURE._serialized_start=4859 - _PROPERTYFEATURE._serialized_end=4983 - _RANGESTATEMENT._serialized_start=4985 - _RANGESTATEMENT._serialized_end=5112 - _REGEXFEATURE._serialized_start=5114 - _REGEXFEATURE._serialized_end=5199 - _RESULTDOCUMENT._serialized_start=5202 - _RESULTDOCUMENT._serialized_end=5346 - _RESULTDOCUMENT_RULESENTRY._serialized_start=5288 - _RESULTDOCUMENT_RULESENTRY._serialized_end=5346 - _RULEMATCHES._serialized_start=5348 - _RULEMATCHES._serialized_end=5444 - _RULEMETADATA._serialized_start=5447 - _RULEMETADATA._serialized_end=5742 - _SAMPLE._serialized_start=5744 - _SAMPLE._serialized_end=5809 - _SCOPES._serialized_start=5811 - _SCOPES._serialized_end=5901 - _SECTIONFEATURE._serialized_start=5903 - _SECTIONFEATURE._serialized_end=5992 - _SOMESTATEMENT._serialized_start=5994 - _SOMESTATEMENT._serialized_end=6080 - _STATEMENTNODE._serialized_start=6083 - _STATEMENTNODE._serialized_end=6271 - _STATICANALYSIS._serialized_start=6274 - _STATICANALYSIS._serialized_end=6520 - _STATICFEATURECOUNTS._serialized_start=6522 - _STATICFEATURECOUNTS._serialized_end=6599 - _STATICLAYOUT._serialized_start=6601 - _STATICLAYOUT._serialized_end=6651 - _STRINGFEATURE._serialized_start=6653 - _STRINGFEATURE._serialized_end=6740 - _SUBSCOPESTATEMENT._serialized_start=6742 - _SUBSCOPESTATEMENT._serialized_end=6840 - _SUBSTRINGFEATURE._serialized_start=6842 - _SUBSTRINGFEATURE._serialized_end=6935 - _THREADLAYOUT._serialized_start=6937 - _THREADLAYOUT._serialized_end=6978 - _ADDRESSES._serialized_start=6980 - _ADDRESSES._serialized_end=7018 - _PAIR_ADDRESS_MATCH._serialized_start=7020 - _PAIR_ADDRESS_MATCH._serialized_end=7090 - _TOKEN_OFFSET._serialized_start=7092 - _TOKEN_OFFSET._serialized_end=7147 - _PPID_PID._serialized_start=7149 - _PPID_PID._serialized_end=7206 - _PPID_PID_TID._serialized_start=7208 - _PPID_PID_TID._serialized_end=7292 - _PPID_PID_TID_ID._serialized_start=7294 - _PPID_PID_TID_ID._serialized_end=7403 - _INTEGER._serialized_start=7405 - _INTEGER._serialized_end=7449 - _NUMBER._serialized_start=7451 - _NUMBER._serialized_end=7507 + _PROCESSLAYOUT._serialized_end=4871 + _PROPERTYFEATURE._serialized_start=4873 + _PROPERTYFEATURE._serialized_end=4997 + _RANGESTATEMENT._serialized_start=4999 + _RANGESTATEMENT._serialized_end=5126 + _REGEXFEATURE._serialized_start=5128 + _REGEXFEATURE._serialized_end=5213 + _RESULTDOCUMENT._serialized_start=5216 + _RESULTDOCUMENT._serialized_end=5360 + _RESULTDOCUMENT_RULESENTRY._serialized_start=5302 + _RESULTDOCUMENT_RULESENTRY._serialized_end=5360 + _RULEMATCHES._serialized_start=5362 + _RULEMATCHES._serialized_end=5458 + _RULEMETADATA._serialized_start=5461 + _RULEMETADATA._serialized_end=5756 + _SAMPLE._serialized_start=5758 + _SAMPLE._serialized_end=5823 + _SCOPES._serialized_start=5825 + _SCOPES._serialized_end=5915 + _SECTIONFEATURE._serialized_start=5917 + _SECTIONFEATURE._serialized_end=6006 + _SOMESTATEMENT._serialized_start=6008 + _SOMESTATEMENT._serialized_end=6094 + _STATEMENTNODE._serialized_start=6097 + _STATEMENTNODE._serialized_end=6285 + _STATICANALYSIS._serialized_start=6288 + _STATICANALYSIS._serialized_end=6534 + _STATICFEATURECOUNTS._serialized_start=6536 + _STATICFEATURECOUNTS._serialized_end=6613 + _STATICLAYOUT._serialized_start=6615 + _STATICLAYOUT._serialized_end=6665 + _STRINGFEATURE._serialized_start=6667 + _STRINGFEATURE._serialized_end=6754 + _SUBSCOPESTATEMENT._serialized_start=6756 + _SUBSCOPESTATEMENT._serialized_end=6854 + _SUBSTRINGFEATURE._serialized_start=6856 + _SUBSTRINGFEATURE._serialized_end=6949 + _THREADLAYOUT._serialized_start=6951 + _THREADLAYOUT._serialized_end=6992 + _ADDRESSES._serialized_start=6994 + _ADDRESSES._serialized_end=7032 + _PAIR_ADDRESS_MATCH._serialized_start=7034 + _PAIR_ADDRESS_MATCH._serialized_end=7104 + _TOKEN_OFFSET._serialized_start=7106 + _TOKEN_OFFSET._serialized_end=7161 + _PPID_PID._serialized_start=7163 + _PPID_PID._serialized_end=7220 + _PPID_PID_TID._serialized_start=7222 + _PPID_PID_TID._serialized_end=7306 + _PPID_PID_TID_ID._serialized_start=7308 + _PPID_PID_TID_ID._serialized_end=7417 + _INTEGER._serialized_start=7419 + _INTEGER._serialized_end=7463 + _NUMBER._serialized_start=7465 + _NUMBER._serialized_end=7521 # @@protoc_insertion_point(module_scope) diff --git a/capa/render/proto/capa_pb2.pyi b/capa/render/proto/capa_pb2.pyi index f90c26b6a..05022e501 100644 --- a/capa/render/proto/capa_pb2.pyi +++ b/capa/render/proto/capa_pb2.pyi @@ -1128,18 +1128,21 @@ class ProcessLayout(google.protobuf.message.Message): ADDRESS_FIELD_NUMBER: builtins.int MATCHED_THREADS_FIELD_NUMBER: builtins.int + NAME_FIELD_NUMBER: builtins.int @property def address(self) -> global___Address: ... @property def matched_threads(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___ThreadLayout]: ... + name: builtins.str def __init__( self, *, address: global___Address | None = ..., matched_threads: collections.abc.Iterable[global___ThreadLayout] | None = ..., + name: builtins.str = ..., ) -> None: ... def HasField(self, field_name: typing_extensions.Literal["address", b"address"]) -> builtins.bool: ... - def ClearField(self, field_name: typing_extensions.Literal["address", b"address", "matched_threads", b"matched_threads"]) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["address", b"address", "matched_threads", b"matched_threads", "name", b"name"]) -> None: ... global___ProcessLayout = ProcessLayout diff --git a/capa/render/vverbose.py b/capa/render/vverbose.py index af0268c87..408b01f6b 100644 --- a/capa/render/vverbose.py +++ b/capa/render/vverbose.py @@ -6,6 +6,7 @@ # is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and limitations under the License. +import logging from typing import Dict, Iterable, Optional import tabulate @@ -22,8 +23,39 @@ from capa.rules import RuleSet from capa.engine import MatchResults +logger = logging.getLogger(__name__) -def render_locations(ostream, locations: Iterable[frz.Address]): + +def _get_process_name(layout: rd.DynamicLayout, addr: frz.Address) -> str: + for p in layout.processes: + if p.address == addr: + return p.name + logger.debug("name not found for process: %s", addr) + return "" + + +def render_process(layout: rd.DynamicLayout, addr: frz.Address) -> str: + process = addr.to_capa() + assert isinstance(process, capa.features.address.ProcessAddress) + name = _get_process_name(layout, addr) + return f"{name}[{process.pid}]" + + +def render_thread(layout: rd.DynamicLayout, addr: frz.Address) -> str: + thread = addr.to_capa() + assert isinstance(thread, capa.features.address.ThreadAddress) + name = _get_process_name(layout, frz.Address.from_capa(thread.process)) + return f"{name}[{thread.process.pid}:{thread.tid}]" + + +def render_call(layout: rd.DynamicLayout, addr: frz.Address) -> str: + call = addr.to_capa() + assert isinstance(call, capa.features.address.DynamicCallAddress) + name = _get_process_name(layout, frz.Address.from_capa(call.thread.process)) + return f"{name}[{call.thread.process.pid}:{call.thread.tid}] XXX[{call.id}](A, B, C)" + + +def render_locations(ostream, layout: rd.Layout, locations: Iterable[frz.Address]): import capa.render.verbose as v # its possible to have an empty locations array here, @@ -35,9 +67,24 @@ def render_locations(ostream, locations: Iterable[frz.Address]): return ostream.write(" @ ") + location0 = locations[0] if len(locations) == 1: - ostream.write(v.format_address(locations[0])) + location = locations[0] + + if location.type == frz.AddressType.CALL: + assert isinstance(layout, rd.DynamicLayout) + ostream.write(render_call(layout, location)) + + else: + ostream.write(v.format_address(locations[0])) + + elif location0.type == frz.AddressType.CALL and len(locations) > 1: + location = locations[0] + + assert isinstance(layout, rd.DynamicLayout) + ostream.write(render_call(layout, location)) + ostream.write(f", and {(len(locations) - 1)} more...") elif len(locations) > 4: # don't display too many locations, because it becomes very noisy. @@ -52,7 +99,7 @@ def render_locations(ostream, locations: Iterable[frz.Address]): raise RuntimeError("unreachable") -def render_statement(ostream, match: rd.Match, statement: rd.Statement, indent=0): +def render_statement(ostream, layout: rd.Layout, match: rd.Match, statement: rd.Statement, indent=0): ostream.write(" " * indent) if isinstance(statement, rd.SubscopeStatement): @@ -114,7 +161,7 @@ def render_statement(ostream, match: rd.Match, statement: rd.Statement, indent=0 if statement.description: ostream.write(f" = {statement.description}") - render_locations(ostream, match.locations) + render_locations(ostream, layout, match.locations) ostream.writeln("") else: @@ -125,7 +172,7 @@ def render_string_value(s: str) -> str: return f'"{capa.features.common.escape_string(s)}"' -def render_feature(ostream, match: rd.Match, feature: frzf.Feature, indent=0): +def render_feature(ostream, layout: rd.Layout, match: rd.Match, feature: frzf.Feature, indent=0): ostream.write(" " * indent) key = feature.type @@ -177,7 +224,7 @@ def render_feature(ostream, match: rd.Match, feature: frzf.Feature, indent=0): ostream.write(feature.description) if not isinstance(feature, (frzf.OSFeature, frzf.ArchFeature, frzf.FormatFeature)): - render_locations(ostream, match.locations) + render_locations(ostream, layout, match.locations) ostream.write("\n") else: # like: @@ -193,15 +240,15 @@ def render_feature(ostream, match: rd.Match, feature: frzf.Feature, indent=0): ostream.write(" " * (indent + 1)) ostream.write("- ") ostream.write(rutils.bold2(render_string_value(capture))) - render_locations(ostream, locations) + render_locations(ostream, layout, locations) ostream.write("\n") -def render_node(ostream, match: rd.Match, node: rd.Node, indent=0): +def render_node(ostream, layout: rd.Layout, match: rd.Match, node: rd.Node, indent=0): if isinstance(node, rd.StatementNode): - render_statement(ostream, match, node.statement, indent=indent) + render_statement(ostream, layout, match, node.statement, indent=indent) elif isinstance(node, rd.FeatureNode): - render_feature(ostream, match, node.feature, indent=indent) + render_feature(ostream, layout, match, node.feature, indent=indent) else: raise RuntimeError("unexpected node type: " + str(node)) @@ -214,7 +261,7 @@ def render_node(ostream, match: rd.Match, node: rd.Node, indent=0): MODE_FAILURE = "failure" -def render_match(ostream, match: rd.Match, indent=0, mode=MODE_SUCCESS): +def render_match(ostream, layout: rd.Layout, match: rd.Match, indent=0, mode=MODE_SUCCESS): child_mode = mode if mode == MODE_SUCCESS: # display only nodes that evaluated successfully. @@ -246,10 +293,10 @@ def render_match(ostream, match: rd.Match, indent=0, mode=MODE_SUCCESS): else: raise RuntimeError("unexpected mode: " + mode) - render_node(ostream, match, match.node, indent=indent) + render_node(ostream, layout, match, match.node, indent=indent) for child in match.children: - render_match(ostream, child, indent=indent + 1, mode=child_mode) + render_match(ostream, layout, child, indent=indent + 1, mode=child_mode) def render_rules(ostream, doc: rd.ResultDocument): @@ -361,32 +408,47 @@ def render_rules(ostream, doc: rd.ResultDocument): # so, lets be explicit about our assumptions and raise an exception if they fail. raise RuntimeError(f"unexpected file scope match count: {len(matches)}") first_address, first_match = matches[0] - render_match(ostream, first_match, indent=0) + render_match(ostream, doc.meta.analysis.layout, first_match, indent=0) else: for location, match in sorted(doc.rules[rule.meta.name].matches): if doc.meta.flavor == rd.Flavor.STATIC: assert rule.meta.scopes.static is not None ostream.write(rule.meta.scopes.static.value) + ostream.write(" @ ") + ostream.write(capa.render.verbose.format_address(location)) + + if rule.meta.scopes.static == capa.rules.Scope.BASIC_BLOCK: + ostream.write( + " in function " + + capa.render.verbose.format_address( + frz.Address.from_capa(functions_by_bb[location.to_capa()]) + ) + ) + elif doc.meta.flavor == rd.Flavor.DYNAMIC: assert rule.meta.scopes.dynamic is not None + assert isinstance(doc.meta.analysis.layout, rd.DynamicLayout) + ostream.write(rule.meta.scopes.dynamic.value) - else: - capa.helpers.assert_never(doc.meta.flavor) + # TODO(mr-tz): process rendering should use human-readable name + # https://github.com/mandiant/capa/issues/1816 - # TODO(mr-tz): process rendering should use human-readable name - # https://github.com/mandiant/capa/issues/1816 + ostream.write(" @ ") - ostream.write(" @ ") - ostream.write(capa.render.verbose.format_address(location)) + if rule.meta.scopes.dynamic == capa.rules.Scope.PROCESS: + ostream.write(render_process(doc.meta.analysis.layout, location)) + elif rule.meta.scopes.dynamic == capa.rules.Scope.THREAD: + ostream.write(render_thread(doc.meta.analysis.layout, location)) + elif rule.meta.scopes.dynamic == capa.rules.Scope.CALL: + ostream.write(render_call(doc.meta.analysis.layout, location)) + else: + capa.helpers.assert_never(rule.meta.scopes.dynamic) - if doc.meta.flavor == rd.Flavor.STATIC and rule.meta.scopes.static == capa.rules.Scope.BASIC_BLOCK: - ostream.write( - " in function " - + capa.render.verbose.format_address(frz.Address.from_capa(functions_by_bb[location.to_capa()])) - ) + else: + capa.helpers.assert_never(doc.meta.flavor) ostream.write("\n") - render_match(ostream, match, indent=1) + render_match(ostream, doc.meta.analysis.layout, match, indent=1) if rule.meta.lib: # only show first match break diff --git a/tests/test_freeze_dynamic.py b/tests/test_freeze_dynamic.py index d7f045bcc..a5ae19262 100644 --- a/tests/test_freeze_dynamic.py +++ b/tests/test_freeze_dynamic.py @@ -45,6 +45,7 @@ ], processes={ ProcessAddress(pid=1): capa.features.extractors.null.ProcessFeatures( + name="explorer.exe", features=[], threads={ ThreadAddress(ProcessAddress(pid=1), tid=1): capa.features.extractors.null.ThreadFeatures( From 393b0e63f0d860eeb74b05e9def09d5d79923882 Mon Sep 17 00:00:00 2001 From: Willi Ballenthin Date: Fri, 20 Oct 2023 12:39:28 +0000 Subject: [PATCH 03/17] layout: capture process name --- capa/render/result_document.py | 1 + 1 file changed, 1 insertion(+) diff --git a/capa/render/result_document.py b/capa/render/result_document.py index 1b1ef479e..da8185cc2 100644 --- a/capa/render/result_document.py +++ b/capa/render/result_document.py @@ -55,6 +55,7 @@ class ThreadLayout(Model): class ProcessLayout(Model): address: frz.Address + name: str matched_threads: Tuple[ThreadLayout, ...] From 9e6919f33cca925292a7a37f9e95a11383af06ca Mon Sep 17 00:00:00 2001 From: Willi Ballenthin Date: Fri, 20 Oct 2023 14:21:13 +0000 Subject: [PATCH 04/17] layout: capture call names so that they can be rendered to output --- capa/features/extractors/base_extractor.py | 10 ++++ capa/features/extractors/cape/extractor.py | 39 +++++++++++++++- capa/features/extractors/null.py | 4 ++ capa/features/freeze/__init__.py | 6 ++- capa/main.py | 54 +++++++++++++++++----- capa/render/proto/__init__.py | 27 ++++++++++- capa/render/proto/capa.proto | 6 +++ capa/render/proto/capa_pb2.py | 52 +++++++++++---------- capa/render/proto/capa_pb2.pyi | 26 ++++++++++- capa/render/result_document.py | 6 +++ capa/render/vverbose.py | 25 +++++++++- tests/test_freeze_dynamic.py | 2 + tests/test_render.py | 4 +- 13 files changed, 217 insertions(+), 44 deletions(-) diff --git a/capa/features/extractors/base_extractor.py b/capa/features/extractors/base_extractor.py index e78feab35..6252d7470 100644 --- a/capa/features/extractors/base_extractor.py +++ b/capa/features/extractors/base_extractor.py @@ -456,5 +456,15 @@ def extract_call_features( """ raise NotImplementedError() + @abc.abstractmethod + def get_call_name(self, ph: ProcessHandle, th: ThreadHandle, ch: CallHandle) -> str: + """ + Returns the human-readable name for the given call, + such as as rendered API log entry, like: + + Foo(1, "two", b"\x00\x11") -> -1 + """ + raise NotImplementedError() + FeatureExtractor: TypeAlias = Union[StaticFeatureExtractor, DynamicFeatureExtractor] diff --git a/capa/features/extractors/cape/extractor.py b/capa/features/extractors/cape/extractor.py index d670d6d82..d490c4468 100644 --- a/capa/features/extractors/cape/extractor.py +++ b/capa/features/extractors/cape/extractor.py @@ -17,7 +17,7 @@ from capa.exceptions import EmptyReportError, UnsupportedFormatError from capa.features.common import Feature, Characteristic from capa.features.address import NO_ADDRESS, Address, AbsoluteVirtualAddress, _NoAddress -from capa.features.extractors.cape.models import Static, Process, CapeReport +from capa.features.extractors.cape.models import Call, Static, Process, CapeReport from capa.features.extractors.base_extractor import ( CallHandle, SampleHashes, @@ -82,6 +82,43 @@ def extract_call_features( ) -> Iterator[Tuple[Feature, Address]]: yield from capa.features.extractors.cape.call.extract_features(ph, th, ch) + def get_call_name(self, ph, th, ch) -> str: + call: Call = ch.inner + + parts = [] + parts.append(call.api) + parts.append("(") + for argument in call.arguments: + parts.append(argument.name) + parts.append("=") + + if argument.pretty_value: + parts.append(argument.pretty_value) + else: + if isinstance(argument.value, int): + parts.append(hex(argument.value)) + elif isinstance(argument.value, str): + parts.append('"') + parts.append(argument.value) + parts.append('"') + elif isinstance(argument.value, list): + pass + else: + capa.helpers.assert_never(argument.value) + + parts.append(", ") + if call.arguments: + # remove the trailing comma + parts.pop() + parts.append(")") + parts.append(" -> ") + if call.pretty_return: + parts.append(call.pretty_return) + else: + parts.append(hex(call.return_)) + + return "".join(parts) + @classmethod def from_report(cls, report: Dict) -> "CapeExtractor": cr = CapeReport.model_validate(report) diff --git a/capa/features/extractors/null.py b/capa/features/extractors/null.py index 82d5c3721..37bd914c9 100644 --- a/capa/features/extractors/null.py +++ b/capa/features/extractors/null.py @@ -97,6 +97,7 @@ def extract_insn_features(self, f, bb, insn): @dataclass class CallFeatures: + name: str features: List[Tuple[Address, Feature]] @@ -162,5 +163,8 @@ def extract_call_features(self, ph, th, ch): for address, feature in self.processes[ph.address].threads[th.address].calls[ch.address].features: yield feature, address + def get_call_name(self, ph, th, ch) -> str: + return self.processes[ph.address].threads[th.address].calls[ch.address].name + NullFeatureExtractor: TypeAlias = Union[NullStaticFeatureExtractor, NullDynamicFeatureExtractor] diff --git a/capa/features/freeze/__init__.py b/capa/features/freeze/__init__.py index 230b8c3b4..9e3f73310 100644 --- a/capa/features/freeze/__init__.py +++ b/capa/features/freeze/__init__.py @@ -289,6 +289,7 @@ class FunctionFeatures(BaseModel): class CallFeatures(BaseModel): address: Address + name: str features: Tuple[CallFeature, ...] @@ -490,6 +491,7 @@ def dumps_dynamic(extractor: DynamicFeatureExtractor) -> str: calls = [] for call in extractor.get_calls(p, t): caddr = Address.from_capa(call.address) + cname = extractor.get_call_name(p, t, call) cfeatures = [ CallFeature( call=caddr, @@ -502,6 +504,7 @@ def dumps_dynamic(extractor: DynamicFeatureExtractor) -> str: calls.append( CallFeatures( address=caddr, + name=cname, features=tuple(cfeatures), ) ) @@ -605,7 +608,8 @@ def loads_dynamic(s: str) -> DynamicFeatureExtractor: features=[(fe.address.to_capa(), fe.feature.to_capa()) for fe in t.features], calls={ c.address.to_capa(): null.CallFeatures( - features=[(fe.address.to_capa(), fe.feature.to_capa()) for fe in c.features] + name=c.name, + features=[(fe.address.to_capa(), fe.feature.to_capa()) for fe in c.features], ) for c in t.calls }, diff --git a/capa/main.py b/capa/main.py index 2e2ed6336..3b238a33c 100644 --- a/capa/main.py +++ b/capa/main.py @@ -20,7 +20,7 @@ import itertools import contextlib import collections -from typing import Any, Dict, List, Tuple, Callable, Optional +from typing import Any, Set, Dict, List, Tuple, Callable, Optional from pathlib import Path import halo @@ -1050,7 +1050,7 @@ def collect_metadata( ) -def compute_dynamic_layout(rules, extractor: DynamicFeatureExtractor, capabilities) -> rdoc.DynamicLayout: +def compute_dynamic_layout(rules, extractor: DynamicFeatureExtractor, capabilities: MatchResults) -> rdoc.DynamicLayout: """ compute a metadata structure that links threads to the processes in which they're found. @@ -1060,23 +1060,43 @@ def compute_dynamic_layout(rules, extractor: DynamicFeatureExtractor, capabiliti a large amount of un-referenced data. """ assert isinstance(extractor, DynamicFeatureExtractor) + + matched_threads: Set[Address] = set() + for rule_name, matches in capabilities.items(): + rule = rules[rule_name] + if capa.rules.Scope.THREAD in rule.scopes: + for addr, _ in matches: + matched_threads.add(addr) + + matched_calls: Set[Address] = set() + + def result_rec(result: capa.features.common.Result): + for loc in result.locations: + if isinstance(loc, capa.features.address.DynamicCallAddress): + matched_calls.add(loc) + for child in result.children: + result_rec(child) + + for matches in capabilities.values(): + for _, result in matches: + result_rec(result) + processes_by_thread: Dict[Address, Address] = {} threads_by_processes: Dict[Address, List[Address]] = {} names_by_process: Dict[Address, str] = {} + calls_by_thread: Dict[Address, List[Address]] = {} + names_by_call: Dict[Address, str] = {} for p in extractor.get_processes(): threads_by_processes[p.address] = [] names_by_process[p.address] = extractor.get_process_name(p) for t in extractor.get_threads(p): processes_by_thread[t.address] = p.address threads_by_processes[p.address].append(t.address) - - matched_threads = set() - for rule_name, matches in capabilities.items(): - rule = rules[rule_name] - if capa.rules.Scope.THREAD in rule.scopes: - for addr, _ in matches: - assert addr in processes_by_thread - matched_threads.add(addr) + calls_by_thread[t.address] = [] + for c in extractor.get_calls(p, t): + calls_by_thread[t.address].append(c.address) + if c.address in matched_calls: + names_by_call[c.address] = extractor.get_call_name(p, t, c) layout = rdoc.DynamicLayout( processes=tuple( @@ -1084,7 +1104,19 @@ def compute_dynamic_layout(rules, extractor: DynamicFeatureExtractor, capabiliti address=frz.Address.from_capa(p), name=names_by_process[p], matched_threads=tuple( - rdoc.ThreadLayout(address=frz.Address.from_capa(t)) for t in threads if t in matched_threads + rdoc.ThreadLayout( + address=frz.Address.from_capa(t), + matched_calls=tuple( + rdoc.CallLayout( + address=frz.Address.from_capa(c), + name=names_by_call[c], + ) + for c in calls_by_thread[t] + if c in matched_calls + ), + ) + for t in threads + if t in matched_threads ) # this object is open to extension in the future, # such as with the function name, etc. ) diff --git a/capa/render/proto/__init__.py b/capa/render/proto/__init__.py index ea3e2e11f..ed4c690e1 100644 --- a/capa/render/proto/__init__.py +++ b/capa/render/proto/__init__.py @@ -225,7 +225,19 @@ def dynamic_analysis_to_pb2(analysis: rd.DynamicAnalysis) -> capa_pb2.DynamicAna capa_pb2.ProcessLayout( address=addr_to_pb2(p.address), name=p.name, - matched_threads=[capa_pb2.ThreadLayout(address=addr_to_pb2(t.address)) for t in p.matched_threads], + matched_threads=[ + capa_pb2.ThreadLayout( + address=addr_to_pb2(t.address), + matched_calls=[ + capa_pb2.CallLayout( + address=addr_to_pb2(c.address), + name=c.name, + ) + for c in t.matched_calls + ], + ) + for t in p.matched_threads + ], ) for p in analysis.layout.processes ] @@ -708,7 +720,18 @@ def dynamic_analysis_from_pb2(analysis: capa_pb2.DynamicAnalysis) -> rd.DynamicA address=addr_from_pb2(p.address), name=p.name, matched_threads=tuple( - [rd.ThreadLayout(address=addr_from_pb2(t.address)) for t in p.matched_threads] + [ + rd.ThreadLayout( + address=addr_from_pb2(t.address), + matched_calls=tuple( + [ + rd.CallLayout(address=addr_from_pb2(c.address), name=c.name) + for c in t.matched_calls + ] + ), + ) + for t in p.matched_threads + ] ), ) for p in analysis.layout.processes diff --git a/capa/render/proto/capa.proto b/capa/render/proto/capa.proto index 41f2c5cb0..904bc04fe 100644 --- a/capa/render/proto/capa.proto +++ b/capa/render/proto/capa.proto @@ -430,8 +430,14 @@ message SubstringFeature { optional string description = 3; } +message CallLayout { + Address address = 1; + string name = 2; +} + message ThreadLayout { Address address = 1; + repeated CallLayout matched_calls = 2; } message Addresses { repeated Address address = 1; } diff --git a/capa/render/proto/capa_pb2.py b/capa/render/proto/capa_pb2.py index bd422bb3d..fdee72927 100644 --- a/capa/render/proto/capa_pb2.py +++ b/capa/render/proto/capa_pb2.py @@ -13,7 +13,7 @@ -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1c\x63\x61pa/render/proto/capa.proto\"Q\n\nAPIFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x0b\n\x03\x61pi\x18\x02 \x01(\t\x12\x18\n\x0b\x64\x65scription\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"\xdf\x01\n\x07\x41\x64\x64ress\x12\x1a\n\x04type\x18\x01 \x01(\x0e\x32\x0c.AddressType\x12\x15\n\x01v\x18\x02 \x01(\x0b\x32\x08.IntegerH\x00\x12%\n\x0ctoken_offset\x18\x03 \x01(\x0b\x32\r.Token_OffsetH\x00\x12\x1d\n\x08ppid_pid\x18\x04 \x01(\x0b\x32\t.Ppid_PidH\x00\x12%\n\x0cppid_pid_tid\x18\x05 \x01(\x0b\x32\r.Ppid_Pid_TidH\x00\x12+\n\x0fppid_pid_tid_id\x18\x06 \x01(\x0b\x32\x10.Ppid_Pid_Tid_IdH\x00\x42\x07\n\x05value\"\xe4\x01\n\x08\x41nalysis\x12\x0e\n\x06\x66ormat\x18\x01 \x01(\t\x12\x0c\n\x04\x61rch\x18\x02 \x01(\t\x12\n\n\x02os\x18\x03 \x01(\t\x12\x11\n\textractor\x18\x04 \x01(\t\x12\r\n\x05rules\x18\x05 \x03(\t\x12\x1e\n\x0c\x62\x61se_address\x18\x06 \x01(\x0b\x32\x08.Address\x12\x17\n\x06layout\x18\x07 \x01(\x0b\x32\x07.Layout\x12&\n\x0e\x66\x65\x61ture_counts\x18\x08 \x01(\x0b\x32\x0e.FeatureCounts\x12+\n\x11library_functions\x18\t \x03(\x0b\x32\x10.LibraryFunction\"S\n\x0b\x41rchFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x0c\n\x04\x61rch\x18\x02 \x01(\t\x12\x18\n\x0b\x64\x65scription\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"`\n\nAttackSpec\x12\r\n\x05parts\x18\x01 \x03(\t\x12\x0e\n\x06tactic\x18\x02 \x01(\t\x12\x11\n\ttechnique\x18\x03 \x01(\t\x12\x14\n\x0csubtechnique\x18\x04 \x01(\t\x12\n\n\x02id\x18\x05 \x01(\t\"K\n\x11\x42\x61sicBlockFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x18\n\x0b\x64\x65scription\x18\x02 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"-\n\x10\x42\x61sicBlockLayout\x12\x19\n\x07\x61\x64\x64ress\x18\x01 \x01(\x0b\x32\x08.Address\"U\n\x0c\x42ytesFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\r\n\x05\x62ytes\x18\x02 \x01(\t\x12\x18\n\x0b\x64\x65scription\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"g\n\x15\x43haracteristicFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x16\n\x0e\x63haracteristic\x18\x02 \x01(\t\x12\x18\n\x0b\x64\x65scription\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"V\n\x0c\x43lassFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x0e\n\x06\x63lass_\x18\x02 \x01(\t\x12\x18\n\x0b\x64\x65scription\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"K\n\x11\x43ompoundStatement\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x18\n\x0b\x64\x65scription\x18\x02 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"\xac\x01\n\x0f\x44ynamicAnalysis\x12\x0e\n\x06\x66ormat\x18\x01 \x01(\t\x12\x0c\n\x04\x61rch\x18\x02 \x01(\t\x12\n\n\x02os\x18\x03 \x01(\t\x12\x11\n\textractor\x18\x04 \x01(\t\x12\r\n\x05rules\x18\x05 \x03(\t\x12\x1e\n\x06layout\x18\x06 \x01(\x0b\x32\x0e.DynamicLayout\x12-\n\x0e\x66\x65\x61ture_counts\x18\x07 \x01(\x0b\x32\x15.DynamicFeatureCounts\"M\n\x14\x44ynamicFeatureCounts\x12\x0c\n\x04\x66ile\x18\x01 \x01(\x04\x12\'\n\tprocesses\x18\x02 \x03(\x0b\x32\x14.ProcessFeatureCount\"2\n\rDynamicLayout\x12!\n\tprocesses\x18\x01 \x03(\x0b\x32\x0e.ProcessLayout\"W\n\rExportFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x0e\n\x06\x65xport\x18\x02 \x01(\t\x12\x18\n\x0b\x64\x65scription\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"G\n\rFeatureCounts\x12\x0c\n\x04\x66ile\x18\x01 \x01(\x04\x12(\n\tfunctions\x18\x02 \x03(\x0b\x32\x15.FunctionFeatureCount\"\xf7\x06\n\x0b\x46\x65\x61tureNode\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x18\n\x02os\x18\x02 \x01(\x0b\x32\n.OSFeatureH\x00\x12\x1c\n\x04\x61rch\x18\x03 \x01(\x0b\x32\x0c.ArchFeatureH\x00\x12 \n\x06\x66ormat\x18\x04 \x01(\x0b\x32\x0e.FormatFeatureH\x00\x12\x1e\n\x05match\x18\x05 \x01(\x0b\x32\r.MatchFeatureH\x00\x12\x30\n\x0e\x63haracteristic\x18\x06 \x01(\x0b\x32\x16.CharacteristicFeatureH\x00\x12 \n\x06\x65xport\x18\x07 \x01(\x0b\x32\x0e.ExportFeatureH\x00\x12!\n\x07import_\x18\x08 \x01(\x0b\x32\x0e.ImportFeatureH\x00\x12\"\n\x07section\x18\t \x01(\x0b\x32\x0f.SectionFeatureH\x00\x12-\n\rfunction_name\x18\n \x01(\x0b\x32\x14.FunctionNameFeatureH\x00\x12&\n\tsubstring\x18\x0b \x01(\x0b\x32\x11.SubstringFeatureH\x00\x12\x1e\n\x05regex\x18\x0c \x01(\x0b\x32\r.RegexFeatureH\x00\x12 \n\x06string\x18\r \x01(\x0b\x32\x0e.StringFeatureH\x00\x12\x1f\n\x06\x63lass_\x18\x0e \x01(\x0b\x32\r.ClassFeatureH\x00\x12&\n\tnamespace\x18\x0f \x01(\x0b\x32\x11.NamespaceFeatureH\x00\x12\x1a\n\x03\x61pi\x18\x10 \x01(\x0b\x32\x0b.APIFeatureH\x00\x12%\n\tproperty_\x18\x11 \x01(\x0b\x32\x10.PropertyFeatureH\x00\x12 \n\x06number\x18\x12 \x01(\x0b\x32\x0e.NumberFeatureH\x00\x12\x1e\n\x05\x62ytes\x18\x13 \x01(\x0b\x32\r.BytesFeatureH\x00\x12 \n\x06offset\x18\x14 \x01(\x0b\x32\x0e.OffsetFeatureH\x00\x12$\n\x08mnemonic\x18\x15 \x01(\x0b\x32\x10.MnemonicFeatureH\x00\x12/\n\x0eoperand_number\x18\x16 \x01(\x0b\x32\x15.OperandNumberFeatureH\x00\x12/\n\x0eoperand_offset\x18\x17 \x01(\x0b\x32\x15.OperandOffsetFeatureH\x00\x12)\n\x0b\x62\x61sic_block\x18\x18 \x01(\x0b\x32\x12.BasicBlockFeatureH\x00\x42\t\n\x07\x66\x65\x61ture\"W\n\rFormatFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x0e\n\x06\x66ormat\x18\x02 \x01(\t\x12\x18\n\x0b\x64\x65scription\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"@\n\x14\x46unctionFeatureCount\x12\x19\n\x07\x61\x64\x64ress\x18\x01 \x01(\x0b\x32\x08.Address\x12\r\n\x05\x63ount\x18\x02 \x01(\x04\"\\\n\x0e\x46unctionLayout\x12\x19\n\x07\x61\x64\x64ress\x18\x01 \x01(\x0b\x32\x08.Address\x12/\n\x14matched_basic_blocks\x18\x02 \x03(\x0b\x32\x11.BasicBlockLayout\"d\n\x13\x46unctionNameFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x15\n\rfunction_name\x18\x02 \x01(\t\x12\x18\n\x0b\x64\x65scription\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"X\n\rImportFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x0f\n\x07import_\x18\x02 \x01(\t\x12\x18\n\x0b\x64\x65scription\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\",\n\x06Layout\x12\"\n\tfunctions\x18\x01 \x03(\x0b\x32\x0f.FunctionLayout\":\n\x0fLibraryFunction\x12\x19\n\x07\x61\x64\x64ress\x18\x01 \x01(\x0b\x32\x08.Address\x12\x0c\n\x04name\x18\x02 \x01(\t\"Y\n\x07MBCSpec\x12\r\n\x05parts\x18\x01 \x03(\t\x12\x11\n\tobjective\x18\x02 \x01(\t\x12\x10\n\x08\x62\x65havior\x18\x03 \x01(\t\x12\x0e\n\x06method\x18\x04 \x01(\t\x12\n\n\x02id\x18\x05 \x01(\t\"\x9a\x01\n\x0cMaecMetadata\x12\x1b\n\x13\x61nalysis_conclusion\x18\x01 \x01(\t\x12\x1e\n\x16\x61nalysis_conclusion_ov\x18\x02 \x01(\t\x12\x16\n\x0emalware_family\x18\x03 \x01(\t\x12\x18\n\x10malware_category\x18\x04 \x01(\t\x12\x1b\n\x13malware_category_ov\x18\x05 \x01(\t\"\x82\x02\n\x05Match\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12#\n\tstatement\x18\x02 \x01(\x0b\x32\x0e.StatementNodeH\x00\x12\x1f\n\x07\x66\x65\x61ture\x18\x03 \x01(\x0b\x32\x0c.FeatureNodeH\x00\x12\x18\n\x08\x63hildren\x18\x05 \x03(\x0b\x32\x06.Match\x12\x1b\n\tlocations\x18\x06 \x03(\x0b\x32\x08.Address\x12&\n\x08\x63\x61ptures\x18\x07 \x03(\x0b\x32\x14.Match.CapturesEntry\x1a;\n\rCapturesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\x19\n\x05value\x18\x02 \x01(\x0b\x32\n.Addresses:\x02\x38\x01\x42\x06\n\x04node\"U\n\x0cMatchFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\r\n\x05match\x18\x02 \x01(\t\x12\x18\n\x0b\x64\x65scription\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"\xf6\x01\n\x08Metadata\x12\x11\n\ttimestamp\x18\x01 \x01(\t\x12\x0f\n\x07version\x18\x02 \x01(\t\x12\x0c\n\x04\x61rgv\x18\x03 \x03(\t\x12\x17\n\x06sample\x18\x04 \x01(\x0b\x32\x07.Sample\x12\x1f\n\x08\x61nalysis\x18\x05 \x01(\x0b\x32\t.AnalysisB\x02\x18\x01\x12\x17\n\x06\x66lavor\x18\x06 \x01(\x0e\x32\x07.Flavor\x12*\n\x0fstatic_analysis\x18\x07 \x01(\x0b\x32\x0f.StaticAnalysisH\x00\x12,\n\x10\x64ynamic_analysis\x18\x08 \x01(\x0b\x32\x10.DynamicAnalysisH\x00\x42\x0b\n\tanalysis2\"[\n\x0fMnemonicFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x10\n\x08mnemonic\x18\x02 \x01(\t\x12\x18\n\x0b\x64\x65scription\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"]\n\x10NamespaceFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x11\n\tnamespace\x18\x02 \x01(\t\x12\x18\n\x0b\x64\x65scription\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"`\n\rNumberFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x17\n\x06number\x18\x02 \x01(\x0b\x32\x07.Number\x12\x18\n\x0b\x64\x65scription\x18\x05 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"O\n\tOSFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\n\n\x02os\x18\x02 \x01(\t\x12\x18\n\x0b\x64\x65scription\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"a\n\rOffsetFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x18\n\x06offset\x18\x02 \x01(\x0b\x32\x08.Integer\x12\x18\n\x0b\x64\x65scription\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"\x7f\n\x14OperandNumberFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\r\n\x05index\x18\x02 \x01(\r\x12 \n\x0eoperand_number\x18\x03 \x01(\x0b\x32\x08.Integer\x12\x18\n\x0b\x64\x65scription\x18\x04 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"\x7f\n\x14OperandOffsetFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\r\n\x05index\x18\x02 \x01(\r\x12 \n\x0eoperand_offset\x18\x03 \x01(\x0b\x32\x08.Integer\x12\x18\n\x0b\x64\x65scription\x18\x04 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"?\n\x13ProcessFeatureCount\x12\x19\n\x07\x61\x64\x64ress\x18\x01 \x01(\x0b\x32\x08.Address\x12\r\n\x05\x63ount\x18\x02 \x01(\x04\"`\n\rProcessLayout\x12\x19\n\x07\x61\x64\x64ress\x18\x01 \x01(\x0b\x32\x08.Address\x12&\n\x0fmatched_threads\x18\x02 \x03(\x0b\x32\r.ThreadLayout\x12\x0c\n\x04name\x18\x03 \x01(\t\"|\n\x0fPropertyFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x11\n\tproperty_\x18\x02 \x01(\t\x12\x13\n\x06\x61\x63\x63\x65ss\x18\x03 \x01(\tH\x00\x88\x01\x01\x12\x18\n\x0b\x64\x65scription\x18\x04 \x01(\tH\x01\x88\x01\x01\x42\t\n\x07_accessB\x0e\n\x0c_description\"\x7f\n\x0eRangeStatement\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x0b\n\x03min\x18\x02 \x01(\x04\x12\x0b\n\x03max\x18\x03 \x01(\x04\x12\x1b\n\x05\x63hild\x18\x04 \x01(\x0b\x32\x0c.FeatureNode\x12\x18\n\x0b\x64\x65scription\x18\x05 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"U\n\x0cRegexFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\r\n\x05regex\x18\x02 \x01(\t\x12\x18\n\x0b\x64\x65scription\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"\x90\x01\n\x0eResultDocument\x12\x17\n\x04meta\x18\x01 \x01(\x0b\x32\t.Metadata\x12)\n\x05rules\x18\x02 \x03(\x0b\x32\x1a.ResultDocument.RulesEntry\x1a:\n\nRulesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\x1b\n\x05value\x18\x02 \x01(\x0b\x32\x0c.RuleMatches:\x02\x38\x01\"`\n\x0bRuleMatches\x12\x1b\n\x04meta\x18\x01 \x01(\x0b\x32\r.RuleMetadata\x12\x0e\n\x06source\x18\x02 \x01(\t\x12$\n\x07matches\x18\x03 \x03(\x0b\x32\x13.Pair_Address_Match\"\xa7\x02\n\x0cRuleMetadata\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x11\n\tnamespace\x18\x02 \x01(\t\x12\x0f\n\x07\x61uthors\x18\x03 \x03(\t\x12\x19\n\x05scope\x18\x04 \x01(\x0e\x32\x06.ScopeB\x02\x18\x01\x12\x1b\n\x06\x61ttack\x18\x05 \x03(\x0b\x32\x0b.AttackSpec\x12\x15\n\x03mbc\x18\x06 \x03(\x0b\x32\x08.MBCSpec\x12\x12\n\nreferences\x18\x07 \x03(\t\x12\x10\n\x08\x65xamples\x18\x08 \x03(\t\x12\x13\n\x0b\x64\x65scription\x18\t \x01(\t\x12\x0b\n\x03lib\x18\n \x01(\x08\x12\x1b\n\x04maec\x18\x0b \x01(\x0b\x32\r.MaecMetadata\x12\x18\n\x10is_subscope_rule\x18\x0c \x01(\x08\x12\x17\n\x06scopes\x18\r \x01(\x0b\x32\x07.Scopes\"A\n\x06Sample\x12\x0b\n\x03md5\x18\x01 \x01(\t\x12\x0c\n\x04sha1\x18\x02 \x01(\t\x12\x0e\n\x06sha256\x18\x03 \x01(\t\x12\x0c\n\x04path\x18\x04 \x01(\t\"Z\n\x06Scopes\x12\x1b\n\x06static\x18\x01 \x01(\x0e\x32\x06.ScopeH\x00\x88\x01\x01\x12\x1c\n\x07\x64ynamic\x18\x02 \x01(\x0e\x32\x06.ScopeH\x01\x88\x01\x01\x42\t\n\x07_staticB\n\n\x08_dynamic\"Y\n\x0eSectionFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x0f\n\x07section\x18\x02 \x01(\t\x12\x18\n\x0b\x64\x65scription\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"V\n\rSomeStatement\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\r\n\x05\x63ount\x18\x02 \x01(\r\x12\x18\n\x0b\x64\x65scription\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"\xbc\x01\n\rStatementNode\x12\x0c\n\x04type\x18\x01 \x01(\t\x12 \n\x05range\x18\x02 \x01(\x0b\x32\x0f.RangeStatementH\x00\x12\x1e\n\x04some\x18\x03 \x01(\x0b\x32\x0e.SomeStatementH\x00\x12&\n\x08subscope\x18\x04 \x01(\x0b\x32\x12.SubscopeStatementH\x00\x12&\n\x08\x63ompound\x18\x05 \x01(\x0b\x32\x12.CompoundStatementH\x00\x42\x0b\n\tstatement\"\xf6\x01\n\x0eStaticAnalysis\x12\x0e\n\x06\x66ormat\x18\x01 \x01(\t\x12\x0c\n\x04\x61rch\x18\x02 \x01(\t\x12\n\n\x02os\x18\x03 \x01(\t\x12\x11\n\textractor\x18\x04 \x01(\t\x12\r\n\x05rules\x18\x05 \x03(\t\x12\x1e\n\x0c\x62\x61se_address\x18\x06 \x01(\x0b\x32\x08.Address\x12\x1d\n\x06layout\x18\x07 \x01(\x0b\x32\r.StaticLayout\x12,\n\x0e\x66\x65\x61ture_counts\x18\x08 \x01(\x0b\x32\x14.StaticFeatureCounts\x12+\n\x11library_functions\x18\t \x03(\x0b\x32\x10.LibraryFunction\"M\n\x13StaticFeatureCounts\x12\x0c\n\x04\x66ile\x18\x01 \x01(\x04\x12(\n\tfunctions\x18\x02 \x03(\x0b\x32\x15.FunctionFeatureCount\"2\n\x0cStaticLayout\x12\"\n\tfunctions\x18\x01 \x03(\x0b\x32\x0f.FunctionLayout\"W\n\rStringFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x0e\n\x06string\x18\x02 \x01(\t\x12\x18\n\x0b\x64\x65scription\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"b\n\x11SubscopeStatement\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x15\n\x05scope\x18\x02 \x01(\x0e\x32\x06.Scope\x12\x18\n\x0b\x64\x65scription\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"]\n\x10SubstringFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x11\n\tsubstring\x18\x02 \x01(\t\x12\x18\n\x0b\x64\x65scription\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\")\n\x0cThreadLayout\x12\x19\n\x07\x61\x64\x64ress\x18\x01 \x01(\x0b\x32\x08.Address\"&\n\tAddresses\x12\x19\n\x07\x61\x64\x64ress\x18\x01 \x03(\x0b\x32\x08.Address\"F\n\x12Pair_Address_Match\x12\x19\n\x07\x61\x64\x64ress\x18\x01 \x01(\x0b\x32\x08.Address\x12\x15\n\x05match\x18\x02 \x01(\x0b\x32\x06.Match\"7\n\x0cToken_Offset\x12\x17\n\x05token\x18\x01 \x01(\x0b\x32\x08.Integer\x12\x0e\n\x06offset\x18\x02 \x01(\x04\"9\n\x08Ppid_Pid\x12\x16\n\x04ppid\x18\x01 \x01(\x0b\x32\x08.Integer\x12\x15\n\x03pid\x18\x02 \x01(\x0b\x32\x08.Integer\"T\n\x0cPpid_Pid_Tid\x12\x16\n\x04ppid\x18\x01 \x01(\x0b\x32\x08.Integer\x12\x15\n\x03pid\x18\x02 \x01(\x0b\x32\x08.Integer\x12\x15\n\x03tid\x18\x03 \x01(\x0b\x32\x08.Integer\"m\n\x0fPpid_Pid_Tid_Id\x12\x16\n\x04ppid\x18\x01 \x01(\x0b\x32\x08.Integer\x12\x15\n\x03pid\x18\x02 \x01(\x0b\x32\x08.Integer\x12\x15\n\x03tid\x18\x03 \x01(\x0b\x32\x08.Integer\x12\x14\n\x02id\x18\x04 \x01(\x0b\x32\x08.Integer\",\n\x07Integer\x12\x0b\n\x01u\x18\x01 \x01(\x04H\x00\x12\x0b\n\x01i\x18\x02 \x01(\x12H\x00\x42\x07\n\x05value\"8\n\x06Number\x12\x0b\n\x01u\x18\x01 \x01(\x04H\x00\x12\x0b\n\x01i\x18\x02 \x01(\x12H\x00\x12\x0b\n\x01\x66\x18\x03 \x01(\x01H\x00\x42\x07\n\x05value*\x92\x02\n\x0b\x41\x64\x64ressType\x12\x1b\n\x17\x41\x44\x44RESSTYPE_UNSPECIFIED\x10\x00\x12\x18\n\x14\x41\x44\x44RESSTYPE_ABSOLUTE\x10\x01\x12\x18\n\x14\x41\x44\x44RESSTYPE_RELATIVE\x10\x02\x12\x14\n\x10\x41\x44\x44RESSTYPE_FILE\x10\x03\x12\x18\n\x14\x41\x44\x44RESSTYPE_DN_TOKEN\x10\x04\x12\x1f\n\x1b\x41\x44\x44RESSTYPE_DN_TOKEN_OFFSET\x10\x05\x12\x1a\n\x16\x41\x44\x44RESSTYPE_NO_ADDRESS\x10\x06\x12\x17\n\x13\x41\x44\x44RESSTYPE_PROCESS\x10\x07\x12\x16\n\x12\x41\x44\x44RESSTYPE_THREAD\x10\x08\x12\x14\n\x10\x41\x44\x44RESSTYPE_CALL\x10\t*G\n\x06\x46lavor\x12\x16\n\x12\x46LAVOR_UNSPECIFIED\x10\x00\x12\x11\n\rFLAVOR_STATIC\x10\x01\x12\x12\n\x0e\x46LAVOR_DYNAMIC\x10\x02*\xa5\x01\n\x05Scope\x12\x15\n\x11SCOPE_UNSPECIFIED\x10\x00\x12\x0e\n\nSCOPE_FILE\x10\x01\x12\x12\n\x0eSCOPE_FUNCTION\x10\x02\x12\x15\n\x11SCOPE_BASIC_BLOCK\x10\x03\x12\x15\n\x11SCOPE_INSTRUCTION\x10\x04\x12\x11\n\rSCOPE_PROCESS\x10\x05\x12\x10\n\x0cSCOPE_THREAD\x10\x06\x12\x0e\n\nSCOPE_CALL\x10\x07\x62\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1c\x63\x61pa/render/proto/capa.proto\"Q\n\nAPIFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x0b\n\x03\x61pi\x18\x02 \x01(\t\x12\x18\n\x0b\x64\x65scription\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"\xdf\x01\n\x07\x41\x64\x64ress\x12\x1a\n\x04type\x18\x01 \x01(\x0e\x32\x0c.AddressType\x12\x15\n\x01v\x18\x02 \x01(\x0b\x32\x08.IntegerH\x00\x12%\n\x0ctoken_offset\x18\x03 \x01(\x0b\x32\r.Token_OffsetH\x00\x12\x1d\n\x08ppid_pid\x18\x04 \x01(\x0b\x32\t.Ppid_PidH\x00\x12%\n\x0cppid_pid_tid\x18\x05 \x01(\x0b\x32\r.Ppid_Pid_TidH\x00\x12+\n\x0fppid_pid_tid_id\x18\x06 \x01(\x0b\x32\x10.Ppid_Pid_Tid_IdH\x00\x42\x07\n\x05value\"\xe4\x01\n\x08\x41nalysis\x12\x0e\n\x06\x66ormat\x18\x01 \x01(\t\x12\x0c\n\x04\x61rch\x18\x02 \x01(\t\x12\n\n\x02os\x18\x03 \x01(\t\x12\x11\n\textractor\x18\x04 \x01(\t\x12\r\n\x05rules\x18\x05 \x03(\t\x12\x1e\n\x0c\x62\x61se_address\x18\x06 \x01(\x0b\x32\x08.Address\x12\x17\n\x06layout\x18\x07 \x01(\x0b\x32\x07.Layout\x12&\n\x0e\x66\x65\x61ture_counts\x18\x08 \x01(\x0b\x32\x0e.FeatureCounts\x12+\n\x11library_functions\x18\t \x03(\x0b\x32\x10.LibraryFunction\"S\n\x0b\x41rchFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x0c\n\x04\x61rch\x18\x02 \x01(\t\x12\x18\n\x0b\x64\x65scription\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"`\n\nAttackSpec\x12\r\n\x05parts\x18\x01 \x03(\t\x12\x0e\n\x06tactic\x18\x02 \x01(\t\x12\x11\n\ttechnique\x18\x03 \x01(\t\x12\x14\n\x0csubtechnique\x18\x04 \x01(\t\x12\n\n\x02id\x18\x05 \x01(\t\"K\n\x11\x42\x61sicBlockFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x18\n\x0b\x64\x65scription\x18\x02 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"-\n\x10\x42\x61sicBlockLayout\x12\x19\n\x07\x61\x64\x64ress\x18\x01 \x01(\x0b\x32\x08.Address\"U\n\x0c\x42ytesFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\r\n\x05\x62ytes\x18\x02 \x01(\t\x12\x18\n\x0b\x64\x65scription\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"g\n\x15\x43haracteristicFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x16\n\x0e\x63haracteristic\x18\x02 \x01(\t\x12\x18\n\x0b\x64\x65scription\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"V\n\x0c\x43lassFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x0e\n\x06\x63lass_\x18\x02 \x01(\t\x12\x18\n\x0b\x64\x65scription\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"K\n\x11\x43ompoundStatement\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x18\n\x0b\x64\x65scription\x18\x02 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"\xac\x01\n\x0f\x44ynamicAnalysis\x12\x0e\n\x06\x66ormat\x18\x01 \x01(\t\x12\x0c\n\x04\x61rch\x18\x02 \x01(\t\x12\n\n\x02os\x18\x03 \x01(\t\x12\x11\n\textractor\x18\x04 \x01(\t\x12\r\n\x05rules\x18\x05 \x03(\t\x12\x1e\n\x06layout\x18\x06 \x01(\x0b\x32\x0e.DynamicLayout\x12-\n\x0e\x66\x65\x61ture_counts\x18\x07 \x01(\x0b\x32\x15.DynamicFeatureCounts\"M\n\x14\x44ynamicFeatureCounts\x12\x0c\n\x04\x66ile\x18\x01 \x01(\x04\x12\'\n\tprocesses\x18\x02 \x03(\x0b\x32\x14.ProcessFeatureCount\"2\n\rDynamicLayout\x12!\n\tprocesses\x18\x01 \x03(\x0b\x32\x0e.ProcessLayout\"W\n\rExportFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x0e\n\x06\x65xport\x18\x02 \x01(\t\x12\x18\n\x0b\x64\x65scription\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"G\n\rFeatureCounts\x12\x0c\n\x04\x66ile\x18\x01 \x01(\x04\x12(\n\tfunctions\x18\x02 \x03(\x0b\x32\x15.FunctionFeatureCount\"\xf7\x06\n\x0b\x46\x65\x61tureNode\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x18\n\x02os\x18\x02 \x01(\x0b\x32\n.OSFeatureH\x00\x12\x1c\n\x04\x61rch\x18\x03 \x01(\x0b\x32\x0c.ArchFeatureH\x00\x12 \n\x06\x66ormat\x18\x04 \x01(\x0b\x32\x0e.FormatFeatureH\x00\x12\x1e\n\x05match\x18\x05 \x01(\x0b\x32\r.MatchFeatureH\x00\x12\x30\n\x0e\x63haracteristic\x18\x06 \x01(\x0b\x32\x16.CharacteristicFeatureH\x00\x12 \n\x06\x65xport\x18\x07 \x01(\x0b\x32\x0e.ExportFeatureH\x00\x12!\n\x07import_\x18\x08 \x01(\x0b\x32\x0e.ImportFeatureH\x00\x12\"\n\x07section\x18\t \x01(\x0b\x32\x0f.SectionFeatureH\x00\x12-\n\rfunction_name\x18\n \x01(\x0b\x32\x14.FunctionNameFeatureH\x00\x12&\n\tsubstring\x18\x0b \x01(\x0b\x32\x11.SubstringFeatureH\x00\x12\x1e\n\x05regex\x18\x0c \x01(\x0b\x32\r.RegexFeatureH\x00\x12 \n\x06string\x18\r \x01(\x0b\x32\x0e.StringFeatureH\x00\x12\x1f\n\x06\x63lass_\x18\x0e \x01(\x0b\x32\r.ClassFeatureH\x00\x12&\n\tnamespace\x18\x0f \x01(\x0b\x32\x11.NamespaceFeatureH\x00\x12\x1a\n\x03\x61pi\x18\x10 \x01(\x0b\x32\x0b.APIFeatureH\x00\x12%\n\tproperty_\x18\x11 \x01(\x0b\x32\x10.PropertyFeatureH\x00\x12 \n\x06number\x18\x12 \x01(\x0b\x32\x0e.NumberFeatureH\x00\x12\x1e\n\x05\x62ytes\x18\x13 \x01(\x0b\x32\r.BytesFeatureH\x00\x12 \n\x06offset\x18\x14 \x01(\x0b\x32\x0e.OffsetFeatureH\x00\x12$\n\x08mnemonic\x18\x15 \x01(\x0b\x32\x10.MnemonicFeatureH\x00\x12/\n\x0eoperand_number\x18\x16 \x01(\x0b\x32\x15.OperandNumberFeatureH\x00\x12/\n\x0eoperand_offset\x18\x17 \x01(\x0b\x32\x15.OperandOffsetFeatureH\x00\x12)\n\x0b\x62\x61sic_block\x18\x18 \x01(\x0b\x32\x12.BasicBlockFeatureH\x00\x42\t\n\x07\x66\x65\x61ture\"W\n\rFormatFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x0e\n\x06\x66ormat\x18\x02 \x01(\t\x12\x18\n\x0b\x64\x65scription\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"@\n\x14\x46unctionFeatureCount\x12\x19\n\x07\x61\x64\x64ress\x18\x01 \x01(\x0b\x32\x08.Address\x12\r\n\x05\x63ount\x18\x02 \x01(\x04\"\\\n\x0e\x46unctionLayout\x12\x19\n\x07\x61\x64\x64ress\x18\x01 \x01(\x0b\x32\x08.Address\x12/\n\x14matched_basic_blocks\x18\x02 \x03(\x0b\x32\x11.BasicBlockLayout\"d\n\x13\x46unctionNameFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x15\n\rfunction_name\x18\x02 \x01(\t\x12\x18\n\x0b\x64\x65scription\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"X\n\rImportFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x0f\n\x07import_\x18\x02 \x01(\t\x12\x18\n\x0b\x64\x65scription\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\",\n\x06Layout\x12\"\n\tfunctions\x18\x01 \x03(\x0b\x32\x0f.FunctionLayout\":\n\x0fLibraryFunction\x12\x19\n\x07\x61\x64\x64ress\x18\x01 \x01(\x0b\x32\x08.Address\x12\x0c\n\x04name\x18\x02 \x01(\t\"Y\n\x07MBCSpec\x12\r\n\x05parts\x18\x01 \x03(\t\x12\x11\n\tobjective\x18\x02 \x01(\t\x12\x10\n\x08\x62\x65havior\x18\x03 \x01(\t\x12\x0e\n\x06method\x18\x04 \x01(\t\x12\n\n\x02id\x18\x05 \x01(\t\"\x9a\x01\n\x0cMaecMetadata\x12\x1b\n\x13\x61nalysis_conclusion\x18\x01 \x01(\t\x12\x1e\n\x16\x61nalysis_conclusion_ov\x18\x02 \x01(\t\x12\x16\n\x0emalware_family\x18\x03 \x01(\t\x12\x18\n\x10malware_category\x18\x04 \x01(\t\x12\x1b\n\x13malware_category_ov\x18\x05 \x01(\t\"\x82\x02\n\x05Match\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12#\n\tstatement\x18\x02 \x01(\x0b\x32\x0e.StatementNodeH\x00\x12\x1f\n\x07\x66\x65\x61ture\x18\x03 \x01(\x0b\x32\x0c.FeatureNodeH\x00\x12\x18\n\x08\x63hildren\x18\x05 \x03(\x0b\x32\x06.Match\x12\x1b\n\tlocations\x18\x06 \x03(\x0b\x32\x08.Address\x12&\n\x08\x63\x61ptures\x18\x07 \x03(\x0b\x32\x14.Match.CapturesEntry\x1a;\n\rCapturesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\x19\n\x05value\x18\x02 \x01(\x0b\x32\n.Addresses:\x02\x38\x01\x42\x06\n\x04node\"U\n\x0cMatchFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\r\n\x05match\x18\x02 \x01(\t\x12\x18\n\x0b\x64\x65scription\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"\xf6\x01\n\x08Metadata\x12\x11\n\ttimestamp\x18\x01 \x01(\t\x12\x0f\n\x07version\x18\x02 \x01(\t\x12\x0c\n\x04\x61rgv\x18\x03 \x03(\t\x12\x17\n\x06sample\x18\x04 \x01(\x0b\x32\x07.Sample\x12\x1f\n\x08\x61nalysis\x18\x05 \x01(\x0b\x32\t.AnalysisB\x02\x18\x01\x12\x17\n\x06\x66lavor\x18\x06 \x01(\x0e\x32\x07.Flavor\x12*\n\x0fstatic_analysis\x18\x07 \x01(\x0b\x32\x0f.StaticAnalysisH\x00\x12,\n\x10\x64ynamic_analysis\x18\x08 \x01(\x0b\x32\x10.DynamicAnalysisH\x00\x42\x0b\n\tanalysis2\"[\n\x0fMnemonicFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x10\n\x08mnemonic\x18\x02 \x01(\t\x12\x18\n\x0b\x64\x65scription\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"]\n\x10NamespaceFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x11\n\tnamespace\x18\x02 \x01(\t\x12\x18\n\x0b\x64\x65scription\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"`\n\rNumberFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x17\n\x06number\x18\x02 \x01(\x0b\x32\x07.Number\x12\x18\n\x0b\x64\x65scription\x18\x05 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"O\n\tOSFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\n\n\x02os\x18\x02 \x01(\t\x12\x18\n\x0b\x64\x65scription\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"a\n\rOffsetFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x18\n\x06offset\x18\x02 \x01(\x0b\x32\x08.Integer\x12\x18\n\x0b\x64\x65scription\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"\x7f\n\x14OperandNumberFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\r\n\x05index\x18\x02 \x01(\r\x12 \n\x0eoperand_number\x18\x03 \x01(\x0b\x32\x08.Integer\x12\x18\n\x0b\x64\x65scription\x18\x04 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"\x7f\n\x14OperandOffsetFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\r\n\x05index\x18\x02 \x01(\r\x12 \n\x0eoperand_offset\x18\x03 \x01(\x0b\x32\x08.Integer\x12\x18\n\x0b\x64\x65scription\x18\x04 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"?\n\x13ProcessFeatureCount\x12\x19\n\x07\x61\x64\x64ress\x18\x01 \x01(\x0b\x32\x08.Address\x12\r\n\x05\x63ount\x18\x02 \x01(\x04\"`\n\rProcessLayout\x12\x19\n\x07\x61\x64\x64ress\x18\x01 \x01(\x0b\x32\x08.Address\x12&\n\x0fmatched_threads\x18\x02 \x03(\x0b\x32\r.ThreadLayout\x12\x0c\n\x04name\x18\x03 \x01(\t\"|\n\x0fPropertyFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x11\n\tproperty_\x18\x02 \x01(\t\x12\x13\n\x06\x61\x63\x63\x65ss\x18\x03 \x01(\tH\x00\x88\x01\x01\x12\x18\n\x0b\x64\x65scription\x18\x04 \x01(\tH\x01\x88\x01\x01\x42\t\n\x07_accessB\x0e\n\x0c_description\"\x7f\n\x0eRangeStatement\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x0b\n\x03min\x18\x02 \x01(\x04\x12\x0b\n\x03max\x18\x03 \x01(\x04\x12\x1b\n\x05\x63hild\x18\x04 \x01(\x0b\x32\x0c.FeatureNode\x12\x18\n\x0b\x64\x65scription\x18\x05 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"U\n\x0cRegexFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\r\n\x05regex\x18\x02 \x01(\t\x12\x18\n\x0b\x64\x65scription\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"\x90\x01\n\x0eResultDocument\x12\x17\n\x04meta\x18\x01 \x01(\x0b\x32\t.Metadata\x12)\n\x05rules\x18\x02 \x03(\x0b\x32\x1a.ResultDocument.RulesEntry\x1a:\n\nRulesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\x1b\n\x05value\x18\x02 \x01(\x0b\x32\x0c.RuleMatches:\x02\x38\x01\"`\n\x0bRuleMatches\x12\x1b\n\x04meta\x18\x01 \x01(\x0b\x32\r.RuleMetadata\x12\x0e\n\x06source\x18\x02 \x01(\t\x12$\n\x07matches\x18\x03 \x03(\x0b\x32\x13.Pair_Address_Match\"\xa7\x02\n\x0cRuleMetadata\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x11\n\tnamespace\x18\x02 \x01(\t\x12\x0f\n\x07\x61uthors\x18\x03 \x03(\t\x12\x19\n\x05scope\x18\x04 \x01(\x0e\x32\x06.ScopeB\x02\x18\x01\x12\x1b\n\x06\x61ttack\x18\x05 \x03(\x0b\x32\x0b.AttackSpec\x12\x15\n\x03mbc\x18\x06 \x03(\x0b\x32\x08.MBCSpec\x12\x12\n\nreferences\x18\x07 \x03(\t\x12\x10\n\x08\x65xamples\x18\x08 \x03(\t\x12\x13\n\x0b\x64\x65scription\x18\t \x01(\t\x12\x0b\n\x03lib\x18\n \x01(\x08\x12\x1b\n\x04maec\x18\x0b \x01(\x0b\x32\r.MaecMetadata\x12\x18\n\x10is_subscope_rule\x18\x0c \x01(\x08\x12\x17\n\x06scopes\x18\r \x01(\x0b\x32\x07.Scopes\"A\n\x06Sample\x12\x0b\n\x03md5\x18\x01 \x01(\t\x12\x0c\n\x04sha1\x18\x02 \x01(\t\x12\x0e\n\x06sha256\x18\x03 \x01(\t\x12\x0c\n\x04path\x18\x04 \x01(\t\"Z\n\x06Scopes\x12\x1b\n\x06static\x18\x01 \x01(\x0e\x32\x06.ScopeH\x00\x88\x01\x01\x12\x1c\n\x07\x64ynamic\x18\x02 \x01(\x0e\x32\x06.ScopeH\x01\x88\x01\x01\x42\t\n\x07_staticB\n\n\x08_dynamic\"Y\n\x0eSectionFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x0f\n\x07section\x18\x02 \x01(\t\x12\x18\n\x0b\x64\x65scription\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"V\n\rSomeStatement\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\r\n\x05\x63ount\x18\x02 \x01(\r\x12\x18\n\x0b\x64\x65scription\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"\xbc\x01\n\rStatementNode\x12\x0c\n\x04type\x18\x01 \x01(\t\x12 \n\x05range\x18\x02 \x01(\x0b\x32\x0f.RangeStatementH\x00\x12\x1e\n\x04some\x18\x03 \x01(\x0b\x32\x0e.SomeStatementH\x00\x12&\n\x08subscope\x18\x04 \x01(\x0b\x32\x12.SubscopeStatementH\x00\x12&\n\x08\x63ompound\x18\x05 \x01(\x0b\x32\x12.CompoundStatementH\x00\x42\x0b\n\tstatement\"\xf6\x01\n\x0eStaticAnalysis\x12\x0e\n\x06\x66ormat\x18\x01 \x01(\t\x12\x0c\n\x04\x61rch\x18\x02 \x01(\t\x12\n\n\x02os\x18\x03 \x01(\t\x12\x11\n\textractor\x18\x04 \x01(\t\x12\r\n\x05rules\x18\x05 \x03(\t\x12\x1e\n\x0c\x62\x61se_address\x18\x06 \x01(\x0b\x32\x08.Address\x12\x1d\n\x06layout\x18\x07 \x01(\x0b\x32\r.StaticLayout\x12,\n\x0e\x66\x65\x61ture_counts\x18\x08 \x01(\x0b\x32\x14.StaticFeatureCounts\x12+\n\x11library_functions\x18\t \x03(\x0b\x32\x10.LibraryFunction\"M\n\x13StaticFeatureCounts\x12\x0c\n\x04\x66ile\x18\x01 \x01(\x04\x12(\n\tfunctions\x18\x02 \x03(\x0b\x32\x15.FunctionFeatureCount\"2\n\x0cStaticLayout\x12\"\n\tfunctions\x18\x01 \x03(\x0b\x32\x0f.FunctionLayout\"W\n\rStringFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x0e\n\x06string\x18\x02 \x01(\t\x12\x18\n\x0b\x64\x65scription\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"b\n\x11SubscopeStatement\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x15\n\x05scope\x18\x02 \x01(\x0e\x32\x06.Scope\x12\x18\n\x0b\x64\x65scription\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"]\n\x10SubstringFeature\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x11\n\tsubstring\x18\x02 \x01(\t\x12\x18\n\x0b\x64\x65scription\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_description\"5\n\nCallLayout\x12\x19\n\x07\x61\x64\x64ress\x18\x01 \x01(\x0b\x32\x08.Address\x12\x0c\n\x04name\x18\x02 \x01(\t\"M\n\x0cThreadLayout\x12\x19\n\x07\x61\x64\x64ress\x18\x01 \x01(\x0b\x32\x08.Address\x12\"\n\rmatched_calls\x18\x02 \x03(\x0b\x32\x0b.CallLayout\"&\n\tAddresses\x12\x19\n\x07\x61\x64\x64ress\x18\x01 \x03(\x0b\x32\x08.Address\"F\n\x12Pair_Address_Match\x12\x19\n\x07\x61\x64\x64ress\x18\x01 \x01(\x0b\x32\x08.Address\x12\x15\n\x05match\x18\x02 \x01(\x0b\x32\x06.Match\"7\n\x0cToken_Offset\x12\x17\n\x05token\x18\x01 \x01(\x0b\x32\x08.Integer\x12\x0e\n\x06offset\x18\x02 \x01(\x04\"9\n\x08Ppid_Pid\x12\x16\n\x04ppid\x18\x01 \x01(\x0b\x32\x08.Integer\x12\x15\n\x03pid\x18\x02 \x01(\x0b\x32\x08.Integer\"T\n\x0cPpid_Pid_Tid\x12\x16\n\x04ppid\x18\x01 \x01(\x0b\x32\x08.Integer\x12\x15\n\x03pid\x18\x02 \x01(\x0b\x32\x08.Integer\x12\x15\n\x03tid\x18\x03 \x01(\x0b\x32\x08.Integer\"m\n\x0fPpid_Pid_Tid_Id\x12\x16\n\x04ppid\x18\x01 \x01(\x0b\x32\x08.Integer\x12\x15\n\x03pid\x18\x02 \x01(\x0b\x32\x08.Integer\x12\x15\n\x03tid\x18\x03 \x01(\x0b\x32\x08.Integer\x12\x14\n\x02id\x18\x04 \x01(\x0b\x32\x08.Integer\",\n\x07Integer\x12\x0b\n\x01u\x18\x01 \x01(\x04H\x00\x12\x0b\n\x01i\x18\x02 \x01(\x12H\x00\x42\x07\n\x05value\"8\n\x06Number\x12\x0b\n\x01u\x18\x01 \x01(\x04H\x00\x12\x0b\n\x01i\x18\x02 \x01(\x12H\x00\x12\x0b\n\x01\x66\x18\x03 \x01(\x01H\x00\x42\x07\n\x05value*\x92\x02\n\x0b\x41\x64\x64ressType\x12\x1b\n\x17\x41\x44\x44RESSTYPE_UNSPECIFIED\x10\x00\x12\x18\n\x14\x41\x44\x44RESSTYPE_ABSOLUTE\x10\x01\x12\x18\n\x14\x41\x44\x44RESSTYPE_RELATIVE\x10\x02\x12\x14\n\x10\x41\x44\x44RESSTYPE_FILE\x10\x03\x12\x18\n\x14\x41\x44\x44RESSTYPE_DN_TOKEN\x10\x04\x12\x1f\n\x1b\x41\x44\x44RESSTYPE_DN_TOKEN_OFFSET\x10\x05\x12\x1a\n\x16\x41\x44\x44RESSTYPE_NO_ADDRESS\x10\x06\x12\x17\n\x13\x41\x44\x44RESSTYPE_PROCESS\x10\x07\x12\x16\n\x12\x41\x44\x44RESSTYPE_THREAD\x10\x08\x12\x14\n\x10\x41\x44\x44RESSTYPE_CALL\x10\t*G\n\x06\x46lavor\x12\x16\n\x12\x46LAVOR_UNSPECIFIED\x10\x00\x12\x11\n\rFLAVOR_STATIC\x10\x01\x12\x12\n\x0e\x46LAVOR_DYNAMIC\x10\x02*\xa5\x01\n\x05Scope\x12\x15\n\x11SCOPE_UNSPECIFIED\x10\x00\x12\x0e\n\nSCOPE_FILE\x10\x01\x12\x12\n\x0eSCOPE_FUNCTION\x10\x02\x12\x15\n\x11SCOPE_BASIC_BLOCK\x10\x03\x12\x15\n\x11SCOPE_INSTRUCTION\x10\x04\x12\x11\n\rSCOPE_PROCESS\x10\x05\x12\x10\n\x0cSCOPE_THREAD\x10\x06\x12\x0e\n\nSCOPE_CALL\x10\x07\x62\x06proto3') _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'capa.render.proto.capa_pb2', globals()) @@ -28,12 +28,12 @@ _RESULTDOCUMENT_RULESENTRY._serialized_options = b'8\001' _RULEMETADATA.fields_by_name['scope']._options = None _RULEMETADATA.fields_by_name['scope']._serialized_options = b'\030\001' - _ADDRESSTYPE._serialized_start=7524 - _ADDRESSTYPE._serialized_end=7798 - _FLAVOR._serialized_start=7800 - _FLAVOR._serialized_end=7871 - _SCOPE._serialized_start=7874 - _SCOPE._serialized_end=8039 + _ADDRESSTYPE._serialized_start=7615 + _ADDRESSTYPE._serialized_end=7889 + _FLAVOR._serialized_start=7891 + _FLAVOR._serialized_end=7962 + _SCOPE._serialized_start=7965 + _SCOPE._serialized_end=8130 _APIFEATURE._serialized_start=32 _APIFEATURE._serialized_end=113 _ADDRESS._serialized_start=116 @@ -148,22 +148,24 @@ _SUBSCOPESTATEMENT._serialized_end=6854 _SUBSTRINGFEATURE._serialized_start=6856 _SUBSTRINGFEATURE._serialized_end=6949 - _THREADLAYOUT._serialized_start=6951 - _THREADLAYOUT._serialized_end=6992 - _ADDRESSES._serialized_start=6994 - _ADDRESSES._serialized_end=7032 - _PAIR_ADDRESS_MATCH._serialized_start=7034 - _PAIR_ADDRESS_MATCH._serialized_end=7104 - _TOKEN_OFFSET._serialized_start=7106 - _TOKEN_OFFSET._serialized_end=7161 - _PPID_PID._serialized_start=7163 - _PPID_PID._serialized_end=7220 - _PPID_PID_TID._serialized_start=7222 - _PPID_PID_TID._serialized_end=7306 - _PPID_PID_TID_ID._serialized_start=7308 - _PPID_PID_TID_ID._serialized_end=7417 - _INTEGER._serialized_start=7419 - _INTEGER._serialized_end=7463 - _NUMBER._serialized_start=7465 - _NUMBER._serialized_end=7521 + _CALLLAYOUT._serialized_start=6951 + _CALLLAYOUT._serialized_end=7004 + _THREADLAYOUT._serialized_start=7006 + _THREADLAYOUT._serialized_end=7083 + _ADDRESSES._serialized_start=7085 + _ADDRESSES._serialized_end=7123 + _PAIR_ADDRESS_MATCH._serialized_start=7125 + _PAIR_ADDRESS_MATCH._serialized_end=7195 + _TOKEN_OFFSET._serialized_start=7197 + _TOKEN_OFFSET._serialized_end=7252 + _PPID_PID._serialized_start=7254 + _PPID_PID._serialized_end=7311 + _PPID_PID_TID._serialized_start=7313 + _PPID_PID_TID._serialized_end=7397 + _PPID_PID_TID_ID._serialized_start=7399 + _PPID_PID_TID_ID._serialized_end=7508 + _INTEGER._serialized_start=7510 + _INTEGER._serialized_end=7554 + _NUMBER._serialized_start=7556 + _NUMBER._serialized_end=7612 # @@protoc_insertion_point(module_scope) diff --git a/capa/render/proto/capa_pb2.pyi b/capa/render/proto/capa_pb2.pyi index 05022e501..ecb330bc6 100644 --- a/capa/render/proto/capa_pb2.pyi +++ b/capa/render/proto/capa_pb2.pyi @@ -1631,20 +1631,44 @@ class SubstringFeature(google.protobuf.message.Message): global___SubstringFeature = SubstringFeature +@typing_extensions.final +class CallLayout(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + ADDRESS_FIELD_NUMBER: builtins.int + NAME_FIELD_NUMBER: builtins.int + @property + def address(self) -> global___Address: ... + name: builtins.str + def __init__( + self, + *, + address: global___Address | None = ..., + name: builtins.str = ..., + ) -> None: ... + def HasField(self, field_name: typing_extensions.Literal["address", b"address"]) -> builtins.bool: ... + def ClearField(self, field_name: typing_extensions.Literal["address", b"address", "name", b"name"]) -> None: ... + +global___CallLayout = CallLayout + @typing_extensions.final class ThreadLayout(google.protobuf.message.Message): DESCRIPTOR: google.protobuf.descriptor.Descriptor ADDRESS_FIELD_NUMBER: builtins.int + MATCHED_CALLS_FIELD_NUMBER: builtins.int @property def address(self) -> global___Address: ... + @property + def matched_calls(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___CallLayout]: ... def __init__( self, *, address: global___Address | None = ..., + matched_calls: collections.abc.Iterable[global___CallLayout] | None = ..., ) -> None: ... def HasField(self, field_name: typing_extensions.Literal["address", b"address"]) -> builtins.bool: ... - def ClearField(self, field_name: typing_extensions.Literal["address", b"address"]) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["address", b"address", "matched_calls", b"matched_calls"]) -> None: ... global___ThreadLayout = ThreadLayout diff --git a/capa/render/result_document.py b/capa/render/result_document.py index da8185cc2..2ef85185e 100644 --- a/capa/render/result_document.py +++ b/capa/render/result_document.py @@ -49,8 +49,14 @@ class FunctionLayout(Model): matched_basic_blocks: Tuple[BasicBlockLayout, ...] +class CallLayout(Model): + address: frz.Address + name: str + + class ThreadLayout(Model): address: frz.Address + matched_calls: Tuple[CallLayout, ...] class ProcessLayout(Model): diff --git a/capa/render/vverbose.py b/capa/render/vverbose.py index 408b01f6b..489eeb293 100644 --- a/capa/render/vverbose.py +++ b/capa/render/vverbose.py @@ -34,6 +34,25 @@ def _get_process_name(layout: rd.DynamicLayout, addr: frz.Address) -> str: return "" +def _get_call_name(layout: rd.DynamicLayout, addr: frz.Address) -> str: + call = addr.to_capa() + assert isinstance(call, capa.features.address.DynamicCallAddress) + + thread = frz.Address.from_capa(call.thread) + process = frz.Address.from_capa(call.thread.process) + + # danger: O(n**3) + for p in layout.processes: + if p.address == process: + for t in p.matched_threads: + if t.address == thread: + for c in t.matched_calls: + if c.address == addr: + return c.name + logger.debug("name not found for call: %s", addr) + return "" + + def render_process(layout: rd.DynamicLayout, addr: frz.Address) -> str: process = addr.to_capa() assert isinstance(process, capa.features.address.ProcessAddress) @@ -51,8 +70,10 @@ def render_thread(layout: rd.DynamicLayout, addr: frz.Address) -> str: def render_call(layout: rd.DynamicLayout, addr: frz.Address) -> str: call = addr.to_capa() assert isinstance(call, capa.features.address.DynamicCallAddress) - name = _get_process_name(layout, frz.Address.from_capa(call.thread.process)) - return f"{name}[{call.thread.process.pid}:{call.thread.tid}] XXX[{call.id}](A, B, C)" + + pname = _get_process_name(layout, frz.Address.from_capa(call.thread.process)) + cname = _get_call_name(layout, addr) + return f"{pname}[{call.thread.process.pid}:{call.thread.tid}][{call.id}] {cname}" def render_locations(ostream, layout: rd.Layout, locations: Iterable[frz.Address]): diff --git a/tests/test_freeze_dynamic.py b/tests/test_freeze_dynamic.py index a5ae19262..b3087c092 100644 --- a/tests/test_freeze_dynamic.py +++ b/tests/test_freeze_dynamic.py @@ -54,6 +54,7 @@ DynamicCallAddress( thread=ThreadAddress(ProcessAddress(pid=1), tid=1), id=1 ): capa.features.extractors.null.CallFeatures( + name="CreateFile(12)", features=[ ( DynamicCallAddress(thread=ThreadAddress(ProcessAddress(pid=1), tid=1), id=1), @@ -68,6 +69,7 @@ DynamicCallAddress( thread=ThreadAddress(ProcessAddress(pid=1), tid=1), id=2 ): capa.features.extractors.null.CallFeatures( + name="WriteFile()", features=[ ( DynamicCallAddress(thread=ThreadAddress(ProcessAddress(pid=1), tid=1), id=2), diff --git a/tests/test_render.py b/tests/test_render.py index a692a61f9..469ca2d2e 100644 --- a/tests/test_render.py +++ b/tests/test_render.py @@ -158,6 +158,8 @@ def test_render_vverbose_feature(feature, expected): captures={}, ) - capa.render.vverbose.render_feature(ostream, matches, feature, indent=0) + layout = capa.render.result_document.StaticLayout(functions=()) + + capa.render.vverbose.render_feature(ostream, layout, matches, feature, indent=0) assert ostream.getvalue().strip() == expected From 4a7e488e4c8333c182038f5fefa202ba5390d777 Mon Sep 17 00:00:00 2001 From: Willi Ballenthin Date: Wed, 1 Nov 2023 12:19:13 +0100 Subject: [PATCH 05/17] Update capa/render/vverbose.py Co-authored-by: Moritz --- capa/render/vverbose.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/capa/render/vverbose.py b/capa/render/vverbose.py index 489eeb293..1a3877b40 100644 --- a/capa/render/vverbose.py +++ b/capa/render/vverbose.py @@ -451,8 +451,6 @@ def render_rules(ostream, doc: rd.ResultDocument): assert isinstance(doc.meta.analysis.layout, rd.DynamicLayout) ostream.write(rule.meta.scopes.dynamic.value) - # TODO(mr-tz): process rendering should use human-readable name - # https://github.com/mandiant/capa/issues/1816 ostream.write(" @ ") From 274a710bb1b457e431e80a7815716ddeef66fe2a Mon Sep 17 00:00:00 2001 From: Willi Ballenthin Date: Fri, 3 Nov 2023 13:15:29 +0000 Subject: [PATCH 06/17] report: better compute dynamic layout --- capa/main.py | 38 +++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/capa/main.py b/capa/main.py index 3b238a33c..1a856b86d 100644 --- a/capa/main.py +++ b/capa/main.py @@ -1061,13 +1061,6 @@ def compute_dynamic_layout(rules, extractor: DynamicFeatureExtractor, capabiliti """ assert isinstance(extractor, DynamicFeatureExtractor) - matched_threads: Set[Address] = set() - for rule_name, matches in capabilities.items(): - rule = rules[rule_name] - if capa.rules.Scope.THREAD in rule.scopes: - for addr, _ in matches: - matched_threads.add(addr) - matched_calls: Set[Address] = set() def result_rec(result: capa.features.common.Result): @@ -1081,22 +1074,33 @@ def result_rec(result: capa.features.common.Result): for _, result in matches: result_rec(result) - processes_by_thread: Dict[Address, Address] = {} - threads_by_processes: Dict[Address, List[Address]] = {} names_by_process: Dict[Address, str] = {} - calls_by_thread: Dict[Address, List[Address]] = {} names_by_call: Dict[Address, str] = {} + + matched_processes: Set[Address] = set() + matched_threads: Set[Address] = set() + + threads_by_process: Dict[Address, List[Address]] = {} + calls_by_thread: Dict[Address, List[Address]] = {} + for p in extractor.get_processes(): - threads_by_processes[p.address] = [] - names_by_process[p.address] = extractor.get_process_name(p) + threads_by_process[p.address] = [] + for t in extractor.get_threads(p): - processes_by_thread[t.address] = p.address - threads_by_processes[p.address].append(t.address) calls_by_thread[t.address] = [] + for c in extractor.get_calls(p, t): - calls_by_thread[t.address].append(c.address) if c.address in matched_calls: names_by_call[c.address] = extractor.get_call_name(p, t, c) + calls_by_thread[t.address].append(c.address) + + if calls_by_thread[t.address]: + matched_threads.add(t.address) + threads_by_process[p.address].append(t.address) + + if threads_by_process[p.address]: + matched_processes.add(p.address) + names_by_process[p.address] = extractor.get_process_name(p) layout = rdoc.DynamicLayout( processes=tuple( @@ -1120,8 +1124,8 @@ def result_rec(result: capa.features.common.Result): ) # this object is open to extension in the future, # such as with the function name, etc. ) - for p, threads in threads_by_processes.items() - if len([t for t in threads if t in matched_threads]) > 0 + for p, threads in threads_by_process.items() + if p in matched_processes ) ) From c141f7ec6eaf348a83d97c809563c6d433810f38 Mon Sep 17 00:00:00 2001 From: Willi Ballenthin Date: Fri, 3 Nov 2023 13:16:29 +0000 Subject: [PATCH 07/17] verbose: better render scopes --- capa/render/verbose.py | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/capa/render/verbose.py b/capa/render/verbose.py index 63b9b8458..6107fa0cc 100644 --- a/capa/render/verbose.py +++ b/capa/render/verbose.py @@ -22,7 +22,6 @@ is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ -import enum from typing import cast import tabulate @@ -109,7 +108,7 @@ def render_static_meta(ostream, meta: rd.StaticMetadata): ("os", meta.analysis.os), ("format", meta.analysis.format), ("arch", meta.analysis.arch), - ("analysis", meta.flavor), + ("analysis", meta.flavor.value), ("extractor", meta.analysis.extractor), ("base address", format_address(meta.analysis.base_address)), ("rules", "\n".join(meta.analysis.rules)), @@ -153,7 +152,7 @@ def render_dynamic_meta(ostream, meta: rd.DynamicMetadata): ("os", meta.analysis.os), ("format", meta.analysis.format), ("arch", meta.analysis.arch), - ("analysis", meta.flavor), + ("analysis", meta.flavor.value), ("extractor", meta.analysis.extractor), ("rules", "\n".join(meta.analysis.rules)), ("process count", len(meta.analysis.feature_counts.processes)), @@ -167,9 +166,9 @@ def render_dynamic_meta(ostream, meta: rd.DynamicMetadata): def render_meta(osstream, doc: rd.ResultDocument): - if doc.meta.flavor is rd.Flavor.STATIC: + if doc.meta.flavor == rd.Flavor.STATIC: render_static_meta(osstream, cast(rd.StaticMetadata, doc.meta)) - elif doc.meta.flavor is rd.Flavor.DYNAMIC: + elif doc.meta.flavor == rd.Flavor.DYNAMIC: render_dynamic_meta(osstream, cast(rd.DynamicMetadata, doc.meta)) else: raise ValueError("invalid meta analysis") @@ -198,18 +197,23 @@ def render_rules(ostream, doc: rd.ResultDocument): had_match = True rows = [] - for key in ("namespace", "description", "scopes"): - v = getattr(rule.meta, key) - if not v: - continue - if isinstance(v, list) and len(v) == 1: - v = v[0] + ns = rule.meta.namespace + if ns: + rows.append(("namespace", ns)) - if isinstance(v, enum.Enum): - v = v.value + desc = rule.meta.description + if desc: + rows.append(("description", desc)) - rows.append((key, v)) + if doc.meta.flavor == rd.Flavor.STATIC: + scope = rule.meta.scopes.static + elif doc.meta.flavor == rd.Flavor.DYNAMIC: + scope = rule.meta.scopes.dynamic + else: + raise ValueError("invalid meta analysis") + if scope: + rows.append(("scope", scope.value)) if capa.rules.Scope.FILE not in rule.meta.scopes: locations = [m[0] for m in doc.rules[rule.meta.name].matches] From 9c81ccf88a928acbaa43c4c0836a489fe36f9493 Mon Sep 17 00:00:00 2001 From: Willi Ballenthin Date: Fri, 3 Nov 2023 14:03:16 +0000 Subject: [PATCH 08/17] vverbose: make missing names an error --- capa/render/vverbose.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/capa/render/vverbose.py b/capa/render/vverbose.py index 1a3877b40..71e073426 100644 --- a/capa/render/vverbose.py +++ b/capa/render/vverbose.py @@ -30,8 +30,8 @@ def _get_process_name(layout: rd.DynamicLayout, addr: frz.Address) -> str: for p in layout.processes: if p.address == addr: return p.name - logger.debug("name not found for process: %s", addr) - return "" + + raise ValueError("name not found for process", addr) def _get_call_name(layout: rd.DynamicLayout, addr: frz.Address) -> str: @@ -49,8 +49,7 @@ def _get_call_name(layout: rd.DynamicLayout, addr: frz.Address) -> str: for c in t.matched_calls: if c.address == addr: return c.name - logger.debug("name not found for call: %s", addr) - return "" + raise ValueError("name not found for call", addr) def render_process(layout: rd.DynamicLayout, addr: frz.Address) -> str: From 0da614aa4f459a04a5d1ddab8eea9a515b5fd036 Mon Sep 17 00:00:00 2001 From: Willi Ballenthin Date: Mon, 6 Nov 2023 09:26:01 +0000 Subject: [PATCH 09/17] vverbose: dynamic: show rendered matching API call --- capa/render/utils.py | 5 +++ capa/render/verbose.py | 3 ++ capa/render/vverbose.py | 67 ++++++++++++++++++++++++++++------------- 3 files changed, 54 insertions(+), 21 deletions(-) diff --git a/capa/render/utils.py b/capa/render/utils.py index fb3932340..642b45a3b 100644 --- a/capa/render/utils.py +++ b/capa/render/utils.py @@ -24,6 +24,11 @@ def bold2(s: str) -> str: return termcolor.colored(s, "green") +def mute(s: str) -> str: + """draw attention away from the given string""" + return termcolor.colored(s, "dark_grey") + + def warn(s: str) -> str: return termcolor.colored(s, "yellow") diff --git a/capa/render/verbose.py b/capa/render/verbose.py index 6107fa0cc..366444a03 100644 --- a/capa/render/verbose.py +++ b/capa/render/verbose.py @@ -59,6 +59,7 @@ def format_address(address: frz.Address) -> str: ppid, pid = address.value assert isinstance(ppid, int) assert isinstance(pid, int) + # TODO fixup this to show process name return f"process ppid: {ppid}, process pid: {pid}" elif address.type == frz.AddressType.THREAD: assert isinstance(address.value, tuple) @@ -66,10 +67,12 @@ def format_address(address: frz.Address) -> str: assert isinstance(ppid, int) assert isinstance(pid, int) assert isinstance(tid, int) + # TODO fixup this to show process name return f"process ppid: {ppid}, process pid: {pid}, thread id: {tid}" elif address.type == frz.AddressType.CALL: assert isinstance(address.value, tuple) ppid, pid, tid, id_ = address.value + # TODO fixup this to show process name return f"process ppid: {ppid}, process pid: {pid}, thread id: {tid}, call: {id_}" elif address.type == frz.AddressType.NO_ADDRESS: return "global" diff --git a/capa/render/vverbose.py b/capa/render/vverbose.py index 71e073426..948a33850 100644 --- a/capa/render/vverbose.py +++ b/capa/render/vverbose.py @@ -5,8 +5,8 @@ # Unless required by applicable law or agreed to in writing, software distributed under the License # is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and limitations under the License. - import logging +import textwrap from typing import Dict, Iterable, Optional import tabulate @@ -72,10 +72,40 @@ def render_call(layout: rd.DynamicLayout, addr: frz.Address) -> str: pname = _get_process_name(layout, frz.Address.from_capa(call.thread.process)) cname = _get_call_name(layout, addr) - return f"{pname}[{call.thread.process.pid}:{call.thread.tid}][{call.id}] {cname}" + fname, _, rest = cname.partition("(") + args, _, rest = rest.rpartition(")") + + s = [] + s.append(f"{fname}(") + for arg in args.split(", "): + s.append(f" {arg},") + s.append(f"){rest}") + + newline = "\n" + return f"{pname}[{call.thread.process.pid}:{call.thread.tid}]\n{rutils.mute(newline.join(s))}" + + +def hanging_indent(s: str, indent: int) -> str: + """ + indent the given string, except the first line, + such as if the string finishes an existing line. + + e.g., -def render_locations(ostream, layout: rd.Layout, locations: Iterable[frz.Address]): + EXISTINGSTUFFHERE + hanging_indent("xxxx...", 1) + + becomes: + + EXISTINGSTUFFHERExxxxx + xxxxxx + xxxxxx + """ + prefix = " " * indent + return textwrap.indent(s, prefix=prefix)[len(prefix) :] + + +def render_locations(ostream, layout: rd.Layout, locations: Iterable[frz.Address], indent: int): import capa.render.verbose as v # its possible to have an empty locations array here, @@ -94,8 +124,7 @@ def render_locations(ostream, layout: rd.Layout, locations: Iterable[frz.Address if location.type == frz.AddressType.CALL: assert isinstance(layout, rd.DynamicLayout) - ostream.write(render_call(layout, location)) - + ostream.write(hanging_indent(render_call(layout, location), indent + 1)) else: ostream.write(v.format_address(locations[0])) @@ -103,8 +132,8 @@ def render_locations(ostream, layout: rd.Layout, locations: Iterable[frz.Address location = locations[0] assert isinstance(layout, rd.DynamicLayout) - ostream.write(render_call(layout, location)) - ostream.write(f", and {(len(locations) - 1)} more...") + s = f"{render_call(layout, location)}\nand {(len(locations) - 1)} more..." + ostream.write(hanging_indent(s, indent + 1)) elif len(locations) > 4: # don't display too many locations, because it becomes very noisy. @@ -119,7 +148,7 @@ def render_locations(ostream, layout: rd.Layout, locations: Iterable[frz.Address raise RuntimeError("unreachable") -def render_statement(ostream, layout: rd.Layout, match: rd.Match, statement: rd.Statement, indent=0): +def render_statement(ostream, layout: rd.Layout, match: rd.Match, statement: rd.Statement, indent: int): ostream.write(" " * indent) if isinstance(statement, rd.SubscopeStatement): @@ -181,7 +210,7 @@ def render_statement(ostream, layout: rd.Layout, match: rd.Match, statement: rd. if statement.description: ostream.write(f" = {statement.description}") - render_locations(ostream, layout, match.locations) + render_locations(ostream, layout, match.locations, indent) ostream.writeln("") else: @@ -192,7 +221,7 @@ def render_string_value(s: str) -> str: return f'"{capa.features.common.escape_string(s)}"' -def render_feature(ostream, layout: rd.Layout, match: rd.Match, feature: frzf.Feature, indent=0): +def render_feature(ostream, layout: rd.Layout, match: rd.Match, feature: frzf.Feature, indent: int): ostream.write(" " * indent) key = feature.type @@ -244,7 +273,7 @@ def render_feature(ostream, layout: rd.Layout, match: rd.Match, feature: frzf.Fe ostream.write(feature.description) if not isinstance(feature, (frzf.OSFeature, frzf.ArchFeature, frzf.FormatFeature)): - render_locations(ostream, layout, match.locations) + render_locations(ostream, layout, match.locations, indent) ostream.write("\n") else: # like: @@ -260,11 +289,11 @@ def render_feature(ostream, layout: rd.Layout, match: rd.Match, feature: frzf.Fe ostream.write(" " * (indent + 1)) ostream.write("- ") ostream.write(rutils.bold2(render_string_value(capture))) - render_locations(ostream, layout, locations) + render_locations(ostream, layout, locations, indent=indent) ostream.write("\n") -def render_node(ostream, layout: rd.Layout, match: rd.Match, node: rd.Node, indent=0): +def render_node(ostream, layout: rd.Layout, match: rd.Match, node: rd.Node, indent: int): if isinstance(node, rd.StatementNode): render_statement(ostream, layout, match, node.statement, indent=indent) elif isinstance(node, rd.FeatureNode): @@ -427,7 +456,7 @@ def render_rules(ostream, doc: rd.ResultDocument): # 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(f"unexpected file scope match count: {len(matches)}") - first_address, first_match = matches[0] + _, first_match = matches[0] render_match(ostream, doc.meta.analysis.layout, first_match, indent=0) else: for location, match in sorted(doc.rules[rule.meta.name].matches): @@ -438,12 +467,8 @@ def render_rules(ostream, doc: rd.ResultDocument): ostream.write(capa.render.verbose.format_address(location)) if rule.meta.scopes.static == capa.rules.Scope.BASIC_BLOCK: - ostream.write( - " in function " - + capa.render.verbose.format_address( - frz.Address.from_capa(functions_by_bb[location.to_capa()]) - ) - ) + func = frz.Address.from_capa(functions_by_bb[location.to_capa()]) + ostream.write(f" in function {capa.render.verbose.format_address(func)}") elif doc.meta.flavor == rd.Flavor.DYNAMIC: assert rule.meta.scopes.dynamic is not None @@ -458,7 +483,7 @@ def render_rules(ostream, doc: rd.ResultDocument): elif rule.meta.scopes.dynamic == capa.rules.Scope.THREAD: ostream.write(render_thread(doc.meta.analysis.layout, location)) elif rule.meta.scopes.dynamic == capa.rules.Scope.CALL: - ostream.write(render_call(doc.meta.analysis.layout, location)) + ostream.write(hanging_indent(render_call(doc.meta.analysis.layout, location), indent=1)) else: capa.helpers.assert_never(rule.meta.scopes.dynamic) From f7c72cd1c308af68d3bcc65390309041dbd848e1 Mon Sep 17 00:00:00 2001 From: Willi Ballenthin Date: Mon, 6 Nov 2023 09:45:10 +0000 Subject: [PATCH 10/17] vverbose: don't repeat rendered calls when in call scope --- capa/render/vverbose.py | 35 +++++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/capa/render/vverbose.py b/capa/render/vverbose.py index 948a33850..e24344ec4 100644 --- a/capa/render/vverbose.py +++ b/capa/render/vverbose.py @@ -221,7 +221,9 @@ def render_string_value(s: str) -> str: return f'"{capa.features.common.escape_string(s)}"' -def render_feature(ostream, layout: rd.Layout, match: rd.Match, feature: frzf.Feature, indent: int): +def render_feature( + ostream, layout: rd.Layout, rule: rd.RuleMatches, match: rd.Match, feature: frzf.Feature, indent: int +): ostream.write(" " * indent) key = feature.type @@ -272,7 +274,16 @@ def render_feature(ostream, layout: rd.Layout, match: rd.Match, feature: frzf.Fe ostream.write(capa.rules.DESCRIPTION_SEPARATOR) ostream.write(feature.description) - if not isinstance(feature, (frzf.OSFeature, frzf.ArchFeature, frzf.FormatFeature)): + if isinstance(feature, (frzf.OSFeature, frzf.ArchFeature, frzf.FormatFeature)): + # don't show the location of these global features + pass + elif isinstance(layout, rd.DynamicLayout) and rule.meta.scopes.dynamic == capa.rules.Scope.CALL: + # if we're in call scope, then the call will have been rendered at the top + # of the output, so don't re-render it again for each feature. + pass + elif isinstance(feature, (frzf.OSFeature, frzf.ArchFeature, frzf.FormatFeature)): + pass + else: render_locations(ostream, layout, match.locations, indent) ostream.write("\n") else: @@ -289,15 +300,19 @@ def render_feature(ostream, layout: rd.Layout, match: rd.Match, feature: frzf.Fe ostream.write(" " * (indent + 1)) ostream.write("- ") ostream.write(rutils.bold2(render_string_value(capture))) - render_locations(ostream, layout, locations, indent=indent) + if isinstance(layout, rd.DynamicLayout) and rule.meta.scopes.dynamic == capa.rules.Scope.CALL: + # like above, don't re-render calls when in call scope. + pass + else: + render_locations(ostream, layout, locations, indent=indent) ostream.write("\n") -def render_node(ostream, layout: rd.Layout, match: rd.Match, node: rd.Node, indent: int): +def render_node(ostream, layout: rd.Layout, rule: rd.RuleMatches, match: rd.Match, node: rd.Node, indent: int): if isinstance(node, rd.StatementNode): render_statement(ostream, layout, match, node.statement, indent=indent) elif isinstance(node, rd.FeatureNode): - render_feature(ostream, layout, match, node.feature, indent=indent) + render_feature(ostream, layout, match, rule, node.feature, indent=indent) else: raise RuntimeError("unexpected node type: " + str(node)) @@ -310,7 +325,7 @@ def render_node(ostream, layout: rd.Layout, match: rd.Match, node: rd.Node, inde MODE_FAILURE = "failure" -def render_match(ostream, layout: rd.Layout, match: rd.Match, indent=0, mode=MODE_SUCCESS): +def render_match(ostream, layout: rd.Layout, rule: rd.RuleMatches, match: rd.Match, indent=0, mode=MODE_SUCCESS): child_mode = mode if mode == MODE_SUCCESS: # display only nodes that evaluated successfully. @@ -342,10 +357,10 @@ def render_match(ostream, layout: rd.Layout, match: rd.Match, indent=0, mode=MOD else: raise RuntimeError("unexpected mode: " + mode) - render_node(ostream, layout, match, match.node, indent=indent) + render_node(ostream, layout, match, rule, match.node, indent=indent) for child in match.children: - render_match(ostream, layout, child, indent=indent + 1, mode=child_mode) + render_match(ostream, layout, rule, child, indent=indent + 1, mode=child_mode) def render_rules(ostream, doc: rd.ResultDocument): @@ -457,7 +472,7 @@ def render_rules(ostream, doc: rd.ResultDocument): # so, lets be explicit about our assumptions and raise an exception if they fail. raise RuntimeError(f"unexpected file scope match count: {len(matches)}") _, first_match = matches[0] - render_match(ostream, doc.meta.analysis.layout, first_match, indent=0) + render_match(ostream, doc.meta.analysis.layout, rule, first_match, indent=0) else: for location, match in sorted(doc.rules[rule.meta.name].matches): if doc.meta.flavor == rd.Flavor.STATIC: @@ -491,7 +506,7 @@ def render_rules(ostream, doc: rd.ResultDocument): capa.helpers.assert_never(doc.meta.flavor) ostream.write("\n") - render_match(ostream, doc.meta.analysis.layout, match, indent=1) + render_match(ostream, doc.meta.analysis.layout, rule, match, indent=1) if rule.meta.lib: # only show first match break From eb12ec43f0454550a8d69e492312341ddef6414a Mon Sep 17 00:00:00 2001 From: Willi Ballenthin Date: Mon, 6 Nov 2023 09:50:59 +0000 Subject: [PATCH 11/17] mypy --- capa/render/vverbose.py | 4 ++-- tests/test_render.py | 29 ++++++++++++++++++++++++++++- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/capa/render/vverbose.py b/capa/render/vverbose.py index e24344ec4..690bdc8c0 100644 --- a/capa/render/vverbose.py +++ b/capa/render/vverbose.py @@ -312,7 +312,7 @@ def render_node(ostream, layout: rd.Layout, rule: rd.RuleMatches, match: rd.Matc if isinstance(node, rd.StatementNode): render_statement(ostream, layout, match, node.statement, indent=indent) elif isinstance(node, rd.FeatureNode): - render_feature(ostream, layout, match, rule, node.feature, indent=indent) + render_feature(ostream, layout, rule, match, node.feature, indent=indent) else: raise RuntimeError("unexpected node type: " + str(node)) @@ -357,7 +357,7 @@ def render_match(ostream, layout: rd.Layout, rule: rd.RuleMatches, match: rd.Mat else: raise RuntimeError("unexpected mode: " + mode) - render_node(ostream, layout, match, rule, match.node, indent=indent) + render_node(ostream, layout, rule, match, match.node, indent=indent) for child in match.children: render_match(ostream, layout, rule, child, indent=indent + 1, mode=child_mode) diff --git a/tests/test_render.py b/tests/test_render.py index 469ca2d2e..86bec8d60 100644 --- a/tests/test_render.py +++ b/tests/test_render.py @@ -160,6 +160,33 @@ def test_render_vverbose_feature(feature, expected): layout = capa.render.result_document.StaticLayout(functions=()) - capa.render.vverbose.render_feature(ostream, layout, matches, feature, indent=0) + src = textwrap.dedent( + """ + rule: + meta: + name: test rule + authors: + - user@domain.com + scopes: + static: function + dynamic: process + examples: + - foo1234 + - bar5678 + features: + - and: + - number: 1 + - number: 2 + """ + ) + rule = capa.rules.Rule.from_yaml(src) + + rm = capa.render.result_document.RuleMatches( + meta=rule.meta, + source=src, + matches=(), + ) + + capa.render.vverbose.render_feature(ostream, layout, rm, matches, feature, indent=0) assert ostream.getvalue().strip() == expected From 75ff58edaaae4417685344e1c1b06282cd84ca88 Mon Sep 17 00:00:00 2001 From: Willi Ballenthin Date: Mon, 6 Nov 2023 10:09:23 +0000 Subject: [PATCH 12/17] vverbose: better render pid/tid/call index --- capa/render/vverbose.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/capa/render/vverbose.py b/capa/render/vverbose.py index 690bdc8c0..f6f82ddca 100644 --- a/capa/render/vverbose.py +++ b/capa/render/vverbose.py @@ -56,14 +56,14 @@ def render_process(layout: rd.DynamicLayout, addr: frz.Address) -> str: process = addr.to_capa() assert isinstance(process, capa.features.address.ProcessAddress) name = _get_process_name(layout, addr) - return f"{name}[{process.pid}]" + return f"{name}{{pid:{process.pid}}}" def render_thread(layout: rd.DynamicLayout, addr: frz.Address) -> str: thread = addr.to_capa() assert isinstance(thread, capa.features.address.ThreadAddress) name = _get_process_name(layout, frz.Address.from_capa(thread.process)) - return f"{name}[{thread.process.pid}:{thread.tid}]" + return f"{name}{{pid:{thread.process.pid},tid:{thread.tid}}}" def render_call(layout: rd.DynamicLayout, addr: frz.Address) -> str: @@ -83,7 +83,9 @@ def render_call(layout: rd.DynamicLayout, addr: frz.Address) -> str: s.append(f"){rest}") newline = "\n" - return f"{pname}[{call.thread.process.pid}:{call.thread.tid}]\n{rutils.mute(newline.join(s))}" + return ( + f"{pname}{{pid:{call.thread.process.pid},tid:{call.thread.tid},call:{call.id}}}\n{rutils.mute(newline.join(s))}" + ) def hanging_indent(s: str, indent: int) -> str: From 76788973346fce6851c2781af9c4d4d0b39a96bb Mon Sep 17 00:00:00 2001 From: Willi Ballenthin Date: Mon, 6 Nov 2023 10:32:44 +0000 Subject: [PATCH 13/17] tests: fix render tests --- tests/test_render.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_render.py b/tests/test_render.py index 86bec8d60..60d62149e 100644 --- a/tests/test_render.py +++ b/tests/test_render.py @@ -182,7 +182,7 @@ def test_render_vverbose_feature(feature, expected): rule = capa.rules.Rule.from_yaml(src) rm = capa.render.result_document.RuleMatches( - meta=rule.meta, + meta=capa.render.result_document.RuleMetadata.from_capa(rule), source=src, matches=(), ) From 5d31bc462bca91571eb6f24d02fa1041efb82649 Mon Sep 17 00:00:00 2001 From: Willi Ballenthin Date: Mon, 6 Nov 2023 10:34:26 +0000 Subject: [PATCH 14/17] verbose: render dynamic match locations --- capa/render/verbose.py | 92 ++++++++++++++++++++++++++++++++++++++++- capa/render/vverbose.py | 73 +++----------------------------- 2 files changed, 97 insertions(+), 68 deletions(-) diff --git a/capa/render/verbose.py b/capa/render/verbose.py index 366444a03..c9ecf32ae 100644 --- a/capa/render/verbose.py +++ b/capa/render/verbose.py @@ -80,6 +80,68 @@ def format_address(address: frz.Address) -> str: raise ValueError("unexpected address type") +def _get_process_name(layout: rd.DynamicLayout, addr: frz.Address) -> str: + for p in layout.processes: + if p.address == addr: + return p.name + + raise ValueError("name not found for process", addr) + + +def _get_call_name(layout: rd.DynamicLayout, addr: frz.Address) -> str: + call = addr.to_capa() + assert isinstance(call, capa.features.address.DynamicCallAddress) + + thread = frz.Address.from_capa(call.thread) + process = frz.Address.from_capa(call.thread.process) + + # danger: O(n**3) + for p in layout.processes: + if p.address == process: + for t in p.matched_threads: + if t.address == thread: + for c in t.matched_calls: + if c.address == addr: + return c.name + raise ValueError("name not found for call", addr) + + +def render_process(layout: rd.DynamicLayout, addr: frz.Address) -> str: + process = addr.to_capa() + assert isinstance(process, capa.features.address.ProcessAddress) + name = _get_process_name(layout, addr) + return f"{name}{{pid:{process.pid}}}" + + +def render_thread(layout: rd.DynamicLayout, addr: frz.Address) -> str: + thread = addr.to_capa() + assert isinstance(thread, capa.features.address.ThreadAddress) + name = _get_process_name(layout, frz.Address.from_capa(thread.process)) + return f"{name}{{pid:{thread.process.pid},tid:{thread.tid}}}" + + +def render_call(layout: rd.DynamicLayout, addr: frz.Address) -> str: + call = addr.to_capa() + assert isinstance(call, capa.features.address.DynamicCallAddress) + + pname = _get_process_name(layout, frz.Address.from_capa(call.thread.process)) + cname = _get_call_name(layout, addr) + + fname, _, rest = cname.partition("(") + args, _, rest = rest.rpartition(")") + + s = [] + s.append(f"{fname}(") + for arg in args.split(", "): + s.append(f" {arg},") + s.append(f"){rest}") + + newline = "\n" + return ( + f"{pname}{{pid:{call.thread.process.pid},tid:{call.thread.tid},call:{call.id}}}\n{rutils.mute(newline.join(s))}" + ) + + def render_static_meta(ostream, meta: rd.StaticMetadata): """ like: @@ -220,7 +282,35 @@ def render_rules(ostream, doc: rd.ResultDocument): if capa.rules.Scope.FILE not in rule.meta.scopes: locations = [m[0] for m in doc.rules[rule.meta.name].matches] - rows.append(("matches", "\n".join(map(format_address, locations)))) + lines = [] + + if doc.meta.flavor == rd.Flavor.STATIC: + lines = [format_address(loc) for loc in locations] + elif doc.meta.flavor == rd.Flavor.DYNAMIC: + assert rule.meta.scopes.dynamic is not None + assert isinstance(doc.meta.analysis.layout, rd.DynamicLayout) + + if rule.meta.scopes.dynamic == capa.rules.Scope.PROCESS: + lines = [render_process(doc.meta.analysis.layout, loc) for loc in locations] + elif rule.meta.scopes.dynamic == capa.rules.Scope.THREAD: + lines = [render_thread(doc.meta.analysis.layout, loc) for loc in locations] + elif rule.meta.scopes.dynamic == capa.rules.Scope.CALL: + # because we're only in verbose mode, we won't show the full call details (name, args, retval) + # we'll only show the details of the thread in which the calls are found. + # so select the thread locations and render those. + thread_locations = set() + for loc in locations: + cloc = loc.to_capa() + assert isinstance(cloc, capa.features.address.DynamicCallAddress) + thread_locations.add(frz.Address.from_capa(cloc.thread)) + + lines = [render_thread(doc.meta.analysis.layout, loc) for loc in thread_locations] + else: + capa.helpers.assert_never(rule.meta.scopes.dynamic) + else: + capa.helpers.assert_never(doc.meta.flavor) + + rows.append(("matches", "\n".join(lines))) ostream.writeln(tabulate.tabulate(rows, tablefmt="plain")) ostream.write("\n") diff --git a/capa/render/vverbose.py b/capa/render/vverbose.py index f6f82ddca..3498d24b8 100644 --- a/capa/render/vverbose.py +++ b/capa/render/vverbose.py @@ -26,68 +26,6 @@ logger = logging.getLogger(__name__) -def _get_process_name(layout: rd.DynamicLayout, addr: frz.Address) -> str: - for p in layout.processes: - if p.address == addr: - return p.name - - raise ValueError("name not found for process", addr) - - -def _get_call_name(layout: rd.DynamicLayout, addr: frz.Address) -> str: - call = addr.to_capa() - assert isinstance(call, capa.features.address.DynamicCallAddress) - - thread = frz.Address.from_capa(call.thread) - process = frz.Address.from_capa(call.thread.process) - - # danger: O(n**3) - for p in layout.processes: - if p.address == process: - for t in p.matched_threads: - if t.address == thread: - for c in t.matched_calls: - if c.address == addr: - return c.name - raise ValueError("name not found for call", addr) - - -def render_process(layout: rd.DynamicLayout, addr: frz.Address) -> str: - process = addr.to_capa() - assert isinstance(process, capa.features.address.ProcessAddress) - name = _get_process_name(layout, addr) - return f"{name}{{pid:{process.pid}}}" - - -def render_thread(layout: rd.DynamicLayout, addr: frz.Address) -> str: - thread = addr.to_capa() - assert isinstance(thread, capa.features.address.ThreadAddress) - name = _get_process_name(layout, frz.Address.from_capa(thread.process)) - return f"{name}{{pid:{thread.process.pid},tid:{thread.tid}}}" - - -def render_call(layout: rd.DynamicLayout, addr: frz.Address) -> str: - call = addr.to_capa() - assert isinstance(call, capa.features.address.DynamicCallAddress) - - pname = _get_process_name(layout, frz.Address.from_capa(call.thread.process)) - cname = _get_call_name(layout, addr) - - fname, _, rest = cname.partition("(") - args, _, rest = rest.rpartition(")") - - s = [] - s.append(f"{fname}(") - for arg in args.split(", "): - s.append(f" {arg},") - s.append(f"){rest}") - - newline = "\n" - return ( - f"{pname}{{pid:{call.thread.process.pid},tid:{call.thread.tid},call:{call.id}}}\n{rutils.mute(newline.join(s))}" - ) - - def hanging_indent(s: str, indent: int) -> str: """ indent the given string, except the first line, @@ -126,7 +64,7 @@ def render_locations(ostream, layout: rd.Layout, locations: Iterable[frz.Address if location.type == frz.AddressType.CALL: assert isinstance(layout, rd.DynamicLayout) - ostream.write(hanging_indent(render_call(layout, location), indent + 1)) + ostream.write(hanging_indent(v.render_call(layout, location), indent + 1)) else: ostream.write(v.format_address(locations[0])) @@ -134,7 +72,7 @@ def render_locations(ostream, layout: rd.Layout, locations: Iterable[frz.Address location = locations[0] assert isinstance(layout, rd.DynamicLayout) - s = f"{render_call(layout, location)}\nand {(len(locations) - 1)} more..." + s = f"{v.render_call(layout, location)}\nand {(len(locations) - 1)} more..." ostream.write(hanging_indent(s, indent + 1)) elif len(locations) > 4: @@ -382,6 +320,7 @@ def render_rules(ostream, doc: rd.ResultDocument): api: kernel32.GetLastError @ 0x10004A87 api: kernel32.OutputDebugString @ 0x10004767, 0x10004787, 0x10004816, 0x10004895 """ + import capa.render.verbose as v functions_by_bb: Dict[capa.features.address.Address, capa.features.address.Address] = {} if isinstance(doc.meta.analysis, rd.StaticAnalysis): @@ -496,11 +435,11 @@ def render_rules(ostream, doc: rd.ResultDocument): ostream.write(" @ ") if rule.meta.scopes.dynamic == capa.rules.Scope.PROCESS: - ostream.write(render_process(doc.meta.analysis.layout, location)) + ostream.write(v.render_process(doc.meta.analysis.layout, location)) elif rule.meta.scopes.dynamic == capa.rules.Scope.THREAD: - ostream.write(render_thread(doc.meta.analysis.layout, location)) + ostream.write(v.render_thread(doc.meta.analysis.layout, location)) elif rule.meta.scopes.dynamic == capa.rules.Scope.CALL: - ostream.write(hanging_indent(render_call(doc.meta.analysis.layout, location), indent=1)) + ostream.write(hanging_indent(v.render_call(doc.meta.analysis.layout, location), indent=1)) else: capa.helpers.assert_never(rule.meta.scopes.dynamic) From a52af3895a5a47aa31894867597ca7914a680c7f Mon Sep 17 00:00:00 2001 From: Willi Ballenthin Date: Mon, 6 Nov 2023 10:37:22 +0000 Subject: [PATCH 15/17] verbose: remove TODOs --- capa/render/verbose.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/capa/render/verbose.py b/capa/render/verbose.py index c9ecf32ae..f6f566dec 100644 --- a/capa/render/verbose.py +++ b/capa/render/verbose.py @@ -59,21 +59,18 @@ def format_address(address: frz.Address) -> str: ppid, pid = address.value assert isinstance(ppid, int) assert isinstance(pid, int) - # TODO fixup this to show process name - return f"process ppid: {ppid}, process pid: {pid}" + return f"process{{pid:{pid}}}" elif address.type == frz.AddressType.THREAD: assert isinstance(address.value, tuple) ppid, pid, tid = address.value assert isinstance(ppid, int) assert isinstance(pid, int) assert isinstance(tid, int) - # TODO fixup this to show process name - return f"process ppid: {ppid}, process pid: {pid}, thread id: {tid}" + return f"process{{pid:{pid},tid:{tid}}}" elif address.type == frz.AddressType.CALL: assert isinstance(address.value, tuple) ppid, pid, tid, id_ = address.value - # TODO fixup this to show process name - return f"process ppid: {ppid}, process pid: {pid}, thread id: {tid}, call: {id_}" + return f"process{{pid:{pid},tid:{tid},call:{id_}}}" elif address.type == frz.AddressType.NO_ADDRESS: return "global" else: From 52997e70a0fdc35d9ad1f81457220209615bfc95 Mon Sep 17 00:00:00 2001 From: Moritz Date: Wed, 8 Nov 2023 16:58:40 +0100 Subject: [PATCH 16/17] fix imports according to ruff --- capa/main.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/capa/main.py b/capa/main.py index 2b93ef37a..bfc84fb26 100644 --- a/capa/main.py +++ b/capa/main.py @@ -18,8 +18,7 @@ import datetime import textwrap import contextlib -import collections -from typing import Any, Set, Dict, List, Tuple, Callable, Optional +from typing import Any, Set, Dict, List, Callable, Optional from pathlib import Path import halo From 82013f0e2430354202fee3cd58f3a48a574c95ed Mon Sep 17 00:00:00 2001 From: Willi Ballenthin Date: Tue, 14 Nov 2023 10:33:36 +0000 Subject: [PATCH 17/17] submodule: tests: data: sync --- tests/data | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/data b/tests/data index 874706000..347b0205e 160000 --- a/tests/data +++ b/tests/data @@ -1 +1 @@ -Subproject commit 8747060007335e3e8528df947e5bd207ca1b0ce3 +Subproject commit 347b0205e72a42a7233b92f19c7c82320013939b