Skip to content

ghidra_analyzer.py

ofrak_ghidra.components.ghidra_analyzer

GhidraCodeRegionModifier (Modifier, OfrakGhidraMixin)

Adjusts CodeRegion virtual addresses to match Ghidra's address space, accounting for PIE binaries. Syncs OFRAK's code region addresses with Ghidra's internal representation. Used internally by Ghidra unpacking workflow.

For more details on the PIE fixups, see gotchas.md.

modify(self, resource, config=None) async

Modify the given resource.

Users should not call this method directly; rather, they should run Resource.run.

Parameters:

Name Type Description Default
resource Resource required
config

Optional config for modification. If an implementation provides a default, this default will always be used when config would otherwise be None. Note that a copy of the default config will be passed, so the default config values cannot be modified persistently by a component run.

None
Source code in ofrak_ghidra/components/ghidra_analyzer.py
async def modify(self, resource: Resource, config=None):
    code_region = await resource.view_as(CodeRegion)
    ghidra_project = await OfrakGhidraMixin.get_ghidra_project(resource)

    ofrak_code_regions = await ghidra_project.resource.get_descendants_as_view(
        v_type=CodeRegion, r_filter=ResourceFilter(tags=[CodeRegion])
    )

    backend_code_regions_json = await self.get_code_regions_script.call_script(resource)
    backend_code_regions = []

    for cr_j in backend_code_regions_json:
        cr = CodeRegion(cr_j["start"], cr_j["size"])
        backend_code_regions.append(cr)

    ofrak_code_regions = sorted(ofrak_code_regions, key=lambda cr: cr.virtual_address)
    backend_code_regions = sorted(backend_code_regions, key=lambda cr: cr.virtual_address)

    program_r = await resource.get_only_ancestor(ResourceFilter.with_tags(Program))
    # We only want to adjust the address of a CodeRegion if the original binary is position-independent.
    # Implement PIE-detection for other file types as necessary.
    if program_r.has_tag(Elf):
        elf_header = await program_r.get_only_descendant_as_view(
            ElfHeader, r_filter=ResourceFilter(tags=[ElfHeader])
        )
        if elf_header is not None and elf_header.e_type == ElfType.ET_DYN.value:
            ghidra_project_r = await resource.get_only_ancestor(
                ResourceFilter.with_tags(GhidraProject)
            )
            ghidra_project_v = await ghidra_project_r.view_as(GhidraProject)

            code_region = await resource.view_as(CodeRegion)
            if ghidra_project_v.base_address:
                new_cr = CodeRegion(
                    code_region.virtual_address + ghidra_project_v.base_address,
                    code_region.size,
                )
                code_region.resource.add_view(new_cr)
            elif len(ofrak_code_regions) > 0:
                relative_va = (
                    code_region.virtual_address - ofrak_code_regions[0].virtual_address
                )

                for backend_cr in backend_code_regions:
                    backend_relative_va = (
                        backend_cr.virtual_address - backend_code_regions[0].virtual_address
                    )

                    if (
                        backend_relative_va == relative_va
                        and backend_cr.size == code_region.size
                    ):
                        resource.add_view(backend_cr)
                        ghidra_project_r.add_view(
                            GhidraProject(
                                project_url=ghidra_project_v.project_url,
                                ghidra_url=ghidra_project_v.ghidra_url,
                                base_address=backend_cr.virtual_address
                                - code_region.virtual_address,
                            )
                        )
                        await ghidra_project_r.save()

                LOGGER.debug(
                    f"No code region with relative offset {relative_va} and size {code_region.size} found in Ghidra"
                )
            else:
                LOGGER.debug("No OFRAK code regions to match in Ghidra")
            await resource.save()

GhidraCustomLoadAnalyzer (GhidraProjectAnalyzer)

Loads raw binaries into Ghidra using BinaryLoader with custom memory block configuration. Use for non-standard formats or raw firmware that need explicit memory region mapping. Creates memory blocks from MemoryRegion resources.

analyze(self, resource, config=None) async

Analyze a resource for to extract specific ResourceAttributes.

Users should not call this method directly; rather, they should run Resource.run or Resource.analyze.

Parameters:

Name Type Description Default
resource Resource

The resource that is being analyzed

required
config Optional[ofrak_ghidra.components.ghidra_analyzer.GhidraProjectConfig]

Optional config for analyzing. If an implementation provides a default, this default will always be used when config would otherwise be None. Note that a copy of the default config will be passed, so the default config values cannot be modified persistently by a component run.

None

Returns:

Type Description
GhidraProject

The analysis results

Source code in ofrak_ghidra/components/ghidra_analyzer.py
async def analyze(
    self, resource: Resource, config: Optional[GhidraProjectConfig] = None
) -> GhidraProject:
    arch_info: ArchInfo = await resource.analyze(ProgramAttributes)
    mem_blocks = await self._get_memory_blocks(await resource.view_as(Program))
    use_existing = config.use_existing if config is not None else False

    async with self._prepare_ghidra_project(resource) as (ghidra_project, full_fname):
        program_name = await self._do_ghidra_import(
            ghidra_project,
            full_fname,
            use_existing=use_existing,
            use_binary_loader=True,
            processor=arch_info,
            blocks=mem_blocks,
        )
        await self._do_ghidra_analyze_and_serve(
            ghidra_project,
            program_name,
            skip_analysis=config is not None,
        )

        return GhidraProject(
            ghidra_project, f"http://{GHIDRA_SERVER_HOST}:{GHIDRA_SERVER_PORT}"
        )

GhidraProgramLoadConfig (ComponentConfig) dataclass

Config for GhidraProjectAnalyzer to pass in a pre-analyzed Ghidra project for a binary as a Ghidra Zip file.

A Ghidra Zip File can be exported from Ghidra's project window, right-clicking on an analyzed file and "Export...". Then select the Ghidra Zip File format and save the file. This will create a .gzf file that you can import with this GhidraProjectConfig.

GhidraProjectAnalyzer (Analyzer)

Creates or loads a Ghidra project and runs Ghidra's automated analysis. Connects to Ghidra server for analysis access. Use for comprehensive binary analysis with Ghidra's powerful analysis engine. Can import existing .gzf files specified in the ComponentConfig or analyze from scratch.

analyze(self, resource, config=None) async

Analyze a resource for to extract specific ResourceAttributes.

Users should not call this method directly; rather, they should run Resource.run or Resource.analyze.

Parameters:

Name Type Description Default
resource Resource

The resource that is being analyzed

required
config Optional[ofrak_ghidra.components.ghidra_analyzer.GhidraProjectConfig]

Optional config for analyzing. If an implementation provides a default, this default will always be used when config would otherwise be None. Note that a copy of the default config will be passed, so the default config values cannot be modified persistently by a component run.

None

Returns:

Type Description
GhidraProject

The analysis results

Source code in ofrak_ghidra/components/ghidra_analyzer.py
async def analyze(
    self, resource: Resource, config: Optional[GhidraProjectConfig] = None
) -> GhidraProject:
    gzf = config.ghidra_zip_file if config is not None else None
    binary_fname = config.name if config is not None else None

    # if passing a name for the file, by default don't overwrite an existing file
    # of the same name in the ghidra project.
    use_existing = config.use_existing if config is not None else binary_fname is not None

    async with self._prepare_ghidra_project(resource, gzf, binary_fname) as (
        ghidra_project,
        full_fname,
    ):
        program_name = await self._do_ghidra_import(
            ghidra_project, full_fname, use_existing=use_existing, use_binary_loader=False
        )
        await self._do_ghidra_analyze_and_serve(
            ghidra_project,
            program_name,
            skip_analysis=config is not None,
        )

        return GhidraProject(
            ghidra_project, f"http://{GHIDRA_SERVER_HOST}:{GHIDRA_SERVER_PORT}"
        )

GhidraProjectConfig (ComponentConfig) dataclass

Config for GhidraProjectAnalyzer to pass in a pre-analyzed Ghidra project for a binary as a Ghidra Zip file.

A Ghidra Zip File can be exported from Ghidra's project window, right-clicking on an analyzed file and "Export...". Then select the Ghidra Zip File format and save the file. This will create a .gzf file that you can import with this GhidraProjectConfig.