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. The goal of the IDA Domain API is simple: make common reverse-engineering automation tasks easier to write, read, and maintain. Instead of navigating low-level SDK concepts, users can work directly with domain objects such as functions, instructions, pseudocode, and microcode.
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:
#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:
#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.
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}")
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.
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}")
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.
for imp in db.imports.get_all_imports():
print(f"{imp.module_name}!{imp.name}")
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.
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")
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:
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 IDA Domain API continues to evolve toward a higher-level scripting experience for IDA. While it complements the existing IDAPython SDK today, our long-term goal is to provide reverse engineers with a more intuitive, domain-focused way to automate analysis, prototype ideas, and build plugins.
Since it is meant to grow with real-world scripting needs, your 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?
- Read the Getting Started Guide for installation details, code samples, and usage tips.
- Explore the Domain API on GitHub
- Check out the reference documentation