Skip to content

unpacker.py

ofrak.core.elf.unpacker

ElfDynamicSectionUnpacker (Unpacker)

unpack(self, resource, config=None) async

Unpack the given resource.

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

Parameters:

Name Type Description Default
resource Resource

The resource that is being unpacked

required
config

Optional config for unpacking. 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/core/elf/unpacker.py
async def unpack(self, resource: Resource, config=None):
    e_section = await resource.view_as(ElfDynamicSection)
    elf_r = await e_section.get_parent()
    e_basic_header = await elf_r.get_basic_header()
    dyn_entry_size = 16 if e_basic_header.get_bitwidth() is BitWidth.BIT_64 else 8
    await make_children_helper(resource, ElfDynamicEntry, dyn_entry_size, None)

ElfPointerArraySectionUnpacker (Unpacker)

unpack(self, resource, config=None) async

Unpack the given resource.

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

Parameters:

Name Type Description Default
resource Resource

The resource that is being unpacked

required
config

Optional config for unpacking. 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/core/elf/unpacker.py
async def unpack(self, resource: Resource, config=None):
    elf_r = await resource.get_only_ancestor_as_view(Elf, ResourceFilter.with_tags(Elf))
    e_basic_header = await elf_r.get_basic_header()
    addr_size = 4 if e_basic_header.get_bitwidth() is BitWidth.BIT_32 else 8
    await make_children_helper(resource, ElfVirtualAddress, addr_size, None)

ElfRelaUnpacker (Unpacker)

unpack(self, resource, config=None) async

Unpack the given resource.

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

Parameters:

Name Type Description Default
resource Resource

The resource that is being unpacked

required
config

Optional config for unpacking. 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/core/elf/unpacker.py
async def unpack(self, resource: Resource, config=None):
    e_section = await resource.view_as(ElfRelaSection)
    elf_r = await e_section.get_parent()
    e_basic_header = await elf_r.get_basic_header()
    rela_size = 24 if e_basic_header.get_bitwidth() is BitWidth.BIT_64 else 12
    await make_children_helper(resource, ElfRelaEntry, rela_size, None)

ElfSymbolUnpacker (Unpacker)

unpack(self, resource, config=None) async

Unpack the given resource.

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

Parameters:

Name Type Description Default
resource Resource

The resource that is being unpacked

required
config

Optional config for unpacking. 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/core/elf/unpacker.py
async def unpack(self, resource: Resource, config=None):
    e_section = await resource.view_as(ElfSymbolSection)
    elf_r = await e_section.get_parent()
    e_basic_header = await elf_r.get_basic_header()
    symbol_size = 16 if e_basic_header.get_bitwidth() is BitWidth.BIT_32 else 24
    await make_children_helper(
        resource, ElfSymbol, symbol_size, ElfSymbolStructure.attributes_type
    )

ElfUnpacker (Unpacker)

unpack(self, resource, config=None) async

Unpack ELF headers and sections / segments into the OFRAK resource tree.

After unpacking the ElfHeader, the unpacker first unpacks ElfSections defined in the ElfSectionHeader into the resource tree.

If no executable sections are found, then the proceeding ElfProgramHeader unpacking routine is permitted to unpack LOAD-able ElfSegments into the resource tree. Executable LOAD-able ElfSegments are unpacked into CodeRegions.

legend: + composable child ^ exclusive child [ ] optional / conditional child (s) one or more instances of child

RESOURCE + ElfBasicHeader + ElfHeader [+] ElfProgramHeader (creates LOAD-able ElfSegments if ElfSectionHeaders yields no CodeRegions) [+] ElfSectionHeader [+] ElfSection(s) [^] ElfFiniArraySection (if FINI_ARRAY) [^] ElfInitArraySection (if INIT_ARRAY) [^] ElfDynamicSection (if DYNAMIC) [^] ElfRelaSection (if RELA) [^] ElfDynSymbolSection (if DYNSYM) [^] ElfSymbolSection (if SYMTAB) [^] ElfStringSection (if STRTAB) [+] CodeRegion (if executable) [+] ElfSectionNameStringSection (if name string section) [+] ElfSegment(s) (if ElfSection yields no CodeRegions, [+] CodeRegion (if executable) LOAD-able ElfSegments will populate the body of the resource tree instead)

Source code in ofrak/core/elf/unpacker.py
async def unpack(self, resource: Resource, config=None):
    """
    Unpack ELF headers and sections / segments into the OFRAK resource tree.

    After unpacking the ElfHeader, the unpacker first unpacks ElfSections defined in the ElfSectionHeader into the
    resource tree.

    If no executable sections are found, then the proceeding ElfProgramHeader unpacking routine is permitted to
    unpack LOAD-able ElfSegments into the resource tree. Executable LOAD-able ElfSegments are unpacked into
    CodeRegions.

    legend:  +    composable child
             ^    exclusive child
            [ ]   optional / conditional child
            (s)   one or more instances of child

    RESOURCE
     + ElfBasicHeader
     + ElfHeader
       [+] ElfProgramHeader              (creates LOAD-able ElfSegments if ElfSectionHeaders yields no CodeRegions)
       [+] ElfSectionHeader
    [+] ElfSection(s)
       [^] ElfFiniArraySection           (if FINI_ARRAY)
       [^] ElfInitArraySection           (if INIT_ARRAY)
       [^] ElfDynamicSection             (if DYNAMIC)
       [^] ElfRelaSection                (if RELA)
       [^] ElfDynSymbolSection           (if DYNSYM)
       [^] ElfSymbolSection              (if SYMTAB)
       [^] ElfStringSection              (if STRTAB)
       [+] CodeRegion                    (if executable)
       [+] ElfSectionNameStringSection   (if name string section)
    [+] ElfSegment(s)                    (if ElfSection yields no CodeRegions,
       [+] CodeRegion (if executable)     LOAD-able ElfSegments will populate the body of the resource tree instead)
    """

    # Unfortunately, ELF files do allow sections to overlap each other.
    await resource.set_data_overlaps_enabled(True)

    e_basic_header_r = await resource.create_child(
        tags=(ElfBasicHeader,), data_range=Range(0, 16)
    )
    e_basic_header = await e_basic_header_r.view_as(ElfBasicHeader)

    e_header_range = Range.from_size(
        16, 36 if e_basic_header.get_bitwidth() is BitWidth.BIT_32 else 48
    )
    e_header_r = await resource.create_child(tags=(ElfHeader,), data_range=e_header_range)
    e_header = await e_header_r.view_as(ElfHeader)

    ###########################################################################################
    ### Unpack section headers and associated sections

    sections_by_range_start: Dict[int, Resource] = dict()
    code_region_present = None

    # Create the section header/body resources
    for index in range(e_header.e_shnum):
        e_section_header_offset = e_header.e_shoff + index * e_header.e_shentsize
        e_section_header_range = Range.from_size(e_section_header_offset, e_header.e_shentsize)
        e_section_header_r = await resource.create_child(
            tags=(ElfSectionHeader,),
            data_range=e_section_header_range,
            attributes=(ElfSectionStructure.attributes_type(index),),  # type: ignore
        )
        e_section_header = await e_section_header_r.view_as(ElfSectionHeader)

        e_section_offset = e_section_header.sh_offset
        e_section_range = Range.from_size(e_section_offset, e_section_header.sh_size)
        opt_e_section_range: Optional[Range] = e_section_range
        if e_section_range.length() == 0:
            # It's possible that the section does not contain any data
            opt_e_section_range = None
        if e_section_header.get_type() is ElfSectionType.NOBITS:
            # NOBITS sections never have data in the file; their sh_size refers to in-mem size
            opt_e_section_range = None
        data_after = sections_by_range_start.get(e_section_offset)
        e_section_r = await resource.create_child(
            tags=(ElfSection,),
            data_range=opt_e_section_range,
            data_after=data_after,
            attributes=(ElfSectionStructure.attributes_type(index),),  # type: ignore
        )
        sections_by_range_start[e_section_offset] = e_section_r
        if e_section_header.get_type() is ElfSectionType.FINI_ARRAY:
            e_section_r.add_tag(ElfFiniArraySection)
        elif e_section_header.get_type() is ElfSectionType.INIT_ARRAY:
            e_section_r.add_tag(ElfInitArraySection)
        elif e_section_header.get_type() is ElfSectionType.DYNAMIC:
            e_section_r.add_tag(ElfDynamicSection)
        elif e_section_header.get_type() is ElfSectionType.RELA:
            e_section_r.add_tag(ElfRelaSection)
        elif e_section_header.get_type() is ElfSectionType.DYNSYM:
            e_section_r.add_tag(ElfDynSymbolSection)
        elif e_section_header.get_type() is ElfSectionType.SYMTAB:
            e_section_r.add_tag(ElfSymbolSection)
        elif e_section_header.get_type() is ElfSectionType.STRTAB:
            e_section_r.add_tag(ElfStringSection)

        if e_section_header.has_flag(ElfSectionFlag.EXECINSTR):
            e_section_r.add_tag(CodeRegion)
            code_region_present = True

        if index == e_header.e_shstrndx:
            e_section_r.add_tag(ElfSectionNameStringSection)

    ###########################################################################################
    ### Unpack segment headers, conditionally unpack associated loadable segments

    preceding_resource: Optional[Resource] = e_header_r

    # Create the program header resources
    for index in range(e_header.e_phnum):
        e_program_header_offset = e_header.e_phoff + (index * e_header.e_phentsize)
        e_program_header_range = Range.from_size(e_program_header_offset, e_header.e_phentsize)

        e_program_header_r = await resource.create_child(
            tags=(ElfProgramHeader,),
            data_range=e_program_header_range,
            attributes=(ElfSegmentStructure.attributes_type(index),),  # type: ignore
        )

        e_program_header = await e_program_header_r.view_as(ElfProgramHeader)

        # Don't unpack loadable segments if the section unpacker had already unpacked any CodeRegions
        if code_region_present:
            continue

        if e_program_header.p_type == ElfProgramHeaderType.LOAD.value:
            e_segment_offset = e_program_header.p_offset
            e_segment_range = Range.from_size(e_segment_offset, e_program_header.p_filesz)
            opt_e_segment_range: Optional[Range] = e_segment_range

            # Loaded segments can have no data to initialize with (heap, bss, etc.)
            if e_segment_range.length() == 0:
                opt_e_segment_range = None

            # We need to inform OFRAK data service the order of child nodes to be populated into the resource tree,
            # since there may be overlapping regions due to flattening non-flat memory structures derived from the
            # binary-under-analysis. We should deprecate `data_after` and `data_before` in favor of a structure
            # that can hold multiple memory views (virtual, physical, file, etc.) of an analyzed binary.
            e_segment_r = await resource.create_child(
                tags=(ElfSegment,),
                data_range=opt_e_segment_range,
                data_before=preceding_resource,
                attributes=(ElfSegmentStructure.attributes_type(index),),  # type: ignore
            )

            # Tag the segment as a CodeRegion if the loaded segment is executable
            if e_program_header.is_executable():
                e_segment_r.add_tag(CodeRegion)
            await e_segment_r.save()

            preceding_resource = None