Compare commits

...

58 Commits

Author SHA1 Message Date
Moritz
10d3031093 Update capa/ida/plugin/form.py
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
2025-11-05 19:03:29 +01:00
mr-tz
e9b02b372a update ida-settings api 2025-11-05 16:52:43 +00:00
Matthew Haigh
503c34b8f9 added mailinglist cta (#2744)
* added mailinglist cta

* Update README.md

Added mailto: link for better user experience

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>

---------

Co-authored-by: Matt Haigh <matthaigh@google.com>
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
2025-11-03 09:11:14 -07:00
dependabot[bot]
888295b37a build(deps): bump types-protobuf from 6.30.2.20250516 to 6.32.1.20250918 (#2733)
Bumps [types-protobuf](https://github.com/typeshed-internal/stub_uploader) from 6.30.2.20250516 to 6.32.1.20250918.
- [Commits](https://github.com/typeshed-internal/stub_uploader/commits)

---
updated-dependencies:
- dependency-name: types-protobuf
  dependency-version: 6.32.1.20250918
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-03 09:07:26 -07:00
dependabot[bot]
5f9c908315 build(deps): bump pip from 25.2 to 25.3 (#2741)
Bumps [pip](https://github.com/pypa/pip) from 25.2 to 25.3.
- [Changelog](https://github.com/pypa/pip/blob/main/NEWS.rst)
- [Commits](https://github.com/pypa/pip/compare/25.2...25.3)

---
updated-dependencies:
- dependency-name: pip
  dependency-version: '25.3'
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
2025-11-03 09:07:02 -07:00
Willi Ballenthin
cb2e2323f9 explorer: add support for IDA 9.2 (#2723)
* ida: add Qt compatibility layer for PyQt5 and PySide6

Introduce a new module `qt_compat.py` providing a unified import
interface and API compatibility for Qt modules. It handles differences between
PyQt5 (used in IDA <9.2) and PySide6 (used in IDA >=9.2). Update all
plugin modules to import Qt components via this compatibility layer
instead of directly importing from PyQt5. This enhances plugin
compatibility across different IDA versions.

thanks @mike-hunhoff!

changelog

* qt_compat: use __all__ rather than noqa

---------

Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
2025-11-03 13:29:06 +01:00
Willi Ballenthin
5ea63770ba Merge pull request #2724 from HexRays-plugin-contributions/ida-plugin-json
add `ida-plugin.json`
2025-10-29 17:55:49 +01:00
Capa Bot
6795813fbe Sync capa rules submodule 2025-10-28 15:21:05 +00:00
Capa Bot
ca708ca52e Sync capa-testfiles submodule 2025-10-28 15:15:42 +00:00
Capa Bot
68cf74d60c Sync capa rules submodule 2025-10-28 13:12:29 +00:00
Moritz
5a0c47419f Merge pull request #2735 from mandiant/dependabot/npm_and_yarn/web/explorer/vite-6.4.1
build(deps-dev): bump vite from 6.4.0 to 6.4.1 in /web/explorer
2025-10-24 12:32:50 +02:00
Moritz
4dbdd9dcfa Merge branch 'master' into dependabot/npm_and_yarn/web/explorer/vite-6.4.1 2025-10-24 12:30:15 +02:00
Moritz
82cbfd33db Merge pull request #2732 from xusheng6/test_fix_binja_crash
binja: fix crash in binja feature extraction when MLIL is unavailable…
2025-10-24 12:29:51 +02:00
dependabot[bot]
5906bb3ecf build(deps-dev): bump vite from 6.4.0 to 6.4.1 in /web/explorer
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 6.4.0 to 6.4.1.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/create-vite@6.4.1/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 6.4.1
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-21 04:19:00 +00:00
Moritz
08319f598f Merge pull request #2730 from mandiant/dependabot/npm_and_yarn/web/explorer/vite-6.4.0
build(deps-dev): bump vite from 6.3.4 to 6.4.0 in /web/explorer
2025-10-20 17:28:58 +02:00
Capa Bot
e6df6ad0cd Sync capa rules submodule 2025-10-20 15:27:46 +00:00
Capa Bot
add09df061 Sync capa-testfiles submodule 2025-10-20 15:18:32 +00:00
Mike Hunhoff
acb34e88d6 Update CHANGELOG.md
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
2025-10-20 09:05:30 -06:00
Xusheng
0099e75704 binja: fix crash in binja feature extraction when MLIL is unavailable. Fix https://github.com/mandiant/capa/issues/2714 2025-10-20 18:46:53 +08:00
dependabot[bot]
da0803b671 build(deps-dev): bump vite from 6.3.4 to 6.4.0 in /web/explorer
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 6.3.4 to 6.4.0.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/create-vite@6.4.0/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 6.4.0
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-16 10:58:41 +00:00
Moritz
789747282d Merge pull request #2728 from mandiant/dependabot/pip/rich-14.2.0
build(deps): bump rich from 14.0.0 to 14.2.0
2025-10-16 12:57:18 +02:00
Capa Bot
3bc2d9915c Sync capa-testfiles submodule 2025-10-13 18:52:26 +00:00
dependabot[bot]
5974440ab7 build(deps): bump rich from 14.0.0 to 14.2.0
Bumps [rich](https://github.com/Textualize/rich) from 14.0.0 to 14.2.0.
- [Release notes](https://github.com/Textualize/rich/releases)
- [Changelog](https://github.com/Textualize/rich/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Textualize/rich/compare/v14.0.0...v14.2.0)

---
updated-dependencies:
- dependency-name: rich
  dependency-version: 14.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-13 14:25:06 +00:00
dependabot[bot]
b9d517a70b build(deps): bump pip from 25.1.1 to 25.2 (#2717)
Bumps [pip](https://github.com/pypa/pip) from 25.1.1 to 25.2.
- [Changelog](https://github.com/pypa/pip/blob/main/NEWS.rst)
- [Commits](https://github.com/pypa/pip/compare/25.1.1...25.2)

---
updated-dependencies:
- dependency-name: pip
  dependency-version: '25.2'
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Mike Hunhoff <mike.hunhoff@gmail.com>
2025-10-06 08:32:13 -06:00
dependabot[bot]
e5b8788620 build(deps): bump humanize from 4.12.0 to 4.13.0 (#2716)
Bumps [humanize](https://github.com/python-humanize/humanize) from 4.12.0 to 4.13.0.
- [Release notes](https://github.com/python-humanize/humanize/releases)
- [Commits](https://github.com/python-humanize/humanize/compare/4.12.0...4.13.0)

---
updated-dependencies:
- dependency-name: humanize
  dependency-version: 4.13.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Mike Hunhoff <mike.hunhoff@gmail.com>
2025-10-06 08:31:46 -06:00
axelmierczuk
ec411f1552 Update pyproject.toml
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
2025-10-01 19:00:26 +02:00
axelmierczuk
6871adc9dc Pin ida-settings version to 2.1.0 2025-10-01 19:00:26 +02:00
Capa Bot
07880c1418 Sync capa rules submodule 2025-09-23 20:18:16 +00:00
Capa Bot
5a6c8ca7c1 Sync capa rules submodule 2025-09-09 19:22:11 +00:00
Capa Bot
3bd8371d0c Sync capa rules submodule 2025-09-03 16:27:26 +00:00
dependabot[bot]
d0c87ef32c build(deps): bump markdown-it-py from 3.0.0 to 4.0.0 (#2711)
Bumps [markdown-it-py](https://github.com/executablebooks/markdown-it-py) from 3.0.0 to 4.0.0.
- [Release notes](https://github.com/executablebooks/markdown-it-py/releases)
- [Changelog](https://github.com/executablebooks/markdown-it-py/blob/master/CHANGELOG.md)
- [Commits](https://github.com/executablebooks/markdown-it-py/compare/v3.0.0...v4.0.0)

---
updated-dependencies:
- dependency-name: markdown-it-py
  dependency-version: 4.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-03 10:11:25 -06:00
dependabot[bot]
bd2731f87f build(deps): bump pytest-sugar from 1.0.0 to 1.1.1 (#2710)
Bumps [pytest-sugar](https://github.com/Teemu/pytest-sugar) from 1.0.0 to 1.1.1.
- [Release notes](https://github.com/Teemu/pytest-sugar/releases)
- [Changelog](https://github.com/Teemu/pytest-sugar/blob/main/CHANGES.rst)
- [Commits](https://github.com/Teemu/pytest-sugar/compare/v1.0.0...v1.1.1)

---
updated-dependencies:
- dependency-name: pytest-sugar
  dependency-version: 1.1.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-03 10:10:32 -06:00
Capa Bot
4a167d7188 Sync capa rules submodule 2025-09-03 16:08:58 +00:00
Capa Bot
c01bc346fc Sync capa rules submodule 2025-09-03 16:05:36 +00:00
Capa Bot
826330f511 Sync capa-testfiles submodule 2025-09-03 15:58:45 +00:00
Capa Bot
40e5095577 Sync capa-testfiles submodule 2025-09-03 15:55:29 +00:00
Capa Bot
c7eede3c53 Sync capa-testfiles submodule 2025-09-03 15:51:51 +00:00
Capa Bot
1a5f50195a Sync capa rules submodule 2025-08-25 19:08:17 +00:00
Capa Bot
aafca2e00a Sync capa-testfiles submodule 2025-08-25 18:59:27 +00:00
Capa Bot
3a24fabeb6 Sync capa rules submodule 2025-08-22 14:58:24 +00:00
Capa Bot
2f81bb79f9 Sync capa rules submodule 2025-08-21 14:57:07 +00:00
Capa Bot
fc83b7b0a1 Sync capa rules submodule 2025-08-21 14:56:48 +00:00
dependabot[bot]
d430aea04e build(deps): bump dnfile from 0.15.0 to 0.16.4 (#2700)
---
updated-dependencies:
- dependency-name: dnfile
  dependency-version: 0.16.4
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Mike Hunhoff <mike.hunhoff@gmail.com>
2025-08-20 15:11:17 -06:00
dependabot[bot]
1eb42599cf build(deps): bump mypy from 1.16.0 to 1.17.1 (#2704)
Bumps [mypy](https://github.com/python/mypy) from 1.16.0 to 1.17.1.
- [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md)
- [Commits](https://github.com/python/mypy/compare/v1.16.0...v1.17.1)

---
updated-dependencies:
- dependency-name: mypy
  dependency-version: 1.17.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Mike Hunhoff <mike.hunhoff@gmail.com>
2025-08-20 15:10:52 -06:00
dependabot[bot]
618ae2111b build(deps): bump form-data from 4.0.0 to 4.0.4 in /web/explorer (#2702)
Bumps [form-data](https://github.com/form-data/form-data) from 4.0.0 to 4.0.4.
- [Release notes](https://github.com/form-data/form-data/releases)
- [Changelog](https://github.com/form-data/form-data/blob/master/CHANGELOG.md)
- [Commits](https://github.com/form-data/form-data/compare/v4.0.0...v4.0.4)

---
updated-dependencies:
- dependency-name: form-data
  dependency-version: 4.0.4
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Mike Hunhoff <mike.hunhoff@gmail.com>
2025-08-20 12:11:46 -06:00
Mike Hunhoff
42b6d8106a binja: update core version info check (#2709) 2025-08-20 11:56:56 -06:00
Capa Bot
78a020e1ac Sync capa rules submodule 2025-08-20 16:04:49 +00:00
Capa Bot
a80f85aab4 Sync capa-testfiles submodule 2025-08-20 15:57:15 +00:00
Capa Bot
f94f554d15 Sync capa-testfiles submodule 2025-08-20 15:32:08 +00:00
Capa Bot
d456d52e81 Sync capa rules submodule 2025-08-14 20:59:31 +00:00
Capa Bot
2a18b08a80 Sync capa rules submodule 2025-08-14 15:11:56 +00:00
Capa Bot
dd2e350a1a Sync capa-testfiles submodule 2025-08-14 15:08:18 +00:00
Capa Bot
164a7bdfb5 Sync capa rules submodule 2025-08-13 14:40:23 +00:00
Capa Bot
d7c896bbc6 Sync capa rules submodule 2025-08-12 16:21:29 +00:00
Capa Bot
8185ac4dde Sync capa rules submodule 2025-08-12 15:43:50 +00:00
Capa Bot
92a6ddff99 Sync capa rules submodule 2025-08-12 15:42:57 +00:00
Capa Bot
af87fae036 Sync capa-testfiles submodule 2025-08-12 15:38:12 +00:00
Capa Bot
c774db26f0 Sync capa-testfiles submodule 2025-08-12 15:37:46 +00:00
21 changed files with 444 additions and 74 deletions

22
.bumpversion.toml Normal file
View 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
View File

@@ -122,6 +122,7 @@ scripts/perf/*.zip
*/.DS_Store */.DS_Store
Pipfile Pipfile
Pipfile.lock Pipfile.lock
uv.lock
/cache/ /cache/
.github/binja/binaryninja .github/binja/binaryninja
.github/binja/download_headless.py .github/binja/download_headless.py

View File

@@ -7,21 +7,46 @@
### Breaking Changes ### 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 - 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 - 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 ### Bug Fixes
- binja: fix a crash during feature extraction when the MLIL is unavailable @xusheng6 #2714
### capa Explorer Web ### capa Explorer Web
### capa Explorer IDA Pro plugin ### 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 ### Development
- ci: remove redundant "test_run" action from build workflow @mike-hunhoff #2692 - 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 ### Raw diffs
- [capa v9.2.1...master](https://github.com/mandiant/capa/compare/v9.2.1...master) - [capa v9.2.1...master](https://github.com/mandiant/capa/compare/v9.2.1...master)

View File

@@ -315,3 +315,6 @@ If you use Ghidra, then you can use the [capa + Ghidra integration](/capa/ghidra
## capa testfiles ## capa testfiles
The [capa-testfiles repository](https://github.com/mandiant/capa-testfiles) contains the data we use to test capa's code and rules 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).

View File

@@ -19,7 +19,6 @@ from binaryninja import (
Function, Function,
BinaryView, BinaryView,
SymbolType, SymbolType,
ILException,
RegisterValueType, RegisterValueType,
VariableSourceType, VariableSourceType,
LowLevelILOperation, LowLevelILOperation,
@@ -192,9 +191,8 @@ def extract_stackstring(fh: FunctionHandle):
if bv is None: if bv is None:
return return
try: mlil = func.mlil
mlil = func.mlil if mlil is None:
except ILException:
return return
for block in mlil.basic_blocks: for block in mlil.basic_blocks:

View File

@@ -14,9 +14,9 @@
import ida_kernwin import ida_kernwin
from PyQt5 import QtCore
from capa.ida.plugin.error import UserCancelledError 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.ida.extractor import IdaFeatureExtractor
from capa.features.extractors.base_extractor import FunctionHandle from capa.features.extractors.base_extractor import FunctionHandle
@@ -24,7 +24,7 @@ from capa.features.extractors.base_extractor import FunctionHandle
class CapaExplorerProgressIndicator(QtCore.QObject): class CapaExplorerProgressIndicator(QtCore.QObject):
"""implement progress signal, used during feature extraction""" """implement progress signal, used during feature extraction"""
progress = QtCore.pyqtSignal(str) progress = Signal(str)
def update(self, text): def update(self, text):
"""emit progress update """emit progress update

View File

@@ -23,7 +23,6 @@ from pathlib import Path
import idaapi import idaapi
import ida_kernwin import ida_kernwin
import ida_settings import ida_settings
from PyQt5 import QtGui, QtCore, QtWidgets
import capa.main import capa.main
import capa.rules 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.model import CapaExplorerDataModel
from capa.ida.plugin.proxy import CapaExplorerRangeProxyModel, CapaExplorerSearchProxyModel from capa.ida.plugin.proxy import CapaExplorerRangeProxyModel, CapaExplorerSearchProxyModel
from capa.ida.plugin.extractor import CapaExplorerFeatureExtractor 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 from capa.features.extractors.base_extractor import FunctionHandle
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
settings = ida_settings.IDASettings("capa")
CAPA_SETTINGS_RULE_PATH = "rule_path" CAPA_SETTINGS_RULE_PATH = "rule_path"
CAPA_SETTINGS_RULEGEN_AUTHOR = "rulegen_author" 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): def write_file(path: Path, data):
""" """ """ """
path.write_bytes(data) path.write_bytes(data)
@@ -107,7 +113,7 @@ class QLineEditClicked(QtWidgets.QLineEdit):
old = self.text() old = self.text()
new = str( new = str(
QtWidgets.QFileDialog.getExistingDirectory( 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: if new:
@@ -125,8 +131,8 @@ class CapaSettingsInputDialog(QtWidgets.QDialog):
self.setMinimumWidth(500) self.setMinimumWidth(500)
self.setWindowFlags(self.windowFlags() & ~QtCore.Qt.WindowContextHelpButtonHint) self.setWindowFlags(self.windowFlags() & ~QtCore.Qt.WindowContextHelpButtonHint)
self.edit_rule_path = QLineEditClicked(settings.user.get(CAPA_SETTINGS_RULE_PATH, "")) self.edit_rule_path = QLineEditClicked(get_setting(CAPA_SETTINGS_RULE_PATH, ""))
self.edit_rule_author = QtWidgets.QLineEdit(settings.user.get(CAPA_SETTINGS_RULEGEN_AUTHOR, "")) self.edit_rule_author = QtWidgets.QLineEdit(get_setting(CAPA_SETTINGS_RULEGEN_AUTHOR, ""))
self.edit_rule_scope = QtWidgets.QComboBox() self.edit_rule_scope = QtWidgets.QComboBox()
self.edit_rules_link = QtWidgets.QLabel() self.edit_rules_link = QtWidgets.QLabel()
self.edit_analyze = QtWidgets.QComboBox() self.edit_analyze = QtWidgets.QComboBox()
@@ -141,11 +147,11 @@ class CapaSettingsInputDialog(QtWidgets.QDialog):
scopes = ("file", "function", "basic block", "instruction") scopes = ("file", "function", "basic block", "instruction")
self.edit_rule_scope.addItems(scopes) 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()) self.edit_analyze.addItems(AnalyzeOptionsText.values())
# set the default analysis option here # 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) buttons = QtWidgets.QDialogButtonBox(QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel, self)
@@ -235,7 +241,7 @@ class CapaExplorerForm(idaapi.PluginForm):
self.Show() 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: if analyze != Options.NO_ANALYSIS or (option & Options.ANALYZE_AUTO) == Options.ANALYZE_AUTO:
self.analyze_program(analyze=analyze) self.analyze_program(analyze=analyze)
@@ -581,7 +587,7 @@ class CapaExplorerForm(idaapi.PluginForm):
def ensure_capa_settings_rule_path(self): def ensure_capa_settings_rule_path(self):
try: 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 # 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 # 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) logger.error("rule path %s does not exist or cannot be accessed", path)
return False return False
settings.user[CAPA_SETTINGS_RULE_PATH] = path ida_settings.set_current_plugin_setting(CAPA_SETTINGS_RULE_PATH, path)
except UserCancelledError: except UserCancelledError:
capa.ida.helpers.inform_user_ida_ui("Analysis requires capa rules") capa.ida.helpers.inform_user_ida_ui("Analysis requires capa rules")
logger.warning( logger.warning(
@@ -635,7 +641,7 @@ class CapaExplorerForm(idaapi.PluginForm):
if not self.ensure_capa_settings_rule_path(): if not self.ensure_capa_settings_rule_path():
return False return False
rule_path: Path = Path(settings.user.get(CAPA_SETTINGS_RULE_PATH, "")) rule_path: Path = Path(get_setting(CAPA_SETTINGS_RULE_PATH, ""))
try: try:
def on_load_rule(_, i, total): def on_load_rule(_, i, total):
@@ -649,10 +655,10 @@ class CapaExplorerForm(idaapi.PluginForm):
return None return None
except Exception as e: except Exception as e:
capa.ida.helpers.inform_user_ida_ui( 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( logger.error(
"Make sure your file directory contains properly " # noqa: G003 [logging statement uses +] "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. " + "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, CAPA_RULESET_DOC_URL,
) )
settings.user[CAPA_SETTINGS_RULE_PATH] = "" ida_settings.set_current_plugin_setting(CAPA_SETTINGS_RULE_PATH, "")
return None return None
def load_capa_results(self, new_analysis, from_cache): def load_capa_results(self, new_analysis, from_cache):
@@ -701,7 +707,7 @@ class CapaExplorerForm(idaapi.PluginForm):
update_wait_box("verifying cached results") update_wait_box("verifying cached results")
count_source_rules = self.program_analysis_ruleset_cache.source_rule_count 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)" 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 # 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") update_wait_box("extracting features")
try: 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( capabilities = capa.capabilities.common.find_capabilities(
ruleset, self.feature_extractor, disable_progress=True ruleset, self.feature_extractor, disable_progress=True
) )
@@ -855,7 +861,7 @@ class CapaExplorerForm(idaapi.PluginForm):
except Exception as e: except Exception as e:
logger.exception("Failed to save results to database (error: %s)", e) logger.exception("Failed to save results to database (error: %s)", e)
return False 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 count_source_rules = self.program_analysis_ruleset_cache.source_rule_count
new_view_status = f"capa rules: {user_settings} ({count_source_rules} rules)" 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 # 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 # load preview and feature tree
self.view_rulegen_preview.load_preview_meta( self.view_rulegen_preview.load_preview_meta(
self.rulegen_current_function.address if self.rulegen_current_function else None, self.rulegen_current_function.address if self.rulegen_current_function else None,
settings.user.get(CAPA_SETTINGS_RULEGEN_AUTHOR, "<insert_author>"), get_setting(CAPA_SETTINGS_RULEGEN_AUTHOR, "<insert_author>"),
settings.user.get(CAPA_SETTINGS_RULEGEN_SCOPE, "function"), get_setting(CAPA_SETTINGS_RULEGEN_SCOPE, "function"),
) )
self.view_rulegen_features.load_features(all_file_features, all_function_features) 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: except Exception as e:
logger.exception("Failed to render views (error: %s)", e) logger.exception("Failed to render views (error: %s)", e)
return False return False
@@ -1303,12 +1309,11 @@ class CapaExplorerForm(idaapi.PluginForm):
""" """ """ """
dialog = CapaSettingsInputDialog("capa explorer settings", parent=self.parent) dialog = CapaSettingsInputDialog("capa explorer settings", parent=self.parent)
if dialog.exec_(): if dialog.exec_():
( rule_path, rule_author, rule_scope, analyze = dialog.get_values()
settings.user[CAPA_SETTINGS_RULE_PATH], ida_settings.set_current_plugin_setting(CAPA_SETTINGS_RULE_PATH, rule_path)
settings.user[CAPA_SETTINGS_RULEGEN_AUTHOR], ida_settings.set_current_plugin_setting(CAPA_SETTINGS_RULEGEN_AUTHOR, rule_author)
settings.user[CAPA_SETTINGS_RULEGEN_SCOPE], ida_settings.set_current_plugin_setting(CAPA_SETTINGS_RULEGEN_SCOPE, rule_scope)
settings.user[CAPA_SETTINGS_ANALYZE], ida_settings.set_current_plugin_setting(CAPA_SETTINGS_ANALYZE, analyze)
) = dialog.get_values()
def save_program_analysis(self): def save_program_analysis(self):
""" """ """ """
@@ -1358,7 +1363,7 @@ class CapaExplorerForm(idaapi.PluginForm):
@param state: checked state @param state: checked state
""" """
if state == QtCore.Qt.Checked: if state:
self.limit_results_to_function(idaapi.get_func(idaapi.get_screen_ea())) self.limit_results_to_function(idaapi.get_func(idaapi.get_screen_ea()))
else: else:
self.range_model_proxy.reset_address_range_filter() 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): 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()) self.view_rulegen_features.filter_items_by_ea(idaapi.get_screen_ea())
else: else:
self.view_rulegen_features.show_all_items() self.view_rulegen_features.show_all_items()
@@ -1408,7 +1413,7 @@ class CapaExplorerForm(idaapi.PluginForm):
"""create Qt dialog to ask user for a directory""" """create Qt dialog to ask user for a directory"""
return str( return str(
QtWidgets.QFileDialog.getExistingDirectory( 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( return QtWidgets.QFileDialog.getSaveFileName(
None, None,
"Please select a location to save capa rule file", "Please select a location to save capa rule file",
settings.user.get(CAPA_SETTINGS_RULE_PATH, ""), get_setting(CAPA_SETTINGS_RULE_PATH, ""),
"*.yml", "*.yml",
)[0] )[0]

View 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"
]
}
}

View File

@@ -18,10 +18,10 @@ from typing import Iterator, Optional
import idc import idc
import idaapi import idaapi
from PyQt5 import QtCore
import capa.ida.helpers import capa.ida.helpers
from capa.features.address import Address, FileOffsetAddress, AbsoluteVirtualAddress 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): def info_to_name(display):
@@ -55,7 +55,7 @@ class CapaExplorerDataItem:
self.flags = QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable self.flags = QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable
if self._can_check: 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: if self.pred:
self.pred.appendChild(self) self.pred.appendChild(self)

View File

@@ -18,7 +18,6 @@ from collections import deque
import idc import idc
import idaapi import idaapi
from PyQt5 import QtGui, QtCore
import capa.rules import capa.rules
import capa.ida.helpers import capa.ida.helpers
@@ -42,6 +41,7 @@ from capa.ida.plugin.item import (
CapaExplorerInstructionViewItem, CapaExplorerInstructionViewItem,
) )
from capa.features.address import Address, AbsoluteVirtualAddress 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 color used in IDA window
DEFAULT_HIGHLIGHT = 0xE6C700 DEFAULT_HIGHLIGHT = 0xE6C700
@@ -269,7 +269,7 @@ class CapaExplorerDataModel(QtCore.QAbstractItemModel):
visited.add(child_index) visited.add(child_index)
for idx in range(self.rowCount(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): def reset_ida_highlighting(self, item, checked):
"""reset IDA highlight for item """reset IDA highlight for item

View File

@@ -12,10 +12,8 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from PyQt5 import QtCore
from PyQt5.QtCore import Qt
from capa.ida.plugin.model import CapaExplorerDataModel from capa.ida.plugin.model import CapaExplorerDataModel
from capa.ida.plugin.qt_compat import Qt, QtCore
class CapaExplorerRangeProxyModel(QtCore.QSortFilterProxyModel): class CapaExplorerRangeProxyModel(QtCore.QSortFilterProxyModel):

View 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"]

View File

@@ -18,7 +18,6 @@ from collections import Counter
import idc import idc
import idaapi import idaapi
from PyQt5 import QtGui, QtCore, QtWidgets
import capa.rules import capa.rules
import capa.engine import capa.engine
@@ -28,6 +27,7 @@ import capa.features.basicblock
from capa.ida.plugin.item import CapaExplorerFunctionItem from capa.ida.plugin.item import CapaExplorerFunctionItem
from capa.features.address import AbsoluteVirtualAddress, _NoAddress from capa.features.address import AbsoluteVirtualAddress, _NoAddress
from capa.ida.plugin.model import CapaExplorerDataModel from capa.ida.plugin.model import CapaExplorerDataModel
from capa.ida.plugin.qt_compat import QtGui, QtCore, Signal, QAction, QtWidgets
MAX_SECTION_SIZE = 750 MAX_SECTION_SIZE = 750
@@ -147,7 +147,7 @@ def calc_item_depth(o):
def build_action(o, display, data, slot): def build_action(o, display, data, slot):
""" """ """ """
action = QtWidgets.QAction(display, o) action = QAction(display, o)
action.setData(data) action.setData(data)
action.triggered.connect(lambda checked: slot(action)) action.triggered.connect(lambda checked: slot(action))
@@ -312,7 +312,7 @@ class CapaExplorerRulegenPreview(QtWidgets.QTextEdit):
class CapaExplorerRulegenEditor(QtWidgets.QTreeWidget): class CapaExplorerRulegenEditor(QtWidgets.QTreeWidget):
updated = QtCore.pyqtSignal() updated = Signal()
def __init__(self, preview, parent=None): def __init__(self, preview, parent=None):
""" """ """ """

View File

@@ -7,6 +7,7 @@
- [ ] Review changes - [ ] Review changes
- capa https://github.com/mandiant/capa/compare/\<last-release\>...master - capa https://github.com/mandiant/capa/compare/\<last-release\>...master
- capa-rules https://github.com/mandiant/capa-rules/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) - [ ] Update [CHANGELOG.md](https://github.com/mandiant/capa/blob/master/CHANGELOG.md)
- Do not forget to add a nice introduction thanking contributors - Do not forget to add a nice introduction thanking contributors
- Remember that we need a major release if we introduce breaking changes - 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 <release>...master](https://github.com/mandiant/capa/compare/<release>...master)
- [capa-rules <release>...master](https://github.com/mandiant/capa-rules/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. - [ ] 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) - [ ] 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). - [ ] 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).

View File

@@ -74,7 +74,7 @@ dependencies = [
# comments and context. # comments and context.
"pyyaml>=6", "pyyaml>=6",
"colorama>=0.4", "colorama>=0.4",
"ida-settings>=2", "ida-settings>=3.1.0",
"ruamel.yaml>=0.18", "ruamel.yaml>=0.18",
"pefile>=2023.2.7", "pefile>=2023.2.7",
"pyelftools>=0.31", "pyelftools>=0.31",
@@ -104,7 +104,7 @@ dependencies = [
"networkx>=3", "networkx>=3",
"dnfile>=0.15.0", "dnfile>=0.17.0",
] ]
dynamic = ["version"] dynamic = ["version"]
@@ -123,7 +123,7 @@ dev = [
# and should not conflict with other libraries/tooling. # and should not conflict with other libraries/tooling.
"pre-commit==4.2.0", "pre-commit==4.2.0",
"pytest==8.0.0", "pytest==8.0.0",
"pytest-sugar==1.0.0", "pytest-sugar==1.1.1",
"pytest-instafail==0.5.0", "pytest-instafail==0.5.0",
"flake8==7.3.0", "flake8==7.3.0",
"flake8-bugbear==24.12.12", "flake8-bugbear==24.12.12",
@@ -139,16 +139,17 @@ dev = [
"ruff==0.12.0", "ruff==0.12.0",
"black==25.1.0", "black==25.1.0",
"isort==6.0.0", "isort==6.0.0",
"mypy==1.16.0", "mypy==1.17.1",
"mypy-protobuf==3.6.0", "mypy-protobuf==3.6.0",
"PyGithub==2.6.0", "PyGithub==2.6.0",
"bump-my-version==1.2.4",
# type stubs for mypy # type stubs for mypy
"types-backports==0.1.3", "types-backports==0.1.3",
"types-colorama==0.4.15.11", "types-colorama==0.4.15.11",
"types-PyYAML==6.0.8", "types-PyYAML==6.0.8",
"types-psutil==7.0.0.20250218", "types-psutil==7.0.0.20250218",
"types_requests==2.32.0.20240712", "types_requests==2.32.0.20240712",
"types-protobuf==6.30.2.20250516", "types-protobuf==6.32.1.20250918",
"deptry==0.23.0" "deptry==0.23.0"
] ]
build = [ build = [
@@ -161,11 +162,13 @@ build = [
"build==1.2.2" "build==1.2.2"
] ]
scripts = [ scripts = [
# can (optionally) be more lenient on dependencies here
# see comment on dependencies for more context
"jschema_to_python==1.2.3", "jschema_to_python==1.2.3",
"psutil==7.0.0", "psutil==7.1.2",
"stix2==3.0.1", "stix2==3.0.1",
"sarif_om==1.0.4", "sarif_om==1.0.4",
"requests==2.32.3", "requests>=2.32.4",
] ]
[tool.deptry] [tool.deptry]
@@ -197,7 +200,8 @@ known_first_party = [
"idc", "idc",
"java", "java",
"netnode", "netnode",
"PyQt5" "PyQt5",
"PySide6"
] ]
[tool.deptry.per_rule_ignores] [tool.deptry.per_rule_ignores]
@@ -205,6 +209,7 @@ known_first_party = [
DEP002 = [ DEP002 = [
"black", "black",
"build", "build",
"bump-my-version",
"deptry", "deptry",
"flake8", "flake8",
"flake8-bugbear", "flake8-bugbear",

View File

@@ -10,18 +10,18 @@ annotated-types==0.7.0
colorama==0.4.6 colorama==0.4.6
cxxfilt==0.3.0 cxxfilt==0.3.0
dncil==1.0.2 dncil==1.0.2
dnfile==0.15.0 dnfile==0.17.0
funcy==2.0 funcy==2.0
humanize==4.12.0 humanize==4.13.0
ida-netnode==3.0 ida-netnode==3.0
ida-settings==2.1.0 ida-settings==3.2.2
intervaltree==3.1.0 intervaltree==3.1.0
markdown-it-py==3.0.0 markdown-it-py==4.0.0
mdurl==0.1.2 mdurl==0.1.2
msgpack==1.0.8 msgpack==1.0.8
networkx==3.4.2 networkx==3.4.2
pefile==2024.8.26 pefile==2024.8.26
pip==25.1.1 pip==25.3
protobuf==6.31.1 protobuf==6.31.1
pyasn1==0.5.1 pyasn1==0.5.1
pyasn1-modules==0.3.0 pyasn1-modules==0.3.0
@@ -36,12 +36,13 @@ pyelftools==0.32
pygments==2.19.1 pygments==2.19.1
python-flirt==0.9.2 python-flirt==0.9.2
pyyaml==6.0.2 pyyaml==6.0.2
rich==14.0.0 rich==14.2.0
ruamel-yaml==0.18.6 ruamel-yaml==0.18.6
ruamel-yaml-clib==0.2.8 ruamel-yaml-clib==0.2.14
setuptools==80.9.0 setuptools==80.9.0
six==1.17.0 six==1.17.0
sortedcontainers==2.4.0 sortedcontainers==2.4.0
viv-utils==0.8.0 viv-utils==0.8.0
vivisect==1.2.1 vivisect==1.2.1
msgspec==0.19.0 msgspec==0.19.0
bump-my-version==1.2.4

2
rules

Submodule rules updated: 2f09b4d471...9e4cc28265

View File

@@ -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") @pytest.mark.skipif(binja_present is False, reason="Skip binja tests if the binaryninja Python API is not installed")
def test_binja_version(): def test_binja_version():
version = binaryninja.core_version_info() version = binaryninja.core_version_info()
assert version.major == 5 and version.minor == 0 assert version.major == 5 and version.minor == 1

View File

@@ -27,7 +27,7 @@
"eslint-plugin-vue": "^9.23.0", "eslint-plugin-vue": "^9.23.0",
"jsdom": "^24.1.0", "jsdom": "^24.1.0",
"prettier": "^3.2.5", "prettier": "^3.2.5",
"vite": "^6.3.4", "vite": "^6.4.1",
"vite-plugin-singlefile": "^2.2.0", "vite-plugin-singlefile": "^2.2.0",
"vitest": "^3.0.9" "vitest": "^3.0.9"
} }
@@ -1416,6 +1416,20 @@
"node": ">=8" "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": { "node_modules/callsites": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
@@ -1646,6 +1660,21 @@
"node": ">=6.0.0" "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": { "node_modules/eastasianwidth": {
"version": "0.2.0", "version": "0.2.0",
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
@@ -1711,6 +1740,26 @@
"url": "https://github.com/fb55/entities?sponsor=1" "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": { "node_modules/es-module-lexer": {
"version": "1.6.0", "version": "1.6.0",
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.6.0.tgz", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.6.0.tgz",
@@ -1718,6 +1767,35 @@
"dev": true, "dev": true,
"license": "MIT" "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": { "node_modules/esbuild": {
"version": "0.25.1", "version": "0.25.1",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.1.tgz", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.1.tgz",
@@ -2108,13 +2186,16 @@
} }
}, },
"node_modules/form-data": { "node_modules/form-data": {
"version": "4.0.0", "version": "4.0.4",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"asynckit": "^0.4.0", "asynckit": "^0.4.0",
"combined-stream": "^1.0.8", "combined-stream": "^1.0.8",
"es-set-tostringtag": "^2.1.0",
"hasown": "^2.0.2",
"mime-types": "^2.1.12" "mime-types": "^2.1.12"
}, },
"engines": { "engines": {
@@ -2141,6 +2222,55 @@
"node": "^8.16.0 || ^10.6.0 || >=11.0.0" "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": { "node_modules/glob": {
"version": "10.4.2", "version": "10.4.2",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.4.2.tgz", "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.2.tgz",
@@ -2215,6 +2345,19 @@
"url": "https://github.com/sponsors/sindresorhus" "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": { "node_modules/graphemer": {
"version": "1.4.0", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
@@ -2230,6 +2373,48 @@
"node": ">=8" "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": { "node_modules/highlight.js": {
"version": "11.9.0", "version": "11.9.0",
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.9.0.tgz", "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.9.0.tgz",
@@ -2608,6 +2793,16 @@
"@jridgewell/sourcemap-codec": "^1.5.0" "@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": { "node_modules/micromatch": {
"version": "4.0.8", "version": "4.0.8",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
@@ -3606,9 +3801,9 @@
"dev": true "dev": true
}, },
"node_modules/vite": { "node_modules/vite": {
"version": "6.3.4", "version": "6.4.1",
"resolved": "https://registry.npmjs.org/vite/-/vite-6.3.4.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz",
"integrity": "sha512-BiReIiMS2fyFqbqNT/Qqt4CVITDU9M9vE+DKcVAsB+ZV0wvTKd+3hMbkpxz1b+NmEDMegpVbisKiAZOnvO92Sw==", "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {

View File

@@ -33,7 +33,7 @@
"eslint-plugin-vue": "^9.23.0", "eslint-plugin-vue": "^9.23.0",
"jsdom": "^24.1.0", "jsdom": "^24.1.0",
"prettier": "^3.2.5", "prettier": "^3.2.5",
"vite": "^6.3.4", "vite": "^6.4.1",
"vite-plugin-singlefile": "^2.2.0", "vite-plugin-singlefile": "^2.2.0",
"vitest": "^3.0.9" "vitest": "^3.0.9"
} }