diff --git a/capa/features/freeze/__init__.py b/capa/features/freeze/__init__.py index e6911c5f..b87f225a 100644 --- a/capa/features/freeze/__init__.py +++ b/capa/features/freeze/__init__.py @@ -268,7 +268,7 @@ def dumps(extractor: capa.features.extractors.base_extractor.FeatureExtractor) - basic_block=bbaddr, address=Address.from_capa(addr), feature=feature_from_capa(feature), - ) + ) # type: ignore for feature, addr in extractor.extract_basic_block_features(f, bb) ] @@ -287,38 +287,38 @@ def dumps(extractor: capa.features.extractors.base_extractor.FeatureExtractor) - instructions.append( InstructionFeatures( address=iaddr, - features=ifeatures, + features=tuple(ifeatures), ) ) basic_blocks.append( BasicBlockFeatures( address=bbaddr, - features=bbfeatures, - instructions=instructions, + features=tuple(bbfeatures), + instructions=tuple(instructions), ) ) function_features.append( FunctionFeatures( address=faddr, - features=ffeatures, + features=tuple(ffeatures), basic_blocks=basic_blocks, - ) + ) # type: ignore ) features = Features( global_=global_features, - file=file_features, - functions=function_features, - ) + file=tuple(file_features), + functions=tuple(function_features), + ) # type: ignore freeze = Freeze( version=2, base_address=Address.from_capa(extractor.get_base_address()), extractor=Extractor(name=extractor.__class__.__name__), features=features, - ) + ) # type: ignore return freeze.json() diff --git a/capa/features/freeze/features.py b/capa/features/freeze/features.py index 0ff21c11..aef7cc32 100644 --- a/capa/features/freeze/features.py +++ b/capa/features/freeze/features.py @@ -101,59 +101,77 @@ class FeatureModel(BaseModel): def feature_from_capa(f: capa.features.common.Feature) -> "Feature": if isinstance(f, capa.features.common.OS): + assert isinstance(f.value,str) return OSFeature(os=f.value, description=f.description) elif isinstance(f, capa.features.common.Arch): + assert isinstance(f.value,str) return ArchFeature(arch=f.value, description=f.description) elif isinstance(f, capa.features.common.Format): + assert isinstance(f.value,str) return FormatFeature(format=f.value, description=f.description) elif isinstance(f, capa.features.common.MatchedRule): + assert isinstance(f.value,str) return MatchFeature(match=f.value, description=f.description) elif isinstance(f, capa.features.common.Characteristic): + assert isinstance(f.value,str) return CharacteristicFeature(characteristic=f.value, description=f.description) elif isinstance(f, capa.features.file.Export): + assert isinstance(f.value,str) return ExportFeature(export=f.value, description=f.description) elif isinstance(f, capa.features.file.Import): - return ImportFeature(import_=f.value, description=f.description) + assert isinstance(f.value,str) + return ImportFeature(import_=f.value, description=f.description) # type: ignore elif isinstance(f, capa.features.file.Section): + assert isinstance(f.value,str) return SectionFeature(section=f.value, description=f.description) elif isinstance(f, capa.features.file.FunctionName): - return FunctionNameFeature(function_name=f.value, description=f.description) + assert isinstance(f.value,str) + return FunctionNameFeature(function_name=f.value, description=f.description) # type: ignore # must come before check for String due to inheritance elif isinstance(f, capa.features.common.Substring): + assert isinstance(f.value,str) return SubstringFeature(substring=f.value, description=f.description) # must come before check for String due to inheritance elif isinstance(f, capa.features.common.Regex): + assert isinstance(f.value,str) return RegexFeature(regex=f.value, description=f.description) elif isinstance(f, capa.features.common.String): + assert isinstance(f.value,str) return StringFeature(string=f.value, description=f.description) elif isinstance(f, capa.features.common.Class): - return ClassFeature(class_=f.value, description=f.description) + assert isinstance(f.value,str) + return ClassFeature(class_=f.value, description=f.description) # type: ignore elif isinstance(f, capa.features.common.Namespace): + assert isinstance(f.value,str) return NamespaceFeature(namespace=f.value, description=f.description) elif isinstance(f, capa.features.basicblock.BasicBlock): + assert isinstance(f.value,(str|None)) return BasicBlockFeature(description=f.description) elif isinstance(f, capa.features.insn.API): + assert isinstance(f.value,str) return APIFeature(api=f.value, description=f.description) elif isinstance(f, capa.features.insn.Property): + assert isinstance(f.value,str) return PropertyFeature(property=f.value, access=f.access, description=f.description) elif isinstance(f, capa.features.insn.Number): + assert isinstance(f.value,(int,float)) return NumberFeature(number=f.value, description=f.description) elif isinstance(f, capa.features.common.Bytes): @@ -162,16 +180,20 @@ def feature_from_capa(f: capa.features.common.Feature) -> "Feature": return BytesFeature(bytes=binascii.hexlify(buf).decode("ascii"), description=f.description) elif isinstance(f, capa.features.insn.Offset): + assert isinstance(f.value,int) return OffsetFeature(offset=f.value, description=f.description) elif isinstance(f, capa.features.insn.Mnemonic): + assert isinstance(f.value,str) return MnemonicFeature(mnemonic=f.value, description=f.description) elif isinstance(f, capa.features.insn.OperandNumber): - return OperandNumberFeature(index=f.index, operand_number=f.value, description=f.description) + assert isinstance(f.value,int) + return OperandNumberFeature(index=f.index, operand_number=f.value, description=f.description) # type: ignore elif isinstance(f, capa.features.insn.OperandOffset): - return OperandOffsetFeature(index=f.index, operand_offset=f.value, description=f.description) + assert isinstance(f.value,int) + return OperandOffsetFeature(index=f.index, operand_offset=f.value, description=f.description) # type: ignore else: raise NotImplementedError(f"feature_from_capa({type(f)}) not implemented") diff --git a/capa/render/result_document.py b/capa/render/result_document.py index 71bdb6bd..db220c70 100644 --- a/capa/render/result_document.py +++ b/capa/render/result_document.py @@ -353,9 +353,9 @@ class Match(BaseModel): return cls( success=success, node=node, - children=children, - locations=locations, - captures=captures, + children=tuple(children), + locations=tuple(locations), + captures = {capture : tuple(captures[capture]) for capture in captures} ) @@ -484,8 +484,8 @@ class RuleMetadata(FrozenModel): namespace=rule.meta.get("namespace"), authors=rule.meta.get("authors"), scope=capa.rules.Scope(rule.meta.get("scope")), - attack=list(map(AttackSpec.from_str, rule.meta.get("att&ck", []))), - mbc=list(map(MBCSpec.from_str, rule.meta.get("mbc", []))), + attack=tuple(map(AttackSpec.from_str, rule.meta.get("att&ck", []))), + mbc=tuple(map(MBCSpec.from_str, rule.meta.get("mbc", []))), references=rule.meta.get("references", []), examples=rule.meta.get("examples", []), description=rule.meta.get("description", ""), @@ -497,8 +497,8 @@ class RuleMetadata(FrozenModel): malware_family=rule.meta.get("maec/malware-family"), malware_category=rule.meta.get("maec/malware-category"), malware_category_ov=rule.meta.get("maec/malware-category-ov"), - ), - ) + ), # type: ignore + ) # type: ignore class Config: frozen = True