From 028bd08585cfd7f8323088ac13bd921d5b41b278 Mon Sep 17 00:00:00 2001 From: harshit Date: Fri, 4 Oct 2024 22:21:15 +0530 Subject: [PATCH 01/16] fix #2307 --- capa/features/freeze/features.py | 120 ++++++++++++++++++------------- 1 file changed, 71 insertions(+), 49 deletions(-) diff --git a/capa/features/freeze/features.py b/capa/features/freeze/features.py index b3d01f08c..44ad56cdf 100644 --- a/capa/features/freeze/features.py +++ b/capa/features/freeze/features.py @@ -6,7 +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 binascii -from typing import Union, Optional +from typing import Union, Optional, Annotated from pydantic import Field, BaseModel, ConfigDict @@ -27,16 +27,19 @@ def to_capa(self) -> capa.features.common.Feature: return capa.features.common.Arch(self.arch, description=self.description) elif isinstance(self, FormatFeature): - return capa.features.common.Format(self.format, description=self.description) + return capa.features.common.Format( + self.format, description=self.description + ) elif isinstance(self, MatchFeature): - return capa.features.common.MatchedRule(self.match, description=self.description) + return capa.features.common.MatchedRule( + self.match, description=self.description + ) - elif isinstance( - self, - CharacteristicFeature, - ): - return capa.features.common.Characteristic(self.characteristic, description=self.description) + elif isinstance(self, CharacteristicFeature,): + return capa.features.common.Characteristic( + self.characteristic, description=self.description + ) elif isinstance(self, ExportFeature): return capa.features.file.Export(self.export, description=self.description) @@ -45,25 +48,35 @@ def to_capa(self) -> capa.features.common.Feature: return capa.features.file.Import(self.import_, description=self.description) elif isinstance(self, SectionFeature): - return capa.features.file.Section(self.section, description=self.description) + return capa.features.file.Section( + self.section, description=self.description + ) elif isinstance(self, FunctionNameFeature): - return capa.features.file.FunctionName(self.function_name, description=self.description) + return capa.features.file.FunctionName( + self.function_name, description=self.description + ) elif isinstance(self, SubstringFeature): - return capa.features.common.Substring(self.substring, description=self.description) + return capa.features.common.Substring( + self.substring, description=self.description + ) elif isinstance(self, RegexFeature): return capa.features.common.Regex(self.regex, description=self.description) elif isinstance(self, StringFeature): - return capa.features.common.String(self.string, description=self.description) + return capa.features.common.String( + self.string, description=self.description + ) elif isinstance(self, ClassFeature): return capa.features.common.Class(self.class_, description=self.description) elif isinstance(self, NamespaceFeature): - return capa.features.common.Namespace(self.namespace, description=self.description) + return capa.features.common.Namespace( + self.namespace, description=self.description + ) elif isinstance(self, BasicBlockFeature): return capa.features.basicblock.BasicBlock(description=self.description) @@ -72,32 +85,34 @@ def to_capa(self) -> capa.features.common.Feature: return capa.features.insn.API(self.api, description=self.description) elif isinstance(self, PropertyFeature): - return capa.features.insn.Property(self.property, access=self.access, description=self.description) + return capa.features.insn.Property( + self.property, access=self.access, description=self.description + ) elif isinstance(self, NumberFeature): return capa.features.insn.Number(self.number, description=self.description) elif isinstance(self, BytesFeature): - return capa.features.common.Bytes(binascii.unhexlify(self.bytes), description=self.description) + return capa.features.common.Bytes( + binascii.unhexlify(self.bytes), description=self.description + ) elif isinstance(self, OffsetFeature): return capa.features.insn.Offset(self.offset, description=self.description) elif isinstance(self, MnemonicFeature): - return capa.features.insn.Mnemonic(self.mnemonic, description=self.description) + return capa.features.insn.Mnemonic( + self.mnemonic, description=self.description + ) elif isinstance(self, OperandNumberFeature): return capa.features.insn.OperandNumber( - self.index, - self.operand_number, - description=self.description, + self.index, self.operand_number, description=self.description, ) elif isinstance(self, OperandOffsetFeature): return capa.features.insn.OperandOffset( - self.index, - self.operand_offset, - description=self.description, + self.index, self.operand_offset, description=self.description, ) else: @@ -175,7 +190,9 @@ def feature_from_capa(f: capa.features.common.Feature) -> "Feature": elif isinstance(f, capa.features.insn.Property): assert isinstance(f.value, str) - return PropertyFeature(property=f.value, access=f.access, description=f.description) + return PropertyFeature( + property=f.value, access=f.access, description=f.description + ) elif isinstance(f, capa.features.insn.Number): assert isinstance(f.value, (int, float)) @@ -184,7 +201,9 @@ def feature_from_capa(f: capa.features.common.Feature) -> "Feature": elif isinstance(f, capa.features.common.Bytes): buf = f.value assert isinstance(buf, bytes) - return BytesFeature(bytes=binascii.hexlify(buf).decode("ascii"), description=f.description) + return BytesFeature( + bytes=binascii.hexlify(buf).decode("ascii"), description=f.description + ) elif isinstance(f, capa.features.insn.Offset): assert isinstance(f.value, int) @@ -348,29 +367,32 @@ class OperandOffsetFeature(FeatureModel): description: Optional[str] = None -Feature = Union[ - OSFeature, - ArchFeature, - FormatFeature, - MatchFeature, - CharacteristicFeature, - ExportFeature, - ImportFeature, - SectionFeature, - FunctionNameFeature, - SubstringFeature, - RegexFeature, - StringFeature, - ClassFeature, - NamespaceFeature, - APIFeature, - PropertyFeature, - NumberFeature, - BytesFeature, - OffsetFeature, - MnemonicFeature, - OperandNumberFeature, - OperandOffsetFeature, - # Note! this must be last, see #1161 - BasicBlockFeature, +Feature = Annotated[ + Union[ + OSFeature, + ArchFeature, + FormatFeature, + MatchFeature, + CharacteristicFeature, + ExportFeature, + ImportFeature, + SectionFeature, + FunctionNameFeature, + SubstringFeature, + RegexFeature, + StringFeature, + ClassFeature, + NamespaceFeature, + APIFeature, + PropertyFeature, + NumberFeature, + BytesFeature, + OffsetFeature, + MnemonicFeature, + OperandNumberFeature, + OperandOffsetFeature, + # Note! this must be last, see #1161 + BasicBlockFeature, + ], + Field(discriminator="type"), ] From 49e8c6176697065a7d7c6b444a146e99fcdf703c Mon Sep 17 00:00:00 2001 From: harshit Date: Fri, 4 Oct 2024 22:21:58 +0530 Subject: [PATCH 02/16] run pre-commit --- capa/features/freeze/features.py | 65 +++++++++++--------------------- 1 file changed, 23 insertions(+), 42 deletions(-) diff --git a/capa/features/freeze/features.py b/capa/features/freeze/features.py index 44ad56cdf..92885b1ca 100644 --- a/capa/features/freeze/features.py +++ b/capa/features/freeze/features.py @@ -27,19 +27,16 @@ def to_capa(self) -> capa.features.common.Feature: return capa.features.common.Arch(self.arch, description=self.description) elif isinstance(self, FormatFeature): - return capa.features.common.Format( - self.format, description=self.description - ) + return capa.features.common.Format(self.format, description=self.description) elif isinstance(self, MatchFeature): - return capa.features.common.MatchedRule( - self.match, description=self.description - ) + return capa.features.common.MatchedRule(self.match, description=self.description) - elif isinstance(self, CharacteristicFeature,): - return capa.features.common.Characteristic( - self.characteristic, description=self.description - ) + elif isinstance( + self, + CharacteristicFeature, + ): + return capa.features.common.Characteristic(self.characteristic, description=self.description) elif isinstance(self, ExportFeature): return capa.features.file.Export(self.export, description=self.description) @@ -48,35 +45,25 @@ def to_capa(self) -> capa.features.common.Feature: return capa.features.file.Import(self.import_, description=self.description) elif isinstance(self, SectionFeature): - return capa.features.file.Section( - self.section, description=self.description - ) + return capa.features.file.Section(self.section, description=self.description) elif isinstance(self, FunctionNameFeature): - return capa.features.file.FunctionName( - self.function_name, description=self.description - ) + return capa.features.file.FunctionName(self.function_name, description=self.description) elif isinstance(self, SubstringFeature): - return capa.features.common.Substring( - self.substring, description=self.description - ) + return capa.features.common.Substring(self.substring, description=self.description) elif isinstance(self, RegexFeature): return capa.features.common.Regex(self.regex, description=self.description) elif isinstance(self, StringFeature): - return capa.features.common.String( - self.string, description=self.description - ) + return capa.features.common.String(self.string, description=self.description) elif isinstance(self, ClassFeature): return capa.features.common.Class(self.class_, description=self.description) elif isinstance(self, NamespaceFeature): - return capa.features.common.Namespace( - self.namespace, description=self.description - ) + return capa.features.common.Namespace(self.namespace, description=self.description) elif isinstance(self, BasicBlockFeature): return capa.features.basicblock.BasicBlock(description=self.description) @@ -85,34 +72,32 @@ def to_capa(self) -> capa.features.common.Feature: return capa.features.insn.API(self.api, description=self.description) elif isinstance(self, PropertyFeature): - return capa.features.insn.Property( - self.property, access=self.access, description=self.description - ) + return capa.features.insn.Property(self.property, access=self.access, description=self.description) elif isinstance(self, NumberFeature): return capa.features.insn.Number(self.number, description=self.description) elif isinstance(self, BytesFeature): - return capa.features.common.Bytes( - binascii.unhexlify(self.bytes), description=self.description - ) + return capa.features.common.Bytes(binascii.unhexlify(self.bytes), description=self.description) elif isinstance(self, OffsetFeature): return capa.features.insn.Offset(self.offset, description=self.description) elif isinstance(self, MnemonicFeature): - return capa.features.insn.Mnemonic( - self.mnemonic, description=self.description - ) + return capa.features.insn.Mnemonic(self.mnemonic, description=self.description) elif isinstance(self, OperandNumberFeature): return capa.features.insn.OperandNumber( - self.index, self.operand_number, description=self.description, + self.index, + self.operand_number, + description=self.description, ) elif isinstance(self, OperandOffsetFeature): return capa.features.insn.OperandOffset( - self.index, self.operand_offset, description=self.description, + self.index, + self.operand_offset, + description=self.description, ) else: @@ -190,9 +175,7 @@ def feature_from_capa(f: capa.features.common.Feature) -> "Feature": elif isinstance(f, capa.features.insn.Property): assert isinstance(f.value, str) - return PropertyFeature( - property=f.value, access=f.access, description=f.description - ) + return PropertyFeature(property=f.value, access=f.access, description=f.description) elif isinstance(f, capa.features.insn.Number): assert isinstance(f.value, (int, float)) @@ -201,9 +184,7 @@ def feature_from_capa(f: capa.features.common.Feature) -> "Feature": elif isinstance(f, capa.features.common.Bytes): buf = f.value assert isinstance(buf, bytes) - return BytesFeature( - bytes=binascii.hexlify(buf).decode("ascii"), description=f.description - ) + return BytesFeature(bytes=binascii.hexlify(buf).decode("ascii"), description=f.description) elif isinstance(f, capa.features.insn.Offset): assert isinstance(f.value, int) From 452cd30b45a2444033e72cefcd9428a22775e2f4 Mon Sep 17 00:00:00 2001 From: harshit Date: Sat, 5 Oct 2024 01:28:19 +0530 Subject: [PATCH 03/16] add literal --- capa/features/freeze/features.py | 46 ++++++++++++++++---------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/capa/features/freeze/features.py b/capa/features/freeze/features.py index 92885b1ca..92048c7ca 100644 --- a/capa/features/freeze/features.py +++ b/capa/features/freeze/features.py @@ -209,140 +209,140 @@ def feature_from_capa(f: capa.features.common.Feature) -> "Feature": class OSFeature(FeatureModel): - type: str = "os" + type: Literal["os"] os: str description: Optional[str] = None class ArchFeature(FeatureModel): - type: str = "arch" + type: Literal["arch"] arch: str description: Optional[str] = None class FormatFeature(FeatureModel): - type: str = "format" + type: Literal["format"] format: str description: Optional[str] = None class MatchFeature(FeatureModel): - type: str = "match" + type: Literal["match"] match: str description: Optional[str] = None class CharacteristicFeature(FeatureModel): - type: str = "characteristic" + type: Literal["characteristic"] characteristic: str description: Optional[str] = None class ExportFeature(FeatureModel): - type: str = "export" + type: Literal["export"] export: str description: Optional[str] = None class ImportFeature(FeatureModel): - type: str = "import" + type: Literal["import"] import_: str = Field(alias="import") description: Optional[str] = None class SectionFeature(FeatureModel): - type: str = "section" + type: Literal["section"] section: str description: Optional[str] = None class FunctionNameFeature(FeatureModel): - type: str = "function name" + type: Literal["function name"] function_name: str = Field(alias="function name") description: Optional[str] = None class SubstringFeature(FeatureModel): - type: str = "substring" + type: Literal["substring"] substring: str description: Optional[str] = None class RegexFeature(FeatureModel): - type: str = "regex" + type: Literal["regex"] regex: str description: Optional[str] = None class StringFeature(FeatureModel): - type: str = "string" + type: Literal["string"] string: str description: Optional[str] = None class ClassFeature(FeatureModel): - type: str = "class" + type: Literal["class"] class_: str = Field(alias="class") description: Optional[str] = None class NamespaceFeature(FeatureModel): - type: str = "namespace" + type: Literal["namespace"] namespace: str description: Optional[str] = None class BasicBlockFeature(FeatureModel): - type: str = "basic block" + type: Literal["basic block"] description: Optional[str] = None class APIFeature(FeatureModel): - type: str = "api" + type: Literal["api"] api: str description: Optional[str] = None class PropertyFeature(FeatureModel): - type: str = "property" + type: Literal["property"] access: Optional[str] = None property: str description: Optional[str] = None class NumberFeature(FeatureModel): - type: str = "number" + type: Literal["number"] number: Union[int, float] description: Optional[str] = None class BytesFeature(FeatureModel): - type: str = "bytes" + type: Literal["bytes"] bytes: str description: Optional[str] = None class OffsetFeature(FeatureModel): - type: str = "offset" + type: Literal["offset"] offset: int description: Optional[str] = None class MnemonicFeature(FeatureModel): - type: str = "mnemonic" + type: Literal["mnemonic"] mnemonic: str description: Optional[str] = None class OperandNumberFeature(FeatureModel): - type: str = "operand number" + type: Literal["operand number"] index: int operand_number: int = Field(alias="operand number") description: Optional[str] = None class OperandOffsetFeature(FeatureModel): - type: str = "operand offset" + type: Literal["operand offset"] index: int operand_offset: int = Field(alias="operand offset") description: Optional[str] = None From f1fbdf41dd84700c43cd4c9e176af78070f07284 Mon Sep 17 00:00:00 2001 From: harshit Date: Sat, 5 Oct 2024 01:32:03 +0530 Subject: [PATCH 04/16] fix linter --- capa/features/freeze/features.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/capa/features/freeze/features.py b/capa/features/freeze/features.py index 92048c7ca..de16f6145 100644 --- a/capa/features/freeze/features.py +++ b/capa/features/freeze/features.py @@ -6,7 +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 binascii -from typing import Union, Optional, Annotated +from typing import Union, Literal, Optional, Annotated from pydantic import Field, BaseModel, ConfigDict From adcaa9b7215475358f25ea4f4056f8873e451743 Mon Sep 17 00:00:00 2001 From: harshit Date: Sat, 5 Oct 2024 01:43:01 +0530 Subject: [PATCH 05/16] fix for mypy --- capa/features/freeze/features.py | 48 +++++++++++++++----------------- 1 file changed, 23 insertions(+), 25 deletions(-) diff --git a/capa/features/freeze/features.py b/capa/features/freeze/features.py index de16f6145..4c4b6ef9b 100644 --- a/capa/features/freeze/features.py +++ b/capa/features/freeze/features.py @@ -209,140 +209,138 @@ def feature_from_capa(f: capa.features.common.Feature) -> "Feature": class OSFeature(FeatureModel): - type: Literal["os"] + type: Literal["os"] = "os" os: str description: Optional[str] = None class ArchFeature(FeatureModel): - type: Literal["arch"] + type: Literal["arch"] = "arch" arch: str description: Optional[str] = None class FormatFeature(FeatureModel): - type: Literal["format"] + type: Literal["format"] = "format" format: str description: Optional[str] = None class MatchFeature(FeatureModel): - type: Literal["match"] + type: Literal["match"] = "match" match: str description: Optional[str] = None class CharacteristicFeature(FeatureModel): - type: Literal["characteristic"] + type: Literal["characteristic"] = "characteristic" characteristic: str description: Optional[str] = None class ExportFeature(FeatureModel): - type: Literal["export"] + type: Literal["export"] = "export" export: str description: Optional[str] = None class ImportFeature(FeatureModel): - type: Literal["import"] + type: Literal["import"] = "import" import_: str = Field(alias="import") description: Optional[str] = None class SectionFeature(FeatureModel): - type: Literal["section"] + type: Literal["section"] = "section" section: str description: Optional[str] = None class FunctionNameFeature(FeatureModel): - type: Literal["function name"] + type: Literal["function name"] = "function name" function_name: str = Field(alias="function name") description: Optional[str] = None class SubstringFeature(FeatureModel): - type: Literal["substring"] + type: Literal["substring"] = "substring" substring: str description: Optional[str] = None class RegexFeature(FeatureModel): - type: Literal["regex"] + type: Literal["regex"] = "regex" regex: str description: Optional[str] = None class StringFeature(FeatureModel): - type: Literal["string"] + type: Literal["string"] = "string" string: str description: Optional[str] = None class ClassFeature(FeatureModel): - type: Literal["class"] + type: Literal["class"] = "class" class_: str = Field(alias="class") description: Optional[str] = None class NamespaceFeature(FeatureModel): - type: Literal["namespace"] + type: Literal["namespace"] = "namespace" namespace: str description: Optional[str] = None class BasicBlockFeature(FeatureModel): - type: Literal["basic block"] + type: Literal["basic block"] = "basic block" description: Optional[str] = None class APIFeature(FeatureModel): - type: Literal["api"] + type: Literal["api"] = "api" api: str description: Optional[str] = None class PropertyFeature(FeatureModel): - type: Literal["property"] + type: Literal["property"] = "property" access: Optional[str] = None - property: str - description: Optional[str] = None class NumberFeature(FeatureModel): - type: Literal["number"] + type: Literal["number"] = "number" number: Union[int, float] description: Optional[str] = None class BytesFeature(FeatureModel): - type: Literal["bytes"] + type: Literal["bytes"] = "bytes" bytes: str description: Optional[str] = None class OffsetFeature(FeatureModel): - type: Literal["offset"] + type: Literal["offset"] = "offset" offset: int description: Optional[str] = None class MnemonicFeature(FeatureModel): - type: Literal["mnemonic"] + type: Literal["mnemonic"] = "mnemonic" mnemonic: str description: Optional[str] = None class OperandNumberFeature(FeatureModel): - type: Literal["operand number"] + type: Literal["operand number"] = "operand number" index: int operand_number: int = Field(alias="operand number") description: Optional[str] = None class OperandOffsetFeature(FeatureModel): - type: Literal["operand offset"] + type: Literal["operand offset"] = "operand offset" index: int operand_offset: int = Field(alias="operand offset") description: Optional[str] = None From 80d92f6c3acf21eba3e5c38761e3eb063e94c515 Mon Sep 17 00:00:00 2001 From: harshit Date: Sat, 5 Oct 2024 01:49:42 +0530 Subject: [PATCH 06/16] add property and desc for property feature --- capa/features/freeze/features.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/capa/features/freeze/features.py b/capa/features/freeze/features.py index 4c4b6ef9b..476aedbd6 100644 --- a/capa/features/freeze/features.py +++ b/capa/features/freeze/features.py @@ -306,7 +306,8 @@ class APIFeature(FeatureModel): class PropertyFeature(FeatureModel): type: Literal["property"] = "property" access: Optional[str] = None - + property: str + description: Optional[str] = None class NumberFeature(FeatureModel): type: Literal["number"] = "number" From b63114ab1ddb7fd020186502be2cc22ed5d0eccc Mon Sep 17 00:00:00 2001 From: harshit Date: Sat, 5 Oct 2024 01:51:56 +0530 Subject: [PATCH 07/16] fix linting with black --- capa/features/freeze/features.py | 1 + 1 file changed, 1 insertion(+) diff --git a/capa/features/freeze/features.py b/capa/features/freeze/features.py index 476aedbd6..ffff1acd5 100644 --- a/capa/features/freeze/features.py +++ b/capa/features/freeze/features.py @@ -309,6 +309,7 @@ class PropertyFeature(FeatureModel): property: str description: Optional[str] = None + class NumberFeature(FeatureModel): type: Literal["number"] = "number" number: Union[int, float] From f936d8ebacb70e445884ef6516805b916c63e3ba Mon Sep 17 00:00:00 2001 From: harshit Date: Sat, 5 Oct 2024 01:53:47 +0530 Subject: [PATCH 08/16] black linting --- capa/features/freeze/features.py | 52 ++++++++++++++++++++++++-------- 1 file changed, 39 insertions(+), 13 deletions(-) diff --git a/capa/features/freeze/features.py b/capa/features/freeze/features.py index ffff1acd5..01b6e9783 100644 --- a/capa/features/freeze/features.py +++ b/capa/features/freeze/features.py @@ -27,16 +27,22 @@ def to_capa(self) -> capa.features.common.Feature: return capa.features.common.Arch(self.arch, description=self.description) elif isinstance(self, FormatFeature): - return capa.features.common.Format(self.format, description=self.description) + return capa.features.common.Format( + self.format, description=self.description + ) elif isinstance(self, MatchFeature): - return capa.features.common.MatchedRule(self.match, description=self.description) + return capa.features.common.MatchedRule( + self.match, description=self.description + ) elif isinstance( self, CharacteristicFeature, ): - return capa.features.common.Characteristic(self.characteristic, description=self.description) + return capa.features.common.Characteristic( + self.characteristic, description=self.description + ) elif isinstance(self, ExportFeature): return capa.features.file.Export(self.export, description=self.description) @@ -45,25 +51,35 @@ def to_capa(self) -> capa.features.common.Feature: return capa.features.file.Import(self.import_, description=self.description) elif isinstance(self, SectionFeature): - return capa.features.file.Section(self.section, description=self.description) + return capa.features.file.Section( + self.section, description=self.description + ) elif isinstance(self, FunctionNameFeature): - return capa.features.file.FunctionName(self.function_name, description=self.description) + return capa.features.file.FunctionName( + self.function_name, description=self.description + ) elif isinstance(self, SubstringFeature): - return capa.features.common.Substring(self.substring, description=self.description) + return capa.features.common.Substring( + self.substring, description=self.description + ) elif isinstance(self, RegexFeature): return capa.features.common.Regex(self.regex, description=self.description) elif isinstance(self, StringFeature): - return capa.features.common.String(self.string, description=self.description) + return capa.features.common.String( + self.string, description=self.description + ) elif isinstance(self, ClassFeature): return capa.features.common.Class(self.class_, description=self.description) elif isinstance(self, NamespaceFeature): - return capa.features.common.Namespace(self.namespace, description=self.description) + return capa.features.common.Namespace( + self.namespace, description=self.description + ) elif isinstance(self, BasicBlockFeature): return capa.features.basicblock.BasicBlock(description=self.description) @@ -72,19 +88,25 @@ def to_capa(self) -> capa.features.common.Feature: return capa.features.insn.API(self.api, description=self.description) elif isinstance(self, PropertyFeature): - return capa.features.insn.Property(self.property, access=self.access, description=self.description) + return capa.features.insn.Property( + self.property, access=self.access, description=self.description + ) elif isinstance(self, NumberFeature): return capa.features.insn.Number(self.number, description=self.description) elif isinstance(self, BytesFeature): - return capa.features.common.Bytes(binascii.unhexlify(self.bytes), description=self.description) + return capa.features.common.Bytes( + binascii.unhexlify(self.bytes), description=self.description + ) elif isinstance(self, OffsetFeature): return capa.features.insn.Offset(self.offset, description=self.description) elif isinstance(self, MnemonicFeature): - return capa.features.insn.Mnemonic(self.mnemonic, description=self.description) + return capa.features.insn.Mnemonic( + self.mnemonic, description=self.description + ) elif isinstance(self, OperandNumberFeature): return capa.features.insn.OperandNumber( @@ -175,7 +197,9 @@ def feature_from_capa(f: capa.features.common.Feature) -> "Feature": elif isinstance(f, capa.features.insn.Property): assert isinstance(f.value, str) - return PropertyFeature(property=f.value, access=f.access, description=f.description) + return PropertyFeature( + property=f.value, access=f.access, description=f.description + ) elif isinstance(f, capa.features.insn.Number): assert isinstance(f.value, (int, float)) @@ -184,7 +208,9 @@ def feature_from_capa(f: capa.features.common.Feature) -> "Feature": elif isinstance(f, capa.features.common.Bytes): buf = f.value assert isinstance(buf, bytes) - return BytesFeature(bytes=binascii.hexlify(buf).decode("ascii"), description=f.description) + return BytesFeature( + bytes=binascii.hexlify(buf).decode("ascii"), description=f.description + ) elif isinstance(f, capa.features.insn.Offset): assert isinstance(f.value, int) From b0ca2e34c790a127053de45700d2e22b8805e80f Mon Sep 17 00:00:00 2001 From: harshit Date: Sat, 5 Oct 2024 01:56:34 +0530 Subject: [PATCH 09/16] pre-commit run black fix --- capa/features/freeze/features.py | 52 ++++++++------------------------ 1 file changed, 13 insertions(+), 39 deletions(-) diff --git a/capa/features/freeze/features.py b/capa/features/freeze/features.py index 01b6e9783..ffff1acd5 100644 --- a/capa/features/freeze/features.py +++ b/capa/features/freeze/features.py @@ -27,22 +27,16 @@ def to_capa(self) -> capa.features.common.Feature: return capa.features.common.Arch(self.arch, description=self.description) elif isinstance(self, FormatFeature): - return capa.features.common.Format( - self.format, description=self.description - ) + return capa.features.common.Format(self.format, description=self.description) elif isinstance(self, MatchFeature): - return capa.features.common.MatchedRule( - self.match, description=self.description - ) + return capa.features.common.MatchedRule(self.match, description=self.description) elif isinstance( self, CharacteristicFeature, ): - return capa.features.common.Characteristic( - self.characteristic, description=self.description - ) + return capa.features.common.Characteristic(self.characteristic, description=self.description) elif isinstance(self, ExportFeature): return capa.features.file.Export(self.export, description=self.description) @@ -51,35 +45,25 @@ def to_capa(self) -> capa.features.common.Feature: return capa.features.file.Import(self.import_, description=self.description) elif isinstance(self, SectionFeature): - return capa.features.file.Section( - self.section, description=self.description - ) + return capa.features.file.Section(self.section, description=self.description) elif isinstance(self, FunctionNameFeature): - return capa.features.file.FunctionName( - self.function_name, description=self.description - ) + return capa.features.file.FunctionName(self.function_name, description=self.description) elif isinstance(self, SubstringFeature): - return capa.features.common.Substring( - self.substring, description=self.description - ) + return capa.features.common.Substring(self.substring, description=self.description) elif isinstance(self, RegexFeature): return capa.features.common.Regex(self.regex, description=self.description) elif isinstance(self, StringFeature): - return capa.features.common.String( - self.string, description=self.description - ) + return capa.features.common.String(self.string, description=self.description) elif isinstance(self, ClassFeature): return capa.features.common.Class(self.class_, description=self.description) elif isinstance(self, NamespaceFeature): - return capa.features.common.Namespace( - self.namespace, description=self.description - ) + return capa.features.common.Namespace(self.namespace, description=self.description) elif isinstance(self, BasicBlockFeature): return capa.features.basicblock.BasicBlock(description=self.description) @@ -88,25 +72,19 @@ def to_capa(self) -> capa.features.common.Feature: return capa.features.insn.API(self.api, description=self.description) elif isinstance(self, PropertyFeature): - return capa.features.insn.Property( - self.property, access=self.access, description=self.description - ) + return capa.features.insn.Property(self.property, access=self.access, description=self.description) elif isinstance(self, NumberFeature): return capa.features.insn.Number(self.number, description=self.description) elif isinstance(self, BytesFeature): - return capa.features.common.Bytes( - binascii.unhexlify(self.bytes), description=self.description - ) + return capa.features.common.Bytes(binascii.unhexlify(self.bytes), description=self.description) elif isinstance(self, OffsetFeature): return capa.features.insn.Offset(self.offset, description=self.description) elif isinstance(self, MnemonicFeature): - return capa.features.insn.Mnemonic( - self.mnemonic, description=self.description - ) + return capa.features.insn.Mnemonic(self.mnemonic, description=self.description) elif isinstance(self, OperandNumberFeature): return capa.features.insn.OperandNumber( @@ -197,9 +175,7 @@ def feature_from_capa(f: capa.features.common.Feature) -> "Feature": elif isinstance(f, capa.features.insn.Property): assert isinstance(f.value, str) - return PropertyFeature( - property=f.value, access=f.access, description=f.description - ) + return PropertyFeature(property=f.value, access=f.access, description=f.description) elif isinstance(f, capa.features.insn.Number): assert isinstance(f.value, (int, float)) @@ -208,9 +184,7 @@ def feature_from_capa(f: capa.features.common.Feature) -> "Feature": elif isinstance(f, capa.features.common.Bytes): buf = f.value assert isinstance(buf, bytes) - return BytesFeature( - bytes=binascii.hexlify(buf).decode("ascii"), description=f.description - ) + return BytesFeature(bytes=binascii.hexlify(buf).decode("ascii"), description=f.description) elif isinstance(f, capa.features.insn.Offset): assert isinstance(f.value, int) From 19eb0f4b7ed0c87f08f791cfa2cfccd47bf1a541 Mon Sep 17 00:00:00 2001 From: harshit Date: Fri, 29 Nov 2024 23:45:41 +0530 Subject: [PATCH 10/16] type: Fix feature type annotations in capa IDA plugin Resolve type incompatibility by introducing FeatureType type alias with strict literal types --- capa/ida/plugin/model.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/capa/ida/plugin/model.py b/capa/ida/plugin/model.py index 0d8221b12..a91ce1979 100644 --- a/capa/ida/plugin/model.py +++ b/capa/ida/plugin/model.py @@ -529,12 +529,21 @@ def render_capa_doc(self, doc: rd.ResultDocument, by_function: bool): # inform model changes have ended self.endResetModel() - def capa_doc_feature_to_display(self, feature: frzf.Feature): + def capa_doc_feature_to_display(self, feature: frzf.Feature) -> str: """convert capa doc feature type string to display string for ui @param feature: capa feature read from doc """ - key = feature.type + # Use the specific type from the feature instead of direct string assignment + FeatureType = Union[Literal[ + 'os', 'arch', 'format', 'match', 'characteristic', + 'export', 'import', 'section', 'function name', + 'substring', 'regex', 'string', 'class', 'namespace', + 'api', 'property', 'number', 'bytes', 'offset', + 'mnemonic', 'operand number', 'operand offset', + 'basic block' + ]] + key: FeatureType = feature.type value = feature.dict(by_alias=True).get(feature.type) if value: From 769490d76c69f9c86222c4ae352470156362fbac Mon Sep 17 00:00:00 2001 From: harshit Date: Fri, 29 Nov 2024 23:48:55 +0530 Subject: [PATCH 11/16] fix: import union & literal --- capa/ida/plugin/model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/capa/ida/plugin/model.py b/capa/ida/plugin/model.py index a91ce1979..c5a26ad13 100644 --- a/capa/ida/plugin/model.py +++ b/capa/ida/plugin/model.py @@ -6,7 +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. -from typing import Optional +from typing import Optional, Union, Literal from collections import deque import idc From 066886184d190e37bbd31175caad51293b75584d Mon Sep 17 00:00:00 2001 From: harshit Date: Fri, 29 Nov 2024 23:52:14 +0530 Subject: [PATCH 12/16] isort fix --- capa/ida/plugin/model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/capa/ida/plugin/model.py b/capa/ida/plugin/model.py index c5a26ad13..cf2ba4c1b 100644 --- a/capa/ida/plugin/model.py +++ b/capa/ida/plugin/model.py @@ -6,7 +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. -from typing import Optional, Union, Literal +from typing import Union, Literal, Optional from collections import deque import idc From a30163d68491de391ba57dd578f2b9f1dde74cb1 Mon Sep 17 00:00:00 2001 From: harshit Date: Fri, 29 Nov 2024 23:55:09 +0530 Subject: [PATCH 13/16] black fix --- capa/ida/plugin/model.py | 148 +++++++++++++++++++++++++++++++-------- 1 file changed, 117 insertions(+), 31 deletions(-) diff --git a/capa/ida/plugin/model.py b/capa/ida/plugin/model.py index cf2ba4c1b..817f73ae9 100644 --- a/capa/ida/plugin/model.py +++ b/capa/ida/plugin/model.py @@ -53,7 +53,9 @@ def __init__(self, parent=None): """initialize model""" super().__init__(parent) # root node does not have parent, contains header columns - self.root_node = CapaExplorerDataItem(None, ["Rule Information", "Address", "Details"]) + self.root_node = CapaExplorerDataItem( + None, ["Rule Information", "Address", "Details"] + ) def reset(self): """reset UI elements (e.g. checkboxes, IDA color highlights) @@ -62,7 +64,9 @@ def reset(self): """ for idx in range(self.root_node.childCount()): root_index = self.index(idx, 0, QtCore.QModelIndex()) - for model_index in self.iterateChildrenIndexFromRootIndex(root_index, ignore_root=False): + for model_index in self.iterateChildrenIndexFromRootIndex( + root_index, ignore_root=False + ): model_index.internalPointer().setChecked(False) self.reset_ida_highlighting(model_index.internalPointer(), False) self.dataChanged.emit(model_index, model_index) @@ -116,7 +120,10 @@ def data(self, model_index, role): # show tooltip containing rule source return item.source - if role == QtCore.Qt.CheckStateRole and column == CapaExplorerDataModel.COLUMN_INDEX_RULE_INFORMATION: + if ( + role == QtCore.Qt.CheckStateRole + and column == CapaExplorerDataModel.COLUMN_INDEX_RULE_INFORMATION + ): # inform view how to display content of checkbox - un/checked if not item.canCheck(): return None @@ -153,7 +160,10 @@ def data(self, model_index, role): font.setBold(True) return font - if role == QtCore.Qt.ForegroundRole and column == CapaExplorerDataModel.COLUMN_INDEX_VIRTUAL_ADDRESS: + if ( + role == QtCore.Qt.ForegroundRole + and column == CapaExplorerDataModel.COLUMN_INDEX_VIRTUAL_ADDRESS + ): # set color for virtual address column return QtGui.QColor(37, 147, 215) @@ -271,7 +281,12 @@ def reset_ida_highlighting(self, item, checked): @param checked: True, item checked, False item not checked """ if not isinstance( - item, (CapaExplorerStringViewItem, CapaExplorerInstructionViewItem, CapaExplorerByteViewItem) + item, + ( + CapaExplorerStringViewItem, + CapaExplorerInstructionViewItem, + CapaExplorerByteViewItem, + ), ): # ignore other item types return @@ -303,10 +318,13 @@ def setData(self, model_index, value, role): if ( role == QtCore.Qt.CheckStateRole - and model_index.column() == CapaExplorerDataModel.COLUMN_INDEX_RULE_INFORMATION + and model_index.column() + == CapaExplorerDataModel.COLUMN_INDEX_RULE_INFORMATION ): # user un/checked box - un/check parent and children - for child_index in self.iterateChildrenIndexFromRootIndex(model_index, ignore_root=False): + for child_index in self.iterateChildrenIndexFromRootIndex( + model_index, ignore_root=False + ): child_index.internalPointer().setChecked(value) self.reset_ida_highlighting(child_index.internalPointer(), value) self.dataChanged.emit(child_index, child_index) @@ -315,7 +333,8 @@ def setData(self, model_index, value, role): if ( role == QtCore.Qt.EditRole and value - and model_index.column() == CapaExplorerDataModel.COLUMN_INDEX_RULE_INFORMATION + and model_index.column() + == CapaExplorerDataModel.COLUMN_INDEX_RULE_INFORMATION and isinstance(model_index.internalPointer(), CapaExplorerFunctionItem) ): # user renamed function - update IDA database and data model @@ -371,7 +390,10 @@ def render_capa_doc_statement_node( if statement.description: display += f" ({statement.description})" return CapaExplorerDefaultItem(parent, display) - elif isinstance(statement, rd.CompoundStatement) and statement.type == rd.CompoundStatementType.NOT: + elif ( + isinstance(statement, rd.CompoundStatement) + and statement.type == rd.CompoundStatementType.NOT + ): # TODO(mike-hunhoff): verify that we can display NOT statements # https://github.com/mandiant/capa/issues/1602 pass @@ -403,7 +425,9 @@ def render_capa_doc_statement_node( for location in locations: # for each location render child node for range statement - self.render_capa_doc_feature(parent2, match, statement.child, location, doc) + self.render_capa_doc_feature( + parent2, match, statement.child, location, doc + ) return parent2 elif isinstance(statement, rd.SubscopeStatement): @@ -414,7 +438,9 @@ def render_capa_doc_statement_node( else: raise RuntimeError("unexpected match statement type: " + str(statement)) - def render_capa_doc_match(self, parent: CapaExplorerDataItem, match: rd.Match, doc: rd.ResultDocument): + def render_capa_doc_match( + self, parent: CapaExplorerDataItem, match: rd.Match, doc: rd.ResultDocument + ): """render capa match read from doc @param parent: parent node to which new child is assigned @@ -427,17 +453,28 @@ def render_capa_doc_match(self, parent: CapaExplorerDataItem, match: rd.Match, d return # optional statement with no successful children is empty - if isinstance(match.node, rd.StatementNode) and match.node.statement.type == rd.CompoundStatementType.OPTIONAL: + if ( + isinstance(match.node, rd.StatementNode) + and match.node.statement.type == rd.CompoundStatementType.OPTIONAL + ): if not any(m.success for m in match.children): return if isinstance(match.node, rd.StatementNode): parent2 = self.render_capa_doc_statement_node( - parent, match, match.node.statement, [addr.to_capa() for addr in match.locations], doc + parent, + match, + match.node.statement, + [addr.to_capa() for addr in match.locations], + doc, ) elif isinstance(match.node, rd.FeatureNode): parent2 = self.render_capa_doc_feature_node( - parent, match, match.node.feature, [addr.to_capa() for addr in match.locations], doc + parent, + match, + match.node.feature, + [addr.to_capa() for addr in match.locations], + doc, ) else: raise RuntimeError("unexpected node type: " + str(match.node.type)) @@ -447,7 +484,9 @@ def render_capa_doc_match(self, parent: CapaExplorerDataItem, match: rd.Match, d def render_capa_doc_by_function(self, doc: rd.ResultDocument): """render rule matches by function meaning each rule match is nested under function where it was found""" - matches_by_function: dict[AbsoluteVirtualAddress, tuple[CapaExplorerFunctionItem, set[str]]] = {} + matches_by_function: dict[ + AbsoluteVirtualAddress, tuple[CapaExplorerFunctionItem, set[str]] + ] = {} for rule in rutils.capability_rules(doc): match_eas: list[int] = [] @@ -468,7 +507,9 @@ def render_capa_doc_by_function(self, doc: rd.ResultDocument): # create a new function root to nest its rule matches; Note: we must use the address of the # function here so everything is displayed properly matches_by_function[func_address] = ( - CapaExplorerFunctionItem(self.root_node, func_address, can_check=False), + CapaExplorerFunctionItem( + self.root_node, func_address, can_check=False + ), set(), ) @@ -484,7 +525,13 @@ def render_capa_doc_by_function(self, doc: rd.ResultDocument): func_root, rule.meta.name, rule.meta.namespace or "", - len([ea for ea in match_eas if capa.ida.helpers.get_func_start_ea(ea) == func_ea]), + len( + [ + ea + for ea in match_eas + if capa.ida.helpers.get_func_start_ea(ea) == func_ea + ] + ), rule.source, can_check=False, ) @@ -494,7 +541,13 @@ def render_capa_doc_by_program(self, doc: rd.ResultDocument): for rule in rutils.capability_rules(doc): rule_name = rule.meta.name rule_namespace = rule.meta.namespace or "" - parent = CapaExplorerRuleItem(self.root_node, rule_name, rule_namespace, len(rule.matches), rule.source) + parent = CapaExplorerRuleItem( + self.root_node, + rule_name, + rule_namespace, + len(rule.matches), + rule.source, + ) for location_, match in rule.matches: location = location_.to_capa() @@ -509,7 +562,9 @@ def render_capa_doc_by_program(self, doc: rd.ResultDocument): elif capa.rules.Scope.INSTRUCTION in rule.meta.scopes: parent2 = CapaExplorerInstructionItem(parent, location) else: - raise RuntimeError("unexpected rule scope: " + str(rule.meta.scopes.static)) + raise RuntimeError( + "unexpected rule scope: " + str(rule.meta.scopes.static) + ) self.render_capa_doc_match(parent2, match, doc) @@ -535,14 +590,33 @@ def capa_doc_feature_to_display(self, feature: frzf.Feature) -> str: @param feature: capa feature read from doc """ # Use the specific type from the feature instead of direct string assignment - FeatureType = Union[Literal[ - 'os', 'arch', 'format', 'match', 'characteristic', - 'export', 'import', 'section', 'function name', - 'substring', 'regex', 'string', 'class', 'namespace', - 'api', 'property', 'number', 'bytes', 'offset', - 'mnemonic', 'operand number', 'operand offset', - 'basic block' - ]] + FeatureType = Union[ + Literal[ + "os", + "arch", + "format", + "match", + "characteristic", + "export", + "import", + "section", + "function name", + "substring", + "regex", + "string", + "class", + "namespace", + "api", + "property", + "number", + "bytes", + "offset", + "mnemonic", + "operand number", + "operand offset", + "basic block", + ] + ] key: FeatureType = feature.type value = feature.dict(by_alias=True).get(feature.type) @@ -641,7 +715,9 @@ def render_capa_doc_feature( if matched_rule is not None: matched_rule_source = matched_rule.source - return CapaExplorerRuleMatchItem(parent, display, source=matched_rule_source) + return CapaExplorerRuleMatchItem( + parent, display, source=matched_rule_source + ) elif isinstance(feature, (frzf.RegexFeature, frzf.SubstringFeature)): for capture, addrs in sorted(match.captures.items()): @@ -649,7 +725,10 @@ def render_capa_doc_feature( assert isinstance(addr, frz.Address) if location == addr.value: return CapaExplorerStringViewItem( - parent, display, location, '"' + capa.features.common.escape_string(capture) + '"' + parent, + display, + location, + '"' + capa.features.common.escape_string(capture) + '"', ) # programming error: the given location should always be found in the regex matches @@ -680,7 +759,10 @@ def render_capa_doc_feature( elif isinstance(feature, frzf.StringFeature): # display string preview return CapaExplorerStringViewItem( - parent, display, location, f'"{capa.features.common.escape_string(feature.string)}"' + parent, + display, + location, + f'"{capa.features.common.escape_string(feature.string)}"', ) elif isinstance( @@ -722,7 +804,11 @@ def update_function_name(self, old_name, new_name): # recursive search for all instances of old function name for model_index in self.match( - root_index, QtCore.Qt.DisplayRole, old_name, hits=-1, flags=QtCore.Qt.MatchRecursive + root_index, + QtCore.Qt.DisplayRole, + old_name, + hits=-1, + flags=QtCore.Qt.MatchRecursive, ): if not isinstance(model_index.internalPointer(), CapaExplorerFunctionItem): continue From a5df0287cb60672835e94a50a418e7922f0b8e14 Mon Sep 17 00:00:00 2001 From: Harshit Wadhwani Date: Fri, 29 Nov 2024 18:48:25 +0000 Subject: [PATCH 14/16] fix lint with pre-commit --- capa/features/address.py | 3 +- .../extractors/binexport2/__init__.py | 6 +- capa/features/extractors/vmray/models.py | 3 +- capa/ida/plugin/form.py | 6 +- capa/ida/plugin/model.py | 70 +++++-------------- capa/render/result_document.py | 9 +-- scripts/compare-backends.py | 1 - scripts/inspect-binexport2.py | 3 - 8 files changed, 32 insertions(+), 69 deletions(-) diff --git a/capa/features/address.py b/capa/features/address.py index 45c3a600f..f0cbd77d2 100644 --- a/capa/features/address.py +++ b/capa/features/address.py @@ -10,7 +10,8 @@ class Address(abc.ABC): @abc.abstractmethod - def __eq__(self, other): ... + def __eq__(self, other): + ... @abc.abstractmethod def __lt__(self, other): diff --git a/capa/features/extractors/binexport2/__init__.py b/capa/features/extractors/binexport2/__init__.py index 8032b2fca..95213434c 100644 --- a/capa/features/extractors/binexport2/__init__.py +++ b/capa/features/extractors/binexport2/__init__.py @@ -315,10 +315,12 @@ def contains(self, address: int) -> bool: return self.address <= address < self.end -class ReadMemoryError(ValueError): ... +class ReadMemoryError(ValueError): + ... -class AddressNotMappedError(ReadMemoryError): ... +class AddressNotMappedError(ReadMemoryError): + ... @dataclass diff --git a/capa/features/extractors/vmray/models.py b/capa/features/extractors/vmray/models.py index c2d6551aa..3515b2427 100644 --- a/capa/features/extractors/vmray/models.py +++ b/capa/features/extractors/vmray/models.py @@ -189,7 +189,8 @@ class GenericReference(BaseModel): source: str -class StaticDataReference(GenericReference): ... +class StaticDataReference(GenericReference): + ... class PEFileBasicInfo(BaseModel): diff --git a/capa/ida/plugin/form.py b/capa/ida/plugin/form.py index 54bd70409..40a2181d6 100644 --- a/capa/ida/plugin/form.py +++ b/capa/ida/plugin/form.py @@ -932,9 +932,9 @@ def get_ask_use_persistent_cache(self, analyze): update_wait_box("verifying cached results") try: - results: Optional[capa.render.result_document.ResultDocument] = ( - capa.ida.helpers.load_and_verify_cached_results() - ) + results: Optional[ + capa.render.result_document.ResultDocument + ] = capa.ida.helpers.load_and_verify_cached_results() except Exception as e: capa.ida.helpers.inform_user_ida_ui("Failed to verify cached results, reanalyzing program") logger.exception("Failed to verify cached results (error: %s)", e) diff --git a/capa/ida/plugin/model.py b/capa/ida/plugin/model.py index 817f73ae9..0947bead6 100644 --- a/capa/ida/plugin/model.py +++ b/capa/ida/plugin/model.py @@ -53,9 +53,7 @@ def __init__(self, parent=None): """initialize model""" super().__init__(parent) # root node does not have parent, contains header columns - self.root_node = CapaExplorerDataItem( - None, ["Rule Information", "Address", "Details"] - ) + self.root_node = CapaExplorerDataItem(None, ["Rule Information", "Address", "Details"]) def reset(self): """reset UI elements (e.g. checkboxes, IDA color highlights) @@ -64,9 +62,7 @@ def reset(self): """ for idx in range(self.root_node.childCount()): root_index = self.index(idx, 0, QtCore.QModelIndex()) - for model_index in self.iterateChildrenIndexFromRootIndex( - root_index, ignore_root=False - ): + for model_index in self.iterateChildrenIndexFromRootIndex(root_index, ignore_root=False): model_index.internalPointer().setChecked(False) self.reset_ida_highlighting(model_index.internalPointer(), False) self.dataChanged.emit(model_index, model_index) @@ -120,10 +116,7 @@ def data(self, model_index, role): # show tooltip containing rule source return item.source - if ( - role == QtCore.Qt.CheckStateRole - and column == CapaExplorerDataModel.COLUMN_INDEX_RULE_INFORMATION - ): + if role == QtCore.Qt.CheckStateRole and column == CapaExplorerDataModel.COLUMN_INDEX_RULE_INFORMATION: # inform view how to display content of checkbox - un/checked if not item.canCheck(): return None @@ -160,10 +153,7 @@ def data(self, model_index, role): font.setBold(True) return font - if ( - role == QtCore.Qt.ForegroundRole - and column == CapaExplorerDataModel.COLUMN_INDEX_VIRTUAL_ADDRESS - ): + if role == QtCore.Qt.ForegroundRole and column == CapaExplorerDataModel.COLUMN_INDEX_VIRTUAL_ADDRESS: # set color for virtual address column return QtGui.QColor(37, 147, 215) @@ -318,13 +308,10 @@ def setData(self, model_index, value, role): if ( role == QtCore.Qt.CheckStateRole - and model_index.column() - == CapaExplorerDataModel.COLUMN_INDEX_RULE_INFORMATION + and model_index.column() == CapaExplorerDataModel.COLUMN_INDEX_RULE_INFORMATION ): # user un/checked box - un/check parent and children - for child_index in self.iterateChildrenIndexFromRootIndex( - model_index, ignore_root=False - ): + for child_index in self.iterateChildrenIndexFromRootIndex(model_index, ignore_root=False): child_index.internalPointer().setChecked(value) self.reset_ida_highlighting(child_index.internalPointer(), value) self.dataChanged.emit(child_index, child_index) @@ -333,8 +320,7 @@ def setData(self, model_index, value, role): if ( role == QtCore.Qt.EditRole and value - and model_index.column() - == CapaExplorerDataModel.COLUMN_INDEX_RULE_INFORMATION + and model_index.column() == CapaExplorerDataModel.COLUMN_INDEX_RULE_INFORMATION and isinstance(model_index.internalPointer(), CapaExplorerFunctionItem) ): # user renamed function - update IDA database and data model @@ -390,10 +376,7 @@ def render_capa_doc_statement_node( if statement.description: display += f" ({statement.description})" return CapaExplorerDefaultItem(parent, display) - elif ( - isinstance(statement, rd.CompoundStatement) - and statement.type == rd.CompoundStatementType.NOT - ): + elif isinstance(statement, rd.CompoundStatement) and statement.type == rd.CompoundStatementType.NOT: # TODO(mike-hunhoff): verify that we can display NOT statements # https://github.com/mandiant/capa/issues/1602 pass @@ -425,9 +408,7 @@ def render_capa_doc_statement_node( for location in locations: # for each location render child node for range statement - self.render_capa_doc_feature( - parent2, match, statement.child, location, doc - ) + self.render_capa_doc_feature(parent2, match, statement.child, location, doc) return parent2 elif isinstance(statement, rd.SubscopeStatement): @@ -438,9 +419,7 @@ def render_capa_doc_statement_node( else: raise RuntimeError("unexpected match statement type: " + str(statement)) - def render_capa_doc_match( - self, parent: CapaExplorerDataItem, match: rd.Match, doc: rd.ResultDocument - ): + def render_capa_doc_match(self, parent: CapaExplorerDataItem, match: rd.Match, doc: rd.ResultDocument): """render capa match read from doc @param parent: parent node to which new child is assigned @@ -453,10 +432,7 @@ def render_capa_doc_match( return # optional statement with no successful children is empty - if ( - isinstance(match.node, rd.StatementNode) - and match.node.statement.type == rd.CompoundStatementType.OPTIONAL - ): + if isinstance(match.node, rd.StatementNode) and match.node.statement.type == rd.CompoundStatementType.OPTIONAL: if not any(m.success for m in match.children): return @@ -484,9 +460,7 @@ def render_capa_doc_match( def render_capa_doc_by_function(self, doc: rd.ResultDocument): """render rule matches by function meaning each rule match is nested under function where it was found""" - matches_by_function: dict[ - AbsoluteVirtualAddress, tuple[CapaExplorerFunctionItem, set[str]] - ] = {} + matches_by_function: dict[AbsoluteVirtualAddress, tuple[CapaExplorerFunctionItem, set[str]]] = {} for rule in rutils.capability_rules(doc): match_eas: list[int] = [] @@ -507,9 +481,7 @@ def render_capa_doc_by_function(self, doc: rd.ResultDocument): # create a new function root to nest its rule matches; Note: we must use the address of the # function here so everything is displayed properly matches_by_function[func_address] = ( - CapaExplorerFunctionItem( - self.root_node, func_address, can_check=False - ), + CapaExplorerFunctionItem(self.root_node, func_address, can_check=False), set(), ) @@ -525,13 +497,7 @@ def render_capa_doc_by_function(self, doc: rd.ResultDocument): func_root, rule.meta.name, rule.meta.namespace or "", - len( - [ - ea - for ea in match_eas - if capa.ida.helpers.get_func_start_ea(ea) == func_ea - ] - ), + len([ea for ea in match_eas if capa.ida.helpers.get_func_start_ea(ea) == func_ea]), rule.source, can_check=False, ) @@ -562,9 +528,7 @@ def render_capa_doc_by_program(self, doc: rd.ResultDocument): elif capa.rules.Scope.INSTRUCTION in rule.meta.scopes: parent2 = CapaExplorerInstructionItem(parent, location) else: - raise RuntimeError( - "unexpected rule scope: " + str(rule.meta.scopes.static) - ) + raise RuntimeError("unexpected rule scope: " + str(rule.meta.scopes.static)) self.render_capa_doc_match(parent2, match, doc) @@ -715,9 +679,7 @@ def render_capa_doc_feature( if matched_rule is not None: matched_rule_source = matched_rule.source - return CapaExplorerRuleMatchItem( - parent, display, source=matched_rule_source - ) + return CapaExplorerRuleMatchItem(parent, display, source=matched_rule_source) elif isinstance(feature, (frzf.RegexFeature, frzf.SubstringFeature)): for capture, addrs in sorted(match.captures.items()): diff --git a/capa/render/result_document.py b/capa/render/result_document.py index 8aece5c9c..fe2c9e2c1 100644 --- a/capa/render/result_document.py +++ b/capa/render/result_document.py @@ -159,7 +159,8 @@ class CompoundStatementType: OPTIONAL = "optional" -class StatementModel(FrozenModel): ... +class StatementModel(FrozenModel): + ... class CompoundStatement(StatementModel): @@ -648,9 +649,9 @@ def from_capa(cls, meta: Metadata, rules: RuleSet, capabilities: MatchResults) - return ResultDocument(meta=meta, rules=rule_matches) def to_capa(self) -> tuple[Metadata, dict]: - capabilities: dict[str, list[tuple[capa.features.address.Address, capa.features.common.Result]]] = ( - collections.defaultdict(list) - ) + capabilities: dict[ + str, list[tuple[capa.features.address.Address, capa.features.common.Result]] + ] = collections.defaultdict(list) # this doesn't quite work because we don't have the rule source for rules that aren't matched. rules_by_name = { diff --git a/scripts/compare-backends.py b/scripts/compare-backends.py index fa4ddb010..37336654c 100644 --- a/scripts/compare-backends.py +++ b/scripts/compare-backends.py @@ -125,7 +125,6 @@ def collect(args): key = str(file) for backend in BACKENDS: - if (backend, file.name) in { ("binja", "0953cc3b77ed2974b09e3a00708f88de931d681e2d0cb64afbaf714610beabe6.exe_") }: diff --git a/scripts/inspect-binexport2.py b/scripts/inspect-binexport2.py index 07fc79eca..2b59e35e9 100644 --- a/scripts/inspect-binexport2.py +++ b/scripts/inspect-binexport2.py @@ -75,7 +75,6 @@ def _render_expression_tree( tree_index: int, o: io.StringIO, ): - expression_index = operand.expression_index[tree_index] expression = be2.expression[expression_index] children_tree_indexes: list[int] = expression_tree[tree_index] @@ -124,7 +123,6 @@ def _render_expression_tree( return elif expression.type == BinExport2.Expression.OPERATOR: - if len(children_tree_indexes) == 1: # prefix operator, like "ds:" if expression.symbol != "!": @@ -250,7 +248,6 @@ def inspect_instruction(be2: BinExport2, instruction: BinExport2.Instruction, ad def main(argv=None): - if argv is None: argv = sys.argv[1:] From 4103b105b1c7d870c99d22587a8453016d399171 Mon Sep 17 00:00:00 2001 From: mr-tz Date: Tue, 3 Dec 2024 12:01:37 +0000 Subject: [PATCH 15/16] black reformat --- capa/features/address.py | 3 +-- capa/features/extractors/binexport2/__init__.py | 6 ++---- capa/features/extractors/vmray/models.py | 3 +-- capa/ida/plugin/form.py | 6 +++--- capa/render/result_document.py | 9 ++++----- 5 files changed, 11 insertions(+), 16 deletions(-) diff --git a/capa/features/address.py b/capa/features/address.py index f0cbd77d2..45c3a600f 100644 --- a/capa/features/address.py +++ b/capa/features/address.py @@ -10,8 +10,7 @@ class Address(abc.ABC): @abc.abstractmethod - def __eq__(self, other): - ... + def __eq__(self, other): ... @abc.abstractmethod def __lt__(self, other): diff --git a/capa/features/extractors/binexport2/__init__.py b/capa/features/extractors/binexport2/__init__.py index 95213434c..8032b2fca 100644 --- a/capa/features/extractors/binexport2/__init__.py +++ b/capa/features/extractors/binexport2/__init__.py @@ -315,12 +315,10 @@ def contains(self, address: int) -> bool: return self.address <= address < self.end -class ReadMemoryError(ValueError): - ... +class ReadMemoryError(ValueError): ... -class AddressNotMappedError(ReadMemoryError): - ... +class AddressNotMappedError(ReadMemoryError): ... @dataclass diff --git a/capa/features/extractors/vmray/models.py b/capa/features/extractors/vmray/models.py index 3515b2427..c2d6551aa 100644 --- a/capa/features/extractors/vmray/models.py +++ b/capa/features/extractors/vmray/models.py @@ -189,8 +189,7 @@ class GenericReference(BaseModel): source: str -class StaticDataReference(GenericReference): - ... +class StaticDataReference(GenericReference): ... class PEFileBasicInfo(BaseModel): diff --git a/capa/ida/plugin/form.py b/capa/ida/plugin/form.py index 40a2181d6..54bd70409 100644 --- a/capa/ida/plugin/form.py +++ b/capa/ida/plugin/form.py @@ -932,9 +932,9 @@ def get_ask_use_persistent_cache(self, analyze): update_wait_box("verifying cached results") try: - results: Optional[ - capa.render.result_document.ResultDocument - ] = capa.ida.helpers.load_and_verify_cached_results() + results: Optional[capa.render.result_document.ResultDocument] = ( + capa.ida.helpers.load_and_verify_cached_results() + ) except Exception as e: capa.ida.helpers.inform_user_ida_ui("Failed to verify cached results, reanalyzing program") logger.exception("Failed to verify cached results (error: %s)", e) diff --git a/capa/render/result_document.py b/capa/render/result_document.py index fe2c9e2c1..8aece5c9c 100644 --- a/capa/render/result_document.py +++ b/capa/render/result_document.py @@ -159,8 +159,7 @@ class CompoundStatementType: OPTIONAL = "optional" -class StatementModel(FrozenModel): - ... +class StatementModel(FrozenModel): ... class CompoundStatement(StatementModel): @@ -649,9 +648,9 @@ def from_capa(cls, meta: Metadata, rules: RuleSet, capabilities: MatchResults) - return ResultDocument(meta=meta, rules=rule_matches) def to_capa(self) -> tuple[Metadata, dict]: - capabilities: dict[ - str, list[tuple[capa.features.address.Address, capa.features.common.Result]] - ] = collections.defaultdict(list) + capabilities: dict[str, list[tuple[capa.features.address.Address, capa.features.common.Result]]] = ( + collections.defaultdict(list) + ) # this doesn't quite work because we don't have the rule source for rules that aren't matched. rules_by_name = { From 26363cd0df1cfb4380713ba73c0cf5d4a328f12b Mon Sep 17 00:00:00 2001 From: mr-tz Date: Wed, 4 Dec 2024 17:10:33 +0000 Subject: [PATCH 16/16] use proper str type --- capa/ida/plugin/model.py | 32 ++------------------------------ capa/render/vverbose.py | 2 +- 2 files changed, 3 insertions(+), 31 deletions(-) diff --git a/capa/ida/plugin/model.py b/capa/ida/plugin/model.py index 0947bead6..5d5731318 100644 --- a/capa/ida/plugin/model.py +++ b/capa/ida/plugin/model.py @@ -6,7 +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. -from typing import Union, Literal, Optional +from typing import Optional from collections import deque import idc @@ -553,35 +553,7 @@ def capa_doc_feature_to_display(self, feature: frzf.Feature) -> str: @param feature: capa feature read from doc """ - # Use the specific type from the feature instead of direct string assignment - FeatureType = Union[ - Literal[ - "os", - "arch", - "format", - "match", - "characteristic", - "export", - "import", - "section", - "function name", - "substring", - "regex", - "string", - "class", - "namespace", - "api", - "property", - "number", - "bytes", - "offset", - "mnemonic", - "operand number", - "operand offset", - "basic block", - ] - ] - key: FeatureType = feature.type + key = str(feature.type) value = feature.dict(by_alias=True).get(feature.type) if value: diff --git a/capa/render/vverbose.py b/capa/render/vverbose.py index c021bfbb0..1c2255e32 100644 --- a/capa/render/vverbose.py +++ b/capa/render/vverbose.py @@ -168,7 +168,7 @@ def render_feature( ): console.write(" " * indent) - key = feature.type + key = str(feature.type) value: Optional[str] if isinstance(feature, frzf.BasicBlockFeature): # i don't think it makes sense to have standalone basic block features.