Skip to content

Examples

This section provides few examples of using the IDA Domain API for common reverse engineering tasks.

Basic Database Operations

Opening and Exploring a Database

"""
Database exploration example for IDA Domain API.

This example demonstrates how to open an IDA database and explore its basic properties.
"""

import argparse
from dataclasses import asdict

import ida_domain
from ida_domain import Database
from ida_domain.database import IdaCommandOptions


def explore_database(db_path):
    """Explore basic database information."""
    ida_options = IdaCommandOptions(auto_analysis=True, new_database=True)
    with Database.open(db_path, ida_options) as db:
        # Get basic information
        print(f'Address range: {hex(db.minimum_ea)} - {hex(db.maximum_ea)}')

        # Get metadata
        print('Database metadata:')
        metadata_dict = asdict(db.metadata)
        for key, value in metadata_dict.items():
            print(f'  {key}: {value}')

        # Count functions
        function_count = 0
        for _ in db.functions:
            function_count += 1
        print(f'Total functions: {function_count}')


def main():
    """Main entry point with argument parsing."""
    parser = argparse.ArgumentParser(description='Database exploration example')
    parser.add_argument(
        '-f', '--input-file', help='Binary input file to be loaded', type=str, required=True
    )
    args = parser.parse_args()
    explore_database(args.input_file)


if __name__ == '__main__':
    main()

Complete Traversal of a Database

This example demonstrates a complete traversal of a database:

#!/usr/bin/env python3
"""
Database Traversal Example for IDA Domain API

This example demonstrates how to systematically traverse an IDA database and
examine available entities. It provides a structured approach to exploring
contents of a binary analysis database.
"""

import argparse
from dataclasses import asdict

import ida_domain
from ida_domain.database import IdaCommandOptions


def print_section_header(title: str, char: str = '=') -> None:
    """Print a formatted section header for better output organization."""
    print(f'\n{char * 60}')
    print(f' {title}')
    print(f'{char * 60}')


def print_subsection_header(title: str) -> None:
    """Print a formatted subsection header."""
    print(f'\n--- {title} ---')


def traverse_metadata(db: ida_domain.Database) -> None:
    """
    Traverse and display database metadata.

    Args:
        db: The IDA database instance
    """
    print_section_header('DATABASE METADATA')

    metadata = asdict(db.metadata)
    if metadata:
        for key, value in metadata.items():
            print(f'  {key:15}: {value}')
    else:
        print('  No metadata available')

    # Additional database properties
    print(f'  {"current_ea":15}: 0x{db.current_ea:x}')
    print(f'  {"minimum_ea":15}: 0x{db.minimum_ea:x}')
    print(f'  {"maximum_ea":15}: 0x{db.maximum_ea:x}')


def traverse_segments(db: ida_domain.Database) -> None:
    """
    Traverse and display memory segments.

    Args:
        db: The IDA database instance
    """
    print_section_header('MEMORY SEGMENTS')

    segments = list(db.segments)
    print(f'Total segments: {len(segments)}')

    for i, segment in enumerate(segments, 1):
        print(
            f'  [{i:2d}] {segment.name:20} | '
            f'Start: 0x{segment.start_ea:08x} | '
            f'End: 0x{segment.end_ea:08x} | '
            f'Size: {segment.size} | '
            f'Type: {segment.type}'
        )


def traverse_functions(db: ida_domain.Database) -> None:
    """
    Traverse and display functions.

    Args:
        db: The IDA database instance
    """
    print_section_header('FUNCTIONS')

    functions = list(db.functions)
    print(f'Total functions: {len(functions)}')

    # Show first 20 functions to avoid overwhelming output
    display_count = min(20, len(functions))
    if display_count < len(functions):
        print(f'Displaying first {display_count} functions:')

    for i, func in enumerate(functions[:display_count], 1):
        print(
            f'  [{i:2d}] {func.name:30} | '
            f'Start: 0x{func.start_ea:08x} | '
            f'End: 0x{func.end_ea:08x} | '
            f'Size: {func.size}'
        )

    if display_count < len(functions):
        print(f'  ... and {len(functions) - display_count} more functions')


def traverse_entries(db: ida_domain.Database) -> None:
    """
    Traverse and display program entries.

    Args:
        db: The IDA database instance
    """
    print_section_header('PROGRAM ENTRIES')

    entries = list(db.entries)
    print(f'Total entries: {len(entries)}')

    for i, entry in enumerate(entries, 1):
        print(
            f'  [{i:2d}] {entry.name:30} | '
            f'Address: 0x{entry.address:08x} | '
            f'Ordinal: {entry.ordinal}'
        )


def traverse_heads(db: ida_domain.Database) -> None:
    """
    Traverse and display heads (data and code locations).

    Args:
        db: The IDA database instance
    """
    print_section_header('HEADS (Data/Code Locations)')

    heads = list(db.heads)
    print(f'Total heads: {len(heads)}')

    # Show first 20 heads to avoid overwhelming output
    display_count = min(20, len(heads))
    if display_count < len(heads):
        print(f'Displaying first {display_count} heads:')

    for i, head in enumerate(heads[:display_count], 1):
        print(f'  [{i:2d}] Address: 0x{head:08x}')

    if display_count < len(heads):
        print(f'  ... and {len(heads) - display_count} more heads')


def traverse_strings(db: ida_domain.Database) -> None:
    """
    Traverse and display identified strings.

    Args:
        db: The IDA database instance
    """
    print_section_header('STRINGS')

    strings = list(db.strings)
    print(f'Total strings: {len(strings)}')

    # Show first 15 strings to avoid overwhelming output
    display_count = min(15, len(strings))
    if display_count < len(strings):
        print(f'Displaying first {display_count} strings:')

    for i, (ea, content) in enumerate(strings[:display_count], 1):
        # Truncate very long strings for display
        display_str = content[:50] + '...' if len(content) > 50 else content
        print(f'  [{i:2d}] 0x{ea:08x}: "{display_str}"')

    if display_count < len(strings):
        print(f'  ... and {len(strings) - display_count} more strings')


def traverse_names(db: ida_domain.Database) -> None:
    """
    Traverse and display names (symbols and labels).

    Args:
        db: The IDA database instance
    """
    print_section_header('NAMES (Symbols & Labels)')

    names = list(db.names)
    print(f'Total names: {len(names)}')

    # Show first 20 names to avoid overwhelming output
    display_count = min(20, len(names))
    if display_count < len(names):
        print(f'Displaying first {display_count} names:')

    for i, (ea, name) in enumerate(names[:display_count], 1):
        print(f'  [{i:2d}] 0x{ea:08x}: {name}')

    if display_count < len(names):
        print(f'  ... and {len(names) - display_count} more names')


def traverse_types(db: ida_domain.Database) -> None:
    """
    Traverse and display type definitions.

    Args:
        db: The IDA database instance
    """
    print_section_header('TYPE DEFINITIONS')

    types = list(db.types)
    print(f'Total types: {len(types)}')

    # Show first 15 types to avoid overwhelming output
    display_count = min(15, len(types))
    if display_count < len(types):
        print(f'Displaying first {display_count} types:')

    for i, type_def in enumerate(types[:display_count], 1):
        type_name = (
            type_def.get_type_name()
            if type_def.get_type_name()
            else f'<unnamed_{type_def.get_tid()}>'
        )
        print(f'  [{i:2d}] {type_name:30} | TID: {type_def.get_tid()}')

    if display_count < len(types):
        print(f'  ... and {len(types) - display_count} more types')


def traverse_comments(db: ida_domain.Database) -> None:
    """
    Traverse and display comments.

    Args:
        db: The IDA database instance
    """
    print_section_header('COMMENTS')

    # Get all comments (regular and repeatable)
    comments = list(db.comments)
    print(f'Total comments: {len(comments)}')

    # Show first 10 comments to avoid overwhelming output
    display_count = min(10, len(comments))
    if display_count < len(comments):
        print(f'Displaying first {display_count} comments:')

    for i, info in enumerate(comments[:display_count], 1):
        # Truncate very long comments for display
        text = info.comment[:60] + '...' if len(info.comment) > 60 else info.comment
        type = 'REP' if info.repeatable else 'REG'
        print(f'  [{i:2d}] 0x{info.ea:08x} [{type}]: {text}')

    if display_count < len(comments):
        print(f'  ... and {len(comments) - display_count} more comments')


def traverse_basic_blocks(db: ida_domain.Database) -> None:
    """
    Traverse and display basic blocks.

    Args:
        db: The IDA database instance
    """
    print_section_header('BASIC BLOCKS')

    basic_blocks = list(db.basic_blocks.get_between(db.minimum_ea, db.maximum_ea))
    print(f'Total basic blocks: {len(basic_blocks)}')

    # Show first 15 basic blocks to avoid overwhelming output
    display_count = min(15, len(basic_blocks))
    if display_count < len(basic_blocks):
        print(f'Displaying first {display_count} basic blocks:')

    for i, bb in enumerate(basic_blocks[:display_count], 1):
        print(f'  [{i:2d}] Start: 0x{bb.start_ea:08x} | End: 0x{bb.end_ea:08x} | Size: {bb.size}')

    if display_count < len(basic_blocks):
        print(f'  ... and {len(basic_blocks) - display_count} more basic blocks')


def traverse_instructions(db: ida_domain.Database) -> None:
    """
    Traverse and display instructions with disassembly.

    Args:
        db: The IDA database instance
    """
    print_section_header('INSTRUCTIONS')

    instructions = list(db.instructions)
    print(f'Total instructions: {len(instructions)}')

    # Show first 20 instructions to avoid overwhelming output
    display_count = min(20, len(instructions))
    if display_count < len(instructions):
        print(f'Displaying first {display_count} instructions:')

    for i, inst in enumerate(instructions[:display_count], 1):
        disasm = db.instructions.get_disassembly(inst)
        if disasm:
            print(f'  [{i:2d}] 0x{inst.ea:08x}: {disasm}')
        else:
            print(f'  [{i:2d}] 0x{inst.ea:08x}: <no disassembly>')

    if display_count < len(instructions):
        print(f'  ... and {len(instructions) - display_count} more instructions')


def traverse_cross_references(db: ida_domain.Database) -> None:
    """
    Traverse and display cross-references.

    Args:
        db: The IDA database instance
    """
    print_section_header('CROSS-REFERENCES')

    # Get a sample of addresses to check for cross-references
    sample_addresses = []

    # Add function start addresses
    functions = list(db.functions)
    sample_addresses.extend([f.start_ea for f in functions[:5]])

    # Add some heads
    heads = list(db.heads)
    sample_addresses.extend(heads[:5])

    xref_count = 0
    print('Sample cross-references:')

    for addr in sample_addresses[:10]:  # Limit to first 10 addresses
        xrefs_to = list(db.xrefs.get_to(addr))
        xrefs_from = list(db.xrefs.get_from(addr))

        if xrefs_to or xrefs_from:
            print(f'  Address 0x{addr:08x}:')

            for xref in xrefs_to[:3]:  # Show max 3 xrefs to
                type_name = db.xrefs.get_ref_type_name(xref.type)
                print(f'    <- FROM 0x{xref.frm:08x} (type: {type_name})')
                xref_count += 1

            for xref in xrefs_from[:3]:  # Show max 3 xrefs from
                type_name = db.xrefs.get_ref_type_name(xref.type)
                print(f'    -> TO   0x{xref.to:08x} (type: {type_name})')
                xref_count += 1

    print(f'Total cross-references displayed: {xref_count}')


def traverse_database(db_path: str):
    """
    Main function to traverse the entire IDA database and display all entities.

    Args:
        db_path: Path to the binary file to analyze
    """
    print_section_header('IDA DOMAIN DATABASE TRAVERSAL', '=')
    print(f'Analyzing file: {db_path}')

    # Configure IDA options for analysis
    ida_options = IdaCommandOptions(auto_analysis=True, new_database=True)

    # Open database
    with ida_domain.Database.open(db_path, ida_options, False) as db:
        # Traverse all database entities
        traverse_metadata(db)
        traverse_segments(db)
        traverse_functions(db)
        traverse_entries(db)
        traverse_heads(db)
        traverse_strings(db)
        traverse_names(db)
        traverse_types(db)
        traverse_comments(db)
        traverse_basic_blocks(db)
        traverse_instructions(db)
        traverse_cross_references(db)

        print_section_header('TRAVERSAL COMPLETE', '=')


def main():
    """Main entry point with argument parsing."""
    parser = argparse.ArgumentParser(description='IDA Database Traversal Example')
    parser.add_argument(
        '-f', '--input-file', help='Binary input file to be loaded', type=str, required=True
    )
    args = parser.parse_args()
    traverse_database(args.input_file)


if __name__ == '__main__':
    main()

Function Analysis

Finding and Analyzing Functions

#!/usr/bin/env python3
"""
Function analysis example for IDA Domain API.

This example demonstrates how to find and analyze functions in an IDA database.
"""

import argparse

import ida_domain
from ida_domain.database import IdaCommandOptions


def analyze_functions(db_path, pattern='main', max_results=10):
    """Find and analyze functions matching a pattern."""
    ida_options = IdaCommandOptions(auto_analysis=True, new_database=True)
    with ida_domain.Database.open(db_path, ida_options, False) as db:
        # Find functions matching a pattern
        matching_functions = []
        for func in db.functions:
            func_name = db.functions.get_name(func)
            if pattern.lower() in func_name.lower():
                matching_functions.append((func, func_name))

        print(f"Found {len(matching_functions)} functions matching '{pattern}':")

        # Limit results if requested
        display_functions = (
            matching_functions[:max_results] if max_results > 0 else matching_functions
        )

        for func, name in display_functions:
            print(f'\nFunction: {name}')
            print(f'Address: {hex(func.start_ea)} - {hex(func.end_ea)}')

            # Get signature
            signature = db.functions.get_signature(func)
            print(f'Signature: {signature}')

            # Get basic blocks
            bb_count = 0
            for _ in db.functions.get_basic_blocks(func):
                bb_count += 1
            print(f'Basic blocks: {bb_count}')

            # Show first few lines of disassembly
            disasm = db.functions.get_disassembly(func)
            print('Disassembly (first 5 lines):')
            for line in disasm[:5]:
                print(f'  {line}')

        if max_results > 0 and len(matching_functions) > max_results:
            print(f'\n... (showing first {max_results} of {len(matching_functions)} matches)')


def main():
    """Main entry point with argument parsing."""
    parser = argparse.ArgumentParser(description='Database exploration example')
    parser.add_argument(
        '-f', '--input-file', help='Binary input file to be loaded', type=str, required=True
    )
    parser.add_argument(
        '-p',
        '--pattern',
        default='main',
        help='Pattern to search for in function names (default: main)',
    )
    parser.add_argument(
        '-m',
        '--max-results',
        type=int,
        default=10,
        help='Maximum number of results to display (0 for all, default: 10)',
    )
    args = parser.parse_args()
    analyze_functions(args.input_file, args.pattern, args.max_results)


if __name__ == '__main__':
    main()

Signature Files

Working with FLIRT signature files

#!/usr/bin/env python3
"""
Using FLIRT signature files example in IDA Domain API.

This example demonstrates how to work with signature files:
  - how to evaluate the matches on your binary
  - how to actually apply a sig file
  - how to generate .sig/.pat from your loaded binary
  - how to use custom signature directories
"""

import argparse
from pathlib import Path

import ida_domain
from ida_domain import Database
from ida_domain.database import IdaCommandOptions
from ida_domain.signature_files import FileInfo


def probe_signature_files(db: ida_domain.Database, min_matches: int, custom_dir: str = None):
    """Probe signature files and collect the ones over the minimum number of matches."""
    print('Probing signature files...')
    directories = [Path(custom_dir)] if custom_dir else None
    files = db.signature_files.get_files(directories=directories)

    good_matches = []
    for sig_file in files:
        results = db.signature_files.apply(sig_file, probe_only=True)
        for result in results:
            if result.matches >= min_matches:
                good_matches.append(result)
                print(f'{sig_file.name}: {result.matches} matches')

    return good_matches


def apply_signature_files(db: ida_domain.Database, matches: list[FileInfo], min_matches: int):
    """Apply signature files over the minimum number of matches."""
    if not matches:
        return

    print('\nApplying signature files...')
    for result in matches:
        if result.matches >= min_matches:
            sig_path = Path(result.path)
            print(f'Applying {sig_path.name}')
            db.signature_files.apply(sig_path, probe_only=False)


def generate_signatures(db: ida_domain.Database):
    """Generate signature files from current database."""
    print('\nGenerating signatures...')
    produced_files = db.signature_files.create()
    if produced_files:
        for file_path in produced_files:
            print(f'Generated: {Path(file_path).name}')


def main():
    """Main entry point."""
    parser = argparse.ArgumentParser(description='FLIRT signature files example')
    parser.add_argument('-f', '--input-file', required=True, help='Binary file to analyze')
    parser.add_argument('-d', '--sig-dir', help='Directory where to look for signature files')
    parser.add_argument('-p', '--min-probe-matches', default=5, type=int)
    parser.add_argument('-a', '--min-apply-matches', default=10, type=int)
    args = parser.parse_args()

    ida_options = IdaCommandOptions(auto_analysis=True, new_database=True)
    with Database.open(args.input_file, ida_options) as db:
        matches = probe_signature_files(db, args.min_probe_matches, args.sig_dir)
        apply_signature_files(db, matches, args.min_apply_matches)
        generate_signatures(db)


if __name__ == '__main__':
    main()

String Analysis

Finding and Analyzing Strings

#!/usr/bin/env python3
"""
String analysis example for IDA Domain API.

This example demonstrates how to find and analyze strings in an IDA database.
"""

import argparse

import ida_domain
from ida_domain import Database
from ida_domain.database import IdaCommandOptions


def analyze_strings(db_path, min_length=5, max_display=20, show_interesting=True):
    """Find and analyze strings in the database."""
    ida_options = IdaCommandOptions(auto_analysis=True, new_database=True)
    with Database.open(db_path, ida_options) as db:
        print(f'Analyzing strings (minimum length: {min_length}):')

        # Collect all strings
        all_strings = []
        interesting_strings = []

        for addr, string_value in db.strings:
            if len(string_value) >= min_length:
                all_strings.append((addr, string_value))

                # Check for interesting keywords
                if show_interesting:
                    lower_str = string_value.lower()
                    interesting_keywords = [
                        'password',
                        'passwd',
                        'pwd',
                        'key',
                        'secret',
                        'token',
                        'api',
                        'username',
                        'user',
                        'login',
                        'config',
                        'settings',
                        'registry',
                        'file',
                        'path',
                        'directory',
                        'http',
                        'https',
                        'ftp',
                        'url',
                        'sql',
                        'database',
                        'query',
                    ]

                    if any(keyword in lower_str for keyword in interesting_keywords):
                        interesting_strings.append((addr, string_value))

        print(f'Total strings: {db.strings.get_count()}')
        print(f'Strings >= {min_length} chars: {len(all_strings)}')

        # Display regular strings
        print(f'\nFirst {max_display} strings:')
        for i, (addr, string_value) in enumerate(all_strings[:max_display]):
            print(f'{hex(addr)}: {repr(string_value)}')

        if len(all_strings) > max_display:
            print(f'... (showing first {max_display} of {len(all_strings)} strings)')

        # Display interesting strings
        if show_interesting and interesting_strings:
            print(f'\nInteresting strings found ({len(interesting_strings)}):')
            for addr, string_value in interesting_strings[:10]:  # Limit to 10
                print(f'{hex(addr)}: {repr(string_value)}')

            if len(interesting_strings) > 10:
                print(f'... (showing first 10 of {len(interesting_strings)} interesting strings)')


def main():
    """Main entry point with argument parsing."""
    parser = argparse.ArgumentParser(description='Database exploration example')
    parser.add_argument(
        '-f', '--input-file', help='Binary input file to be loaded', type=str, required=True
    )
    parser.add_argument(
        '-l', '--min-length', type=int, default=5, help='Minimum string length(default: 5)'
    )
    parser.add_argument(
        '-m', '--max-display', type=int, default=20, help='Maximum displayed strings (default: 20)'
    )
    parser.add_argument(
        '-s',
        '--show-interesting',
        type=bool,
        default=True,
        help='Highlight interesting strings (default True)',
    )
    args = parser.parse_args()
    analyze_strings(args.input_file, args.min_length, args.max_display, args.show_interesting)


if __name__ == '__main__':
    main()

Bytes Analysis

Analyzing and Manipulating Bytes

#!/usr/bin/env python3
"""
Byte analysis example for IDA Domain API.

This example demonstrates how to analyze, search, and manipulate bytes in an IDA database.
It showcases the comprehensive byte manipulation capabilities including data type operations,
patching, flag checking, and search functionality.
"""

import argparse

import ida_domain
from ida_domain import Database
from ida_domain.bytes import ByteFlags, SearchFlags, StringType
from ida_domain.database import IdaCommandOptions


def analyze_bytes(db_path, search_pattern=None, patch_demo=False, max_results=20):
    """Analyze and manipulate bytes in the database."""
    ida_options = IdaCommandOptions(auto_analysis=True, new_database=True)
    with Database.open(path=db_path, args=ida_options, save_on_close=False) as db:
        bytes_handler = db.bytes

        print('=== IDA Domain Bytes Analysis ===\n')

        # 1. Basic byte reading operations
        print('1. Basic Byte Reading Operations:')
        print('-' * 40)

        # Read different data types from entry point
        entry_point = db.minimum_ea
        print(f'Entry point: {hex(entry_point)}')

        byte_val = bytes_handler.get_byte_at(entry_point)
        word_val = bytes_handler.get_word_at(entry_point)
        dword_val = bytes_handler.get_dword_at(entry_point)
        qword_val = bytes_handler.get_qword_at(entry_point)

        print(f'  Byte:  0x{byte_val:02x} ({byte_val})')
        print(f'  Word:  0x{word_val:04x} ({word_val})')
        print(f'  DWord: 0x{dword_val:08x} ({dword_val})')
        print(f'  QWord: 0x{qword_val:016x} ({qword_val})')

        # Get disassembly
        disasm = bytes_handler.get_disassembly_at(entry_point)
        print(f'  Disassembly: {disasm}')

        # 2. Data type analysis using flags
        print('\n2. Data Type Analysis:')
        print('-' * 40)

        # Analyze different addresses
        test_addresses = [entry_point, entry_point + 0x10, entry_point + 0x20]
        for addr in test_addresses:
            if not db.is_valid_ea(addr):
                continue

            flags = bytes_handler.get_flags_at(addr)
            data_size = bytes_handler.get_data_size_at(addr)

            # Use new flag checking methods
            is_code = bytes_handler.check_flags_at(addr, ByteFlags.CODE)
            is_data = bytes_handler.check_flags_at(addr, ByteFlags.DATA)
            has_any_data_flags = bytes_handler.has_any_flags_at(
                addr, ByteFlags.BYTE | ByteFlags.WORD | ByteFlags.DWORD
            )

            print(f'  Address {hex(addr)}:')
            print(f'    Flags: 0x{flags:x}')
            print(f'    Is Code: {is_code}, Is Data: {is_data}')
            print(f'    Has Data Flags: {has_any_data_flags}')
            print(f'    DataSize: {data_size}')

        # 3. Search operations
        print('\n3. Search Operations:')
        print('-' * 40)

        # Search for common patterns
        patterns_to_search = [
            (b'\x48\x89\xe5', 'Function prologue (mov rbp, rsp)'),
            (b'\x55', 'Push rbp'),
            (b'\xc3', 'Return instruction'),
        ]

        for pattern, description in patterns_to_search:
            found_addr = bytes_handler.find_bytes_between(pattern)
            if found_addr:
                print(f'  Found {description} at {hex(found_addr)}')
            else:
                print(f'  {description} not found')

        # Text search with flags
        if search_pattern:
            print(f"\n  Searching for text: '{search_pattern}'")
            # Case-sensitive search
            addr_case = bytes_handler.find_text(
                search_pattern, flags=SearchFlags.DOWN | SearchFlags.CASE
            )
            # Case-insensitive search
            addr_nocase = bytes_handler.find_text_between(search_pattern, flags=SearchFlags.DOWN)

            if addr_case:
                print(f'    Case-sensitive found at: {hex(addr_case)}')
            if addr_nocase and addr_nocase != addr_case:
                print(f'    Case-insensitive found at: {hex(addr_nocase)}')
            if not addr_case and not addr_nocase:
                print(f"    Text '{search_pattern}' not found")

        # Search for immediate values
        immediate_addr = bytes_handler.find_immediate_between(1)
        if immediate_addr is not None:
            print(f'  Found immediate value 1 at {hex(immediate_addr)}')

        # 4. String operations
        print('\n4. String Operations:')
        print('-' * 40)

        # Find and analyze strings
        string_count = 0
        for addr, string_val in db.strings:
            if string_count >= 3:  # Limit output
                break

            print(f'  String at {hex(addr)}: {repr(string_val)}')

            # Try different string reading methods
            cstring = bytes_handler.get_cstring_at(addr)
            if cstring:
                print(f'    C-string: {repr(cstring)}')

            string_count += 1

        # 5. Data type creation
        print('\n5. Data Type Creation:')
        print('-' * 40)

        # Find a suitable data address for demonstration
        data_addr = None
        for addr in range(db.minimum_ea, min(db.minimum_ea + 0x100, db.maximum_ea), 4):
            if bytes_handler.is_data_at(addr) or bytes_handler.is_unknown_at(addr):
                data_addr = addr
                break

        if data_addr:
            print(f'  Working with data at {hex(data_addr)}')

            # Create different data types
            original_flags = bytes_handler.get_flags_at(data_addr)
            print(f'    Original flags: {original_flags}')

            # Make it a byte
            if bytes_handler.make_byte_at(data_addr):
                print(f'    Successfully created byte at {hex(data_addr)}')

            # Make it a word
            if bytes_handler.make_word(data_addr):
                print(f'    Successfully created word at {hex(data_addr)}')

            # Create a string with specific type
            string_addr = data_addr + 8
            if bytes_handler.make_string(string_addr, string_type=StringType.C):
                print(f'    Successfully created C-string at {hex(string_addr)}')

        # 6. Patching demonstration (if requested)
        if patch_demo:
            print('\n6. Patching Demonstration:')
            print('-' * 40)

            # Find a safe address to patch (data section)
            patch_addr = None
            for addr in range(db.minimum_ea, min(db.minimum_ea + 0x200, db.maximum_ea)):
                if bytes_handler.is_data(addr):
                    patch_addr = addr
                    break

            if patch_addr:
                print(f'  Demonstrating patching at {hex(patch_addr)}')

                # Get original values
                orig_byte = bytes_handler.get_byte_at(patch_addr)
                orig_word = bytes_handler.get_word_at(patch_addr)

                print(f'    Original byte: 0x{orig_byte:02x}')
                print(f'    Original word: 0x{orig_word:04x}')

                # Patch byte
                if bytes_handler.patch_byte_at(patch_addr, 0xAB):
                    new_byte = bytes_handler.get_byte_at(patch_addr)
                    print(f'    Patched byte: 0x{new_byte:02x}')

                    # Get original value
                    retrieved_orig = bytes_handler.get_original_byte_at(patch_addr)
                    print(f'    Retrieved original: 0x{retrieved_orig:02x}')

                    # Revert patch
                    if bytes_handler.revert_byte_at(patch_addr):
                        reverted_byte = bytes_handler.get_byte_at(patch_addr)
                        print(f'    Reverted byte: 0x{reverted_byte:02x}')

                # Patch multiple bytes
                test_data = b'\x90\x90\x90\x90'  # NOP instructions
                if bytes_handler.patch_bytes(patch_addr, test_data):
                    print(f'    Patched {len(test_data)} bytes with NOPs')

                    # Get original bytes
                    success, orig_bytes = bytes_handler.get_original_bytes_at(
                        patch_addr, len(test_data)
                    )
                    if success:
                        print(f'    Original bytes: {orig_bytes.hex()}')

        # 7. Navigation helpers
        print('\n7. Navigation Helpers:')
        print('-' * 40)

        test_addr = entry_point + 0x10
        if test_addr <= db.maximum_ea:
            next_head = bytes_handler.get_next_head(test_addr)
            prev_head = bytes_handler.get_prev_head(test_addr)
            next_addr = bytes_handler.get_next_addr(test_addr)
            prev_addr = bytes_handler.get_prev_addr(test_addr)

            print(f'  From address {hex(test_addr)}:')
            print(
                f'    Next head: {hex(next_head) if next_head != 0xFFFFFFFFFFFFFFFF else "None"}'
            )
            print(
                f'    Prev head: {hex(prev_head) if prev_head != 0xFFFFFFFFFFFFFFFF else "None"}'
            )
            print(f'    Next addr: {hex(next_addr)}')
            print(f'    Prev addr: {hex(prev_addr)}')

        # 8. Summary statistics
        print('\n8. Summary Statistics:')
        print('-' * 40)

        code_count = data_count = unknown_count = 0
        sample_size = db.maximum_ea - db.minimum_ea

        for addr in range(db.minimum_ea, db.minimum_ea + sample_size):
            if not db.is_valid_ea(addr):
                continue
            if bytes_handler.is_code_at(addr):
                code_count += 1
            elif bytes_handler.is_data_at(addr):
                data_count += 1
            elif bytes_handler.is_unknown_at(addr):
                unknown_count += 1

        print(f'  Sample size: {sample_size} bytes')
        print(f'  Code bytes: {code_count} ({code_count / sample_size * 100:.1f}%)')
        print(f'  Data bytes: {data_count} ({data_count / sample_size * 100:.1f}%)')
        print(f'  Unknown bytes: {unknown_count} ({unknown_count / sample_size * 100:.1f}%)')

        print('\n=== Analysis Complete ===')


def main():
    """Main entry point with argument parsing."""
    parser = argparse.ArgumentParser(description='Byte analysis example for IDA Domain API')
    parser.add_argument(
        '-f', '--input-file', help='Binary input file to be loaded', type=str, required=True
    )
    parser.add_argument(
        '-s',
        '--search-pattern',
        help='Text pattern to search for in the binary',
        type=str,
        default=None,
    )
    parser.add_argument(
        '-p',
        '--patch-demo',
        action='store_true',
        help='Demonstrate patching operations (modifies database temporarily)',
    )
    parser.add_argument(
        '-m',
        '--max-results',
        type=int,
        default=20,
        help='Maximum number of results to display (default: 20)',
    )

    args = parser.parse_args()
    analyze_bytes(args.input_file, args.search_pattern, args.patch_demo, args.max_results)


if __name__ == '__main__':
    main()

Type Analysis

Analyzing and Working with Types

#!/usr/bin/env python3
"""
Types example for IDA Domain API.

This example demonstrates how to work with IDA's type information libraries.
"""

import argparse
import tempfile
from pathlib import Path

import ida_domain
from ida_domain import Database


def print_section_header(title: str, char: str = '=') -> None:
    """Print a formatted section header for better output organization."""
    print(f'\n{char * 60}')
    print(f' {title}')
    print(f'{char * 60}')


def print_subsection_header(title: str) -> None:
    """Print a formatted subsection header."""
    print(f'\n--- {title} ---')


declarations = """
typedef unsigned char uint8_t;
typedef unsigned int uint32_t;

struct STRUCT_EXAMPLE
{
    char *text;
    unsigned int length;
    uint32_t reserved;
};

"""


def create_types(db: Database, library_path: Path):
    """Create a type library and fill it with types parsed from declaration"""

    til = db.types.create_library(library_path, 'Example type information library')
    db.types.parse_declarations(til, declarations)
    db.types.save_library(til, library_path)
    db.types.unload_library(til)


def import_types(db: Database, library_path: Path):
    """Import all types from external library"""

    til = db.types.load_library(library_path)

    print_subsection_header(f'Type names from external library {library_path}')
    for name in db.types.get_all(library=til):
        print(name)

    print_subsection_header('Type information objects in local library (before import)')
    for item in sorted(list(db.types), key=lambda i: i.get_ordinal()):
        print(f'{item.get_ordinal()}. {item}')

    db.types.import_types_from_library(til)

    print_subsection_header('Type information objects in local library (after import)')
    for item in sorted(list(db.types), key=lambda i: i.get_ordinal()):
        print(f'{item.get_ordinal()}. {item}')

    db.types.unload_library(til)


def export_types(db: Database, library_path: Path):
    """Export all types from database to external library"""

    til = db.types.create_library(library_path, 'Exported type library')
    db.types.export_types_to_library(til)
    db.types.save_library(til, library_path)
    db.types.unload_library(til)

    print_subsection_header(f'Types exported to {library_path}')
    til = db.types.load_library(library_path)
    for t in db.types.get_all(library=til):
        print(t)
    db.types.unload_library(til)


def main():
    parser = argparse.ArgumentParser(
        description=f'IDA Domain usage example, version {ida_domain.__version__}'
    )
    parser.add_argument(
        '-f', '--input-file', help='Binary input file to be loaded', type=str, required=True
    )
    args = parser.parse_args()

    library_dir = Path(tempfile.gettempdir()) / 'ida_domain_example'
    library_dir.mkdir(parents=True, exist_ok=True)
    library_create_path = library_dir / 'new.til'
    library_import_path = library_dir / 'new.til'
    library_export_path = library_dir / 'exported.til'

    print_section_header('Working with type information libraries')

    with Database.open(args.input_file) as db:
        create_types(db, library_create_path)
        import_types(db, library_import_path)
        export_types(db, library_export_path)


if __name__ == '__main__':
    main()

Cross-Reference Analysis

Analyzing Cross-References

#!/usr/bin/env python3
"""
Cross-reference analysis example for IDA Domain API.

This example demonstrates how to analyze cross-references in an IDA database.
"""

import argparse

import ida_domain
from ida_domain import Database
from ida_domain.database import IdaCommandOptions


def analyze_xrefs(db_path, target_addr):
    """Analyze cross-references to and from a target address."""
    ida_options = IdaCommandOptions(auto_analysis=True, new_database=True)
    with Database.open(db_path, ida_options) as db:
        print(f'Cross-references to {hex(target_addr)}:')

        # Get references TO the target address
        xref_to_count = 0
        for xref in db.xrefs.get_to(target_addr):
            xref_type_name = db.xrefs.get_name(xref)
            print(f'  From {hex(xref.frm)} to {hex(xref.to)} (type: {xref_type_name})')
            xref_to_count += 1

        if xref_to_count == 0:
            print('  No cross-references found')
        else:
            print(f'  Total: {xref_to_count} references')

        print(f'\nCross-references from {hex(target_addr)}:')

        # Get references FROM the target address
        xref_from_count = 0
        for xref in db.xrefs.get_from(target_addr):
            xref_type_name = db.xrefs.get_name(xref)
            print(f'  From {hex(xref.frm)} to {hex(xref.to)} (type: {xref_type_name})')
            xref_from_count += 1

        if xref_from_count == 0:
            print('  No outgoing references found')
        else:
            print(f'  Total: {xref_from_count} outgoing references')

        # Use convenience methods for specific xref types
        call_count = sum(1 for _ in db.xrefs.get_calls_to(target_addr))
        jump_count = sum(1 for _ in db.xrefs.get_jumps_to(target_addr))
        read_count = sum(1 for _ in db.xrefs.get_data_reads_of(target_addr))
        write_count = sum(1 for _ in db.xrefs.get_data_writes_to(target_addr))

        # Summary
        print(f'\nSummary for {hex(target_addr)}:')
        print(f'  Calls to address: {call_count}')
        print(f'  Jumps to address: {jump_count}')
        print(f'  Data reads to address: {read_count}')
        print(f'  Data writes to address: {write_count}')
        print(f'  Incoming references: {xref_to_count}')
        print(f'  Outgoing references: {xref_from_count}')


def parse_address(value):
    """Parse address as either decimal or hexadecimal"""
    try:
        if value.lower().startswith('0x'):
            return int(value, 16)
        else:
            return int(value, 10)
    except ValueError:
        raise argparse.ArgumentTypeError(f'Invalid address format: {value}')


def main():
    """Main entry point with argument parsing."""
    parser = argparse.ArgumentParser(description='Database exploration example')
    parser.add_argument(
        '-f', '--input-file', help='Binary input file to be loaded', type=str, required=True
    )
    parser.add_argument(
        '-a',
        '--address',
        help='Address (decimal or hex with 0x prefix)',
        type=parse_address,
        required=True,
    )
    args = parser.parse_args()
    analyze_xrefs(args.input_file, args.address)


if __name__ == '__main__':
    main()

Event Handling (Hooks)

Hooking and Logging Events

#!/usr/bin/env python3
"""
Event handling / hook usage example for IDA Domain API.

This example demonstrates how to handle IDA events.
"""

import argparse
import logging

from ida_domain import database, hooks  # isort: skip
import ida_idaapi  # isort: skip

logging.basicConfig(level=logging.DEBUG, format='%(asctime)s %(levelname)s %(name)s %(message)s')


# Processor hooks example
class MyProcHooks(hooks.ProcessorHooks):
    def __init__(self):
        super().__init__()

    def ev_creating_segm(self, seg: 'segment_t *') -> int:
        self.log()
        return super().ev_creating_segm(seg)

    def ev_moving_segm(self, seg: 'segment_t *', to: ida_idaapi.ea_t, flags: int) -> int:
        self.log()
        return super().ev_moving_segm(seg, to, flags)


# UI hooks example
class MyUIHooks(hooks.UIHooks):
    def __init__(self):
        super().__init__()

    def widget_visible(self, widget: 'TWidget *') -> None:
        self.log()

    def widget_closing(self, widget: 'TWidget *') -> None:
        self.log()

    def widget_invisible(self, widget: 'TWidget *') -> None:
        self.log()


# View hooks example
class MyViewHooks(hooks.ViewHooks):
    def __init__(self):
        super().__init__()

    def view_activated(self, view: 'TWidget *') -> None:
        self.log()

    def view_deactivated(self, view: 'TWidget *') -> None:
        self.log()


# Decompiler hooks example
class MyDecompilerHooks(hooks.DecompilerHooks):
    def __init__(self):
        super().__init__()

    def open_pseudocode(self, vu: 'vdui_t') -> int:
        self.log()
        return super().open_pseudocode()

    def switch_pseudocode(self, vu: 'vdui_t') -> int:
        self.log()
        return super().switch_pseudocode()

    def refresh_pseudocode(self, vu: 'vdui_t') -> int:
        self.log()
        return super().refresh_pseudocode()

    def close_pseudocode(self, vu: 'vdui_t') -> int:
        self.log()
        return super().close_pseudocode()


# Database hooks example
class MyDatabaseHooks(hooks.DatabaseHooks):
    def __init__(self):
        super().__init__()
        self.count = 0

    def closebase(self) -> None:
        self.log()

    def auto_empty(self):
        self.log()

    def segm_added(self, s) -> None:
        self.log()


proc_hook = MyProcHooks()
ui_hook = MyUIHooks()
view_hook = MyViewHooks()
decomp_hook = MyDecompilerHooks()
db_hook = MyDatabaseHooks()

all_hooks: hooks.HooksList = [
    proc_hook,
    ui_hook,
    view_hook,
    decomp_hook,
    db_hook,
]


def log_events(idb_path):
    with database.Database.open(path=idb_path, hooks=all_hooks) as db:
        pass


def main():
    """Main entry point with argument parsing."""
    parser = argparse.ArgumentParser(description='Database exploration example')
    parser.add_argument(
        '-f', '--input-file', help='Binary input file to be loaded', type=str, required=True
    )
    args = parser.parse_args()
    log_events(args.input_file)


if __name__ == '__main__':
    main()

Running the Examples

To run these examples, save them to Python files and execute them with your IDA database path:

python example_script.py

Make sure you have:

  1. Set the IDADIR environment variable
  2. Installed the ida-domain package