Skip to content

linkable_binary.py

ofrak.core.patch_maker.linkable_binary

LinkableBinary (GenericBinary) dataclass

A resource with accompanying metadata which provide a mapping between symbols, and their corresponding offset and type within the program.

get_only_symbol(self, *, name=None, vaddr=None) async

Get exactly one LinkableSymbol from this LinkableBinary matching a given name, virtual address, or both. If values for both vaddr and name are specified, only finds symbols which match both vaddr AND name, not vaddr OR name.

Parameters:

Name Type Description Default
name Optional[str]

Name of the symbol to look for.

None
vaddr Optional[int]

Virtual address of the symbol to look for.

None

Returns:

Type Description
LinkableSymbol

The only LinkableSymbol with the given name and/or vaddr.

Exceptions:

Type Description
ValueError

if both name and vaddr arguments are None.

NotFoundError

if no symbol or more than one is found with name and/or vaddr.

Source code in ofrak/core/patch_maker/linkable_binary.py
async def get_only_symbol(
    self, *, name: Optional[str] = None, vaddr: Optional[int] = None
) -> LinkableSymbol:
    """
    Get exactly one LinkableSymbol from this LinkableBinary matching a given name,
    virtual address, or both. If values for both vaddr and name are specified, only finds
    symbols which match both vaddr AND name, not vaddr OR name.

    :param name: Name of the symbol to look for.
    :param vaddr: Virtual address of the symbol to look for.

    :return: The only LinkableSymbol with the given name and/or vaddr.

    :raises ValueError: if both `name` and `vaddr` arguments are None.
    :raises NotFoundError: if no symbol or more than one is found with name and/or vaddr.
    """
    attributes_filters = []
    if name is None and vaddr is None:
        raise ValueError("At least one of the arguments name, vaddr must not be None!")
    if vaddr is not None:
        attributes_filters.append(
            ResourceAttributeValueFilter(LinkableSymbol.VirtualAddress, vaddr)
        )
    if name is not None:
        attributes_filters.append(ResourceAttributeValueFilter(LinkableSymbol.Label, name))

    return await self.resource.get_only_descendant_as_view(
        LinkableSymbol,
        r_filter=ResourceFilter(
            tags=(LinkableSymbol,),
            attribute_filters=tuple(attributes_filters),
        ),
    )

get_symbols(self, *, name=None, vaddr=None) async

Get exactly all LinkableSymbols from this LinkableBinary matching a given name, virtual address, or both. If values for both vaddr and name are specified, only finds symbols which match both vaddr AND name, not vaddr OR name.

Parameters:

Name Type Description Default
name Optional[str]

Name of the symbols to look for.

None
vaddr Optional[int]

Virtual address of the symbols to look for.

None

Returns:

Type Description
Iterable[ofrak.core.patch_maker.linkable_symbol.LinkableSymbol]

All LinkableSymbol with the given name and/or vaddr.

Exceptions:

Type Description
NotFoundError

if no symbols are found with name and/or vaddr.

Source code in ofrak/core/patch_maker/linkable_binary.py
async def get_symbols(
    self, *, name: Optional[str] = None, vaddr: Optional[int] = None
) -> Iterable[LinkableSymbol]:
    """
    Get exactly all LinkableSymbols from this LinkableBinary matching a given name,
    virtual address, or both. If values for both vaddr and name are specified, only finds
    symbols which match both vaddr AND name, not vaddr OR name.

    :param name: Name of the symbols to look for.
    :param vaddr: Virtual address of the symbols to look for.

    :return: All LinkableSymbol with the given name and/or vaddr.

    :raises NotFoundError: if no symbols are found with name and/or vaddr.
    """
    attributes_filters = []
    if vaddr is not None:
        attributes_filters.append(
            ResourceAttributeValueFilter(LinkableSymbol.VirtualAddress, vaddr)
        )
    if name is not None:
        attributes_filters.append(ResourceAttributeValueFilter(LinkableSymbol.Label, name))

    return await self.resource.get_descendants_as_view(
        LinkableSymbol,
        r_filter=ResourceFilter(
            tags=(LinkableSymbol,),
            attribute_filters=tuple(attributes_filters) if attributes_filters else None,
        ),
    )

define_linkable_symbols(self, proto_symbols) async

From some basic info about symbols in this program, create a LinkableSymbol resource for each one and get any remaining needed info to do this. Usage is to pass a dictionary that defines each symbol like so: "symbol_name": (symbol_vaddr, symbol_type)

Parameters:

Name Type Description Default
proto_symbols Dict[str, Tuple[int, ofrak_type.symbol_type.LinkableSymbolType]]

Mapping of each symbol name to its known vaddr and symbol type.

required

Exceptions:

Type Description
NotFoundError

if a ComplexBlock resource with the indicated vaddr does not exist for a provided FUNC symbol.

Source code in ofrak/core/patch_maker/linkable_binary.py
async def define_linkable_symbols(
    self, proto_symbols: Dict[str, Tuple[int, LinkableSymbolType]]
):
    """
    From some basic info about symbols in this program, create a LinkableSymbol resource for
    each one and get any remaining needed info to do this. Usage is to pass a dictionary that
    defines each symbol like so:
    "symbol_name": (symbol_vaddr, symbol_type)

    :param proto_symbols: Mapping of each symbol name to its known vaddr and symbol type.

    :raises NotFoundError: if a ComplexBlock resource with the indicated vaddr does not exist
    for a provided FUNC symbol.
    """
    symbols = []
    for sym_name, (sym_vaddr, sym_type) in proto_symbols.items():
        mode = InstructionSetMode.NONE
        if sym_type is LinkableSymbolType.FUNC:
            try:
                cb = await self.resource.get_only_descendant_as_view(
                    ComplexBlock,
                    r_filter=ResourceFilter(
                        tags=(ComplexBlock,),
                        attribute_filters=(
                            ResourceAttributeValueFilter(
                                ComplexBlock.VirtualAddress, sym_vaddr
                            ),
                        ),
                    ),
                )
            except NotFoundError:
                raise NotFoundError(
                    f"No ComplexBlock resource exists at vaddr 0x"
                    f"{sym_vaddr:x}; cannot infer its mode."
                )
            mode = await cb.get_mode()
        symbols.append(LinkableSymbol(sym_vaddr, sym_name, sym_type, mode))

    await self.resource.run(
        UpdateLinkableSymbolsModifier,
        UpdateLinkableSymbolsModifierConfig(tuple(symbols)),
    )

define_linkable_symbols_from_patch(self, proto_symbols, program_attributes) async

From some basic info about symbols returned from [get_bin_file_symbols][ofrak_patch_maker.toolchain.abstract.get_bin_file_symbols], create a LinkableSymbol resource for each one. Usage is to pass a dictionary that defines each symbol like so: "symbol_name": (symbol_vaddr, symbol_type)

Parameters:

Name Type Description Default
proto_symbols Mapping[str, Tuple[int, ofrak_type.symbol_type.LinkableSymbolType]]

Mapping of each symbol name to its known vaddr and symbol type.

required
program_attributes ProgramAttributes

Attributes of the program being patched, assumed to be the same ISA as the patch files.

required
Source code in ofrak/core/patch_maker/linkable_binary.py
async def define_linkable_symbols_from_patch(
    self,
    proto_symbols: Mapping[str, Tuple[int, LinkableSymbolType]],
    program_attributes: ProgramAttributes,
):
    """
    From some basic info about symbols returned from
    [get_bin_file_symbols][ofrak_patch_maker.toolchain.abstract.get_bin_file_symbols],
    create a LinkableSymbol resource for each one. Usage is to pass a dictionary that defines
    each symbol like so:
    "symbol_name": (symbol_vaddr, symbol_type)

    :param proto_symbols: Mapping of each symbol name to its known vaddr and symbol type.
    :param program_attributes: Attributes of the program being patched, assumed to be the same
    ISA as the patch files.
    """
    symbols = []
    for sym_name, (sym_vaddr, sym_type) in proto_symbols.items():
        mode = InstructionSetMode.NONE
        if sym_type is LinkableSymbolType.FUNC:
            if program_attributes.isa == InstructionSet.ARM and sym_vaddr & 0x01 == 1:
                mode = InstructionSetMode.THUMB
            symbols.append(LinkableSymbol(sym_vaddr, sym_name, sym_type, mode))

    await self.resource.run(
        UpdateLinkableSymbolsModifier,
        UpdateLinkableSymbolsModifierConfig(tuple(symbols)),
    )

make_linkable_bom(self, patch_maker, build_tmp_dir, unresolved_symbols) async

Build a BOM with all the symbols known to SymbolizedBinary. This BOM can be used to build the FEM so that it has access to all those symbols as weak symbols. This BOM in practice enables linking against the target binary. Because they are weak symbols, the patch is free to redefine any of these symbols - for example, if we are patching in a new body for a function, it will not conflict with the existing symbol for that function in the target binary.

We create stubs for symbols rather than treat them purely as symbols because the linker in some cases needs a little more information than the symbol alone can provide - ARM/Thumb interworking in particular pushed us in this direction. Creating stubs for all symbols is simpler than trying to decide whether we need to create a symbol or stub for each symbol based on its mode compared to the mode of something we are trying to inject.

Parameters:

Name Type Description Default
patch_maker PatchMaker

PatchMaker instance to use to build the BOM.

required
build_tmp_dir str

A temporary directory to use for the BOM files.

required
unresolved_symbols Set[str]

All symbols used in a patch BOM but defined elsewhere. May be defined in target binary or in an earlier patch.

required

Returns:

Type Description
Tuple[ofrak_patch_maker.model.BOM, ofrak_patch_maker.model.PatchRegionConfig]

Tuple consisting of a BOM and PatchConfig representing the weak symbol definitions for all LinkableSymbols in the binary, ready to be passed in the boms argument to PatchMaker.make_fem(...).

Source code in ofrak/core/patch_maker/linkable_binary.py
async def make_linkable_bom(
    self,
    patch_maker: "PatchMaker",  # type: ignore
    build_tmp_dir: str,
    unresolved_symbols: Set[str],
) -> Tuple[BOM, PatchRegionConfig]:
    """
    Build a BOM with all the symbols known to SymbolizedBinary. This BOM can be used to build
    the FEM so that it has access to all those symbols as weak symbols. This BOM in practice
    enables linking against the target binary. Because they are weak symbols, the patch is
    free to redefine any of these symbols - for example, if we are patching in a new body for
    a function, it will not conflict with the existing symbol for that function in the target
    binary.

    We create stubs for symbols rather than treat them purely as symbols because the linker in
    some cases needs a little more information than the symbol alone can provide - ARM/Thumb
    interworking in particular pushed us in this direction. Creating stubs for all symbols is
    simpler than trying to decide whether we need to create a symbol or stub for each symbol
    based on its mode compared to the mode of something we are trying to inject.

    :param patch_maker: PatchMaker instance to use to build the BOM.
    :param build_tmp_dir: A temporary directory to use for the BOM files.
    :param unresolved_symbols: All symbols used in a patch BOM but defined elsewhere. May be
    defined in target binary or in an earlier patch.

    :return: Tuple consisting of a BOM and PatchConfig representing the weak symbol
    definitions for all LinkableSymbols in the binary, ready to be passed in the `boms`
    argument to PatchMaker.make_fem(...).
    """
    stubs: Dict[str, Tuple[Segment, ...]] = dict()
    for symbol in await self.get_symbols():
        if symbol.name in unresolved_symbols:
            stubs_file = os.path.join(build_tmp_dir, f"stub_{symbol.name}.as")
            stub_info = symbol.get_stub_info()
            stub_body = "\n".join(
                stub_info.asm_prefixes + [f".global {symbol.name}", f"{symbol.name}:", ""]
            )

            with open(stubs_file, "w+") as f:
                f.write(stub_body)
            stubs[stubs_file] = stub_info.segments

    if stubs:
        stubs_bom = patch_maker.make_bom(
            name="stubs",
            source_list=list(stubs.keys()),
            object_list=[],
            header_dirs=[],
        )
        stubs_object_segments = {
            stubs_bom.object_map[stubs_file].path: stub_segments
            for stubs_file, stub_segments in stubs.items()
        }

    else:
        empty_source = os.path.join(build_tmp_dir, "empty_source.c")
        with open(empty_source, "w") as f:
            pass

        stubs_bom = patch_maker.make_bom(
            name="stubs",
            source_list=[empty_source],
            object_list=[],
            header_dirs=[],
        )

        stubs_object_segments = {stubs_bom.object_map[empty_source].path: ()}

    return stubs_bom, PatchRegionConfig("stubs_segments", stubs_object_segments)

UpdateLinkableSymbolsModifier (Modifier)

Add or update the LinkableSymbols in a LinkableBinary.

Exceptions:

Type Description
ValueError

if not all the provided symbols have unique vaddrs.

modify(self, resource, config) 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 UpdateLinkableSymbolsModifierConfig

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.

required
Source code in ofrak/core/patch_maker/linkable_binary.py
async def modify(self, resource: Resource, config: UpdateLinkableSymbolsModifierConfig) -> None:
    unhandled_symbols = {symbol.name: symbol for symbol in config.updated_symbols}
    unhandled_vaddrs: Dict[int, LinkableSymbol] = {}
    for symbol in config.updated_symbols:
        if symbol.virtual_address not in unhandled_vaddrs:
            unhandled_vaddrs[symbol.virtual_address] = symbol
        else:
            # Multiple symbols defined for an address (which is valid)
            # However it would be pointless to look for and rename an existing resource at that
            # address multiple times
            # So after the first one, create a new child for each symbol sharing an address
            await resource.create_child_from_view(symbol)

    # Overwrite existing ComplexBlock with new LinkableSymbols
    filter_for_cb_by_vaddr = ResourceFilter(
        tags=(ComplexBlock,),
        attribute_filters=(
            ResourceAttributeValuesFilter(
                ComplexBlock.VirtualAddress,
                tuple(
                    vaddr
                    for vaddr, symbol in unhandled_vaddrs.items()
                    if symbol.symbol_type is LinkableSymbolType.FUNC
                ),
            ),
        ),
    )

    try:
        for existing_cb in await resource.get_descendants_as_view(
            ComplexBlock, r_filter=filter_for_cb_by_vaddr
        ):
            symbol = unhandled_vaddrs[existing_cb.virtual_address]
            existing_cb.resource.add_view(symbol)
            # Update the value of the ComplexBlock.name attribute
            existing_cb.resource.add_view(dataclasses.replace(existing_cb, name=symbol.name))
            unhandled_symbols.pop(symbol.name)
    except NotFoundError:
        LOGGER.debug("No existing ComplexBlocks found for the provided symbols, moving on")

    # Overwrite existing LabeledAddress with new LinkableSymbols
    filter_for_label_by_name = ResourceFilter(
        tags=(LabeledAddress,),
        attribute_filters=(
            ResourceAttributeValuesFilter(
                LabeledAddress.Label, tuple(unhandled_symbols.keys())
            ),
        ),
    )
    try:
        for existing_label in await resource.get_descendants_as_view(
            LabeledAddress,
            r_filter=filter_for_label_by_name,
        ):
            if existing_label.name not in unhandled_symbols:
                raise SymbolExistsError(
                    f"Multiple LabeledAddress resources with name {existing_label.name}!"
                )
            existing_label.resource.add_view(unhandled_symbols.pop(existing_label.name))
    except NotFoundError:
        LOGGER.debug("No existing LabeledAddresses found for the provided symbols, moving on")

    # Overwrite existing Addressables with new LinkableSymbols
    # Only do this for LinkableSymbols which did not override existing LabeledAddress above
    unhandled_vaddrs = {symbol.virtual_address: symbol for symbol in unhandled_symbols.values()}

    filter_for_label_by_vaddr = ResourceFilter(
        tags=(Addressable,),
        attribute_filters=(
            ResourceAttributeValuesFilter(
                Addressable.VirtualAddress, tuple(unhandled_vaddrs.keys())
            ),
        ),
    )

    try:
        for existing_addressable in await resource.get_descendants_as_view(
            Addressable,
            r_filter=filter_for_label_by_vaddr,
        ):
            symbol = unhandled_vaddrs[existing_addressable.virtual_address]
            existing_addressable.resource.add_view(symbol)
            unhandled_symbols.pop(symbol.name)
    except NotFoundError:
        LOGGER.debug("No existing Addressable found for the provided symbols, moving on")

    # For any new LinkableSymbols that we could not add to existing resources, create a new
    # resource
    for symbol in unhandled_symbols.values():
        await resource.create_child_from_view(symbol)

UpdateLinkableSymbolsModifierConfig (ComponentConfig) dataclass

UpdateLinkableSymbolsModifierConfig(updated_symbols: Tuple[ofrak.core.patch_maker.linkable_symbol.LinkableSymbol, ...])