diff --git a/CHANGELOG.md b/CHANGELOG.md
index b8e32a9a..20ec8545 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,11 +13,14 @@
### Bug Fixes
- extractor: fix exception when PE extractor encounters unknown architecture #2440 @Tamir-K
+- IDA Pro: rename ida to idapro module for plugin and idalib in IDA 9.0 #2453 @mr-tz
### capa Explorer Web
### capa Explorer IDA Pro plugin
+- fix bug preventing saving of capa results via Save button @mr-tz
+
### Development
### Raw diffs
diff --git a/capa/features/extractors/ida/helpers.py b/capa/features/extractors/ida/helpers.py
index a40ca3fd..fc22bc38 100644
--- a/capa/features/extractors/ida/helpers.py
+++ b/capa/features/extractors/ida/helpers.py
@@ -41,7 +41,7 @@ if hasattr(ida_bytes, "parse_binpat_str"):
return
while True:
- ea, _ = ida_bytes.bin_search3(start, end, patterns, ida_bytes.BIN_SEARCH_FORWARD)
+ ea, _ = ida_bytes.bin_search(start, end, patterns, ida_bytes.BIN_SEARCH_FORWARD)
if ea == idaapi.BADADDR:
break
start = ea + 1
diff --git a/capa/features/extractors/ida/idalib.py b/capa/features/extractors/ida/idalib.py
index df1e3172..f0627971 100644
--- a/capa/features/extractors/ida/idalib.py
+++ b/capa/features/extractors/ida/idalib.py
@@ -18,7 +18,7 @@ logger = logging.getLogger(__name__)
def is_idalib_installed() -> bool:
try:
- return importlib.util.find_spec("ida") is not None
+ return importlib.util.find_spec("idapro") is not None
except ModuleNotFoundError:
return False
@@ -44,6 +44,7 @@ def get_idalib_user_config_path() -> Optional[Path]:
def find_idalib() -> Optional[Path]:
config_path = get_idalib_user_config_path()
if not config_path:
+ logger.error("IDA Pro user configuration does not exist, please make sure you've installed idalib properly.")
return None
config = json.loads(config_path.read_text(encoding="utf-8"))
@@ -51,6 +52,9 @@ def find_idalib() -> Optional[Path]:
try:
ida_install_dir = Path(config["Paths"]["ida-install-dir"])
except KeyError:
+ logger.error(
+ "IDA Pro user configuration does not contain location of IDA Pro installation, please make sure you've installed idalib properly."
+ )
return None
if not ida_install_dir.exists():
@@ -73,7 +77,7 @@ def find_idalib() -> Optional[Path]:
if not idalib_path.exists():
return None
- if not (idalib_path / "ida" / "__init__.py").is_file():
+ if not (idalib_path / "idapro" / "__init__.py").is_file():
return None
return idalib_path
@@ -96,7 +100,7 @@ def has_idalib() -> bool:
def load_idalib() -> bool:
try:
- import ida
+ import idapro
return True
except ImportError:
@@ -106,7 +110,7 @@ def load_idalib() -> bool:
sys.path.append(idalib_path.absolute().as_posix())
try:
- import ida # noqa: F401 unused import
+ import idapro # noqa: F401 unused import
return True
except ImportError:
diff --git a/capa/ida/plugin/form.py b/capa/ida/plugin/form.py
index 0aee6cea..028ce207 100644
--- a/capa/ida/plugin/form.py
+++ b/capa/ida/plugin/form.py
@@ -1309,10 +1309,17 @@ class CapaExplorerForm(idaapi.PluginForm):
s = self.resdoc_cache.model_dump_json().encode("utf-8")
- path = Path(self.ask_user_capa_json_file())
- if not path.exists():
+ path = self.ask_user_capa_json_file()
+ if not path:
+ # dialog canceled
return
+ path = Path(path)
+ if not path.parent.exists():
+ logger.warning("Failed to save file: parent directory '%s' does not exist.", path.parent)
+ return
+
+ logger.info("Saving capa results to %s.", path)
write_file(path, s)
def save_function_analysis(self):
diff --git a/capa/loader.py b/capa/loader.py
index c4c8c1af..f481d7b8 100644
--- a/capa/loader.py
+++ b/capa/loader.py
@@ -323,7 +323,7 @@ def get_extractor(
if not idalib.load_idalib():
raise RuntimeError("failed to load IDA idalib module.")
- import ida
+ import idapro
import ida_auto
import capa.features.extractors.ida.extractor
@@ -333,7 +333,7 @@ def get_extractor(
# so as not to screw up structured output.
with capa.helpers.stdout_redirector(io.BytesIO()):
with console.status("analyzing program...", spinner="dots"):
- if ida.open_database(str(input_path), run_auto_analysis=True):
+ if idapro.open_database(str(input_path), run_auto_analysis=True):
raise RuntimeError("failed to analyze input file")
logger.debug("idalib: waiting for analysis...")
diff --git a/pyproject.toml b/pyproject.toml
index d3a5481a..3416c3a9 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -177,7 +177,7 @@ known_first_party = [
"binaryninja",
"flirt",
"ghidra",
- "ida",
+ "idapro",
"ida_ida",
"ida_auto",
"ida_bytes",
diff --git a/web/explorer/src/components/RuleMatchesTable.vue b/web/explorer/src/components/RuleMatchesTable.vue
index fe891901..3340ef87 100644
--- a/web/explorer/src/components/RuleMatchesTable.vue
+++ b/web/explorer/src/components/RuleMatchesTable.vue
@@ -160,7 +160,7 @@
diff --git a/web/explorer/src/components/columns/RuleColumn.vue b/web/explorer/src/components/columns/RuleColumn.vue
index 2a23a274..7afc18e3 100644
--- a/web/explorer/src/components/columns/RuleColumn.vue
+++ b/web/explorer/src/components/columns/RuleColumn.vue
@@ -55,7 +55,12 @@
-
+ The v7.4.0 capa release fixes a bug when processing VMRay analysis archives and enhances API extraction for all dynamic backends. For better terminal rendering capa now solely relies on the rich library.
+ The standalone capa executable can now automatically detect installations of relevant third party applications and use their backends (notably, idalib and Binary Ninja). For the extra standalone Linux build we've upgraded from Python 3.11 to 3.12.
+