How to Add Support for New Binary File Formats in Ghidra: A Complete Developer Guide

To add support for a new binary file format in Ghidra, extend AbstractProgramLoader from the ghidra.app.util.opinion package, implement the required API methods, and register your class via Java's Service Provider Interface in META-INF/services/ghidra.app.util.opinion.Loader.

Ghidra, the NSA's open-source reverse engineering framework, processes unknown binaries through a pluggable Loader architecture. When you need to analyze proprietary or custom firmware formats, implementing a custom loader allows Ghidra to parse headers, map memory segments, and set entry points automatically during the import process.

Understanding Ghidra's Loader Architecture

The Loader Interface and AbstractProgramLoader

All binary import functionality in Ghidra centers on the Loader interface defined in Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/Loader.java. This interface declares the contract between the framework and format-specific parsers, specifying methods for file type detection, option handling, and program creation.

Most developers extend AbstractProgramLoader (located in Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/AbstractProgramLoader.java) rather than implementing Loader directly. This base class provides common utilities for file-type detection, option handling, and logging, reducing boilerplate code when targeting new formats.

How Loaders Are Discovered at Runtime

Ghidra uses the Java Service Provider Interface (SPI) mechanism to discover loaders dynamically at startup. The framework scans the classpath for a resource file at META-INF/services/ghidra.app.util.opinion.Loader, which contains the fully-qualified class names of all available loaders. Each line in this file points to a concrete loader implementation that Ghidra instantiates and registers in the Import File dialog without requiring manual configuration.

Step-by-Step Implementation Guide

1. Create a Loader Class Extending AbstractProgramLoader

Create a new Java class in your module that extends AbstractProgramLoader. You must override three critical methods:

  • getName() – Returns the display name shown in Ghidra's import dialog
  • getSupportedLoadSpecs(ByteProvider provider) – Analyzes the file to determine if your loader can handle it
  • load(ByteProvider provider, LoadSpec loadSpec, List<Option> options, Program program, TaskMonitor monitor) – Performs the actual parsing and memory mapping

In Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/PeLoader.java, you can see how the PE/COFF loader implements these methods to parse Windows executables, providing a production-ready reference for your implementation.

2. Define Supported File Extensions with LoadSpec

The getSupportedLoadSpecs() method returns a list of LoadSpec objects that describe which language/compiler specifications your format supports. Use the LoadSpecBuilder or constructor to specify:

  • The language ID (e.g., "x86:LE:32:default" for 32-bit little-endian x86)
  • Whether the loader can handle the specific file
  • Optional file extension matchers

When you add extensions via loaderInfo.addExtension(".foo") or return a LoadSpec with a FileExtension matcher, Ghidra automatically filters the Import File dialog to show your loader for matching files.

3. Implement the load() Method

The load() method is where you transform the raw bytes into a Ghidra Program database. Typical operations include:

  • Creating memory blocks using program.getMemory().createInitializedBlock()
  • Mapping file sections to specific addresses
  • Setting the image base with program.setImageBase()
  • Defining entry points and symbols via program.getSymbolTable().createLabel()

You receive a ByteProvider for raw file access, a LoadSpec containing the selected language, and a TaskMonitor for reporting progress to the user.

4. Register Your Loader via SPI

To make Ghidra aware of your loader, create or edit the file at src/main/resources/META-INF/services/ghidra.app.util.opinion.Loader in your module. Add a single line containing your loader's fully-qualified class name:

ghidra.myloaders.FooLoader

The build system packages this service file into your extension JAR. At startup, Ghidra's classloader reads this file, instantiates your loader, and adds it to the available importers list.

5. Package as a Ghidra Module

Structure your loader as a standard Ghidra module:

  1. Create a Gradle module under Ghidra/Features for core contributions or Ghidra/Extensions for third-party add-ons
  2. Add your module to Ghidra/gradle.properties to include it in the build
  3. Place resources (icons, help docs) in src/main/resources
  4. Ensure your module.manifest declares any dependencies on Base or Generic frameworks

Complete Code Example: FooLoader

The following implementation demonstrates a minimal loader for a fictional .foo binary format. This class extends AbstractProgramLoader, checks for the .foo extension, and maps the entire file into memory at address 0x1000.

package ghidra.myloaders;

import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
import java.util.List;

import ghidra.app.util.Option;
import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.opinion.AbstractProgramLoader;
import ghidra.app.util.opinion.LoadSpec;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.MemoryBlock;
import ghidra.program.model.symbol.SourceType;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;

/**
 * Minimal loader for the fictional ".foo" binary format.
 */
public class FooLoader extends AbstractProgramLoader {

    private static final String FILE_EXTENSION = "foo";

    @Override
    public String getName() {
        return "Foo Binary Loader";
    }

    @Override
    public List<LoadSpec> getSupportedLoadSpecs(ByteProvider provider) throws IOException {
        // Accept any file that ends with .foo
        if (provider.getAbsolutePath().toLowerCase().endsWith("." + FILE_EXTENSION)) {
            // Use the default language for this format (e.g., x86:LE:32:default)
            return Collections.singletonList(
                new LoadSpec(this, 0, 
                    new ghidra.program.model.lang.LanguageCompilerSpecPair("x86:LE:32:default"), 
                    true)
            );
        }
        return Collections.emptyList();
    }

    @Override
    protected void load(ByteProvider provider, LoadSpec loadSpec,
                        List<Option> options, Program program, TaskMonitor monitor)
            throws IOException, CancelledException {

        // Example: map the whole file into the program's memory at 0x1000
        long length = provider.length();
        MemoryBlock block = program.getMemory()
                .createInitializedBlock("FOO", addr(program, 0x1000), length, (byte)0, monitor, false);
        
        try (InputStream in = provider.getInputStream(0)) {
            block.putBytes(0, in.readAllBytes());
        }

        // Set the entry point (if the format defines one)
        program.getSymbolTable().createLabel(addr(program, 0x1000), "entry", SourceType.USER_DEFINED);
        program.setImageBase(addr(program, 0x1000), true);
    }

    private Address addr(Program program, long offset) {
        return program.getAddressFactory().getDefaultAddressSpace().getAddress(offset);
    }
}

Remember to add ghidra.myloaders.FooLoader to your META-INF/services/ghidra.app.util.opinion.Loader file to activate the loader.

Testing Your Custom Loader

Validate your implementation using Ghidra's test framework. Write JUnit tests that invoke ProgramLoader.builder() to load sample files and assert that the resulting Program contains the expected memory layout, entry points, and symbol tables. Reference Ghidra/Features/Base/src/test/java/ghidra/app/util/opinion/PeLoaderTest.java for established patterns on verifying loader correctness, including how to mock ByteProvider instances and inspect imported program structures.

Summary

  • Extend AbstractProgramLoader from ghidra.app.util.opinion to inherit common loading utilities
  • Implement getName(), getSupportedLoadSpecs(), and load() to define your format's detection logic and parsing behavior
  • Use LoadSpec to declare which processor languages and compiler specifications your binary format supports
  • Register via SPI by adding your class name to META-INF/services/ghidra.app.util.opinion.Loader for automatic discovery
  • Reference PeLoader.java in the Ghidra source tree as a production-quality implementation example

Frequently Asked Questions

What is the difference between Loader and AbstractProgramLoader in Ghidra?

Loader is the core interface in ghidra.app.util.opinion that defines the contract for all binary importers, while AbstractProgramLoader is a base class that implements common functionality like file-type detection and option handling. According to the NSA Ghidra source code, you should extend AbstractProgramLoader rather than implementing Loader directly to avoid rewriting boilerplate code for standard import operations.

How does Ghidra discover new loaders at runtime?

Ghidra discovers loaders through the Java Service Provider Interface (SPI) mechanism. At startup, the framework scans the classpath for META-INF/services/ghidra.app.util.opinion.Loader files, instantiates each listed class, and adds them to the import dialog. This registration happens automatically without requiring changes to Ghidra's core configuration files.

Can I add loader options for user configuration during import?

Yes, override getDefaultOptions() to return a list of Option objects defining parameters like base address or endianness, and implement processOptions() to validate and apply user selections. These options appear in the import dialog when your loader is selected, allowing analysts to customize the loading behavior for specific binary instances.

Where should I place my custom loader in the Ghidra source tree?

Create a new Gradle module under Ghidra/Extensions for third-party loaders or Ghidra/Features for contributions to the core distribution. Place your Java source in src/main/java and SPI registration files in src/main/resources/META-INF/services. Add the module name to Ghidra/gradle.properties to ensure it builds with the rest of the project.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →