mirror of
https://github.com/mandiant/capa.git
synced 2026-03-18 07:49:04 -07:00
Compare commits
6 Commits
fix-2745
...
update-ida
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
10d3031093 | ||
|
|
e9b02b372a | ||
|
|
503c34b8f9 | ||
|
|
888295b37a | ||
|
|
5f9c908315 | ||
|
|
cb2e2323f9 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -122,6 +122,7 @@ scripts/perf/*.zip
|
||||
*/.DS_Store
|
||||
Pipfile
|
||||
Pipfile.lock
|
||||
uv.lock
|
||||
/cache/
|
||||
.github/binja/binaryninja
|
||||
.github/binja/download_headless.py
|
||||
|
||||
@@ -40,6 +40,8 @@
|
||||
### capa Explorer IDA Pro plugin
|
||||
|
||||
- add `ida-plugin.json` for inclusion in the IDA Pro plugin repository @williballenthin
|
||||
- ida plugin: add Qt compatibility layer for PyQt5 and PySide6 support @williballenthin #2707
|
||||
- ida plugin: update ida-settings API @mr-tz #2736
|
||||
|
||||
### Development
|
||||
|
||||
|
||||
@@ -315,3 +315,6 @@ If you use Ghidra, then you can use the [capa + Ghidra integration](/capa/ghidra
|
||||
|
||||
## capa testfiles
|
||||
The [capa-testfiles repository](https://github.com/mandiant/capa-testfiles) contains the data we use to test capa's code and rules
|
||||
|
||||
## mailing list
|
||||
Subscribe to the FLARE mailing list for community announcements! Email "subscribe" to [flare-external@google.com](mailto:flare-external@google.com?subject=subscribe).
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
|
||||
|
||||
import ida_kernwin
|
||||
from PyQt5 import QtCore
|
||||
|
||||
from capa.ida.plugin.error import UserCancelledError
|
||||
from capa.ida.plugin.qt_compat import QtCore, Signal
|
||||
from capa.features.extractors.ida.extractor import IdaFeatureExtractor
|
||||
from capa.features.extractors.base_extractor import FunctionHandle
|
||||
|
||||
@@ -24,7 +24,7 @@ from capa.features.extractors.base_extractor import FunctionHandle
|
||||
class CapaExplorerProgressIndicator(QtCore.QObject):
|
||||
"""implement progress signal, used during feature extraction"""
|
||||
|
||||
progress = QtCore.pyqtSignal(str)
|
||||
progress = Signal(str)
|
||||
|
||||
def update(self, text):
|
||||
"""emit progress update
|
||||
|
||||
@@ -23,7 +23,6 @@ from pathlib import Path
|
||||
import idaapi
|
||||
import ida_kernwin
|
||||
import ida_settings
|
||||
from PyQt5 import QtGui, QtCore, QtWidgets
|
||||
|
||||
import capa.main
|
||||
import capa.rules
|
||||
@@ -51,10 +50,10 @@ from capa.ida.plugin.hooks import CapaExplorerIdaHooks
|
||||
from capa.ida.plugin.model import CapaExplorerDataModel
|
||||
from capa.ida.plugin.proxy import CapaExplorerRangeProxyModel, CapaExplorerSearchProxyModel
|
||||
from capa.ida.plugin.extractor import CapaExplorerFeatureExtractor
|
||||
from capa.ida.plugin.qt_compat import QtGui, QtCore, QtWidgets
|
||||
from capa.features.extractors.base_extractor import FunctionHandle
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
settings = ida_settings.IDASettings("capa")
|
||||
|
||||
CAPA_SETTINGS_RULE_PATH = "rule_path"
|
||||
CAPA_SETTINGS_RULEGEN_AUTHOR = "rulegen_author"
|
||||
@@ -79,6 +78,13 @@ AnalyzeOptionsText = {
|
||||
}
|
||||
|
||||
|
||||
def get_setting(key: str, default=None):
|
||||
try:
|
||||
return ida_settings.get_current_plugin_setting(key)
|
||||
except KeyError:
|
||||
return default
|
||||
|
||||
|
||||
def write_file(path: Path, data):
|
||||
""" """
|
||||
path.write_bytes(data)
|
||||
@@ -107,7 +113,7 @@ class QLineEditClicked(QtWidgets.QLineEdit):
|
||||
old = self.text()
|
||||
new = str(
|
||||
QtWidgets.QFileDialog.getExistingDirectory(
|
||||
self.parent(), "Please select a capa rules directory", settings.user.get(CAPA_SETTINGS_RULE_PATH, "")
|
||||
self.parent(), "Please select a capa rules directory", get_setting(CAPA_SETTINGS_RULE_PATH, "")
|
||||
)
|
||||
)
|
||||
if new:
|
||||
@@ -125,8 +131,8 @@ class CapaSettingsInputDialog(QtWidgets.QDialog):
|
||||
self.setMinimumWidth(500)
|
||||
self.setWindowFlags(self.windowFlags() & ~QtCore.Qt.WindowContextHelpButtonHint)
|
||||
|
||||
self.edit_rule_path = QLineEditClicked(settings.user.get(CAPA_SETTINGS_RULE_PATH, ""))
|
||||
self.edit_rule_author = QtWidgets.QLineEdit(settings.user.get(CAPA_SETTINGS_RULEGEN_AUTHOR, ""))
|
||||
self.edit_rule_path = QLineEditClicked(get_setting(CAPA_SETTINGS_RULE_PATH, ""))
|
||||
self.edit_rule_author = QtWidgets.QLineEdit(get_setting(CAPA_SETTINGS_RULEGEN_AUTHOR, ""))
|
||||
self.edit_rule_scope = QtWidgets.QComboBox()
|
||||
self.edit_rules_link = QtWidgets.QLabel()
|
||||
self.edit_analyze = QtWidgets.QComboBox()
|
||||
@@ -141,11 +147,11 @@ class CapaSettingsInputDialog(QtWidgets.QDialog):
|
||||
|
||||
scopes = ("file", "function", "basic block", "instruction")
|
||||
self.edit_rule_scope.addItems(scopes)
|
||||
self.edit_rule_scope.setCurrentIndex(scopes.index(settings.user.get(CAPA_SETTINGS_RULEGEN_SCOPE, "function")))
|
||||
self.edit_rule_scope.setCurrentIndex(scopes.index(get_setting(CAPA_SETTINGS_RULEGEN_SCOPE, "function")))
|
||||
|
||||
self.edit_analyze.addItems(AnalyzeOptionsText.values())
|
||||
# set the default analysis option here
|
||||
self.edit_analyze.setCurrentIndex(settings.user.get(CAPA_SETTINGS_ANALYZE, Options.NO_ANALYSIS))
|
||||
self.edit_analyze.setCurrentIndex(get_setting(CAPA_SETTINGS_ANALYZE, Options.NO_ANALYSIS))
|
||||
|
||||
buttons = QtWidgets.QDialogButtonBox(QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel, self)
|
||||
|
||||
@@ -235,7 +241,7 @@ class CapaExplorerForm(idaapi.PluginForm):
|
||||
|
||||
self.Show()
|
||||
|
||||
analyze = settings.user.get(CAPA_SETTINGS_ANALYZE)
|
||||
analyze = get_setting(CAPA_SETTINGS_ANALYZE)
|
||||
if analyze != Options.NO_ANALYSIS or (option & Options.ANALYZE_AUTO) == Options.ANALYZE_AUTO:
|
||||
self.analyze_program(analyze=analyze)
|
||||
|
||||
@@ -581,7 +587,7 @@ class CapaExplorerForm(idaapi.PluginForm):
|
||||
|
||||
def ensure_capa_settings_rule_path(self):
|
||||
try:
|
||||
path: str = settings.user.get(CAPA_SETTINGS_RULE_PATH, "")
|
||||
path: str = get_setting(CAPA_SETTINGS_RULE_PATH, "")
|
||||
|
||||
# resolve rules directory - check self and settings first, then ask user
|
||||
# pathlib.Path considers "" equivalent to "." so we first check if rule path is an empty string
|
||||
@@ -611,7 +617,7 @@ class CapaExplorerForm(idaapi.PluginForm):
|
||||
logger.error("rule path %s does not exist or cannot be accessed", path)
|
||||
return False
|
||||
|
||||
settings.user[CAPA_SETTINGS_RULE_PATH] = path
|
||||
ida_settings.set_current_plugin_setting(CAPA_SETTINGS_RULE_PATH, path)
|
||||
except UserCancelledError:
|
||||
capa.ida.helpers.inform_user_ida_ui("Analysis requires capa rules")
|
||||
logger.warning(
|
||||
@@ -635,7 +641,7 @@ class CapaExplorerForm(idaapi.PluginForm):
|
||||
if not self.ensure_capa_settings_rule_path():
|
||||
return False
|
||||
|
||||
rule_path: Path = Path(settings.user.get(CAPA_SETTINGS_RULE_PATH, ""))
|
||||
rule_path: Path = Path(get_setting(CAPA_SETTINGS_RULE_PATH, ""))
|
||||
try:
|
||||
|
||||
def on_load_rule(_, i, total):
|
||||
@@ -649,10 +655,10 @@ class CapaExplorerForm(idaapi.PluginForm):
|
||||
return None
|
||||
except Exception as e:
|
||||
capa.ida.helpers.inform_user_ida_ui(
|
||||
f"Failed to load capa rules from {settings.user[CAPA_SETTINGS_RULE_PATH]}"
|
||||
f"Failed to load capa rules from {get_setting(CAPA_SETTINGS_RULE_PATH)}"
|
||||
)
|
||||
|
||||
logger.error("Failed to load capa rules from %s (error: %s).", settings.user[CAPA_SETTINGS_RULE_PATH], e)
|
||||
logger.error("Failed to load capa rules from %s (error: %s).", get_setting(CAPA_SETTINGS_RULE_PATH), e)
|
||||
logger.error(
|
||||
"Make sure your file directory contains properly " # noqa: G003 [logging statement uses +]
|
||||
+ "formatted capa rules. You can download and extract the official rules from %s. "
|
||||
@@ -661,7 +667,7 @@ class CapaExplorerForm(idaapi.PluginForm):
|
||||
CAPA_RULESET_DOC_URL,
|
||||
)
|
||||
|
||||
settings.user[CAPA_SETTINGS_RULE_PATH] = ""
|
||||
ida_settings.set_current_plugin_setting(CAPA_SETTINGS_RULE_PATH, "")
|
||||
return None
|
||||
|
||||
def load_capa_results(self, new_analysis, from_cache):
|
||||
@@ -701,7 +707,7 @@ class CapaExplorerForm(idaapi.PluginForm):
|
||||
update_wait_box("verifying cached results")
|
||||
|
||||
count_source_rules = self.program_analysis_ruleset_cache.source_rule_count
|
||||
user_settings = settings.user[CAPA_SETTINGS_RULE_PATH]
|
||||
user_settings = get_setting(CAPA_SETTINGS_RULE_PATH)
|
||||
view_status_rules: str = f"{user_settings} ({count_source_rules} rules)"
|
||||
|
||||
# warn user about potentially outdated rules, depending on the use-case this may be expected
|
||||
@@ -775,7 +781,7 @@ class CapaExplorerForm(idaapi.PluginForm):
|
||||
update_wait_box("extracting features")
|
||||
|
||||
try:
|
||||
meta = capa.ida.helpers.collect_metadata([Path(settings.user[CAPA_SETTINGS_RULE_PATH])])
|
||||
meta = capa.ida.helpers.collect_metadata([Path(get_setting(CAPA_SETTINGS_RULE_PATH))])
|
||||
capabilities = capa.capabilities.common.find_capabilities(
|
||||
ruleset, self.feature_extractor, disable_progress=True
|
||||
)
|
||||
@@ -855,7 +861,7 @@ class CapaExplorerForm(idaapi.PluginForm):
|
||||
except Exception as e:
|
||||
logger.exception("Failed to save results to database (error: %s)", e)
|
||||
return False
|
||||
user_settings = settings.user[CAPA_SETTINGS_RULE_PATH]
|
||||
user_settings = get_setting(CAPA_SETTINGS_RULE_PATH)
|
||||
count_source_rules = self.program_analysis_ruleset_cache.source_rule_count
|
||||
new_view_status = f"capa rules: {user_settings} ({count_source_rules} rules)"
|
||||
# regardless of new analysis, render results - e.g. we may only want to render results after checking
|
||||
@@ -1076,13 +1082,13 @@ class CapaExplorerForm(idaapi.PluginForm):
|
||||
# load preview and feature tree
|
||||
self.view_rulegen_preview.load_preview_meta(
|
||||
self.rulegen_current_function.address if self.rulegen_current_function else None,
|
||||
settings.user.get(CAPA_SETTINGS_RULEGEN_AUTHOR, "<insert_author>"),
|
||||
settings.user.get(CAPA_SETTINGS_RULEGEN_SCOPE, "function"),
|
||||
get_setting(CAPA_SETTINGS_RULEGEN_AUTHOR, "<insert_author>"),
|
||||
get_setting(CAPA_SETTINGS_RULEGEN_SCOPE, "function"),
|
||||
)
|
||||
|
||||
self.view_rulegen_features.load_features(all_file_features, all_function_features)
|
||||
|
||||
self.set_view_status_label(f"capa rules: {settings.user[CAPA_SETTINGS_RULE_PATH]}")
|
||||
self.set_view_status_label(f"capa rules: {get_setting(CAPA_SETTINGS_RULE_PATH)}")
|
||||
except Exception as e:
|
||||
logger.exception("Failed to render views (error: %s)", e)
|
||||
return False
|
||||
@@ -1303,12 +1309,11 @@ class CapaExplorerForm(idaapi.PluginForm):
|
||||
""" """
|
||||
dialog = CapaSettingsInputDialog("capa explorer settings", parent=self.parent)
|
||||
if dialog.exec_():
|
||||
(
|
||||
settings.user[CAPA_SETTINGS_RULE_PATH],
|
||||
settings.user[CAPA_SETTINGS_RULEGEN_AUTHOR],
|
||||
settings.user[CAPA_SETTINGS_RULEGEN_SCOPE],
|
||||
settings.user[CAPA_SETTINGS_ANALYZE],
|
||||
) = dialog.get_values()
|
||||
rule_path, rule_author, rule_scope, analyze = dialog.get_values()
|
||||
ida_settings.set_current_plugin_setting(CAPA_SETTINGS_RULE_PATH, rule_path)
|
||||
ida_settings.set_current_plugin_setting(CAPA_SETTINGS_RULEGEN_AUTHOR, rule_author)
|
||||
ida_settings.set_current_plugin_setting(CAPA_SETTINGS_RULEGEN_SCOPE, rule_scope)
|
||||
ida_settings.set_current_plugin_setting(CAPA_SETTINGS_ANALYZE, analyze)
|
||||
|
||||
def save_program_analysis(self):
|
||||
""" """
|
||||
@@ -1358,7 +1363,7 @@ class CapaExplorerForm(idaapi.PluginForm):
|
||||
|
||||
@param state: checked state
|
||||
"""
|
||||
if state == QtCore.Qt.Checked:
|
||||
if state:
|
||||
self.limit_results_to_function(idaapi.get_func(idaapi.get_screen_ea()))
|
||||
else:
|
||||
self.range_model_proxy.reset_address_range_filter()
|
||||
@@ -1367,7 +1372,7 @@ class CapaExplorerForm(idaapi.PluginForm):
|
||||
|
||||
def slot_checkbox_limit_features_by_ea(self, state):
|
||||
""" """
|
||||
if state == QtCore.Qt.Checked:
|
||||
if state:
|
||||
self.view_rulegen_features.filter_items_by_ea(idaapi.get_screen_ea())
|
||||
else:
|
||||
self.view_rulegen_features.show_all_items()
|
||||
@@ -1408,7 +1413,7 @@ class CapaExplorerForm(idaapi.PluginForm):
|
||||
"""create Qt dialog to ask user for a directory"""
|
||||
return str(
|
||||
QtWidgets.QFileDialog.getExistingDirectory(
|
||||
self.parent, "Please select a capa rules directory", settings.user.get(CAPA_SETTINGS_RULE_PATH, "")
|
||||
self.parent, "Please select a capa rules directory", get_setting(CAPA_SETTINGS_RULE_PATH, "")
|
||||
)
|
||||
)
|
||||
|
||||
@@ -1417,7 +1422,7 @@ class CapaExplorerForm(idaapi.PluginForm):
|
||||
return QtWidgets.QFileDialog.getSaveFileName(
|
||||
None,
|
||||
"Please select a location to save capa rule file",
|
||||
settings.user.get(CAPA_SETTINGS_RULE_PATH, ""),
|
||||
get_setting(CAPA_SETTINGS_RULE_PATH, ""),
|
||||
"*.yml",
|
||||
)[0]
|
||||
|
||||
|
||||
@@ -18,10 +18,10 @@ from typing import Iterator, Optional
|
||||
|
||||
import idc
|
||||
import idaapi
|
||||
from PyQt5 import QtCore
|
||||
|
||||
import capa.ida.helpers
|
||||
from capa.features.address import Address, FileOffsetAddress, AbsoluteVirtualAddress
|
||||
from capa.ida.plugin.qt_compat import QtCore, qt_get_item_flag_tristate
|
||||
|
||||
|
||||
def info_to_name(display):
|
||||
@@ -55,7 +55,7 @@ class CapaExplorerDataItem:
|
||||
self.flags = QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable
|
||||
|
||||
if self._can_check:
|
||||
self.flags = self.flags | QtCore.Qt.ItemIsUserCheckable | QtCore.Qt.ItemIsTristate
|
||||
self.flags = self.flags | QtCore.Qt.ItemIsUserCheckable | qt_get_item_flag_tristate()
|
||||
|
||||
if self.pred:
|
||||
self.pred.appendChild(self)
|
||||
|
||||
@@ -18,7 +18,6 @@ from collections import deque
|
||||
|
||||
import idc
|
||||
import idaapi
|
||||
from PyQt5 import QtGui, QtCore
|
||||
|
||||
import capa.rules
|
||||
import capa.ida.helpers
|
||||
@@ -42,6 +41,7 @@ from capa.ida.plugin.item import (
|
||||
CapaExplorerInstructionViewItem,
|
||||
)
|
||||
from capa.features.address import Address, AbsoluteVirtualAddress
|
||||
from capa.ida.plugin.qt_compat import QtGui, QtCore
|
||||
|
||||
# default highlight color used in IDA window
|
||||
DEFAULT_HIGHLIGHT = 0xE6C700
|
||||
@@ -269,7 +269,7 @@ class CapaExplorerDataModel(QtCore.QAbstractItemModel):
|
||||
visited.add(child_index)
|
||||
|
||||
for idx in range(self.rowCount(child_index)):
|
||||
stack.append(child_index.child(idx, 0))
|
||||
stack.append(self.index(idx, 0, child_index))
|
||||
|
||||
def reset_ida_highlighting(self, item, checked):
|
||||
"""reset IDA highlight for item
|
||||
|
||||
@@ -12,10 +12,8 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from PyQt5 import QtCore
|
||||
from PyQt5.QtCore import Qt
|
||||
|
||||
from capa.ida.plugin.model import CapaExplorerDataModel
|
||||
from capa.ida.plugin.qt_compat import Qt, QtCore
|
||||
|
||||
|
||||
class CapaExplorerRangeProxyModel(QtCore.QSortFilterProxyModel):
|
||||
|
||||
79
capa/ida/plugin/qt_compat.py
Normal file
79
capa/ida/plugin/qt_compat.py
Normal file
@@ -0,0 +1,79 @@
|
||||
# Copyright 2020 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Qt compatibility layer for capa IDA Pro plugin.
|
||||
|
||||
Handles PyQt5 (IDA < 9.2) vs PySide6 (IDA >= 9.2) differences.
|
||||
This module provides a unified import interface for Qt modules and handles
|
||||
API changes between Qt5 and Qt6.
|
||||
"""
|
||||
|
||||
try:
|
||||
# IDA 9.2+ uses PySide6
|
||||
from PySide6 import QtGui, QtCore, QtWidgets
|
||||
from PySide6.QtGui import QAction
|
||||
|
||||
QT_LIBRARY = "PySide6"
|
||||
Signal = QtCore.Signal
|
||||
except ImportError:
|
||||
# Older IDA versions use PyQt5
|
||||
try:
|
||||
from PyQt5 import QtGui, QtCore, QtWidgets
|
||||
from PyQt5.QtWidgets import QAction
|
||||
|
||||
QT_LIBRARY = "PyQt5"
|
||||
Signal = QtCore.pyqtSignal
|
||||
except ImportError:
|
||||
raise ImportError("Neither PySide6 nor PyQt5 is available. Cannot initialize capa IDA plugin.")
|
||||
|
||||
Qt = QtCore.Qt
|
||||
|
||||
|
||||
def qt_get_item_flag_tristate():
|
||||
"""
|
||||
Get the tristate item flag compatible with Qt5 and Qt6.
|
||||
|
||||
Qt5 (PyQt5): Uses Qt.ItemIsTristate
|
||||
Qt6 (PySide6): Qt.ItemIsTristate was removed, uses Qt.ItemIsAutoTristate
|
||||
|
||||
ItemIsAutoTristate automatically manages tristate based on child checkboxes,
|
||||
matching the original ItemIsTristate behavior where parent checkboxes reflect
|
||||
the check state of their children.
|
||||
|
||||
Returns:
|
||||
int: The appropriate flag value for the Qt version
|
||||
|
||||
Raises:
|
||||
AttributeError: If the tristate flag cannot be found in the Qt library
|
||||
"""
|
||||
if QT_LIBRARY == "PySide6":
|
||||
# Qt6: ItemIsTristate was removed, replaced with ItemIsAutoTristate
|
||||
# Try different possible locations (API varies slightly across PySide6 versions)
|
||||
if hasattr(Qt, "ItemIsAutoTristate"):
|
||||
return Qt.ItemIsAutoTristate
|
||||
elif hasattr(Qt, "ItemFlag") and hasattr(Qt.ItemFlag, "ItemIsAutoTristate"):
|
||||
return Qt.ItemFlag.ItemIsAutoTristate
|
||||
else:
|
||||
raise AttributeError(
|
||||
"Cannot find ItemIsAutoTristate in PySide6. "
|
||||
+ "Your PySide6 version may be incompatible with capa. "
|
||||
+ f"Available Qt attributes: {[attr for attr in dir(Qt) if 'Item' in attr]}"
|
||||
)
|
||||
else:
|
||||
# Qt5: Use the original ItemIsTristate flag
|
||||
return Qt.ItemIsTristate
|
||||
|
||||
|
||||
__all__ = ["qt_get_item_flag_tristate", "Signal", "QAction", "QtGui", "QtCore", "QtWidgets"]
|
||||
@@ -18,7 +18,6 @@ from collections import Counter
|
||||
|
||||
import idc
|
||||
import idaapi
|
||||
from PyQt5 import QtGui, QtCore, QtWidgets
|
||||
|
||||
import capa.rules
|
||||
import capa.engine
|
||||
@@ -28,6 +27,7 @@ import capa.features.basicblock
|
||||
from capa.ida.plugin.item import CapaExplorerFunctionItem
|
||||
from capa.features.address import AbsoluteVirtualAddress, _NoAddress
|
||||
from capa.ida.plugin.model import CapaExplorerDataModel
|
||||
from capa.ida.plugin.qt_compat import QtGui, QtCore, Signal, QAction, QtWidgets
|
||||
|
||||
MAX_SECTION_SIZE = 750
|
||||
|
||||
@@ -147,7 +147,7 @@ def calc_item_depth(o):
|
||||
|
||||
def build_action(o, display, data, slot):
|
||||
""" """
|
||||
action = QtWidgets.QAction(display, o)
|
||||
action = QAction(display, o)
|
||||
|
||||
action.setData(data)
|
||||
action.triggered.connect(lambda checked: slot(action))
|
||||
@@ -312,7 +312,7 @@ class CapaExplorerRulegenPreview(QtWidgets.QTextEdit):
|
||||
|
||||
|
||||
class CapaExplorerRulegenEditor(QtWidgets.QTreeWidget):
|
||||
updated = QtCore.pyqtSignal()
|
||||
updated = Signal()
|
||||
|
||||
def __init__(self, preview, parent=None):
|
||||
""" """
|
||||
|
||||
@@ -74,7 +74,7 @@ dependencies = [
|
||||
# comments and context.
|
||||
"pyyaml>=6",
|
||||
"colorama>=0.4",
|
||||
"ida-settings>=2.1.0,<3", # v3 has breaking changes
|
||||
"ida-settings>=3.1.0",
|
||||
"ruamel.yaml>=0.18",
|
||||
"pefile>=2023.2.7",
|
||||
"pyelftools>=0.31",
|
||||
@@ -104,7 +104,7 @@ dependencies = [
|
||||
|
||||
"networkx>=3",
|
||||
|
||||
"dnfile>=0.15.0",
|
||||
"dnfile>=0.17.0",
|
||||
]
|
||||
dynamic = ["version"]
|
||||
|
||||
@@ -149,7 +149,7 @@ dev = [
|
||||
"types-PyYAML==6.0.8",
|
||||
"types-psutil==7.0.0.20250218",
|
||||
"types_requests==2.32.0.20240712",
|
||||
"types-protobuf==6.30.2.20250516",
|
||||
"types-protobuf==6.32.1.20250918",
|
||||
"deptry==0.23.0"
|
||||
]
|
||||
build = [
|
||||
@@ -162,11 +162,13 @@ build = [
|
||||
"build==1.2.2"
|
||||
]
|
||||
scripts = [
|
||||
# can (optionally) be more lenient on dependencies here
|
||||
# see comment on dependencies for more context
|
||||
"jschema_to_python==1.2.3",
|
||||
"psutil==7.0.0",
|
||||
"psutil==7.1.2",
|
||||
"stix2==3.0.1",
|
||||
"sarif_om==1.0.4",
|
||||
"requests==2.32.3",
|
||||
"requests>=2.32.4",
|
||||
]
|
||||
|
||||
[tool.deptry]
|
||||
@@ -198,7 +200,8 @@ known_first_party = [
|
||||
"idc",
|
||||
"java",
|
||||
"netnode",
|
||||
"PyQt5"
|
||||
"PyQt5",
|
||||
"PySide6"
|
||||
]
|
||||
|
||||
[tool.deptry.per_rule_ignores]
|
||||
|
||||
@@ -10,18 +10,18 @@ annotated-types==0.7.0
|
||||
colorama==0.4.6
|
||||
cxxfilt==0.3.0
|
||||
dncil==1.0.2
|
||||
dnfile==0.16.4
|
||||
dnfile==0.17.0
|
||||
funcy==2.0
|
||||
humanize==4.13.0
|
||||
ida-netnode==3.0
|
||||
ida-settings==2.1.0
|
||||
ida-settings==3.2.2
|
||||
intervaltree==3.1.0
|
||||
markdown-it-py==4.0.0
|
||||
mdurl==0.1.2
|
||||
msgpack==1.0.8
|
||||
networkx==3.4.2
|
||||
pefile==2024.8.26
|
||||
pip==25.2
|
||||
pip==25.3
|
||||
protobuf==6.31.1
|
||||
pyasn1==0.5.1
|
||||
pyasn1-modules==0.3.0
|
||||
@@ -38,7 +38,7 @@ python-flirt==0.9.2
|
||||
pyyaml==6.0.2
|
||||
rich==14.2.0
|
||||
ruamel-yaml==0.18.6
|
||||
ruamel-yaml-clib==0.2.8
|
||||
ruamel-yaml-clib==0.2.14
|
||||
setuptools==80.9.0
|
||||
six==1.17.0
|
||||
sortedcontainers==2.4.0
|
||||
|
||||
Reference in New Issue
Block a user