Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix: Issue #2307 #2439

Merged
merged 17 commits into from
Dec 5, 2024
101 changes: 52 additions & 49 deletions capa/features/freeze/features.py
Original file line number Diff line number Diff line change
Expand Up @@ -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, Literal, Optional, Annotated

from pydantic import Field, BaseModel, ConfigDict

Expand Down Expand Up @@ -209,168 +209,171 @@ def feature_from_capa(f: capa.features.common.Feature) -> "Feature":


class OSFeature(FeatureModel):
type: str = "os"
type: Literal["os"] = "os"
os: str
description: Optional[str] = None


class ArchFeature(FeatureModel):
type: str = "arch"
type: Literal["arch"] = "arch"
arch: str
description: Optional[str] = None


class FormatFeature(FeatureModel):
type: str = "format"
type: Literal["format"] = "format"
format: str
description: Optional[str] = None


class MatchFeature(FeatureModel):
type: str = "match"
type: Literal["match"] = "match"
match: str
description: Optional[str] = None


class CharacteristicFeature(FeatureModel):
type: str = "characteristic"
type: Literal["characteristic"] = "characteristic"
characteristic: str
description: Optional[str] = None


class ExportFeature(FeatureModel):
type: str = "export"
type: Literal["export"] = "export"
export: str
description: Optional[str] = None


class ImportFeature(FeatureModel):
type: str = "import"
type: Literal["import"] = "import"
import_: str = Field(alias="import")
description: Optional[str] = None


class SectionFeature(FeatureModel):
type: str = "section"
type: Literal["section"] = "section"
section: str
description: Optional[str] = None


class FunctionNameFeature(FeatureModel):
type: str = "function name"
type: Literal["function name"] = "function name"
function_name: str = Field(alias="function name")
description: Optional[str] = None


class SubstringFeature(FeatureModel):
type: str = "substring"
type: Literal["substring"] = "substring"
substring: str
description: Optional[str] = None


class RegexFeature(FeatureModel):
type: str = "regex"
type: Literal["regex"] = "regex"
regex: str
description: Optional[str] = None


class StringFeature(FeatureModel):
type: str = "string"
type: Literal["string"] = "string"
string: str
description: Optional[str] = None


class ClassFeature(FeatureModel):
type: str = "class"
type: Literal["class"] = "class"
class_: str = Field(alias="class")
description: Optional[str] = None


class NamespaceFeature(FeatureModel):
type: str = "namespace"
type: Literal["namespace"] = "namespace"
namespace: str
description: Optional[str] = None


class BasicBlockFeature(FeatureModel):
type: str = "basic block"
type: Literal["basic block"] = "basic block"
description: Optional[str] = None


class APIFeature(FeatureModel):
type: str = "api"
type: Literal["api"] = "api"
api: str
description: Optional[str] = None


class PropertyFeature(FeatureModel):
type: str = "property"
type: Literal["property"] = "property"
access: Optional[str] = None
property: str
description: Optional[str] = None


class NumberFeature(FeatureModel):
type: str = "number"
type: Literal["number"] = "number"
number: Union[int, float]
description: Optional[str] = None


class BytesFeature(FeatureModel):
type: str = "bytes"
type: Literal["bytes"] = "bytes"
bytes: str
description: Optional[str] = None


class OffsetFeature(FeatureModel):
type: str = "offset"
type: Literal["offset"] = "offset"
offset: int
description: Optional[str] = None


class MnemonicFeature(FeatureModel):
type: str = "mnemonic"
type: Literal["mnemonic"] = "mnemonic"
mnemonic: str
description: Optional[str] = None


class OperandNumberFeature(FeatureModel):
type: str = "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: str = "operand offset"
type: Literal["operand offset"] = "operand offset"
index: int
operand_offset: int = Field(alias="operand offset")
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"),
]
77 changes: 67 additions & 10 deletions capa/ida/plugin/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 Union, Literal, Optional
from collections import deque

import idc
Expand Down Expand Up @@ -271,7 +271,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
Expand Down Expand Up @@ -433,11 +438,19 @@ def render_capa_doc_match(self, parent: CapaExplorerDataItem, match: rd.Match, d

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))
Expand Down Expand Up @@ -494,7 +507,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()
Expand Down Expand Up @@ -529,12 +548,40 @@ 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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use a string here (just for rendering)? (and same in vverbose?)

value = feature.dict(by_alias=True).get(feature.type)

if value:
Expand Down Expand Up @@ -640,7 +687,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
Expand Down Expand Up @@ -671,7 +721,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(
Expand Down Expand Up @@ -713,7 +766,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
Expand Down
1 change: 0 additions & 1 deletion scripts/compare-backends.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,6 @@ def collect(args):
key = str(file)

for backend in BACKENDS:

if (backend, file.name) in {
("binja", "0953cc3b77ed2974b09e3a00708f88de931d681e2d0cb64afbaf714610beabe6.exe_")
}:
Expand Down
Loading
Loading