mirror of
https://github.com/mandiant/capa.git
synced 2025-12-13 08:00:44 -08:00
Compare commits
58 Commits
ci/add-gem
...
update-ida
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
10d3031093 | ||
|
|
e9b02b372a | ||
|
|
503c34b8f9 | ||
|
|
888295b37a | ||
|
|
5f9c908315 | ||
|
|
cb2e2323f9 | ||
|
|
5ea63770ba | ||
|
|
6795813fbe | ||
|
|
ca708ca52e | ||
|
|
68cf74d60c | ||
|
|
5a0c47419f | ||
|
|
4dbdd9dcfa | ||
|
|
82cbfd33db | ||
|
|
5906bb3ecf | ||
|
|
08319f598f | ||
|
|
e6df6ad0cd | ||
|
|
add09df061 | ||
|
|
acb34e88d6 | ||
|
|
0099e75704 | ||
|
|
da0803b671 | ||
|
|
789747282d | ||
|
|
3bc2d9915c | ||
|
|
5974440ab7 | ||
|
|
b9d517a70b | ||
|
|
e5b8788620 | ||
|
|
ec411f1552 | ||
|
|
6871adc9dc | ||
|
|
07880c1418 | ||
|
|
5a6c8ca7c1 | ||
|
|
3bd8371d0c | ||
|
|
d0c87ef32c | ||
|
|
bd2731f87f | ||
|
|
4a167d7188 | ||
|
|
c01bc346fc | ||
|
|
826330f511 | ||
|
|
40e5095577 | ||
|
|
c7eede3c53 | ||
|
|
1a5f50195a | ||
|
|
aafca2e00a | ||
|
|
3a24fabeb6 | ||
|
|
2f81bb79f9 | ||
|
|
fc83b7b0a1 | ||
|
|
d430aea04e | ||
|
|
1eb42599cf | ||
|
|
618ae2111b | ||
|
|
42b6d8106a | ||
|
|
78a020e1ac | ||
|
|
a80f85aab4 | ||
|
|
f94f554d15 | ||
|
|
d456d52e81 | ||
|
|
2a18b08a80 | ||
|
|
dd2e350a1a | ||
|
|
164a7bdfb5 | ||
|
|
d7c896bbc6 | ||
|
|
8185ac4dde | ||
|
|
92a6ddff99 | ||
|
|
af87fae036 | ||
|
|
c774db26f0 |
22
.bumpversion.toml
Normal file
22
.bumpversion.toml
Normal file
@@ -0,0 +1,22 @@
|
||||
[tool.bumpversion]
|
||||
current_version = "9.2.1"
|
||||
|
||||
[[tool.bumpversion.files]]
|
||||
filename = "capa/version.py"
|
||||
search = '__version__ = "{current_version}"'
|
||||
replace = '__version__ = "{new_version}"'
|
||||
|
||||
[[tool.bumpversion.files]]
|
||||
filename = "capa/ida/plugin/ida-plugin.json"
|
||||
search = '"version": "{current_version}"'
|
||||
replace = '"version": "{new_version}"'
|
||||
|
||||
[[tool.bumpversion.files]]
|
||||
filename = "capa/ida/plugin/ida-plugin.json"
|
||||
search = '"flare-capa=={current_version}"'
|
||||
replace = '"flare-capa=={new_version}"'
|
||||
|
||||
[[tool.bumpversion.files]]
|
||||
filename = "CHANGELOG.md"
|
||||
search = "v{current_version}...master"
|
||||
replace = "{current_version}...{new_version}"
|
||||
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
|
||||
|
||||
27
CHANGELOG.md
27
CHANGELOG.md
@@ -7,21 +7,46 @@
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
### New Rules (2)
|
||||
### New Rules (21)
|
||||
|
||||
- anti-analysis/anti-vm/vm-detection/detect-mouse-movement-via-activity-checks-on-windows tevajdr@gmail.com
|
||||
- nursery/create-executable-heap moritz.raabe@mandiant.com
|
||||
- anti-analysis/packer/dxpack/packed-with-dxpack jakubjozwiak@google.com
|
||||
- anti-analysis/anti-av/patch-bitdefender-hooking-dll-function jakubjozwiak@google.com
|
||||
- nursery/acquire-load-driver-privileges mehunhoff@google.com
|
||||
- nursery/communicate-using-ftp mehunhoff@google.com
|
||||
- linking/static/eclipse-paho-mqtt-c/linked-against-eclipse-paho-mqtt-c jakubjozwiak@google.com
|
||||
- linking/static/qmqtt/linked-against-qmqtt jakubjozwiak@google.com
|
||||
- anti-analysis/anti-forensic/disable-powershell-transcription jakubjozwiak@google.com
|
||||
- host-interaction/powershell/bypass-powershell-constrained-language-mode-via-getsystemlockdownpolicy-patch jakubjozwiak@google.com
|
||||
- linking/static/grpc/linked-against-grpc jakubjozwiak@google.com
|
||||
- linking/static/hp-socket/linked-against-hp-socket jakubjozwiak@google.com
|
||||
- load-code/execute-jscript-via-vsaengine-in-dotnet jakubjozwiak@google.com
|
||||
- linking/static/funchook/linked-against-funchook jakubjozwiak@google.com
|
||||
- linking/static/plthook/linked-against-plthook jakubjozwiak@google.com
|
||||
- host-interaction/network/enumerate-tcp-connections-via-wmi-com-api jakubjozwiak@google.com
|
||||
- host-interaction/network/routing-table/create-routing-table-entry jakubjozwiak@google.com
|
||||
- host-interaction/network/routing-table/get-routing-table michael.hunhoff@mandiant.com
|
||||
- host-interaction/file-system/use-io_uring-io-interface-on-linux jakubjozwiak@google.com
|
||||
- collection/keylog/log-keystrokes-via-direct-input zeze-zeze
|
||||
-
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- binja: fix a crash during feature extraction when the MLIL is unavailable @xusheng6 #2714
|
||||
|
||||
### capa Explorer Web
|
||||
|
||||
### 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
|
||||
|
||||
- ci: remove redundant "test_run" action from build workflow @mike-hunhoff #2692
|
||||
- dev: add bumpmyversion to bump and sync versions across the project @mr-tz
|
||||
|
||||
### Raw diffs
|
||||
- [capa v9.2.1...master](https://github.com/mandiant/capa/compare/v9.2.1...master)
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -19,7 +19,6 @@ from binaryninja import (
|
||||
Function,
|
||||
BinaryView,
|
||||
SymbolType,
|
||||
ILException,
|
||||
RegisterValueType,
|
||||
VariableSourceType,
|
||||
LowLevelILOperation,
|
||||
@@ -192,9 +191,8 @@ def extract_stackstring(fh: FunctionHandle):
|
||||
if bv is None:
|
||||
return
|
||||
|
||||
try:
|
||||
mlil = func.mlil
|
||||
except ILException:
|
||||
if mlil is None:
|
||||
return
|
||||
|
||||
for block in mlil.basic_blocks:
|
||||
|
||||
@@ -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]
|
||||
|
||||
|
||||
38
capa/ida/plugin/ida-plugin.json
Normal file
38
capa/ida/plugin/ida-plugin.json
Normal file
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"IDAMetadataDescriptorVersion": 1,
|
||||
"plugin": {
|
||||
"name": "capa",
|
||||
"entryPoint": "capa_explorer.py",
|
||||
"version": "9.2.1",
|
||||
"idaVersions": ">=7.4",
|
||||
"description": "Identify capabilities in executable files using FLARE's capa framework",
|
||||
"license": "Apache-2.0",
|
||||
"categories": [
|
||||
"malware-analysis",
|
||||
"api-scripting-and-automation",
|
||||
"ui-ux-and-visualization"
|
||||
],
|
||||
"pythonDependencies": ["flare-capa==9.2.1"],
|
||||
"urls": {
|
||||
"repository": "https://github.com/mandiant/capa"
|
||||
},
|
||||
"authors": [
|
||||
{"name": "Willi Ballenthin", "email": "wballenthin@hex-rays.com"},
|
||||
{"name": "Moritz Raabe", "email": "moritzraabe@google.com"},
|
||||
{"name": "Mike Hunhoff", "email": "mike.hunhoff@gmail.com"},
|
||||
{"name": "Yacine Elhamer", "email": "elhamer.yacine@gmail.com"}
|
||||
],
|
||||
"keywords": [
|
||||
"capability-detection",
|
||||
"malware-analysis",
|
||||
"behavior-analysis",
|
||||
"reverse-engineering",
|
||||
"att&ck",
|
||||
"rule-engine",
|
||||
"feature-extraction",
|
||||
"yara-like-rules",
|
||||
"static-analysis",
|
||||
"dynamic-analysis"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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):
|
||||
""" """
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
- [ ] Review changes
|
||||
- capa https://github.com/mandiant/capa/compare/\<last-release\>...master
|
||||
- capa-rules https://github.com/mandiant/capa-rules/compare/\<last-release>\...master
|
||||
- [ ] Run `$ bump-my-version bump {patch/minor/major} [--allow-dirty]` to update [capa/version.py](https://github.com/mandiant/capa/blob/master/capa/version.py) and other version files
|
||||
- [ ] Update [CHANGELOG.md](https://github.com/mandiant/capa/blob/master/CHANGELOG.md)
|
||||
- Do not forget to add a nice introduction thanking contributors
|
||||
- Remember that we need a major release if we introduce breaking changes
|
||||
@@ -36,7 +37,6 @@
|
||||
- [capa <release>...master](https://github.com/mandiant/capa/compare/<release>...master)
|
||||
- [capa-rules <release>...master](https://github.com/mandiant/capa-rules/compare/<release>...master)
|
||||
```
|
||||
- [ ] Update [capa/version.py](https://github.com/mandiant/capa/blob/master/capa/version.py)
|
||||
- [ ] Create a PR with the updated [CHANGELOG.md](https://github.com/mandiant/capa/blob/master/CHANGELOG.md) and [capa/version.py](https://github.com/mandiant/capa/blob/master/capa/version.py). Copy this checklist in the PR description.
|
||||
- [ ] Update the [homepage](https://github.com/mandiant/capa/blob/master/web/public/index.html) (i.e. What's New section)
|
||||
- [ ] After PR review, merge the PR and [create the release in GH](https://github.com/mandiant/capa/releases/new) using text from the [CHANGELOG.md](https://github.com/mandiant/capa/blob/master/CHANGELOG.md).
|
||||
|
||||
@@ -74,7 +74,7 @@ dependencies = [
|
||||
# comments and context.
|
||||
"pyyaml>=6",
|
||||
"colorama>=0.4",
|
||||
"ida-settings>=2",
|
||||
"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"]
|
||||
|
||||
@@ -123,7 +123,7 @@ dev = [
|
||||
# and should not conflict with other libraries/tooling.
|
||||
"pre-commit==4.2.0",
|
||||
"pytest==8.0.0",
|
||||
"pytest-sugar==1.0.0",
|
||||
"pytest-sugar==1.1.1",
|
||||
"pytest-instafail==0.5.0",
|
||||
"flake8==7.3.0",
|
||||
"flake8-bugbear==24.12.12",
|
||||
@@ -139,16 +139,17 @@ dev = [
|
||||
"ruff==0.12.0",
|
||||
"black==25.1.0",
|
||||
"isort==6.0.0",
|
||||
"mypy==1.16.0",
|
||||
"mypy==1.17.1",
|
||||
"mypy-protobuf==3.6.0",
|
||||
"PyGithub==2.6.0",
|
||||
"bump-my-version==1.2.4",
|
||||
# type stubs for mypy
|
||||
"types-backports==0.1.3",
|
||||
"types-colorama==0.4.15.11",
|
||||
"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 = [
|
||||
@@ -161,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]
|
||||
@@ -197,7 +200,8 @@ known_first_party = [
|
||||
"idc",
|
||||
"java",
|
||||
"netnode",
|
||||
"PyQt5"
|
||||
"PyQt5",
|
||||
"PySide6"
|
||||
]
|
||||
|
||||
[tool.deptry.per_rule_ignores]
|
||||
@@ -205,6 +209,7 @@ known_first_party = [
|
||||
DEP002 = [
|
||||
"black",
|
||||
"build",
|
||||
"bump-my-version",
|
||||
"deptry",
|
||||
"flake8",
|
||||
"flake8-bugbear",
|
||||
|
||||
@@ -10,18 +10,18 @@ annotated-types==0.7.0
|
||||
colorama==0.4.6
|
||||
cxxfilt==0.3.0
|
||||
dncil==1.0.2
|
||||
dnfile==0.15.0
|
||||
dnfile==0.17.0
|
||||
funcy==2.0
|
||||
humanize==4.12.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==3.0.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.1.1
|
||||
pip==25.3
|
||||
protobuf==6.31.1
|
||||
pyasn1==0.5.1
|
||||
pyasn1-modules==0.3.0
|
||||
@@ -36,12 +36,13 @@ pyelftools==0.32
|
||||
pygments==2.19.1
|
||||
python-flirt==0.9.2
|
||||
pyyaml==6.0.2
|
||||
rich==14.0.0
|
||||
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
|
||||
viv-utils==0.8.0
|
||||
vivisect==1.2.1
|
||||
msgspec==0.19.0
|
||||
bump-my-version==1.2.4
|
||||
|
||||
2
rules
2
rules
Submodule rules updated: 2f09b4d471...9e4cc28265
Submodule tests/data updated: 836bd7acc0...5ea5d9f572
@@ -70,4 +70,4 @@ def test_standalone_binja_backend():
|
||||
@pytest.mark.skipif(binja_present is False, reason="Skip binja tests if the binaryninja Python API is not installed")
|
||||
def test_binja_version():
|
||||
version = binaryninja.core_version_info()
|
||||
assert version.major == 5 and version.minor == 0
|
||||
assert version.major == 5 and version.minor == 1
|
||||
|
||||
209
web/explorer/package-lock.json
generated
209
web/explorer/package-lock.json
generated
@@ -27,7 +27,7 @@
|
||||
"eslint-plugin-vue": "^9.23.0",
|
||||
"jsdom": "^24.1.0",
|
||||
"prettier": "^3.2.5",
|
||||
"vite": "^6.3.4",
|
||||
"vite": "^6.4.1",
|
||||
"vite-plugin-singlefile": "^2.2.0",
|
||||
"vitest": "^3.0.9"
|
||||
}
|
||||
@@ -1416,6 +1416,20 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/call-bind-apply-helpers": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
||||
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/callsites": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
|
||||
@@ -1646,6 +1660,21 @@
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/dunder-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"gopd": "^1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/eastasianwidth": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
|
||||
@@ -1711,6 +1740,26 @@
|
||||
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/es-define-property": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-errors": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
||||
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-module-lexer": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.6.0.tgz",
|
||||
@@ -1718,6 +1767,35 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/es-object-atoms": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
|
||||
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-set-tostringtag": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
||||
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"get-intrinsic": "^1.2.6",
|
||||
"has-tostringtag": "^1.0.2",
|
||||
"hasown": "^2.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.25.1",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.1.tgz",
|
||||
@@ -2108,13 +2186,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
||||
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
|
||||
"integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"es-set-tostringtag": "^2.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
@@ -2141,6 +2222,55 @@
|
||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/function-bind": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/get-intrinsic": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.2",
|
||||
"es-define-property": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"es-object-atoms": "^1.1.1",
|
||||
"function-bind": "^1.1.2",
|
||||
"get-proto": "^1.0.1",
|
||||
"gopd": "^1.2.0",
|
||||
"has-symbols": "^1.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"math-intrinsics": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/get-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"dunder-proto": "^1.0.1",
|
||||
"es-object-atoms": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/glob": {
|
||||
"version": "10.4.2",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-10.4.2.tgz",
|
||||
@@ -2215,6 +2345,19 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/gopd": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/graphemer": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
|
||||
@@ -2230,6 +2373,48 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/has-symbols": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-tostringtag": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
||||
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"has-symbols": "^1.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/hasown": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/highlight.js": {
|
||||
"version": "11.9.0",
|
||||
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.9.0.tgz",
|
||||
@@ -2608,6 +2793,16 @@
|
||||
"@jridgewell/sourcemap-codec": "^1.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/math-intrinsics": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/micromatch": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
|
||||
@@ -3606,9 +3801,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "6.3.4",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-6.3.4.tgz",
|
||||
"integrity": "sha512-BiReIiMS2fyFqbqNT/Qqt4CVITDU9M9vE+DKcVAsB+ZV0wvTKd+3hMbkpxz1b+NmEDMegpVbisKiAZOnvO92Sw==",
|
||||
"version": "6.4.1",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz",
|
||||
"integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
"eslint-plugin-vue": "^9.23.0",
|
||||
"jsdom": "^24.1.0",
|
||||
"prettier": "^3.2.5",
|
||||
"vite": "^6.3.4",
|
||||
"vite": "^6.4.1",
|
||||
"vite-plugin-singlefile": "^2.2.0",
|
||||
"vitest": "^3.0.9"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user