Back

What's new in the IDA Domain API?

What's new in the IDA Domain API?

What's new in the IDA Domain API?

It has been a while since our first post about the IDA Domain API, our open-source Python API designed to make scripting in IDA simpler, more consistent, and more approachable.

Since last year's launch, the project has kept moving. With the recent v0.5.0 release, the API covers most of the everyday scripting surface in IDA.

This post highlights the most important additions and compares a few examples with equivalent IDAPython snippets. IDAPython is still there, the point of the Domain API is to make the common cases read like normal Python, so scripts are easier to write, review, and maintain.

Design principles

The Domain API exposes popular IDA functionality through objects that match the reverse engineering concepts you already work with: functions, instructions, operands, imports, pseudocode, microcode, and so on. The goal is to avoid forcing every script to start with low-level SDK details before it can answer a simple question.

At the same time, the Domain API is intentionally compatible with IDAPython. When you need something that is not wrapped yet, you can still call IDAPython directly.

For SDK objects returned by the Domain API, you can often pass the object straight to IDAPython:

Python
#IDA Domain
func = db.functions.get_at(0x401000)

#IDAPython
print(ida_funcs.calc_func_size(func))

For wrapped objects, use the raw_* property to reach the underlying IDAPython object:

Python
#IDA Domain
fn = db.pseudocode.decompile(0x401000)

#IDAPython
print(fn.raw_cfunc.print_dcl())

New modules

Microcode

The new microcode module exposes IDA's microcode as a regular Python object. You can iterate a micro block array, walk instructions, ask high-level questions about operands, and use named enums instead of magic integers.

For example, here is a small snippet that finds every call instruction in a function and prints the call site and callee address.

IDA Domain
func = db.functions.get_at(0x401000)

mba = db.microcode.generate(func, MicroMaturity.LOCOPT)
for insn in mba.instructions():
    if insn.is_call():
        target = insn.l.global_address
        target = f"{target:#x}" if target is not None else "<indirect>"
        print(f"{insn.ea:#x}: -> {target}")
IDAPython
func = ida_funcs.get_func(0x401000)

mbr = ida_hexrays.mba_ranges_t(func)
hf = ida_hexrays.hexrays_failure_t()
ml = ida_hexrays.mlist_t()
mba = ida_hexrays.gen_microcode(mbr, hf, ml, 0, ida_hexrays.MMAT_LOCOPT)

for i in range(mba.qty):
    insn = mba.get_mblock(i).head
    while insn is not None:
        if insn.opcode in (ida_hexrays.m_call,
ida_hexrays.m_icall):
            target = f"{insn.l.g:#x}" if insn.l.t == ida_hexrays.mop_v else "<indirect>"
            print(f"{insn.ea:#x}: -> {target}")
        insn = insn.next

Click and drag, I'm interactive.

Pseudocode

The pseudocode module gives Python-friendly access to Hex-Rays decompiler output. It wraps the decompiled function, exposes the C tree as iterable expressions and statements, and gives typed accessors for common constructs such as calls, numbers, comparisons, control-flow blocks, and returns.

Here is a simple example that walks a function and prints every numeric constant recovered by the decompiler.

IDA Domain
func = db.pseudocode.decompile(0x401000)

for expr in func.walk_expressions():
    if expr.is_number:
        print(f"{expr.ea:#x}: {expr.number.unsigned_value:#x}")
IDAPython
cfunc = ida_hexrays.decompile(0x401000)

class NumVisitor(ida_hexrays.ctree_visitor_t):
    def __init__(self):
        super().__init__(ida_hexrays.CV_FAST)

    def visit_expr(self, expr):
        if expr.op == ida_hexrays.cot_num:
            print(f"{expr.ea:#x}: {expr.n._value:#x}")
        return 0

NumVisitor().apply_to(cfunc.body, None)

Imports

The imports module gives you a unified view of every external symbol the binary pulls in. Imports are returned as plain Python data objects, so you can iterate, filter, and look them up by name or address without rewriting the same enumeration callback each time.

This snippet prints every imported symbol as module!name.

IDA Domain
for imp in db.imports.get_all_imports():
    print(f"{imp.module_name}!{imp.name}")
IDAPython
for i in range(ida_nalt.get_import_module_qty()):
    mod = ida_nalt.get_import_module_name(i) or
f"module_{i}"

    def cb(ea, name, ordinal):
        print(f"{mod}!{name}")
        return True

    ida_nalt.enum_import_names(i, cb)

Flowcharts

The flowchart module exposes a function's control-flow graph as a regular Python object. You can iterate basic blocks, walk their instructions, and follow successors or predecessors with normal Python iterators.

The example below counts instructions in each basic block of a function.

Python
func = db.functions.get_at(0x401000)

  for bb in db.functions.get_flowchart(func):
      n = len(list(bb.get_instructions()))
      print(f"{bb.start_ea:#x}: {n} insn")
IDA Domain
func = ida_funcs.get_func(0x401000)
insn = ida_ua.insn_t()

for bb in ida_gdl.FlowChart(func):
    n, ea = 0, bb.start_ea
    while ea < bb.end_ea:
        size = ida_ua.decode_insn(insn, ea)
        if size <= 0:
            break
        n += 1
        ea += size
    print(f"{bb.start_ea:#x}: {n} insn")

Exceptions

Error handling is also becoming more consistent. Instead of mixing sentinel values, and failed lookups, the Domain API now raises Python exceptions from the shared IdaDomainError hierarchy.

For example:

Python
try:
    func = db.functions.get_at(0xDEADBEEF)
except InvalidEAError as e:
    print(f"bad address: {e}")

try:
    cfunc = db.pseudocode.decompile(0x401000)
except DecompilerError as e:
    print(f"decompile failed: {e}")

Feedback

The Domain API is meant to grow with real-world scripting needs, so feedback is especially valuable now. If something still requires too much boilerplate, if a wrapper feels awkward, or if an IDAPython pattern should have a clearer Domain API equivalent, we would like to hear about it.

The project is open source, and we welcome user contributions too — whether that's filing an issue, sending a pull request with a new wrapper, improving documentation, or sharing example scripts.

Ready to try it?