From 00e4a684a36418c3d6fec38fd1ec1c3356467e5a Mon Sep 17 00:00:00 2001 From: Romain Thomas Date: Sat, 3 Feb 2024 15:51:22 +0100 Subject: [PATCH] Add support for `DT_RELR/DT_ANDROID_RELA` relocations This commit adds the support for the (new) relative relocations as well as the Android packed relocation format. Resolve: #111 #991 --- api/python/lief/ELF.pyi | 39 +- api/python/src/ELF/objects/pyBinary.cpp | 28 ++ api/python/src/ELF/objects/pyBuilder.cpp | 2 + api/python/src/ELF/objects/pyRelocation.cpp | 35 +- doc/sphinx/changelog.rst | 3 + include/LIEF/ELF/Binary.hpp | 12 + include/LIEF/ELF/Builder.hpp | 8 + include/LIEF/ELF/Parser.hpp | 14 + include/LIEF/ELF/Relocation.hpp | 56 ++- include/LIEF/errors.hpp | 8 + src/Abstract/Relocation.cpp | 4 - src/ELF/Binary.cpp | 106 +++-- src/ELF/Binary.tcc | 18 +- src/ELF/Builder.tcc | 271 +++++++---- src/ELF/DynamicEntryFlags.cpp | 2 +- src/ELF/ExeLayout.hpp | 372 ++++++++++++++- src/ELF/Parser.cpp | 16 + src/ELF/Parser.tcc | 466 +++++++++++++------ src/ELF/Relocation.cpp | 20 +- src/ELF/SizingInfo.hpp | 2 + tests/elf/test_android_packed_relocations.py | 151 ++++++ tests/elf/test_modify_relocations.py | 9 +- tests/elf/test_parser.py | 6 +- tests/elf/test_relr_relocations.py | 43 ++ 24 files changed, 1391 insertions(+), 300 deletions(-) create mode 100644 tests/elf/test_android_packed_relocations.py create mode 100644 tests/elf/test_relr_relocations.py diff --git a/api/python/lief/ELF.pyi b/api/python/lief/ELF.pyi index 063f3810d4..e58d492d1d 100644 --- a/api/python/lief/ELF.pyi +++ b/api/python/lief/ELF.pyi @@ -369,6 +369,10 @@ class Binary(lief.Binary): def add_pltgot_relocation(self, relocation: lief.ELF.Relocation) -> lief.ELF.Relocation: ... def add_symtab_symbol(self, symbol: lief.ELF.Symbol) -> lief.ELF.Symbol: ... @overload + def dynsym_idx(self, name: str) -> int: ... + @overload + def dynsym_idx(self, symbol: lief.ELF.Symbol) -> int: ... + @overload def export_symbol(self, symbol: lief.ELF.Symbol) -> lief.ELF.Symbol: ... @overload def export_symbol(self, symbol_name: str, value: int = ...) -> lief.ELF.Symbol: ... @@ -437,6 +441,10 @@ class Binary(lief.Binary): def segment_from_offset(self, offset: int) -> lief.ELF.Segment: ... def segment_from_virtual_address(self, address: int) -> lief.ELF.Segment: ... def strip(self) -> None: ... + @overload + def symtab_idx(self, name: str) -> int: ... + @overload + def symtab_idx(self, symbol: lief.ELF.Symbol) -> int: ... def virtual_address_to_offset(self, virtual_address: int) -> Union[int,lief.lief_errors]: ... @overload def write(self, output: str) -> None: ... @@ -547,6 +555,7 @@ class Binary(lief.Binary): class Builder: class config_t: + android_rela: bool dt_hash: bool dyn_str: bool dynamic_section: bool @@ -558,6 +567,7 @@ class Builder: notes: bool preinit_array: bool rela: bool + relr: bool static_symtab: bool sym_verdef: bool sym_verneed: bool @@ -1576,6 +1586,26 @@ class ParserConfig: def all(self) -> lief.ELF.ParserConfig: ... class Relocation(lief.Relocation): + class ENCODING: + ANDROID_SLEB: ClassVar[Relocation.ENCODING] = ... + REL: ClassVar[Relocation.ENCODING] = ... + RELA: ClassVar[Relocation.ENCODING] = ... + RELR: ClassVar[Relocation.ENCODING] = ... + UNKNOWN: ClassVar[Relocation.ENCODING] = ... + __name__: str + def __init__(self, *args, **kwargs) -> None: ... + @staticmethod + def from_value(arg: int, /) -> lief.ELF.Relocation.ENCODING: ... + def __ge__(self, other) -> bool: ... + def __gt__(self, other) -> bool: ... + def __hash__(self) -> int: ... + def __index__(self) -> Any: ... + def __int__(self) -> int: ... + def __le__(self, other) -> bool: ... + def __lt__(self, other) -> bool: ... + @property + def value(self) -> int: ... + class PURPOSE: DYNAMIC: ClassVar[Relocation.PURPOSE] = ... NONE: ClassVar[Relocation.PURPOSE] = ... @@ -2545,16 +2575,23 @@ class Relocation(lief.Relocation): @overload def __init__(self, arch: lief.ELF.ARCH) -> None: ... @overload - def __init__(self, address: int, type: lief.ELF.Relocation.TYPE, is_rela: bool = ...) -> None: ... + def __init__(self, address: int, type: lief.ELF.Relocation.TYPE, encoding: lief.ELF.Relocation.ENCODING) -> None: ... + def r_info(self, clazz: lief.ELF.Header.CLASS) -> int: ... + @property + def encoding(self) -> lief.ELF.Relocation.ENCODING: ... @property def has_section(self) -> bool: ... @property def has_symbol(self) -> bool: ... @property + def is_android_packed(self) -> bool: ... + @property def is_rel(self) -> bool: ... @property def is_rela(self) -> bool: ... @property + def is_relatively_encoded(self) -> bool: ... + @property def section(self) -> lief.ELF.Section: ... @property def symbol_table(self) -> lief.ELF.Section: ... diff --git a/api/python/src/ELF/objects/pyBinary.cpp b/api/python/src/ELF/objects/pyBinary.cpp index e359be820f..501d0b4861 100644 --- a/api/python/src/ELF/objects/pyBinary.cpp +++ b/api/python/src/ELF/objects/pyBinary.cpp @@ -399,6 +399,34 @@ void create(nb::module_& m) { "Patch the imported " RST_CLASS_REF(lief.ELF.Symbol) " with the ``address``"_doc, "symbol"_a, "address"_a) + .def("dynsym_idx", + nb::overload_cast(&Binary::dynsym_idx, nb::const_), + R"doc( + Get the symbol index in the **dynamic** symbol from the given name or + return -1 if the symbol does not exist. + )doc"_doc, "name"_a) + + .def("dynsym_idx", + nb::overload_cast(&Binary::dynsym_idx, nb::const_), + R"doc( + Get the symbol index in the **dynamic** symbol table for the given symbol + or return -1 if the symbol does not exist + )doc"_doc, "symbol"_a) + + .def("symtab_idx", + nb::overload_cast(&Binary::symtab_idx, nb::const_), + R"doc( + Get the symbol index in the ``.symtab`` section from the given name or + return -1 if the symbol does not exist. + )doc"_doc, "name"_a) + + .def("symtab_idx", + nb::overload_cast(&Binary::symtab_idx, nb::const_), + R"doc( + Get the symbol index in the ``.symtab`` section or return -1 if the + symbol does not exist + )doc"_doc, "symbol"_a) + .def("has_section", &Binary::has_section, "Check if a " RST_CLASS_REF(lief.ELF.Section) " with the given name exists in the binary"_doc, diff --git a/api/python/src/ELF/objects/pyBuilder.cpp b/api/python/src/ELF/objects/pyBuilder.cpp index cd9994f268..e9c60803d7 100644 --- a/api/python/src/ELF/objects/pyBuilder.cpp +++ b/api/python/src/ELF/objects/pyBuilder.cpp @@ -46,6 +46,8 @@ void create(nb::module_& m) { .def_rw("jmprel", &Builder::config_t::jmprel, "Rebuild :attr:`~lief.ELF.DynamicEntry.TAG.JMPREL`"_doc) .def_rw("notes", &Builder::config_t::notes, "Rebuild `PT_NOTES` segment(s)"_doc) .def_rw("preinit_array", &Builder::config_t::preinit_array, "Rebuild :attr:`~lief.ELF.DynamicEntry.TAG.PREINIT_ARRAY`"_doc) + .def_rw("relr", &Builder::config_t::relr, "Rebuild :attr:`~lief.ELF.DynamicEntry.TAG.RELR`"_doc) + .def_rw("android_rela", &Builder::config_t::android_rela, "Rebuild :attr:`~lief.ELF.DynamicEntry.TAG.ANDROID_RELA`"_doc) .def_rw("rela", &Builder::config_t::rela, "Rebuild :attr:`~lief.ELF.DynamicEntry.TAG.RELA`"_doc) .def_rw("static_symtab", &Builder::config_t::static_symtab, "Rebuild `.symtab` section"_doc) .def_rw("sym_verdef", &Builder::config_t::sym_verdef, "Rebuild :attr:`~lief.ELF.DynamicEntry.TAG.VERDEF`"_doc) diff --git a/api/python/src/ELF/objects/pyRelocation.cpp b/api/python/src/ELF/objects/pyRelocation.cpp index 33a32b9f5c..e11a2d10f8 100644 --- a/api/python/src/ELF/objects/pyRelocation.cpp +++ b/api/python/src/ELF/objects/pyRelocation.cpp @@ -43,11 +43,22 @@ void create(nb::module_& m) { .value("DYNAMIC", Relocation::PURPOSE::DYNAMIC) .value("OBJECT", Relocation::PURPOSE::OBJECT); + enum_(reloc, "ENCODING") + .value("UNKNOWN", Relocation::ENCODING::UNKNOWN) + .value("ANDROID_SLEB", Relocation::ENCODING::ANDROID_SLEB, + "The relocation is using the packed Android-SLEB128 format"_doc) + .value("REL", Relocation::ENCODING::REL, + "The relocation is using the regular Elf_Rel structure"_doc) + .value("RELR", Relocation::ENCODING::RELR, + "The relocation is using the relative relocation format"_doc) + .value("RELA", Relocation::ENCODING::RELA, + "The relocation is using the regular Elf_Rela structure"_doc); + reloc .def(nb::init<>()) .def(nb::init(), "arch"_a) - .def(nb::init(), - "address"_a, "type"_a = Relocation::TYPE::UNKNOWN, "is_rela"_a = false) + .def(nb::init(), + "address"_a, "type"_a, "encoding"_a) .def_prop_rw("addend", nb::overload_cast<>(&Relocation::addend, nb::const_), @@ -115,6 +126,26 @@ void create(nb::module_& m) { &Relocation::is_rel, "``True`` if the relocation **doesn't use** the :attr:`~lief.ELF.Relocation.addend` proprety"_doc) + .def("r_info", &Relocation::r_info, + R"delim( + (re)Compute the raw ``r_info`` attribute based on the given ELF class + )delim"_doc, "clazz"_a) + + .def_prop_ro("is_relatively_encoded", &Relocation::is_relatively_encoded, + "True if the relocation is using the relative encoding"_doc) + + .def_prop_ro("is_android_packed", &Relocation::is_android_packed, + "True if the relocation is using the Android packed relocation format"_doc) + + .def_prop_ro("is_rel", &Relocation::is_rel, + R"delim( + Check if the relocation uses the implicit addend + (i.e. not present in the ELF structure) + )delim"_doc) + + .def_prop_ro("encoding", &Relocation::encoding, + "The encoding of the relocation") + LIEF_DEFAULT_STR(Relocation); } diff --git a/doc/sphinx/changelog.rst b/doc/sphinx/changelog.rst index aab35565c6..2f9762e052 100644 --- a/doc/sphinx/changelog.rst +++ b/doc/sphinx/changelog.rst @@ -35,6 +35,9 @@ Changelog * ``RELOC_x86_64``, ``RELOC_i386``, ... have been re-scoped **and merged** into :class:`lief.ELF.Relocation.TYPE` + * Add support for Android packed relocation format (``DT_ANDROID_REL{A}``) + * Add support for relative relocation format (``DT_RELR``) + :CMake: * ``LIEFConfig.cmake`` is now installed in ``/lib/cmake/LIEF/`` diff --git a/include/LIEF/ELF/Binary.hpp b/include/LIEF/ELF/Binary.hpp index 0ac38f1e08..950f93c7c7 100644 --- a/include/LIEF/ELF/Binary.hpp +++ b/include/LIEF/ELF/Binary.hpp @@ -317,6 +317,7 @@ class LIEF_API Binary : public LIEF::Binary { it_dynamic_symbols dynamic_symbols() { return dynamic_symbols_; } + it_const_dynamic_symbols dynamic_symbols() const { return dynamic_symbols_; } @@ -721,6 +722,17 @@ class LIEF_API Binary : public LIEF::Binary { //! Check if the binary uses the ``NX`` protection (Non executable stack) bool has_nx() const override; + //! Symbol index in the dynamic symbol table or -1 if the symbol + //! does not exist. + int64_t dynsym_idx(const std::string& name) const; + + int64_t dynsym_idx(const Symbol& sym) const; + + //! Symbol index from the `.symtab` section or -1 if the symbol is not present + int64_t symtab_idx(const std::string& name) const; + + int64_t symtab_idx(const Symbol& sym) const; + //! Return the ELF::Section from the given @p offset. Return a nullptr //! if a section can't be found //! diff --git a/include/LIEF/ELF/Builder.hpp b/include/LIEF/ELF/Builder.hpp index 6486e91c46..7d1dcce88d 100644 --- a/include/LIEF/ELF/Builder.hpp +++ b/include/LIEF/ELF/Builder.hpp @@ -66,6 +66,8 @@ class LIEF_API Builder { bool jmprel = true; /// Rebuild DT_JMPREL bool notes = false; /// Disable note building since it can break the default layout bool preinit_array = true; /// Rebuild DT_PREINIT_ARRAY + bool relr = true; /// Rebuild DT_RELR + bool android_rela = true; /// Rebuild DT_ANDROID_REL[A] bool rela = true; /// Rebuild DT_REL[A] bool static_symtab = true; /// Rebuild `.symtab` bool sym_verdef = true; /// Rebuild DT_VERDEF @@ -140,6 +142,12 @@ class LIEF_API Builder { template ok_error_t build_dynamic_relocations(); + template + ok_error_t build_relative_relocations(); + + template + ok_error_t build_android_relocations(); + template ok_error_t build_pltgot_relocations(); diff --git a/include/LIEF/ELF/Parser.hpp b/include/LIEF/ELF/Parser.hpp index a50c8e3209..e1d4435085 100644 --- a/include/LIEF/ELF/Parser.hpp +++ b/include/LIEF/ELF/Parser.hpp @@ -39,6 +39,7 @@ class Binary; class Segment; class Symbol; class Note; +class Relocation; //! Class which parses and transforms an ELF file into a ELF::Binary object class LIEF_API Parser : public LIEF::Parser { @@ -188,6 +189,17 @@ class LIEF_API Parser : public LIEF::Parser { ok_error_t parse_pltgot_relocations(uint64_t offset, uint64_t size); + //! Parse *relative* relocations + template + ok_error_t parse_relative_relocations(uint64_t offset, uint64_t size); + + //! Parse Android packed relocations + template + ok_error_t parse_packed_relocations(uint64_t offset, uint64_t size); + + template + ok_error_t process_dynamic_table(); + //! Parse relocations using LIEF::ELF::Section. //! Section relocations are usually found in object files template @@ -241,6 +253,8 @@ class LIEF_API Parser : public LIEF::Parser { //! Check if the given Section is wrapped by the given segment static bool check_section_in_segment(const Section& section, const Segment& segment); + bool bind_symbol(Relocation& R); + std::unique_ptr stream_; std::unique_ptr binary_; ParserConfig config_; diff --git a/include/LIEF/ELF/Relocation.hpp b/include/LIEF/ELF/Relocation.hpp index 9e0ca50587..14b5530d9a 100644 --- a/include/LIEF/ELF/Relocation.hpp +++ b/include/LIEF/ELF/Relocation.hpp @@ -25,6 +25,7 @@ #include "LIEF/Abstract/Relocation.hpp" #include "LIEF/ELF/enums.hpp" +#include "LIEF/ELF/Header.hpp" namespace LIEF { namespace ELF { @@ -52,6 +53,14 @@ class LIEF_API Relocation : public LIEF::Relocation { OBJECT = 3, ///< The relocation is used in an object file }; + enum class ENCODING { + UNKNOWN = 0, + REL, ///< The relocation is using the regular Elf_Rel structure + RELA, ///< The relocation is using the regular Elf_Rela structure + RELR, ///< The relocation is using the relative relocation format + ANDROID_SLEB, ///< The relocation is using the packed Android-SLEB128 format + }; + static constexpr uint64_t R_BIT = 27; static constexpr uint64_t R_MASK = (uint64_t(1) << R_BIT) - 1; @@ -122,10 +131,7 @@ class LIEF_API Relocation : public LIEF::Relocation { return static_cast(type) & R_MASK; } - template - LIEF_LOCAL Relocation(const T& header, PURPOSE purpose, ARCH arch); - - Relocation(uint64_t address, TYPE type = TYPE::UNKNOWN, bool is_rela = false); + Relocation(uint64_t address, TYPE type, ENCODING enc); Relocation() = default; Relocation(ARCH arch) { @@ -150,13 +156,23 @@ class LIEF_API Relocation : public LIEF::Relocation { /// Check if the relocation uses the explicit addend() field /// (this is usually the case for 64 bits binaries) bool is_rela() const { - return isRela_; + return encoding_ == ENCODING::RELA; } /// Check if the relocation uses the implicit addend /// (i.e. not present in the ELF structure) bool is_rel() const { - return !isRela_; + return encoding_ == ENCODING::REL; + } + + /// True if the relocation is using the relative encoding + bool is_relatively_encoded() const { + return encoding_ == ENCODING::RELR; + } + + /// True if the relocation is using the Android packed relocation format + bool is_android_packed() const { + return encoding_ == ENCODING::ANDROID_SLEB; } /// Relocation info which contains, for instance, the symbol index @@ -164,6 +180,16 @@ class LIEF_API Relocation : public LIEF::Relocation { return info_; } + /// (re)Compute the *raw* `r_info` attribute based on the given ELF class + uint64_t r_info(Header::CLASS clazz) const { + if (clazz == Header::CLASS::NONE) { + return 0; + } + return clazz == Header::CLASS::ELF32 ? + uint32_t(info()) << 8 | to_value(type()) : + uint64_t(info()) << 32 | (to_value(type()) & 0xffffffffL); + } + /// Target architecture for this relocation ARCH architecture() const { return architecture_; @@ -173,6 +199,19 @@ class LIEF_API Relocation : public LIEF::Relocation { return purpose_; } + /// The encoding of the relocation + ENCODING encoding() const { + return encoding_; + } + + /// True if the semantic of the relocation is `_RELATIVE` + bool is_relative() const { + return type_ == TYPE::AARCH64_RELATIVE || type_ == TYPE::X86_64_RELATIVE || + type_ == TYPE::X86_RELATIVE || type_ == TYPE::ARM_RELATIVE || + type_ == TYPE::HEX_RELATIVE || type_ == TYPE::PPC64_RELATIVE || + type_ == TYPE::PPC_RELATIVE; + } + /// Return the size (in **bits**) of the value associated with this relocation /// Return -1 if the size can't be determined size_t size() const override; @@ -247,9 +286,12 @@ class LIEF_API Relocation : public LIEF::Relocation { LIEF_API friend std::ostream& operator<<(std::ostream& os, const Relocation& entry); private: + template + LIEF_LOCAL Relocation(const T& header, PURPOSE purpose, ENCODING enc, ARCH arch); + TYPE type_ = TYPE::UNKNOWN; int64_t addend_ = 0; - bool isRela_ = false; + ENCODING encoding_ = ENCODING::UNKNOWN; Symbol* symbol_ = nullptr; ARCH architecture_ = ARCH::NONE; PURPOSE purpose_ = PURPOSE::NONE; diff --git a/include/LIEF/errors.hpp b/include/LIEF/errors.hpp index bc78c102a0..174a49c168 100644 --- a/include/LIEF/errors.hpp +++ b/include/LIEF/errors.hpp @@ -105,6 +105,14 @@ inline ok_t ok() { //! \endcode using ok_error_t = result; +inline bool is_ok(const ok_error_t& val) { + return val.has_value(); +} + +inline bool is_err(const ok_error_t& val) { + return !is_ok(val); +} + } diff --git a/src/Abstract/Relocation.cpp b/src/Abstract/Relocation.cpp index 6191784363..9ba8460178 100644 --- a/src/Abstract/Relocation.cpp +++ b/src/Abstract/Relocation.cpp @@ -61,10 +61,6 @@ void Relocation::accept(Visitor& visitor) const { visitor.visit(*this); } - - - - bool Relocation::operator<(const Relocation& rhs) const { return address() < rhs.address(); } diff --git a/src/ELF/Binary.cpp b/src/ELF/Binary.cpp index cac6202afd..3760636313 100644 --- a/src/ELF/Binary.cpp +++ b/src/ELF/Binary.cpp @@ -69,20 +69,20 @@ namespace ELF { inline size_t get_relocation_sizeof(const Binary& bin, const Relocation& R) { - const bool is64 = (bin.type() == Header::CLASS::ELF64); - const bool is_rela = R.is_rela(); - - return is64 ? - (is_rela ? sizeof(details::Elf64_Rela) : sizeof(details::Elf64_Rel)) : - (is_rela ? sizeof(details::Elf32_Rela) : sizeof(details::Elf32_Rel)); + const bool is64 = bin.type() == Header::CLASS::ELF64; + if (R.is_rel() || R.is_rela()) { + return is64 ? + (R.is_rela() ? sizeof(details::Elf64_Rela) : sizeof(details::Elf64_Rel)) : + (R.is_rela() ? sizeof(details::Elf32_Rela) : sizeof(details::Elf32_Rel)); + } + LIEF_WARN("get_relocation_sizeof() only supports REL/RELA encoding"); + return size_t(-1); } Binary::Binary() : LIEF::Binary(LIEF::Binary::FORMATS::ELF), sizing_info_{std::make_unique()} -{ - -} +{} size_t Binary::hash(const std::string& name) { if (type_ == Header::CLASS::ELF32) { @@ -260,6 +260,48 @@ void Binary::remove(Note::TYPE type) { } +int64_t Binary::symtab_idx(const std::string& name) const { + if (symtab_symbols_.empty()) { + return -1; + } + + auto it = std::find_if(symtab_symbols_.begin(), symtab_symbols_.end(), + [&name] (const std::unique_ptr& S) { + return S->name() == name; + } + ); + + if (it == symtab_symbols_.end()) { + return -1; + } + + return std::distance(symtab_symbols_.begin(), it); +} + +int64_t Binary::symtab_idx(const Symbol& sym) const { + return symtab_idx(sym.name()); +} + +int64_t Binary::dynsym_idx(const Symbol& sym) const { + return dynsym_idx(sym.name()); +} + +int64_t Binary::dynsym_idx(const std::string& name) const { + if (dynamic_symbols_.empty()) { + return -1; + } + + auto it = std::find_if(dynamic_symbols_.begin(), dynamic_symbols_.end(), + [&name] (const std::unique_ptr& S) { + return S->name() == name; + } + ); + if (it == dynamic_symbols_.end()) { + return -1; + } + return std::distance(dynamic_symbols_.begin(), it); +} + Symbol& Binary::export_symbol(const Symbol& symbol) { @@ -657,6 +699,11 @@ Binary::it_const_dynamic_relocations Binary::dynamic_relocations() const { } Relocation& Binary::add_dynamic_relocation(const Relocation& relocation) { + if (!relocation.is_rel() && !relocation.is_rela()) { + LIEF_WARN("LIEF only supports regulard rel/rela relocations"); + static Relocation None; + return None; + } auto relocation_ptr = std::make_unique(relocation); relocation_ptr->purpose(Relocation::PURPOSE::DYNAMIC); relocation_ptr->architecture_ = header().machine_type(); @@ -1858,6 +1905,7 @@ void Binary::shift_dynamic_entries(uint64_t from, uint64_t shift) { case DynamicEntry::TAG::STRTAB: case DynamicEntry::TAG::SYMTAB: case DynamicEntry::TAG::RELA: + case DynamicEntry::TAG::RELR: case DynamicEntry::TAG::REL: case DynamicEntry::TAG::JMPREL: case DynamicEntry::TAG::INIT: @@ -1866,7 +1914,6 @@ void Binary::shift_dynamic_entries(uint64_t from, uint64_t shift) { case DynamicEntry::TAG::VERDEF: case DynamicEntry::TAG::VERNEED: { - if (entry->value() >= from) { entry->value(entry->value() + shift); } @@ -1921,48 +1968,19 @@ void Binary::shift_relocations(uint64_t from, uint64_t shift) { switch(arch) { case ARCH::ARM: - { - patch_relocations(from, shift); - break; - } + patch_relocations(from, shift); return; case ARCH::AARCH64: - { - patch_relocations(from, shift); - break; - } + patch_relocations(from, shift); return; case ARCH::X86_64: - { - patch_relocations(from, shift); - break; - } + patch_relocations(from, shift); return; case ARCH::I386: - { - patch_relocations(from, shift); - break; - } + patch_relocations(from, shift); return; case ARCH::PPC: - { - patch_relocations(from, shift); - break; - } - - /* - case ARCH::PPC64: - { - patch_relocations(from, shift); - break; - } - - case ARCH::RISCV: - { - patch_relocations(from, shift); - break; - } - */ + patch_relocations(from, shift); return; default: { diff --git a/src/ELF/Binary.tcc b/src/ELF/Binary.tcc index 00ec873c81..5a5cf3b239 100644 --- a/src/ELF/Binary.tcc +++ b/src/ELF/Binary.tcc @@ -51,6 +51,10 @@ void Binary::patch_relocations(uint64_t from, uint64_t shift) { relocation.address(relocation.address() + shift); } + if (relocation.encoding() == Relocation::ENCODING::RELR) { + continue; + } + const Relocation::TYPE type = relocation.type(); switch (type) { @@ -85,6 +89,10 @@ void Binary::patch_relocations(uint64_t from, uint64_t shift) { relocation.address(relocation.address() + shift); } + if (relocation.encoding() == Relocation::ENCODING::RELR) { + continue; + } + const Relocation::TYPE type = relocation.type(); switch (type) { @@ -153,6 +161,10 @@ void Binary::patch_relocations(uint64_t from, uint64_t shift) { relocation.address(relocation.address() + shift); } + if (relocation.encoding() == Relocation::ENCODING::RELR) { + continue; + } + const Relocation::TYPE type = relocation.type(); switch (type) { @@ -188,10 +200,13 @@ template<> void Binary::patch_relocations(uint64_t from, uint64_t shift) { for (Relocation& relocation : relocations()) { if (relocation.address() >= from) { - //shift_code(relocation.address(), shift, relocation.size() / 8); relocation.address(relocation.address() + shift); } + if (relocation.encoding() == Relocation::ENCODING::RELR) { + continue; + } + const Relocation::TYPE type = relocation.type(); switch (type) { @@ -253,7 +268,6 @@ void Binary::patch_relocations(uint64_t from, uint64_t shift) { template void Binary::patch_addend(Relocation& relocation, uint64_t from, uint64_t shift) { - if (static_cast(relocation.addend()) >= from) { relocation.addend(relocation.addend() + shift); } diff --git a/src/ELF/Builder.tcc b/src/ELF/Builder.tcc index e8a34784a7..4019d0d826 100644 --- a/src/ELF/Builder.tcc +++ b/src/ELF/Builder.tcc @@ -52,8 +52,7 @@ #include "Object.tcc" #include "ExeLayout.hpp" #include "ObjectFileLayout.hpp" - - +#include "internal_utils.hpp" namespace LIEF { namespace ELF { @@ -181,7 +180,9 @@ ok_error_t Builder::build_exe_lib() { } else { LIEF_DEBUG("PT_DYNAMIC: -0x{:x} bytes", osize - dynamic_needed_size); } } - if (binary_->has(DynamicEntry::TAG::RELA) || binary_->has(DynamicEntry::TAG::REL)) { + if (binary_->has(DynamicEntry::TAG::RELA) || + binary_->has(DynamicEntry::TAG::REL)) + { const size_t dyn_reloc_needed_size = layout->dynamic_relocations_size(); if (config_.rela) { const uint64_t osize = binary_->sizing_info_->rela; @@ -193,6 +194,31 @@ ok_error_t Builder::build_exe_lib() { } } + if ((binary_->has(DynamicEntry::TAG::RELR) || + binary_->has(DynamicEntry::TAG::ANDROID_RELR)) && config_.relr) + { + const size_t relr_reloc_size = layout->relative_relocations_size(); + const uint64_t osize = binary_->sizing_info_->relr; + const bool should_relocate = relr_reloc_size > osize || config_.force_relocate; + if (should_relocate) { + LIEF_DEBUG("[-] Need to relocate DT_RELR (0x{:x} new bytes)", relr_reloc_size - osize); + layout->relocate_relr(true); + } else { LIEF_DEBUG("DT_RELR: -0x{:x} bytes", osize - relr_reloc_size); } + } + + if ((binary_->has(DynamicEntry::TAG::ANDROID_RELA) || + binary_->has(DynamicEntry::TAG::ANDROID_REL)) && config_.android_rela) + { + const size_t android_rela_sz = layout->android_relocations_size(); + const uint64_t osize = binary_->sizing_info_->android_rela; + const bool should_relocate = android_rela_sz > osize || config_.force_relocate; + if (should_relocate) { + LIEF_DEBUG("[-] Need to relocate DT_ANDROID_REL[A] (0x{:x} new bytes)", + android_rela_sz - osize); + layout->relocate_android_rela(true); + } else { LIEF_DEBUG("DT_ANDROID_REL[A]: -0x{:x} bytes", osize - android_rela_sz); } + } + if (config_.jmprel && binary_->has(DynamicEntry::TAG::JMPREL)) { const size_t plt_reloc_needed_size = layout->pltgot_relocations_size(); const uint64_t osize = binary_->sizing_info_->jmprel; @@ -223,7 +249,8 @@ ok_error_t Builder::build_exe_lib() { } else { LIEF_DEBUG("DT_SYMTAB: -0x{:x} bytes", osize - dynsym_needed_size); } } - if (binary_->has(DynamicEntry::TAG::INIT_ARRAY) && binary_->has(DynamicEntry::TAG::INIT_ARRAYSZ) && + if (binary_->has(DynamicEntry::TAG::INIT_ARRAY) && + binary_->has(DynamicEntry::TAG::INIT_ARRAYSZ) && config_.init_array) { const size_t needed_size = layout->dynamic_arraysize(DynamicEntry::TAG::INIT_ARRAY); @@ -238,7 +265,8 @@ ok_error_t Builder::build_exe_lib() { } else { LIEF_DEBUG("DT_INIT_ARRAY: -0x{:x} bytes", osize - needed_size); } } - if (binary_->has(DynamicEntry::TAG::PREINIT_ARRAY) && binary_->has(DynamicEntry::TAG::PREINIT_ARRAYSZ) && + if (binary_->has(DynamicEntry::TAG::PREINIT_ARRAY) && + binary_->has(DynamicEntry::TAG::PREINIT_ARRAYSZ) && config_.preinit_array) { const size_t needed_size = layout->dynamic_arraysize(DynamicEntry::TAG::PREINIT_ARRAY); @@ -250,7 +278,8 @@ ok_error_t Builder::build_exe_lib() { } else { LIEF_DEBUG("DT_PREINIT_ARRAY: -0x{:x} bytes", osize - needed_size); } } - if (binary_->has(DynamicEntry::TAG::FINI_ARRAY) && binary_->has(DynamicEntry::TAG::FINI_ARRAYSZ) && + if (binary_->has(DynamicEntry::TAG::FINI_ARRAY) && + binary_->has(DynamicEntry::TAG::FINI_ARRAYSZ) && config_.fini_array) { const size_t needed_size = layout->dynamic_arraysize(DynamicEntry::TAG::FINI_ARRAY); @@ -404,6 +433,18 @@ ok_error_t Builder::build_exe_lib() { build_symbol_requirement(); } + if (config_.relr) { + if (ok_error_t ret = build_relative_relocations(); !is_ok(ret)) { + return ret; + } + } + + if (config_.android_rela) { + if (ok_error_t ret = build_android_relocations(); !is_ok(ret)) { + return ret; + } + } + if (config_.rela) { build_dynamic_relocations(); } @@ -1233,41 +1274,42 @@ ok_error_t Builder::build_section_relocations() { [] (const Relocation* lhs, const Relocation* rhs) { return lhs->address() < rhs->address(); }); - for (const Relocation* reloc : relocs) { + for (Relocation* reloc : relocs) { Section* reloc_section = sec_relo_map.at(section); uint32_t symidx = 0; - const Symbol* sym = reloc->symbol(); - if (sym != nullptr) { - const auto it_sym = std::find_if(std::begin(binary_->symtab_symbols_), std::end(binary_->symtab_symbols_), - [sym] (const std::unique_ptr& s) { - return s.get() == sym; - }); - if (it_sym == std::end(binary_->symtab_symbols_)) { - LIEF_WARN("Can find the relocation's symbol '{}'", sym->name()); - continue; - } - symidx = static_cast(std::distance(std::begin(binary_->symtab_symbols_), it_sym)); + if (const Symbol* symbol = reloc->symbol()) { + int64_t symtab_idx = binary_->symtab_idx(*symbol); + if (0 <= symtab_idx) { + symidx = static_cast(symtab_idx); + } else { + LIEF_ERR("Can't find the symbol idx associated with the relocation ({})", + symbol->name()); + } } + Elf_Xword info = reloc->info(); - Elf_Xword info = 0; - if (std::is_same::value) { - info = (static_cast(symidx) << 8) | Relocation::to_value(reloc->type()); - } else { - // NOTE: To suppress a warning we require a cast here, this path is not constexpr but only uses Elf64_Xword - info = (static_cast(symidx) << 32) | (Relocation::to_value(reloc->type()) & 0xffffffffL); + if (symidx > 0) { + if (symidx != info) { + LIEF_DEBUG("Fixing symbol idx for {}", to_string(*reloc)); + } + reloc->info(symidx); } + uint64_t r_info = reloc->r_info(std::is_same_v ? + Header::CLASS::ELF32 : + Header::CLASS::ELF64); + if (is_rela) { Elf_Rela relahdr; relahdr.r_offset = static_cast(reloc->address()); - relahdr.r_info = static_cast(info); + relahdr.r_info = static_cast(r_info); relahdr.r_addend = static_cast(reloc->addend()); section_content[reloc_section].write(relahdr); } else { Elf_Rel relhdr; relhdr.r_offset = static_cast(reloc->address()); - relhdr.r_info = static_cast(info); + relhdr.r_info = static_cast(r_info); section_content[reloc_section].write(relhdr); } } @@ -1282,6 +1324,80 @@ ok_error_t Builder::build_section_relocations() { return ok(); } +template +ok_error_t Builder::build_android_relocations() { + LIEF_DEBUG("Build DT_ANDROID_REL[A] relocations"); + if (!config_.android_rela) { + return ok(); + } + + /* The relocations might have been update when adding the new segment + * (->relocate()). Thus the cache might be invalidated + */ + auto& layout = static_cast(*layout_); + const size_t computed_size = layout.android_relocations_size(); + const size_t new_size = layout.android_relocations_size(/*force=*/true); + if (computed_size != new_size) { + if (computed_size < new_size) { + LIEF_ERR("New ANDROID_REL[A] is larger than the in-cache size"); + return make_error_code(lief_errors::build_error); + } + LIEF_WARN("New ANDROID_REL[A] is smaller than the in-cache size. It might require padding"); + } + + if (const DynamicEntry* entry = binary_->get(DynamicEntry::TAG::ANDROID_RELA)) { + binary_->patch_address(entry->value(), layout.raw_android_rela()); + if (DynamicEntry* dt_size = binary_->get(DynamicEntry::TAG::ANDROID_RELASZ)) { + dt_size->value(layout.raw_android_rela().size()); + } + } + else if (const DynamicEntry* entry = binary_->get(DynamicEntry::TAG::ANDROID_REL)) { + binary_->patch_address(entry->value(), layout.raw_android_rela()); + if (DynamicEntry* dt_size = binary_->get(DynamicEntry::TAG::ANDROID_RELSZ)) { + dt_size->value(layout.raw_android_rela().size()); + } + } + + return ok(); +} + +template +ok_error_t Builder::build_relative_relocations() { + LIEF_DEBUG("Build DT_RELR relocations"); + + if (!config_.relr) { + return ok(); + } + /* The relocations might have been update when adding the new segment + * (->relocate()). Thus the cache might be invalidated + */ + auto& layout = static_cast(*layout_); + const size_t computed_size = layout.relative_relocations_size(); + const size_t new_size = layout.relative_relocations_size(/*force=*/true); + if (computed_size != new_size) { + if (computed_size < new_size) { + LIEF_ERR("New RELR is larger than the in-cache size"); + return make_error_code(lief_errors::build_error); + } + LIEF_WARN("New RELR is smaller than the in-cache size. It might require padding"); + } + if (const DynamicEntry* entry = binary_->get(DynamicEntry::TAG::RELR)) { + binary_->patch_address(entry->value(), layout.raw_relr()); + if (DynamicEntry* dt_size = binary_->get(DynamicEntry::TAG::RELRSZ)) { + dt_size->value(layout.raw_relr().size()); + } + } + + if (const DynamicEntry* entry = binary_->get(DynamicEntry::TAG::ANDROID_RELR)) { + binary_->patch_address(entry->value(), layout.raw_relr()); + + if (DynamicEntry* dt_size = binary_->get(DynamicEntry::TAG::ANDROID_RELRSZ)) { + dt_size->value(layout.raw_relr().size()); + } + } + return ok(); +} + template ok_error_t Builder::build_dynamic_relocations() { using Elf_Addr = typename ELF_T::Elf_Addr; @@ -1306,11 +1422,16 @@ ok_error_t Builder::build_dynamic_relocations() { return ok(); } - LIEF_DEBUG("[+] Building dynamic relocations"); + DynamicEntry* dt_rela = binary_->get(DynamicEntry::TAG::RELA); + DynamicEntry* dt_rel = binary_->get(DynamicEntry::TAG::REL); + if (dt_rela == nullptr && dt_rel == nullptr) { + return ok(); + } + LIEF_DEBUG("Building DT_REL/DT_RELA"); + DynamicEntry* dt_reloc = nullptr; DynamicEntry* dt_relocsz = nullptr; - DynamicEntry* dt_rela = binary_->get(DynamicEntry::TAG::RELA); const bool is_rela = dt_rela != nullptr; if (dt_rela != nullptr) { @@ -1318,11 +1439,10 @@ ok_error_t Builder::build_dynamic_relocations() { dt_relocsz = binary_->get(DynamicEntry::TAG::RELASZ); } else { // Fallback on relation type REL - dt_reloc = binary_->get(DynamicEntry::TAG::REL); + dt_reloc = dt_rel; dt_relocsz = binary_->get(DynamicEntry::TAG::RELSZ); } - if (dt_reloc == nullptr) { LIEF_ERR("Unable to find the DT_REL/DT_RELA"); return make_error_code(lief_errors::not_found); @@ -1335,41 +1455,34 @@ ok_error_t Builder::build_dynamic_relocations() { vector_iostream content(should_swap()); - for (const Relocation& relocation : binary_->dynamic_relocations()) { + for (Relocation& relocation : binary_->dynamic_relocations()) { + if (!relocation.is_rel() && !relocation.is_rela()) { + continue; + } // look for symbol index uint32_t idx = 0; - const Symbol* symbol = relocation.symbol(); - if (symbol != nullptr) { - const std::string& name = symbol->name(); - const auto it_name = std::find_if( - std::begin(binary_->dynamic_symbols_), std::end(binary_->dynamic_symbols_), - [&name] (const std::unique_ptr& s) { - return s->name() == name; - }); - - if (it_name == std::end(binary_->dynamic_symbols_)) { - LIEF_ERR("Unable to find the symbol associated with the relocation"); - return make_error_code(lief_errors::not_found); + if (const Symbol* symbol = relocation.symbol()) { + int64_t dynsym_idx = binary_->dynsym_idx(*symbol); + if (0 <= dynsym_idx) { + idx = static_cast(dynsym_idx); + } else { + LIEF_ERR("Can't find the symbol idx associated with the relocation ({})", + symbol->name()); } - - idx = static_cast(std::distance(std::begin(binary_->dynamic_symbols_), it_name)); } uint32_t info = relocation.info(); if (idx > 0) { - info = idx; - } - - Elf_Xword r_info = 0; - if (std::is_same::value) { - r_info = (static_cast(info) << 8) | Relocation::to_value(relocation.type()); - } else { - // NOTE: To suppress a warning we require a cast here, this path is not constexpr but only uses Elf64_Xword - r_info = (static_cast(info) << 32) | (Relocation::to_value(relocation.type()) & 0xffffffffL); + if (idx != info) { + LIEF_DEBUG("Fixing symbol idx for {}", to_string(relocation)); + } + relocation.info(idx); } - + uint64_t r_info = relocation.r_info(std::is_same_v ? + Header::CLASS::ELF32 : + Header::CLASS::ELF64); if (is_rela) { Elf_Rela relahdr; relahdr.r_offset = static_cast(relocation.address()); @@ -1413,7 +1526,7 @@ ok_error_t Builder::build_pltgot_relocations() { bool is_rela = false; DynamicEntry* dt_pltrel = binary_->get(DynamicEntry::TAG::PLTREL); if (dt_pltrel != nullptr) { - is_rela = dt_pltrel->value() == static_cast(DynamicEntry::TAG::RELA); + is_rela = DynamicEntry::TAG(dt_pltrel->value()) == DynamicEntry::TAG::RELA; } DynamicEntry* dt_jmprel = binary_->get(DynamicEntry::TAG::JMPREL); DynamicEntry* dt_pltrelsz = binary_->get(DynamicEntry::TAG::PLTRELSZ); @@ -1428,45 +1541,41 @@ ok_error_t Builder::build_pltgot_relocations() { } vector_iostream content(should_swap()); // Section's content - for (const Relocation& relocation : binary_->pltgot_relocations()) { + for (Relocation& relocation : binary_->pltgot_relocations()) { uint32_t idx = 0; - const Symbol* symbol = relocation.symbol(); - if (symbol != nullptr) { - // look for symbol index - const std::string& name = symbol->name(); - const auto& it_name = std::find_if( - std::begin(binary_->dynamic_symbols_), std::end(binary_->dynamic_symbols_), - [&name] (const std::unique_ptr& s) { - return s->name() == name; - }); - - if (it_name == std::end(binary_->dynamic_symbols_)) { - LIEF_ERR("Unable to find the symbol associated with the relocation"); - return make_error_code(lief_errors::not_found); + if (const Symbol* symbol = relocation.symbol()) { + int64_t dynsym_idx = binary_->dynsym_idx(*symbol); + if (0 <= dynsym_idx) { + idx = static_cast(dynsym_idx); + } else { + LIEF_ERR("Can't find the symbol idx associated with the relocation ({})", + symbol->name()); } - - idx = static_cast(std::distance(std::begin(binary_->dynamic_symbols_), it_name)); } - Elf_Xword info = 0; - if constexpr (std::is_same_v) { - info = (static_cast(idx) << 8) | Relocation::to_value(relocation.type()); - } else { - // NOTE: To suppress a warning we require a cast here, this path is not constexpr but only uses Elf64_Xword - info = (static_cast(idx) << 32) | (Relocation::to_value(relocation.type()) & 0xffffffffL); + uint32_t info = relocation.info(); + if (idx > 0) { + if (idx != info) { + LIEF_DEBUG("Fixing symbol idx for {}", to_string(relocation)); + } + relocation.info(idx); } + uint64_t r_info = relocation.r_info(std::is_same_v ? + Header::CLASS::ELF32 : + Header::CLASS::ELF64); + if (is_rela) { Elf_Rela relahdr; relahdr.r_offset = static_cast(relocation.address()); - relahdr.r_info = static_cast(info); + relahdr.r_info = static_cast(r_info); relahdr.r_addend = static_cast(relocation.addend()); content.write_conv(relahdr); } else { Elf_Rel relhdr; relhdr.r_offset = static_cast(relocation.address()); - relhdr.r_info = static_cast(info); + relhdr.r_info = static_cast(r_info); content.write_conv(relhdr); } @@ -1553,7 +1662,7 @@ ok_error_t Builder::build_symbol_requirement() { continue; } uint32_t new_hash = 0; - if (std::is_same::value) { + if constexpr (std::is_same_v) { new_hash = hash32(svar_name.c_str()); } else { new_hash = hash64(svar_name.c_str()); diff --git a/src/ELF/DynamicEntryFlags.cpp b/src/ELF/DynamicEntryFlags.cpp index 54a39b0483..7055d19814 100644 --- a/src/ELF/DynamicEntryFlags.cpp +++ b/src/ELF/DynamicEntryFlags.cpp @@ -157,7 +157,7 @@ void DynamicEntryFlags::accept(Visitor& visitor) const { std::ostream& DynamicEntryFlags::print(std::ostream& os) const { DynamicEntry::print(os); - os << " " << fmt::to_string(flags()); + os << fmt::to_string(flags()); return os; } diff --git a/src/ELF/ExeLayout.hpp b/src/ELF/ExeLayout.hpp index 7bc887d35d..50ef7b9435 100644 --- a/src/ELF/ExeLayout.hpp +++ b/src/ELF/ExeLayout.hpp @@ -49,6 +49,29 @@ namespace LIEF { namespace ELF { +inline Relocation::TYPE relative_from_arch(ARCH arch) { + using TYPE = Relocation::TYPE; + switch (arch) { + case ARCH::AARCH64: + return TYPE::AARCH64_RELATIVE; + case ARCH::ARM: + return TYPE::ARM_RELATIVE; + case ARCH::X86_64: + return TYPE::X86_64_RELATIVE; + case ARCH::I386: + return TYPE::X86_RELATIVE; + case ARCH::PPC: + return TYPE::PPC_RELATIVE; + case ARCH::PPC64: + return TYPE::PPC64_RELATIVE; + case ARCH::HEXAGON: + return TYPE::HEX_RELATIVE; + default: + return TYPE::UNKNOWN; + } + return TYPE::UNKNOWN; +} + //! Compute the size and the offset of the elements //! needed to rebuild the ELF file. class LIEF_LOCAL ExeLayout : public Layout { @@ -395,10 +418,15 @@ class LIEF_LOCAL ExeLayout : public Layout { using Elf_Rela = typename ELF_T::Elf_Rela; using Elf_Rel = typename ELF_T::Elf_Rel; const Binary::it_dynamic_relocations& dyn_relocs = binary_->dynamic_relocations(); + const size_t nb_rel_a = std::count_if(dyn_relocs.begin(), dyn_relocs.end(), + [] (const Relocation& R) { + return R.is_rel() || R.is_rela(); + } + ); const size_t computed_size = binary_->has(DynamicEntry::TAG::RELA) ? - dyn_relocs.size() * sizeof(Elf_Rela) : - dyn_relocs.size() * sizeof(Elf_Rel); + nb_rel_a * sizeof(Elf_Rela) : + nb_rel_a * sizeof(Elf_Rel); return computed_size; } @@ -458,6 +486,253 @@ class LIEF_LOCAL ExeLayout : public Layout { return binary_->interpreter_.size() + 1; } + template + size_t android_relocations_size(bool force = false) { + static constexpr uint64_t GROUPED_BY_INFO_FLAG = 1 << 0; + static constexpr uint64_t GROUPED_BY_OFFSET_DELTA_FLAG = 1 << 1; + static constexpr uint64_t GROUPED_BY_ADDEND_FLAG = 1 << 2; + static constexpr uint64_t GROUP_HAS_ADDEND_FLAG = 1 << 3; + + using Elf_Xword = typename ELF_T::Elf_Xword; + + // This code reproduces what the lld linker is doing for generating the + // packed relocations. See lld/ELF/SyntheticSections.cpp - + // AndroidPackedRelocationSection:updateAllocSize + constexpr size_t wordsize = sizeof(typename ELF_T::Elf_Addr); + const bool is_rela = binary_->has(DynamicEntry::TAG::ANDROID_RELA); + const Relocation::TYPE relative_reloc = relative_from_arch(binary_->header().machine_type()); + const uint64_t raw_relative_reloc = Relocation::to_value(relative_reloc); + + const Header::CLASS elf_class = std::is_same_v ? + Header::CLASS::ELF32 : Header::CLASS::ELF64; + + if (force) { + raw_android_rela_.clear(); + } + + if (!raw_android_rela_.empty()) { + return raw_android_rela_.size(); + } + + std::vector android_relocs; + std::vector relative_rels; + std::vector non_relative_rels; + android_relocs.reserve(20); + + for (const Relocation& R : binary_->relocations()) { + if (!R.is_android_packed()) { + continue; + } + android_relocs.push_back(&R); + R.is_relative() ? relative_rels.push_back(&R) : + non_relative_rels.push_back(&R); + } + + std::sort(relative_rels.begin(), relative_rels.end(), + [] (const Relocation* lhs, const Relocation* rhs) { + return lhs->address() < rhs->address(); + } + ); + + std::vector ungrouped_relative; + std::vector> relative_groups; + for (auto i = relative_rels.begin(), e = relative_rels.end(); i != e;) { + std::vector group; + do { + group.push_back(*i++); + } while (i != e && (*(i - 1))->address() + wordsize == (*i)->address()); + + if (group.size() < 8) { + ungrouped_relative.insert(ungrouped_relative.end(), + group.begin(), group.end()); + } else { + relative_groups.emplace_back(std::move(group)); + } + } + + + std::sort(non_relative_rels.begin(), non_relative_rels.end(), + [&elf_class] (const Relocation* lhs, const Relocation* rhs) { + if (lhs->r_info(elf_class) != rhs->r_info(elf_class)) { + return lhs->r_info(elf_class) < rhs->r_info(elf_class); + } + if (lhs->addend() != rhs->addend()) { + return lhs->addend() < rhs->addend(); + } + return lhs->address() < rhs->address(); + } + ); + + std::vector ungrouped_non_relative; + std::vector> non_relative_group; + + for (auto i = non_relative_rels.begin(), + e = non_relative_rels.end(); i != e;) + { + auto j = i + 1; + while (j != e && (*i)->r_info(elf_class) == (*j)->r_info(elf_class) && + (!is_rela || (*i)->addend() == (*j)->addend())) + { + ++j; + } + + if ((j - i) < 3 || (is_rela && (*i)->addend() != 0)) { + ungrouped_non_relative.insert(ungrouped_non_relative.end(), i, j); + } else { + non_relative_group.emplace_back(i, j); + } + i = j; + } + + std::sort(ungrouped_non_relative.begin(), ungrouped_non_relative.end(), + [] (const Relocation* lhs, const Relocation* rhs) { + return lhs->address() < rhs->address(); + } + ); + + const unsigned has_addend_with_rela = is_rela ? GROUP_HAS_ADDEND_FLAG : 0; + uint64_t offset = 0; + uint64_t addend = 0; + + vector_iostream ios; + ios.write('A') + .write('P') + .write('S') + .write('2'); + + ios.write_sleb128(android_relocs.size()); + ios.write_sleb128(0); + + for (const std::vector& g : relative_groups) { + ios.write_sleb128(1); + ios.write_sleb128(GROUPED_BY_OFFSET_DELTA_FLAG | GROUPED_BY_INFO_FLAG | + has_addend_with_rela); + ios.write_sleb128(g[0]->address() - offset); + ios.write_sleb128(raw_relative_reloc); + if (is_rela) { + ios.write_sleb128(g[0]->addend() - addend); + addend = g[0]->addend(); + } + + ios.write_sleb128(g.size() - 1); + ios.write_sleb128(GROUPED_BY_OFFSET_DELTA_FLAG | GROUPED_BY_INFO_FLAG | + has_addend_with_rela); + ios.write_sleb128(wordsize); + ios.write_sleb128(raw_relative_reloc); + if (is_rela) { + auto it = g.begin(); ++it; + for (; it != g.end(); ++it) { + ios.write_sleb128((*it)->addend() - addend); + addend = (*it)->addend(); + } + } + offset = g.back()->address(); + } + + if (!ungrouped_relative.empty()) { + ios.write_sleb128(ungrouped_relative.size()); + ios.write_sleb128(GROUPED_BY_INFO_FLAG | has_addend_with_rela); + ios.write_sleb128(raw_relative_reloc); + for (const Relocation* R : ungrouped_relative) { + ios.write_sleb128(R->address() - offset); + offset = R->address(); + if (is_rela) { + ios.write_sleb128(R->addend() - addend); + addend = R->addend(); + } + } + } + + for (const std::vector& g: non_relative_group) { + ios.write_sleb128(g.size()); + ios.write_sleb128(GROUPED_BY_INFO_FLAG); + ios.write_sleb128(static_cast(g[0]->r_info(elf_class))); + + for (const Relocation* R : g) { + ios.write_sleb128(R->address() - offset); + offset = R->address(); + } + addend = 0; + } + + if (!ungrouped_non_relative.empty()) { + ios.write_sleb128(ungrouped_non_relative.size()); + ios.write_sleb128(has_addend_with_rela); + for (const Relocation* R : ungrouped_non_relative) { + ios.write_sleb128(R->address() - offset); + offset = R->address(); + ios.write_sleb128(static_cast(R->r_info(elf_class))); + if (is_rela) { + ios.write_sleb128(R->addend() - addend); + addend = R->addend(); + } + } + } + + ios.move(raw_android_rela_); + return raw_android_rela_.size(); + } + + template + size_t relative_relocations_size(bool force = false) { + // This code is inspired from LLVM-lld: + // lld/ELF/SyntheticSections.cpp - RelrSection::updateAllocSize + // https://github.com/llvm/llvm-project/blob/754a8add57098ef71e4a51a9caa0cc175e94377d/lld/ELF/SyntheticSections.cpp#L1997-L2078 + using Elf_Addr = typename ELF_T::Elf_Addr; + + if (force) { + raw_relr_.clear(); + } + + if (!raw_relr_.empty()) { + return raw_relr_.size(); + } + + std::vector relr_relocs; + relr_relocs.reserve(20); + + for (const Relocation& R : binary_->relocations()) { + if (R.is_relatively_encoded()) { + relr_relocs.push_back(&R); + } + } + + std::unique_ptr offsets(new uint64_t[relr_relocs.size()]); + for (size_t i = 0; i < relr_relocs.size(); ++i) { + offsets[i] = relr_relocs[i]->address(); + } + std::sort(offsets.get(), offsets.get() + relr_relocs.size()); + + const size_t wordsize = sizeof(Elf_Addr); + const size_t nbits = wordsize * 8 - 1; + + vector_iostream raw_relr; + + for (size_t i = 0, e = relr_relocs.size(); i != e;) { + raw_relr.write(offsets[i]); + uint64_t base = offsets[i] + wordsize; + ++i; + + for (;;) { + uint64_t bitmap = 0; + for (; i != e; ++i) { + uint64_t d = offsets[i] - base; + if (nbits <= (d * wordsize) || (d % wordsize) != 0) { + break; + } + bitmap |= uint64_t(1) << (d / wordsize); + } + if (!bitmap) { + break; + } + raw_relr.write((bitmap << 1) | 1); + base += nbits * wordsize; + } + } + raw_relr.move(raw_relr_); + return raw_relr_.size(); + } + void relocate_dynamic(uint64_t size) { dynamic_size_ = size; } @@ -466,6 +741,14 @@ class LIEF_LOCAL ExeLayout : public Layout { relocate_dynstr_ = val; } + void relocate_relr(bool val) { + relocate_relr_ = val; + } + + void relocate_android_rela(bool val) { + relocate_android_rela_ = val; + } + void relocate_shstr(bool val) { relocate_shstrtab_ = val; } @@ -554,6 +837,14 @@ class LIEF_LOCAL ExeLayout : public Layout { return verdef_info_; } + const std::vector& raw_relr() const { + return raw_relr_; + } + + const std::vector& raw_android_rela() const { + return raw_android_rela_; + } + result relocate() { /* PT_INTERP segment (optional) * @@ -583,14 +874,20 @@ class LIEF_LOCAL ExeLayout : public Layout { * .gnu.version_r * .rela.dyn * .rela.plt + * .relr.dyn * Perm: READ ONLY * Align: 0x1000 */ - uint64_t read_segment = - interp_size_ + sysv_size_ + - dynsym_size_ + - sver_size_ + sverd_size_ + sverr_size_ + - dynamic_reloc_size_ + pltgot_reloc_size_; + uint64_t read_segment = interp_size_ + sysv_size_ + dynsym_size_ + + sver_size_ + sverd_size_ + sverr_size_ + + dynamic_reloc_size_ + pltgot_reloc_size_; + if (relocate_relr_) { + read_segment += raw_relr_.size(); + } + + if (relocate_android_rela_) { + read_segment += raw_android_rela_.size(); + } if (relocate_notes_) { read_segment += raw_notes_.size(); @@ -651,8 +948,6 @@ class LIEF_LOCAL ExeLayout : public Layout { } } - - if (relocate_shstrtab_) { LIEF_DEBUG("[-] Relocate .shstrtab"); @@ -980,6 +1275,59 @@ class LIEF_LOCAL ExeLayout : public Layout { va_r_base += pltgot_reloc_size_; } + if (relocate_relr_) { + DynamicEntry* dt_relr = binary_->get(DynamicEntry::TAG::RELR); + if (dt_relr == nullptr) { + LIEF_ERR("Can't find DT_RELR"); + return make_error_code(lief_errors::file_format_error); + } + + uint64_t offset_r_base = 0; + if (auto res = binary_->virtual_address_to_offset(va_r_base)) { + offset_r_base = *res; + } else { + return make_error_code(lief_errors::build_error); + } + + if (Section* section = binary_->section_from_virtual_address(dt_relr->value())) { + section->virtual_address(va_r_base); + section->size(raw_relr_.size()); + section->offset(offset_r_base); + section->original_size_ = raw_relr_.size(); + } + + dt_relr->value(va_r_base); + va_r_base += raw_relr_.size(); + } + + if (relocate_android_rela_) { + DynamicEntry* dt_rel = binary_->get(DynamicEntry::TAG::ANDROID_RELA); + if (dt_rel == nullptr) { + dt_rel = binary_->get(DynamicEntry::TAG::ANDROID_REL); + } + + if (dt_rel == nullptr) { + LIEF_ERR("Can't find DT_ANDROID_REL[A]"); + return make_error_code(lief_errors::file_format_error); + } + + uint64_t offset_r_base = 0; + if (auto res = binary_->virtual_address_to_offset(va_r_base)) { + offset_r_base = *res; + } else { + return make_error_code(lief_errors::build_error); + } + + if (Section* section = binary_->section_from_virtual_address(dt_rel->value())) { + section->virtual_address(va_r_base); + section->size(raw_android_rela_.size()); + section->offset(offset_r_base); + section->original_size_ = raw_android_rela_.size(); + } + + dt_rel->value(va_r_base); + va_r_base += raw_android_rela_.size(); + } if (relocate_gnu_hash_) { // Update .gnu.hash section / DT_GNU_HASH @@ -1355,6 +1703,12 @@ class LIEF_LOCAL ExeLayout : public Layout { std::vector raw_gnu_hash_; bool relocate_gnu_hash_{false}; + std::vector raw_relr_; + bool relocate_relr_{false}; + + std::vector raw_android_rela_; + bool relocate_android_rela_{false}; + uint64_t sysv_size_{0}; uint64_t dynamic_size_{0}; diff --git a/src/ELF/Parser.cpp b/src/ELF/Parser.cpp index 4d01028d43..840d119bfd 100644 --- a/src/ELF/Parser.cpp +++ b/src/ELF/Parser.cpp @@ -646,5 +646,21 @@ ok_error_t Parser::link_symbol_section(Symbol& sym) { return ok(); } + +bool Parser::bind_symbol(Relocation& R) { + if (!config_.parse_dyn_symbols) { + return false; + } + const uint32_t idx = R.info(); + if (idx >= binary_->dynamic_symbols_.size()) { + LIEF_DEBUG("Index #{} is out of range for reloc: {}", idx, to_string(R)); + return false; + } + + R.symbol_ = binary_->dynamic_symbols_[idx].get(); + + return true; +} + } } diff --git a/src/ELF/Parser.tcc b/src/ELF/Parser.tcc index fb0ada604c..b32cb53634 100644 --- a/src/ELF/Parser.tcc +++ b/src/ELF/Parser.tcc @@ -93,9 +93,97 @@ ok_error_t Parser::parse_binary() { binary_->sizing_info_->dynamic = size; } + process_dynamic_table(); - // Parse dynamic symbols - // ===================== + if (const Section* sec_symbtab = binary_->get(Section::TYPE::SYMTAB)) { + auto nb_entries = static_cast((sec_symbtab->size() / sizeof(typename ELF_T::Elf_Sym))); + nb_entries = std::min(nb_entries, Parser::NB_MAX_SYMBOLS); + + if (sec_symbtab->link() == 0 || sec_symbtab->link() >= binary_->sections_.size()) { + LIEF_WARN("section->link() is not valid !"); + } else { + if (config_.parse_symtab_symbols) { + // We should have: + // nb_entries == section->information()) + // but lots of compiler not respect this rule + parse_symtab_symbols(sec_symbtab->file_offset(), nb_entries, + *binary_->sections_[sec_symbtab->link()]); + } + } + } + + + // Parse Symbols's hash + // ==================== + if (DynamicEntry* dt_hash = binary_->get(DynamicEntry::TAG::HASH)) { + if (auto res = binary_->virtual_address_to_offset(dt_hash->value())) { + parse_symbol_sysv_hash(*res); + } else { + LIEF_WARN("Can't convert DT_HASH.virtual_address into an offset (0x{:x})", dt_hash->value()); + } + } + + + if (DynamicEntry* dt_gnu_hash = binary_->get(DynamicEntry::TAG::GNU_HASH)) { + if (auto res = binary_->virtual_address_to_offset(dt_gnu_hash->value())) { + parse_symbol_gnu_hash(*res); + } else { + LIEF_WARN("Can't convert DT_GNU_HASH.virtual_address into an offset (0x{:x})", dt_gnu_hash->value()); + } + } + + if (config_.parse_notes) { + // Parse Note segment + // ================== + for (const Segment& segment : binary_->segments()) { + if (segment.type() != Segment::TYPE::NOTE) { + continue; + } + parse_notes(segment.file_offset(), segment.physical_size()); + } + + // Parse Note Sections + // =================== + for (const Section& section : binary_->sections()) { + if (section.type() != Section::TYPE::NOTE) { + continue; + } + LIEF_DEBUG("Notes from section: {}", section.name()); + parse_notes(section.offset(), section.size()); + } + } + + // Try to parse using sections + // If we don't have any relocations, we parse all relocation sections + // otherwise, only the non-allocated sections to avoid parsing dynamic + // relocations (or plt relocations) twice. + if (config_.parse_relocations) { + bool skip_allocated_sections = !binary_->relocations_.empty(); + for (const Section& section : binary_->sections()) { + if (skip_allocated_sections && section.has(Section::FLAGS::ALLOC)){ + continue; + } + if (section.type() == Section::TYPE::REL) { + parse_section_relocations(section); + } + else if (section.type() == Section::TYPE::RELA) { + parse_section_relocations(section); + } + } + } + if (config_.parse_symbol_versions) { + link_symbol_version(); + } + + if (config_.parse_overlay) { + parse_overlay(); + } + return ok(); +} + + +template +ok_error_t Parser::process_dynamic_table() { { DynamicEntry* dt_symtab = binary_->get(DynamicEntry::TAG::SYMTAB); DynamicEntry* dt_syment = binary_->get(DynamicEntry::TAG::SYMENT); @@ -109,12 +197,6 @@ ok_error_t Parser::parse_binary() { } } } - - // Parse dynamic relocations - // ========================= - - // RELA - // ---- { DynamicEntry* dt_rela = binary_->get(DynamicEntry::TAG::RELA); DynamicEntry* dt_relasz = binary_->get(DynamicEntry::TAG::RELASZ); @@ -130,10 +212,6 @@ ok_error_t Parser::parse_binary() { } } } - - - // REL - // --- { DynamicEntry* dt_rel = binary_->get(DynamicEntry::TAG::REL); DynamicEntry* dt_relsz = binary_->get(DynamicEntry::TAG::RELSZ); @@ -149,9 +227,55 @@ ok_error_t Parser::parse_binary() { } } } + { + DynamicEntry* dt_relr = binary_->get(DynamicEntry::TAG::RELR); + DynamicEntry* dt_relrsz = binary_->get(DynamicEntry::TAG::RELRSZ); - // Parse PLT/GOT Relocations - // ========================== + if (dt_relr != nullptr && dt_relrsz != nullptr && config_.parse_relocations) { + const uint64_t virtual_address = dt_relr->value(); + const uint64_t size = dt_relrsz->value(); + if (auto res = binary_->virtual_address_to_offset(virtual_address)) { + parse_relative_relocations(*res, size); + binary_->sizing_info_->relr = size; + } else { + LIEF_WARN("Can't convert DT_RELR.virtual_address into an offset (0x{:x})", virtual_address); + } + } + } + { + DynamicEntry* dt_relr = binary_->get(DynamicEntry::TAG::ANDROID_RELR); + DynamicEntry* dt_relrsz = binary_->get(DynamicEntry::TAG::ANDROID_RELRSZ); + + if (dt_relr != nullptr && dt_relrsz != nullptr && config_.parse_relocations) { + const uint64_t virtual_address = dt_relr->value(); + const uint64_t size = dt_relrsz->value(); + if (auto res = binary_->virtual_address_to_offset(virtual_address)) { + parse_relative_relocations(*res, size); + binary_->sizing_info_->relr = size; + } else { + LIEF_WARN("Can't convert (Android)DT_RELR.virtual_address into an offset (0x{:x})", virtual_address); + } + } + } + { + DynamicEntry* dt_rela = binary_->get(DynamicEntry::TAG::ANDROID_RELA); + DynamicEntry* dt_relasz = binary_->get(DynamicEntry::TAG::ANDROID_RELASZ); + if (dt_rela == nullptr) { + dt_rela = binary_->get(DynamicEntry::TAG::ANDROID_REL); + dt_relasz = binary_->get(DynamicEntry::TAG::ANDROID_RELSZ); + } + + if (dt_rela != nullptr && dt_relasz != nullptr && config_.parse_relocations) { + const uint64_t virtual_address = dt_rela->value(); + const uint64_t size = dt_relasz->value(); + if (auto res = binary_->virtual_address_to_offset(virtual_address)) { + parse_packed_relocations(*res, size); + binary_->sizing_info_->android_rela = size; + } else { + LIEF_WARN("Can't convert DT_ANDROID_REL[A].virtual_address into an offset (0x{:x})", virtual_address); + } + } + } { DynamicEntry* dt_jmprel = binary_->get(DynamicEntry::TAG::JMPREL); DynamicEntry* dt_pltrelsz = binary_->get(DynamicEntry::TAG::PLTRELSZ); @@ -182,9 +306,6 @@ ok_error_t Parser::parse_binary() { } } } - - // Parse Symbol Version - // ==================== if (config_.parse_symbol_versions && config_.parse_dyn_symbols) { if (DynamicEntry* dt_versym = binary_->get(DynamicEntry::TAG::VERSYM)) { const uint64_t virtual_address = dt_versym->value(); @@ -197,9 +318,6 @@ ok_error_t Parser::parse_binary() { } } - - // Parse Symbol Version Requirement - // ================================ if (config_.parse_symbol_versions) { DynamicEntry* dt_verneed = binary_->get(DynamicEntry::TAG::VERNEED); DynamicEntry* dt_verneed_num = binary_->get(DynamicEntry::TAG::VERNEEDNUM); @@ -217,8 +335,6 @@ ok_error_t Parser::parse_binary() { } } - // Parse Symbol Version Definition - // =============================== if (config_.parse_symbol_versions) { DynamicEntry* dt_verdef = binary_->get(DynamicEntry::TAG::VERDEF); DynamicEntry* dt_verdef_num = binary_->get(DynamicEntry::TAG::VERDEFNUM); @@ -234,93 +350,9 @@ ok_error_t Parser::parse_binary() { } } - if (const Section* sec_symbtab = binary_->get(Section::TYPE::SYMTAB)) { - auto nb_entries = static_cast((sec_symbtab->size() / sizeof(typename ELF_T::Elf_Sym))); - nb_entries = std::min(nb_entries, Parser::NB_MAX_SYMBOLS); - - if (sec_symbtab->link() == 0 || sec_symbtab->link() >= binary_->sections_.size()) { - LIEF_WARN("section->link() is not valid !"); - } else { - if (config_.parse_symtab_symbols) { - // We should have: - // nb_entries == section->information()) - // but lots of compiler not respect this rule - parse_symtab_symbols(sec_symbtab->file_offset(), nb_entries, - *binary_->sections_[sec_symbtab->link()]); - } - } - } - - - // Parse Symbols's hash - // ==================== - if (DynamicEntry* dt_hash = binary_->get(DynamicEntry::TAG::HASH)) { - if (auto res = binary_->virtual_address_to_offset(dt_hash->value())) { - parse_symbol_sysv_hash(*res); - } else { - LIEF_WARN("Can't convert DT_HASH.virtual_address into an offset (0x{:x})", dt_hash->value()); - } - } - - - if (DynamicEntry* dt_gnu_hash = binary_->get(DynamicEntry::TAG::GNU_HASH)) { - if (auto res = binary_->virtual_address_to_offset(dt_gnu_hash->value())) { - parse_symbol_gnu_hash(*res); - } else { - LIEF_WARN("Can't convert DT_GNU_HASH.virtual_address into an offset (0x{:x})", dt_gnu_hash->value()); - } - } - - if (config_.parse_notes) { - // Parse Note segment - // ================== - for (const Segment& segment : binary_->segments()) { - if (segment.type() != Segment::TYPE::NOTE) { - continue; - } - parse_notes(segment.file_offset(), segment.physical_size()); - } - - // Parse Note Sections - // =================== - for (const Section& section : binary_->sections()) { - if (section.type() != Section::TYPE::NOTE) { - continue; - } - LIEF_DEBUG("Notes from section: {}", section.name()); - parse_notes(section.offset(), section.size()); - } - } - - // Try to parse using sections - // If we don't have any relocations, we parse all relocation sections - // otherwise, only the non-allocated sections to avoid parsing dynamic - // relocations (or plt relocations) twice. - if (config_.parse_relocations) { - bool skip_allocated_sections = !binary_->relocations_.empty(); - for (const Section& section : binary_->sections()) { - if (skip_allocated_sections && section.has(Section::FLAGS::ALLOC)){ - continue; - } - if (section.type() == Section::TYPE::REL) { - parse_section_relocations(section); - } - else if (section.type() == Section::TYPE::RELA) { - parse_section_relocations(section); - } - } - } - if (config_.parse_symbol_versions) { - link_symbol_version(); - } - - if (config_.parse_overlay) { - parse_overlay(); - } return ok(); } - template ok_error_t Parser::parse_header() { using Elf_Half = typename ELF_T::Elf_Half; @@ -931,11 +963,181 @@ ok_error_t Parser::parse_segments() { } +template +ok_error_t Parser::parse_packed_relocations(uint64_t offset, uint64_t size) { + using Elf_Rela = typename ELF_T::Elf_Rela; + static constexpr uint64_t GROUPED_BY_INFO_FLAG = 1 << 0; + static constexpr uint64_t GROUPED_BY_OFFSET_DELTA_FLAG = 1 << 1; + static constexpr uint64_t GROUPED_BY_ADDEND_FLAG = 1 << 2; + static constexpr uint64_t GROUP_HAS_ADDEND_FLAG = 1 << 3; + + LIEF_DEBUG("Parsing Android packed relocations"); + if (size < 4) { + LIEF_ERR("Invalid Android packed relocation header"); + return make_error_code(lief_errors::read_error); + } + ScopedStream rel_stream(*stream_, offset); + + const auto H0 = stream_->read().value_or(0); + const auto H1 = stream_->read().value_or(0); + const auto H2 = stream_->read().value_or(0); + const auto H3 = stream_->read().value_or(0); + + LIEF_DEBUG("Header: {} {} {} {}", H0, H1, H2, H3); + + // Check for the Magic: APS2 + if (H0 != 'A' || H1 != 'P' || H2 != 'S' || H3 != '2') { + LIEF_ERR("Invalid Android packed relocation magic header: " + "{} {} {} {}", H0, H1, H2, H3); + return make_error_code(lief_errors::read_error); + } + + auto res_nb_relocs = rel_stream->read_sleb128(); + if (!res_nb_relocs) { + LIEF_ERR("Can't read number of relocations"); + return make_error_code(lief_errors::read_error); + } + auto res_rels_offset = rel_stream->read_sleb128(); + if (!res_rels_offset) { + LIEF_ERR("Can't read offset"); + return make_error_code(lief_errors::read_error); + } + + uint64_t nb_relocs = *res_nb_relocs; + uint64_t r_offset = *res_rels_offset; + uint64_t addend = 0; + const ARCH arch = binary_->header().machine_type(); + + LIEF_DEBUG("Nb relocs: {}", nb_relocs); + + while (nb_relocs > 0) { + auto nb_reloc_group_r = rel_stream->read_sleb128(); + if (!nb_reloc_group_r) { + break; + } + + uint64_t nb_reloc_group = *nb_reloc_group_r; + LIEF_DEBUG(" Nb relocs in group: {}", nb_reloc_group); + + if (nb_reloc_group > nb_relocs) { + break; + } + + nb_relocs -= nb_reloc_group; + + auto group_flag_r = rel_stream->read_sleb128(); + if (!group_flag_r) { + LIEF_ERR("Can't read group flag"); + break; + } + uint64_t group_flag = *group_flag_r; + + const bool g_by_info = group_flag & GROUPED_BY_INFO_FLAG; + const bool g_by_offset_delta = group_flag & GROUPED_BY_OFFSET_DELTA_FLAG; + const bool g_by_addend = group_flag & GROUPED_BY_ADDEND_FLAG; + const bool g_has_addend = group_flag & GROUP_HAS_ADDEND_FLAG; + + uint64_t group_off_delta = + g_by_offset_delta ? rel_stream->read_sleb128().value_or(0) : 0; + + uint64_t groupr_info = + g_by_info ? rel_stream->read_sleb128().value_or(0) : 0; + + if (g_by_addend && g_has_addend) { + addend += rel_stream->read_sleb128().value_or(0); + } + + if (!g_has_addend) { + addend = 0; + } + + for (size_t i = 0; i < nb_reloc_group; ++i) { + if (!*rel_stream) { + break; + } + r_offset += g_by_offset_delta ? group_off_delta : rel_stream->read_sleb128().value_or(0); + uint64_t info = g_by_info ? groupr_info : rel_stream->read_sleb128().value_or(0); + if (g_has_addend && !g_by_addend) { + addend += rel_stream->read_sleb128().value_or(0); + } + + Elf_Rela R; + R.r_info = info; + R.r_addend = addend; + R.r_offset = r_offset; + auto reloc = std::unique_ptr(new Relocation(R, Relocation::PURPOSE::DYNAMIC, + Relocation::ENCODING::ANDROID_SLEB, arch)); + bind_symbol(*reloc); + binary_->relocations_.push_back(std::move(reloc)); + } + } + return ok(); +} + +template +ok_error_t Parser::parse_relative_relocations(uint64_t offset, uint64_t size) { + LIEF_DEBUG("Parsing relative relocations"); + using Elf_Relr = typename ELF_T::uint; + using Elf_Addr = typename ELF_T::uint; + ScopedStream rel_stream(*stream_, offset); + + Elf_Addr base = 0; + const ARCH arch = binary_->header().machine_type(); + Relocation::TYPE type = Relocation::TYPE::UNKNOWN; + switch (arch) { + case ARCH::AARCH64: + type = Relocation::TYPE::AARCH64_RELATIVE; break; + case ARCH::X86_64: + type = Relocation::TYPE::X86_64_RELATIVE; break; + case ARCH::ARM: + type = Relocation::TYPE::ARM_RELATIVE; break; + case ARCH::HEXAGON: + type = Relocation::TYPE::HEX_RELATIVE; break; + case ARCH::PPC64: + type = Relocation::TYPE::PPC64_RELATIVE; break; + case ARCH::PPC: + type = Relocation::TYPE::PPC_RELATIVE; break; + case ARCH::I386: + case ARCH::IAMCU: + type = Relocation::TYPE::X86_RELATIVE; break; + default: + break; + } + + while (rel_stream->pos() < (offset + size)) { + auto opt_relr = rel_stream->read(); + if (!opt_relr) { + break; + } + Elf_Relr rel = *opt_relr; + if ((rel & 1) == 0) { + Elf_Addr r_offset = rel; + auto reloc = std::make_unique(r_offset, type, + Relocation::ENCODING::RELR); + reloc->purpose(Relocation::PURPOSE::DYNAMIC); + binary_->relocations_.push_back(std::move(reloc)); + base = rel + sizeof(Elf_Addr); + } else { + for (Elf_Addr offset = base; (rel >>= 1) != 0; offset += sizeof(Elf_Addr)) { + if ((rel & 1) != 0) { + Elf_Addr r_offset = offset; + auto reloc = std::make_unique(r_offset, type, + Relocation::ENCODING::RELR); + reloc->purpose(Relocation::PURPOSE::DYNAMIC); + binary_->relocations_.push_back(std::move(reloc)); + } + } + base += (8 * sizeof(Elf_Relr) - 1) * sizeof(Elf_Addr); + } + } + return ok(); +} + template ok_error_t Parser::parse_dynamic_relocations(uint64_t relocations_offset, uint64_t size) { - static_assert(std::is_same::value || - std::is_same::value, "REL_T must be Elf_Rel || Elf_Rela"); + static_assert(std::is_same_v || + std::is_same_v, "REL_T must be Elf_Rel || Elf_Rela"); LIEF_DEBUG("== Parsing dynamic relocations =="); // Already parsed @@ -943,8 +1145,6 @@ ok_error_t Parser::parse_dynamic_relocations(uint64_t relocations_offset, uint64 return ok(); } - const uint8_t shift = std::is_same::value ? 8 : 32; - auto nb_entries = static_cast(size / sizeof(REL_T)); nb_entries = std::min(nb_entries, Parser::NB_MAX_RELOCATIONS); @@ -952,23 +1152,19 @@ ok_error_t Parser::parse_dynamic_relocations(uint64_t relocations_offset, uint64 stream_->setpos(relocations_offset); const ARCH arch = binary_->header().machine_type(); + const Relocation::ENCODING enc = + std::is_same_v ? Relocation::ENCODING::REL : + Relocation::ENCODING::RELA; + for (uint32_t i = 0; i < nb_entries; ++i) { const auto raw_reloc = stream_->read_conv(); if (!raw_reloc) { break; } - auto reloc = std::make_unique( - std::move(*raw_reloc), Relocation::PURPOSE::DYNAMIC, arch); - const auto idx = static_cast(raw_reloc->r_info >> shift); - if (config_.parse_dyn_symbols) { - if (idx < binary_->dynamic_symbols_.size()) { - reloc->symbol_ = binary_->dynamic_symbols_[idx].get(); - } else { - LIEF_WARN("Unable to find the symbol associated with the relocation " - "(idx: {}) {}", idx, to_string(*reloc)); - } - } + auto reloc = std::unique_ptr(new Relocation( + std::move(*raw_reloc), Relocation::PURPOSE::DYNAMIC, enc, arch)); + bind_symbol(*reloc); binary_->relocations_.push_back(std::move(reloc)); } @@ -1292,13 +1488,15 @@ ok_error_t Parser::parse_pltgot_relocations(uint64_t offset, uint64_t size) { } const Elf_Off offset_relocations = offset; - const uint8_t shift = std::is_same::value ? 8 : 32; auto nb_entries = static_cast(size / sizeof(REL_T)); nb_entries = std::min(nb_entries, Parser::NB_MAX_RELOCATIONS); const ARCH arch = binary_->header_.machine_type(); + const Relocation::ENCODING enc = + std::is_same_v ? Relocation::ENCODING::REL : + Relocation::ENCODING::RELA; stream_->setpos(offset_relocations); for (uint32_t i = 0; i < nb_entries; ++i) { const auto rel_hdr = stream_->read_conv(); @@ -1306,16 +1504,9 @@ ok_error_t Parser::parse_pltgot_relocations(uint64_t offset, uint64_t size) { break; } - auto reloc = std::make_unique( - std::move(*rel_hdr), Relocation::PURPOSE::PLTGOT, arch); - - const auto idx = static_cast(rel_hdr->r_info >> shift); - if (config_.parse_dyn_symbols && - 0 < idx && idx < binary_->dynamic_symbols_.size()) - { - reloc->symbol_ = binary_->dynamic_symbols_[idx].get(); - } - + auto reloc = std::unique_ptr(new Relocation( + std::move(*rel_hdr), Relocation::PURPOSE::PLTGOT, enc, arch)); + bind_symbol(*reloc); binary_->relocations_.push_back(std::move(reloc)); } return ok(); @@ -1383,6 +1574,12 @@ ok_error_t Parser::parse_section_relocations(const Section& section) { const uint64_t offset_relocations = section.file_offset(); const uint8_t shift = std::is_same::value ? 8 : 32; + const ARCH arch = binary_->header_.machine_type(); + + const Relocation::ENCODING enc = + std::is_same_v ? Relocation::ENCODING::REL : + Relocation::ENCODING::RELA; + auto nb_entries = static_cast(section.size() / sizeof(REL_T)); nb_entries = std::min(nb_entries, Parser::NB_MAX_RELOCATIONS); @@ -1394,8 +1591,9 @@ ok_error_t Parser::parse_section_relocations(const Section& section) { break; } - auto reloc = std::make_unique( - *rel_hdr, Relocation::PURPOSE::NONE, binary_->header_.machine_type()); + auto reloc = std::unique_ptr(new Relocation( + *rel_hdr, Relocation::PURPOSE::NONE, enc, arch)); + reloc->section_ = applies_to; reloc->symbol_table_ = symbol_table; if (binary_->header().file_type() == Header::FILE_TYPE::REL && diff --git a/src/ELF/Relocation.cpp b/src/ELF/Relocation.cpp index 52510d188d..80c33ea28e 100644 --- a/src/ELF/Relocation.cpp +++ b/src/ELF/Relocation.cpp @@ -63,7 +63,7 @@ Relocation::Relocation(const Relocation& other) : LIEF::Relocation{other}, type_{other.type_}, addend_{other.addend_}, - isRela_{other.isRela_}, + encoding_{other.encoding_}, architecture_{other.architecture_} {} @@ -73,8 +73,9 @@ Relocation& Relocation::operator=(Relocation other) { } template -Relocation::Relocation(const T& header, PURPOSE purpose, ARCH arch) : +Relocation::Relocation(const T& header, PURPOSE purpose, ENCODING enc, ARCH arch) : LIEF::Relocation{header.r_offset, 0}, + encoding_{enc}, architecture_{arch}, purpose_{purpose} { @@ -95,15 +96,14 @@ Relocation::Relocation(const T& header, PURPOSE purpose, ARCH arch) : if constexpr (std::is_same_v || std::is_same_v) { - isRela_ = true; addend_ = header.r_addend; } } -Relocation::Relocation(uint64_t address, TYPE type, bool is_rela) : +Relocation::Relocation(uint64_t address, TYPE type, ENCODING encoding) : LIEF::Relocation(address, 0), type_(type), - isRela_(is_rela) + encoding_(encoding) { if (type != TYPE::UNKNOWN) { auto raw_type = static_cast(type); @@ -141,16 +141,16 @@ Relocation::Relocation(uint64_t address, TYPE type, bool is_rela) : } } -template Relocation::Relocation(const details::Elf32_Rel&, PURPOSE, ARCH); -template Relocation::Relocation(const details::Elf32_Rela&, PURPOSE, ARCH); -template Relocation::Relocation(const details::Elf64_Rel&, PURPOSE, ARCH); -template Relocation::Relocation(const details::Elf64_Rela&, PURPOSE, ARCH); +template Relocation::Relocation(const details::Elf32_Rel&, PURPOSE, ENCODING, ARCH); +template Relocation::Relocation(const details::Elf32_Rela&, PURPOSE, ENCODING, ARCH); +template Relocation::Relocation(const details::Elf64_Rel&, PURPOSE, ENCODING, ARCH); +template Relocation::Relocation(const details::Elf64_Rela&, PURPOSE, ENCODING, ARCH); void Relocation::swap(Relocation& other) { std::swap(address_, other.address_); std::swap(type_, other.type_); std::swap(addend_, other.addend_); - std::swap(isRela_, other.isRela_); + std::swap(encoding_, other.encoding_); std::swap(symbol_, other.symbol_); std::swap(architecture_, other.architecture_); std::swap(purpose_, other.purpose_); diff --git a/src/ELF/SizingInfo.hpp b/src/ELF/SizingInfo.hpp index f7f261309d..f4091889e9 100644 --- a/src/ELF/SizingInfo.hpp +++ b/src/ELF/SizingInfo.hpp @@ -30,6 +30,8 @@ struct sizing_info_t { uint64_t gnu_hash = 0; uint64_t hash = 0; uint64_t rela = 0; + uint64_t relr = 0; + uint64_t android_rela = 0; uint64_t jmprel = 0; uint64_t versym = 0; uint64_t verdef = 0; diff --git a/tests/elf/test_android_packed_relocations.py b/tests/elf/test_android_packed_relocations.py new file mode 100644 index 0000000000..0889d216f9 --- /dev/null +++ b/tests/elf/test_android_packed_relocations.py @@ -0,0 +1,151 @@ +import lief +from utils import get_sample +from pathlib import Path + +def test_chrome_arm64(tmp_path: Path): + chrome_sample = Path(get_sample("ELF/libmonochrome-arm64.so")) + chrome = lief.ELF.parse(chrome_sample) + assert lief.ELF.DynamicEntry.TAG.AARCH64_BTI_PLT in chrome + + packed_relocs = [r for r in chrome.dynamic_relocations if r.is_android_packed] + assert len(packed_relocs) == 145599 + + assert packed_relocs[0].address == 0x6426910 + assert packed_relocs[0].addend == 0x6426910 + assert packed_relocs[0].type == lief.ELF.Relocation.TYPE.AARCH64_RELATIVE + + assert packed_relocs[145576].address == 0x65e3598 + assert packed_relocs[145576].addend == 0 + assert packed_relocs[145576].symbol.name == "memfd_create" + assert packed_relocs[145576].type == lief.ELF.Relocation.TYPE.AARCH64_GLOB_DAT + + assert packed_relocs[145598].address == 0x6646a48 + assert packed_relocs[145598].addend == 0 + assert packed_relocs[145598].symbol.name == "ioctl" + assert packed_relocs[145598].type == lief.ELF.Relocation.TYPE.AARCH64_ABS64 + + out_simple = tmp_path / "chrome_simple.so" + chrome.write(out_simple.as_posix()) + original_size = chrome_sample.stat().st_size + new_size = out_simple.stat().st_size + assert new_size <= original_size + + new = lief.ELF.parse(out_simple.as_posix()) + + new_packed_relocs = [r for r in new.dynamic_relocations if r.is_android_packed] + assert len(new_packed_relocs) == 145599 + + assert new_packed_relocs[0].address == 0x6426910 + assert new_packed_relocs[0].addend == 0x6426910 + assert new_packed_relocs[0].type == lief.ELF.Relocation.TYPE.AARCH64_RELATIVE + + assert new_packed_relocs[145576].address == 0x65e3598 + assert new_packed_relocs[145576].addend == 0 + assert new_packed_relocs[145576].symbol.name == "memfd_create" + assert new_packed_relocs[145576].type == lief.ELF.Relocation.TYPE.AARCH64_GLOB_DAT + + assert new_packed_relocs[145598].address == 0x6646a48 + assert new_packed_relocs[145598].addend == 0 + assert new_packed_relocs[145598].symbol.name == "ioctl" + assert new_packed_relocs[145598].type == lief.ELF.Relocation.TYPE.AARCH64_ABS64 + + chrome_mod = lief.ELF.parse(chrome_sample) + chrome_mod_out = tmp_path / "chrome_mod.so" + builder = lief.ELF.Builder(chrome_mod) + builder.config.force_relocate = True + builder.build() + builder.write(chrome_mod_out.as_posix()) + assert abs(chrome_mod_out.stat().st_size - original_size) < 0x5cf000 + + chrome_mod = lief.ELF.parse(chrome_mod_out) + + mod_packed_relocs = [r for r in chrome_mod.dynamic_relocations if r.is_android_packed] + assert len(mod_packed_relocs) == 145599 + + assert mod_packed_relocs[0].address == 0x6426910 + 0x1000 + assert mod_packed_relocs[0].addend == 0x6426910 + 0x1000 + assert mod_packed_relocs[0].type == lief.ELF.Relocation.TYPE.AARCH64_RELATIVE + + assert mod_packed_relocs[145576].address == 0x65e3598 + 0x1000 + assert mod_packed_relocs[145576].addend == 0 + assert mod_packed_relocs[145576].symbol.name == "memfd_create" + assert mod_packed_relocs[145576].type == lief.ELF.Relocation.TYPE.AARCH64_GLOB_DAT + + assert mod_packed_relocs[145598].address == 0x6646a48 + 0x1000 + assert mod_packed_relocs[145598].addend == 0 + assert mod_packed_relocs[145598].symbol.name == "ioctl" + assert mod_packed_relocs[145598].type == lief.ELF.Relocation.TYPE.AARCH64_ABS64 + + +def test_chrome_armv7(tmp_path: Path): + chrome_sample = Path(get_sample("ELF/libmonochrome-armv7.so")) + chrome = lief.ELF.parse(chrome_sample) + + packed_relocs = [r for r in chrome.dynamic_relocations if r.is_android_packed] + assert len(packed_relocs) == 513897 + + assert packed_relocs[0].address == 0x471b14c + assert packed_relocs[0].addend == 0 + assert packed_relocs[0].type == lief.ELF.Relocation.TYPE.ARM_RELATIVE + + assert packed_relocs[513855].address == 0x49bbad0 + assert packed_relocs[513855].addend == 0 + assert packed_relocs[513855].symbol.name == "android_fdsan_exchange_owner_tag" + assert packed_relocs[513855].type == lief.ELF.Relocation.TYPE.ARM_GLOB_DAT + + assert packed_relocs[513896].address == 0x49fd61c + assert packed_relocs[513896].addend == 0 + assert packed_relocs[513896].symbol.name == "ioctl" + assert packed_relocs[513896].type == lief.ELF.Relocation.TYPE.ARM_ABS32 + + out_simple = tmp_path / "chrome_simple.so" + chrome.write(out_simple.as_posix()) + original_size = chrome_sample.stat().st_size + new_size = out_simple.stat().st_size + assert new_size <= original_size + + new = lief.ELF.parse(out_simple.as_posix()) + + new_packed_relocs = [r for r in new.dynamic_relocations if r.is_android_packed] + assert len(new_packed_relocs) == 513897 + + assert new_packed_relocs[0].address == 0x471b14c + assert new_packed_relocs[0].addend == 0 + assert new_packed_relocs[0].type == lief.ELF.Relocation.TYPE.ARM_RELATIVE + + assert new_packed_relocs[513855].address == 0x49bbad0 + assert new_packed_relocs[513855].addend == 0 + assert new_packed_relocs[513855].symbol.name == "android_fdsan_exchange_owner_tag" + assert new_packed_relocs[513855].type == lief.ELF.Relocation.TYPE.ARM_GLOB_DAT + + assert new_packed_relocs[513896].address == 0x49fd61c + assert new_packed_relocs[513896].addend == 0 + assert new_packed_relocs[513896].symbol.name == "ioctl" + assert new_packed_relocs[513896].type == lief.ELF.Relocation.TYPE.ARM_ABS32 + + chrome_mod = lief.ELF.parse(chrome_sample) + chrome_mod_out = tmp_path / "chrome_mod.so" + builder = lief.ELF.Builder(chrome_mod) + builder.config.force_relocate = True + builder.build() + builder.write(chrome_mod_out.as_posix()) + assert abs(chrome_mod_out.stat().st_size - original_size) < 0x5cf000 + + chrome_mod = lief.ELF.parse(chrome_mod_out) + + mod_packed_relocs = [r for r in chrome_mod.dynamic_relocations if r.is_android_packed] + assert len(mod_packed_relocs) == 513897 + + assert mod_packed_relocs[0].address == 0x471b14c + 0x1000 + assert mod_packed_relocs[0].addend == 0 + assert mod_packed_relocs[0].type == lief.ELF.Relocation.TYPE.ARM_RELATIVE + + assert mod_packed_relocs[513855].address == 0x49bbad0 + 0x1000 + assert mod_packed_relocs[513855].addend == 0 + assert mod_packed_relocs[513855].symbol.name == "android_fdsan_exchange_owner_tag" + assert mod_packed_relocs[513855].type == lief.ELF.Relocation.TYPE.ARM_GLOB_DAT + + assert mod_packed_relocs[513896].address == 0x49fd61c + 0x1000 + assert mod_packed_relocs[513896].addend == 0 + assert mod_packed_relocs[513896].symbol.name == "ioctl" + assert mod_packed_relocs[513896].type == lief.ELF.Relocation.TYPE.ARM_ABS32 diff --git a/tests/elf/test_modify_relocations.py b/tests/elf/test_modify_relocations.py index d4682cf330..c4cef4380d 100644 --- a/tests/elf/test_modify_relocations.py +++ b/tests/elf/test_modify_relocations.py @@ -19,7 +19,8 @@ def test_simple(tmp_path: Path): ls = lief.ELF.parse(sample_path) - relocation = lief.ELF.Relocation(0x61D370, type=lief.ELF.Relocation.TYPE.X86_64_JUMP_SLOT, is_rela=True) + relocation = lief.ELF.Relocation(0x61D370, type=lief.ELF.Relocation.TYPE.X86_64_JUMP_SLOT, + encoding=lief.ELF.Relocation.ENCODING.RELA) symbol = lief.ELF.Symbol() symbol.name = "printf123" @@ -45,7 +46,8 @@ def test_all(tmp_path: Path): target = lief.ELF.parse(sample_path) - relocation = lief.ELF.Relocation(0x201028, type=lief.ELF.Relocation.TYPE.X86_64_JUMP_SLOT, is_rela=True) + relocation = lief.ELF.Relocation(0x201028, type=lief.ELF.Relocation.TYPE.X86_64_JUMP_SLOT, + encoding=lief.ELF.Relocation.ENCODING.RELA) symbol = lief.ELF.Symbol() symbol.name = "printf123" @@ -71,7 +73,8 @@ def test_all32(tmp_path: Path): target = lief.ELF.parse(sample_path) - relocation = lief.ELF.Relocation(0x2018, type=lief.ELF.Relocation.TYPE.X86_JUMP_SLOT, is_rela=False) + relocation = lief.ELF.Relocation(0x2018, type=lief.ELF.Relocation.TYPE.X86_JUMP_SLOT, + encoding=lief.ELF.Relocation.ENCODING.REL) symbol = lief.ELF.Symbol() symbol.name = "printf123" diff --git a/tests/elf/test_parser.py b/tests/elf/test_parser.py index 4aa3d1db4a..32f7cdf995 100644 --- a/tests/elf/test_parser.py +++ b/tests/elf/test_parser.py @@ -1,5 +1,5 @@ import lief -from utils import get_sample, is_64bits_platform +from utils import get_sample, is_64bits_platform, glibc_version from pathlib import Path def test_symbol_count(): @@ -140,4 +140,6 @@ def test_975(): elf = lief.ELF.parse(get_sample('ELF/issue_975_aarch64.o')) for note in elf.notes: print(note) - #assert len(elf.sections) > 0 + + + diff --git a/tests/elf/test_relr_relocations.py b/tests/elf/test_relr_relocations.py new file mode 100644 index 0000000000..5c7ebe2911 --- /dev/null +++ b/tests/elf/test_relr_relocations.py @@ -0,0 +1,43 @@ +import lief +import ctypes +from utils import get_sample, glibc_version +from pathlib import Path + + +def test_relr_relocations(tmp_path: Path): + elf = lief.ELF.parse(get_sample('ELF/libm-ubuntu24.so')) + + relr_reloc = [r for r in elf.relocations if r.encoding == lief.ELF.Relocation.ENCODING.RELR] + assert len(relr_reloc) == 3 + + assert relr_reloc[0].address == 0xe9c48 + assert relr_reloc[0].type == lief.ELF.Relocation.TYPE.X86_64_RELATIVE + + assert relr_reloc[1].address == 0xe9c50 + assert relr_reloc[1].type == lief.ELF.Relocation.TYPE.X86_64_RELATIVE + + assert relr_reloc[2].address == 0xea000 + assert relr_reloc[2].type == lief.ELF.Relocation.TYPE.X86_64_RELATIVE + + out = tmp_path / "libm.so.6" + + builder = lief.ELF.Builder(elf) + builder.config.force_relocate = True + builder.build() + builder.write(out.as_posix()) + + new = lief.ELF.parse(out.as_posix()) + + new_relr_reloc = [r for r in new.relocations if r.encoding == lief.ELF.Relocation.ENCODING.RELR] + assert len(new_relr_reloc) == 3 + + assert new_relr_reloc[0].address == 0x1000 + 0xe9c48 + assert new_relr_reloc[1].address == 0x1000 + 0xe9c50 + assert new_relr_reloc[2].address == 0x1000 + 0xea000 + + if (2, 38) <= glibc_version(): + print("Trying to load libm.so") + out.chmod(0o755) + lib = ctypes.cdll.LoadLibrary(out.as_posix()) + assert lib.cos is not None +