diff --git a/capa/features/extractors/base_extractor.py b/capa/features/extractors/base_extractor.py index fbf4b0f37..6252d7470 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]: """ @@ -448,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 1c8cfd2a0..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, CapeReport +from capa.features.extractors.cape.models import Call, 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) @@ -78,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 89358a595..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]] @@ -110,6 +111,7 @@ class ThreadFeatures: class ProcessFeatures: features: List[Tuple[Address, Feature]] threads: Dict[Address, ThreadFeatures] + name: str @dataclass @@ -140,6 +142,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) @@ -158,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 84969c868..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, ...] @@ -300,6 +301,7 @@ class ThreadFeatures(BaseModel): class ProcessFeatures(BaseModel): address: Address + name: str features: Tuple[ProcessFeature, ...] threads: Tuple[ThreadFeatures, ...] @@ -463,6 +465,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, @@ -488,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, @@ -500,6 +504,7 @@ def dumps_dynamic(extractor: DynamicFeatureExtractor) -> str: calls.append( CallFeatures( address=caddr, + name=cname, features=tuple(cfeatures), ) ) @@ -515,6 +520,7 @@ def dumps_dynamic(extractor: DynamicFeatureExtractor) -> str: process_features.append( ProcessFeatures( address=paddr, + name=pname, features=tuple(pfeatures), threads=tuple(threads), ) @@ -595,13 +601,15 @@ 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( 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 47a95a577..bfc84fb26 100644 --- a/capa/main.py +++ b/capa/main.py @@ -18,7 +18,7 @@ import datetime import textwrap import contextlib -from typing import Any, Dict, List, Callable, Optional +from typing import Any, Set, Dict, List, Callable, Optional from pathlib import Path import halo @@ -592,7 +592,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. @@ -602,33 +602,72 @@ def compute_dynamic_layout(rules, extractor: DynamicFeatureExtractor, capabiliti a large amount of un-referenced data. """ assert isinstance(extractor, DynamicFeatureExtractor) - processes_by_thread: Dict[Address, Address] = {} - threads_by_processes: Dict[Address, List[Address]] = {} + + 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) + + names_by_process: Dict[Address, str] = {} + 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] = [] + 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] = [] - 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) + for c in extractor.get_calls(p, t): + 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( 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 + 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. ) - 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 ) ) diff --git a/capa/render/proto/__init__.py b/capa/render/proto/__init__.py index ad81ced57..ed4c690e1 100644 --- a/capa/render/proto/__init__.py +++ b/capa/render/proto/__init__.py @@ -224,7 +224,20 @@ def dynamic_analysis_to_pb2(analysis: rd.DynamicAnalysis) -> capa_pb2.DynamicAna processes=[ capa_pb2.ProcessLayout( address=addr_to_pb2(p.address), - matched_threads=[capa_pb2.ThreadLayout(address=addr_to_pb2(t.address)) for t in p.matched_threads], + name=p.name, + 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 ] @@ -705,8 +718,20 @@ 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] + [ + 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 7cd6a3529..904bc04fe 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 { @@ -429,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 e855c863f..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\"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\"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=7510 - _ADDRESSTYPE._serialized_end=7784 - _FLAVOR._serialized_start=7786 - _FLAVOR._serialized_end=7857 - _SCOPE._serialized_start=7860 - _SCOPE._serialized_end=8025 + _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 @@ -111,59 +111,61 @@ _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 + _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 f90c26b6a..ecb330bc6 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 @@ -1628,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 1b1ef479e..2ef85185e 100644 --- a/capa/render/result_document.py +++ b/capa/render/result_document.py @@ -49,12 +49,19 @@ 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): address: frz.Address + name: str matched_threads: Tuple[ThreadLayout, ...] 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 63b9b8458..f6f566dec 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 @@ -60,24 +59,86 @@ def format_address(address: frz.Address) -> str: ppid, pid = address.value assert isinstance(ppid, int) assert isinstance(pid, int) - 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) - 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 - 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: 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: @@ -109,7 +170,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 +214,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 +228,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,22 +259,55 @@ 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] - 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 ba2328846..3498d24b8 100644 --- a/capa/render/vverbose.py +++ b/capa/render/vverbose.py @@ -5,7 +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 @@ -22,8 +23,29 @@ from capa.rules import RuleSet from capa.engine import MatchResults +logger = logging.getLogger(__name__) + + +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., + + EXISTINGSTUFFHERE + hanging_indent("xxxx...", 1) -def render_locations(ostream, locations: Iterable[frz.Address]): + 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, @@ -35,9 +57,23 @@ 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(hanging_indent(v.render_call(layout, location), indent + 1)) + 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) + s = f"{v.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. @@ -52,7 +88,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: int): ostream.write(" " * indent) if isinstance(statement, rd.SubscopeStatement): @@ -114,7 +150,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, indent) ostream.writeln("") else: @@ -125,7 +161,9 @@ 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, rule: rd.RuleMatches, match: rd.Match, feature: frzf.Feature, indent: int +): ostream.write(" " * indent) key = feature.type @@ -176,8 +214,17 @@ def render_feature(ostream, match: rd.Match, feature: frzf.Feature, indent=0): ostream.write(capa.rules.DESCRIPTION_SEPARATOR) ostream.write(feature.description) - if not isinstance(feature, (frzf.OSFeature, frzf.ArchFeature, frzf.FormatFeature)): - render_locations(ostream, match.locations) + 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: # like: @@ -193,15 +240,19 @@ 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) + 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, match: rd.Match, node: rd.Node, indent=0): +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, 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, rule, match, node.feature, indent=indent) else: raise RuntimeError("unexpected node type: " + str(node)) @@ -214,7 +265,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, rule: rd.RuleMatches, match: rd.Match, indent=0, mode=MODE_SUCCESS): child_mode = mode if mode == MODE_SUCCESS: # display only nodes that evaluated successfully. @@ -246,10 +297,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, rule, match, match.node, indent=indent) for child in match.children: - render_match(ostream, 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): @@ -269,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): @@ -331,10 +383,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]))) @@ -358,31 +412,42 @@ 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] - render_match(ostream, first_match, indent=0) + _, first_match = matches[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: - ostream.write(f"{rule.meta.scopes.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: + 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: - ostream.write(f"{rule.meta.scopes.dynamic}") - else: - capa.helpers.assert_never(doc.meta.flavor) + assert rule.meta.scopes.dynamic is not None + assert isinstance(doc.meta.analysis.layout, rd.DynamicLayout) - # TODO(mr-tz): process rendering should use human-readable name - # https://github.com/mandiant/capa/issues/1816 + ostream.write(rule.meta.scopes.dynamic.value) - ostream.write(" @ ") - ostream.write(capa.render.verbose.format_address(location)) + ostream.write(" @ ") - 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()])) - ) + if rule.meta.scopes.dynamic == capa.rules.Scope.PROCESS: + ostream.write(v.render_process(doc.meta.analysis.layout, location)) + elif rule.meta.scopes.dynamic == capa.rules.Scope.THREAD: + ostream.write(v.render_thread(doc.meta.analysis.layout, location)) + elif rule.meta.scopes.dynamic == capa.rules.Scope.CALL: + ostream.write(hanging_indent(v.render_call(doc.meta.analysis.layout, location), indent=1)) + else: + capa.helpers.assert_never(rule.meta.scopes.dynamic) + + else: + capa.helpers.assert_never(doc.meta.flavor) ostream.write("\n") - render_match(ostream, match, indent=1) + render_match(ostream, doc.meta.analysis.layout, rule, match, indent=1) if rule.meta.lib: # only show first match break 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 diff --git a/tests/test_freeze_dynamic.py b/tests/test_freeze_dynamic.py index d7f045bcc..b3087c092 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( @@ -53,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), @@ -67,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..60d62149e 100644 --- a/tests/test_render.py +++ b/tests/test_render.py @@ -158,6 +158,35 @@ 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=()) + + 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=capa.render.result_document.RuleMetadata.from_capa(rule), + source=src, + matches=(), + ) + + capa.render.vverbose.render_feature(ostream, layout, rm, matches, feature, indent=0) assert ostream.getvalue().strip() == expected