use new IDAPython 9.0 APIs (#2339)

* use new IDAPython 9.0 APIs

* add IDAPython compatibility wrappers
This commit is contained in:
Moritz
2024-09-10 12:55:42 +02:00
committed by GitHub
parent 113b2593fa
commit 9459251e12
6 changed files with 58 additions and 22 deletions

View File

@@ -11,7 +11,8 @@ Unlock powerful malware analysis with capa's new [VMRay sandbox](https://www.vmr
- add .justfile @williballenthin #2325
- dynamic: add support for VMRay dynamic sandbox traces #2208 @mike-hunhoff @r-sm2024 @mr-tz
- cli: use modern terminal features to hyperlink to the rules website #2337 @williballenthin
- update IDAPython to IDA Pro 9.0 @mr-tz
### Breaking Changes
### New Rules (0)

View File

@@ -14,6 +14,7 @@ import idaapi
import idautils
import ida_entry
import capa.ida.helpers
import capa.features.extractors.common
import capa.features.extractors.helpers
import capa.features.extractors.strings
@@ -177,17 +178,17 @@ def extract_file_function_names() -> Iterator[Tuple[Feature, Address]]:
def extract_file_format() -> Iterator[Tuple[Feature, Address]]:
file_info = idaapi.get_inf_structure()
filetype = capa.ida.helpers.get_filetype()
if file_info.filetype in (idaapi.f_PE, idaapi.f_COFF):
if filetype in (idaapi.f_PE, idaapi.f_COFF):
yield Format(FORMAT_PE), NO_ADDRESS
elif file_info.filetype == idaapi.f_ELF:
elif filetype == idaapi.f_ELF:
yield Format(FORMAT_ELF), NO_ADDRESS
elif file_info.filetype == idaapi.f_BIN:
elif filetype == idaapi.f_BIN:
# no file type to return when processing a binary file, but we want to continue processing
return
else:
raise NotImplementedError(f"unexpected file format: {file_info.filetype}")
raise NotImplementedError(f"unexpected file format: {filetype}")
def extract_features() -> Iterator[Tuple[Feature, Address]]:

View File

@@ -9,7 +9,6 @@ import logging
import contextlib
from typing import Tuple, Iterator
import idaapi
import ida_loader
import capa.ida.helpers
@@ -48,12 +47,12 @@ def extract_os() -> Iterator[Tuple[Feature, Address]]:
def extract_arch() -> Iterator[Tuple[Feature, Address]]:
info: idaapi.idainfo = idaapi.get_inf_structure()
if info.procname == "metapc" and info.is_64bit():
procname = capa.ida.helpers.get_processor_name()
if procname == "metapc" and capa.ida.helpers.is_64bit():
yield Arch(ARCH_AMD64), NO_ADDRESS
elif info.procname == "metapc" and info.is_32bit():
elif procname == "metapc" and capa.ida.helpers.is_32bit():
yield Arch(ARCH_I386), NO_ADDRESS
elif info.procname == "metapc":
elif procname == "metapc":
logger.debug("unsupported architecture: non-32-bit nor non-64-bit intel")
return
else:
@@ -61,5 +60,5 @@ def extract_arch() -> Iterator[Tuple[Feature, Address]]:
# 1. handling a new architecture (e.g. aarch64)
#
# for (1), this logic will need to be updated as the format is implemented.
logger.debug("unsupported architecture: %s", info.procname)
logger.debug("unsupported architecture: %s", procname)
return

View File

@@ -21,6 +21,8 @@ from capa.features.extractors.base_extractor import FunctionHandle
IDA_NALT_ENCODING = ida_nalt.get_default_encoding_idx(ida_nalt.BPU_1B) # use one byte-per-character encoding
# TODO (mr): use find_bytes
# https://github.com/mandiant/capa/issues/2339
def find_byte_sequence(start: int, end: int, seq: bytes) -> Iterator[int]:
"""yield all ea of a given byte sequence
@@ -38,7 +40,7 @@ def find_byte_sequence(start: int, end: int, seq: bytes) -> Iterator[int]:
return
while True:
ea = ida_bytes.bin_search(start, end, patterns, ida_bytes.BIN_SEARCH_FORWARD)
ea, _ = ida_bytes.bin_search3(start, end, patterns, ida_bytes.BIN_SEARCH_FORWARD)
if ea == idaapi.BADADDR:
break
start = ea + 1

View File

@@ -13,6 +13,7 @@ from pathlib import Path
import idc
import idaapi
import ida_ida
import idautils
import ida_bytes
import ida_loader
@@ -45,6 +46,39 @@ NETNODE_RESULTS = "results"
NETNODE_RULES_CACHE_ID = "rules-cache-id"
# wrappers for IDA Pro (IDAPython) 7, 8 and 9 compability
version = float(idaapi.get_kernel_version())
if version < 9.0:
def get_filetype() -> "ida_ida.filetype_t":
return idaapi.get_inf_structure().filetype
def get_processor_name() -> str:
return idaapi.get_inf_structure().procname
def is_32bit() -> bool:
info: idaapi.idainfo = idaapi.get_inf_structure()
return info.is_32bit()
def is_64bit() -> bool:
info: idaapi.idainfo = idaapi.get_inf_structure()
return info.is_64bit()
else:
def get_filetype() -> "ida_ida.filetype_t":
return ida_ida.inf_get_filetype()
def get_processor_name() -> str:
return idc.get_processor_name()
def is_32bit() -> bool:
return idaapi.inf_is_32bit_exactly()
def is_64bit() -> bool:
return idaapi.inf_is_64bit()
def inform_user_ida_ui(message):
# this isn't a logger, this is IDA's logging facility
idaapi.info(f"{message}. Please refer to IDA Output window for more information.") # noqa: G004
@@ -52,17 +86,16 @@ def inform_user_ida_ui(message):
def is_supported_ida_version():
version = float(idaapi.get_kernel_version())
if version < 7.4 or version >= 9:
if version < 7.4 or version >= 10:
warning_msg = "This plugin does not support your IDA Pro version"
logger.warning(warning_msg)
logger.warning("Your IDA Pro version is: %s. Supported versions are: IDA >= 7.4 and IDA < 9.0.", version)
logger.warning("Your IDA Pro version is: %s. Supported versions are: IDA >= 7.4 and IDA < 10.0.", version)
return False
return True
def is_supported_file_type():
file_info = idaapi.get_inf_structure()
if file_info.filetype not in SUPPORTED_FILE_TYPES:
if get_filetype() not in SUPPORTED_FILE_TYPES:
logger.error("-" * 80)
logger.error(" Input file does not appear to be a supported file type.")
logger.error(" ")
@@ -76,8 +109,7 @@ def is_supported_file_type():
def is_supported_arch_type():
file_info = idaapi.get_inf_structure()
if file_info.procname not in SUPPORTED_ARCH_TYPES or not any((file_info.is_32bit(), file_info.is_64bit())):
if get_processor_name() not in SUPPORTED_ARCH_TYPES or not any((is_32bit(), is_64bit())):
logger.error("-" * 80)
logger.error(" Input file does not appear to target a supported architecture.")
logger.error(" ")
@@ -125,10 +157,10 @@ def collect_metadata(rules: List[Path]):
md5 = get_file_md5()
sha256 = get_file_sha256()
info: idaapi.idainfo = idaapi.get_inf_structure()
if info.procname == "metapc" and info.is_64bit():
procname = get_processor_name()
if procname == "metapc" and is_64bit():
arch = "x86_64"
elif info.procname == "metapc" and info.is_32bit():
elif procname == "metapc" and is_32bit():
arch = "x86"
else:
arch = "unknown arch"

View File

@@ -183,6 +183,7 @@ known_first_party = [
"binaryninja",
"flirt",
"ghidra",
"ida_ida",
"ida_bytes",
"ida_entry",
"ida_funcs",