/* Copyright 2017 - 2026 R. Thomas
* Copyright 2017 - 2026 Quarkslab
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <memory>
#include <algorithm>
#include <cstring>
#include "logging.hpp"
#include "LIEF/BinaryStream/VectorStream.hpp"
#include "LIEF/BinaryStream/MemoryStream.hpp"
#include "LIEF/BinaryStream/DumpStream.hpp"
#include "LIEF/utils.hpp"
#include "LIEF/ELF/utils.hpp"
#include "LIEF/ELF/Parser.hpp"
#include "LIEF/ELF/Binary.hpp"
#include "LIEF/ELF/SymbolVersion.hpp"
#include "LIEF/ELF/Segment.hpp"
#include "LIEF/ELF/Section.hpp"
#include "LIEF/ELF/Symbol.hpp"
#include "LIEF/ELF/Note.hpp"
#include "LIEF/ELF/SysvHash.hpp"
#include "ELF/DataHandler/Handler.hpp"
#include "ELF/elf_utils.hpp"
#include "LIEF/ELF/EnumToString.hpp"
#include "Parser.tcc"
#include "internal_utils.hpp"
namespace LIEF::ELF {
struct Target {
Header::CLASS clazz = Header::CLASS::NONE;
ARCH arch = ARCH::NONE;
};
Parser::Parser() = default;
Parser::~Parser() = default;
Parser::Parser(const std::vector<uint8_t>& data, ParserConfig conf) :
stream_{std::make_unique<VectorStream>(data)},
binary_{new Binary{}},
config_{conf} {}
Parser::Parser(std::unique_ptr<BinaryStream> stream, ParserConfig conf) :
stream_{std::move(stream)},
binary_{new Binary{}},
config_{conf} {
if (const auto* memory = stream_->cast<MemoryStream>()) {
memory_address_ = memory->base_address();
}
if (const auto* dump = stream_->cast<DumpStream>()) {
memory_address_ = dump->base_address();
}
}
Parser::Parser(const std::string& file, ParserConfig conf) :
binary_{new Binary{}},
config_{conf} {
if (auto s = VectorStream::from_file(file)) {
stream_ = std::make_unique<VectorStream>(std::move(*s));
}
}
Header::ELF_DATA determine_elf_endianess(ARCH machine) {
switch (machine) {
/* Architectures that are known to be big-endian only */
case ARCH::H8_300:
case ARCH::SPARC:
case ARCH::SPARCV9:
case ARCH::S390:
case ARCH::M68K:
case ARCH::OPENRISC: return Header::ELF_DATA::MSB;
/* Architectures that are known to be little-endian only */
case ARCH::HEXAGON:
case ARCH::ALPHA:
case ARCH::ALTERA_NIOS2:
case ARCH::CRIS:
case ARCH::I386: // x86
case ARCH::X86_64:
case ARCH::LOONGARCH: return Header::ELF_DATA::LSB;
default: return Header::ELF_DATA::NONE;
}
}
/*
* Get the endianness of the current architecture
*/
constexpr Header::ELF_DATA get_endianess() {
#ifdef __BYTE_ORDER__
#if defined(__ORDER_LITTLE_ENDIAN__) && \
(__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)
return Header::ELF_DATA::LSB;
#elif defined(__ORDER_BIG_ENDIAN__) && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
return Header::ELF_DATA::MSB;
#endif
#endif
/* If there is no __BYTE_ORDER__ we take the (arbitrary) decision that we are
* on a little-endian architecture.
*/
return Header::ELF_DATA::LSB;
}
constexpr Header::ELF_DATA invert_endianess(Header::ELF_DATA endian) {
if (endian == Header::ELF_DATA::MSB) {
return Header::ELF_DATA::LSB;
}
if (endian == Header::ELF_DATA::LSB) {
return Header::ELF_DATA::MSB;
}
return Header::ELF_DATA::NONE;
}
Header::ELF_DATA determine_elf_endianess(BinaryStream& stream) {
static constexpr auto BOTH_ENDIANESS = {
ARCH::AARCH64, ARCH::ARM, ARCH::SH, ARCH::XTENSA, ARCH::ARC,
ARCH::MIPS, ARCH::PPC, ARCH::PPC64, ARCH::IA_64,
};
Header::ELF_DATA from_ei_data = Header::ELF_DATA::NONE;
/* ELF_DATA from_e_machine = ELF_DATA::ELFDATANONE; */
// First, check EI_CLASS
if (auto res = stream.peek<Header::identity_t>()) {
auto ident = *res;
uint32_t ei_data = ident[Header::ELI_DATA];
const auto data = static_cast<Header::ELF_DATA>(ei_data);
if (data == Header::ELF_DATA::LSB || data == Header::ELF_DATA::MSB) {
from_ei_data = data;
}
}
// Try to determine the size based on Elf_Ehdr.e_machine
//
// typedef struct {
// unsigned char e_ident[EI_NIDENT]; | +0x00
// uint16_t e_type; | +0x10
// uint16_t e_machine; | +0x12 <------ THIS
// uint32_t e_version; |
// ....
// } ElfN_Ehdr;
constexpr size_t e_machine_off = offsetof(details::Elf32_Ehdr, e_machine);
{
// Read Machine type with both endianness
ARCH machine = ARCH::NONE; // e_machine value without endian swap enabled
ARCH machine_swap = ARCH::NONE; // e_machine value with endian swap enabled
if (auto res = stream.peek<uint16_t>(e_machine_off)) {
machine = static_cast<ARCH>(*res);
}
{
ToggleEndianness swapped(stream);
if (auto res = swapped->peek<uint16_t>(e_machine_off)) {
machine_swap = static_cast<ARCH>(*res);
}
}
LIEF_DEBUG("Machine: '{}' ({:#x})", to_string(machine), (int)machine);
LIEF_DEBUG("Machine swap: '{}' ({:#x})", to_string(machine_swap),
(int)machine);
const Header::ELF_DATA endian = determine_elf_endianess(machine);
const Header::ELF_DATA endian_swap = determine_elf_endianess(machine_swap);
LIEF_DEBUG("Endian: {}", to_string(endian));
LIEF_DEBUG("Endian (swap): {}", to_string(endian_swap));
if (endian != Header::ELF_DATA::NONE) {
return endian;
}
if (endian_swap != Header::ELF_DATA::NONE) {
return endian_swap;
}
{
auto it = std::find(BOTH_ENDIANESS.begin(), BOTH_ENDIANESS.end(), machine);
if (it != BOTH_ENDIANESS.end()) {
return get_endianess();
}
}
{
auto it =
std::find(BOTH_ENDIANESS.begin(), BOTH_ENDIANESS.end(), machine_swap);
if (it != BOTH_ENDIANESS.end()) {
return invert_endianess(get_endianess());
}
}
}
return from_ei_data;
}
bool Parser::should_swap() const {
const Header::ELF_DATA binary_endian = determine_elf_endianess(*stream_);
const Header::ELF_DATA current_endian = get_endianess();
LIEF_DEBUG("LIEF Endianness: '{}'", to_string(current_endian));
LIEF_DEBUG("Binary Endianness: '{}'", to_string(binary_endian));
if (binary_endian != Header::ELF_DATA::NONE &&
current_endian != Header::ELF_DATA::NONE)
{
return binary_endian != current_endian;
}
return false;
}
Target determine_elf_target(BinaryStream& stream) {
auto from_ei_class = Header::CLASS::NONE;
auto from_e_machine = Header::CLASS::NONE;
auto file_type = Header::FILE_TYPE::NONE;
// First, check EI_CLASS
if (auto res = stream.peek<Header::identity_t>()) {
auto ident = *res;
uint32_t ei_class = ident[Header::ELI_CLASS];
const auto typed = Header::CLASS(ei_class);
if (typed == Header::CLASS::ELF32 || typed == Header::CLASS::ELF64) {
from_ei_class = typed;
}
}
constexpr size_t e_type_off = offsetof(details::Elf32_Ehdr, e_type);
if (auto res = stream.peek<uint16_t>(e_type_off)) {
file_type = Header::FILE_TYPE(*res);
}
// Try to determine the size based on Elf_Ehdr.e_machine
//
// typedef struct {
// unsigned char e_ident[EI_NIDENT]; | +0x00
// uint16_t e_type; | +0x10
// uint16_t e_machine; | +0x12 <------ THIS
// uint32_t e_version; |
// ....
// } ElfN_Ehdr;
constexpr size_t e_machine_off = offsetof(details::Elf32_Ehdr, e_machine);
auto machine = ARCH::NONE;
if (auto res = stream.peek<uint16_t>(e_machine_off)) {
machine = (ARCH)*res;
switch (machine) {
case ARCH::AARCH64:
case ARCH::X86_64:
case ARCH::PPC64:
case ARCH::SPARCV9:
{
from_e_machine = Header::CLASS::ELF64;
break;
}
case ARCH::I386:
case ARCH::ARM:
case ARCH::PPC:
{
from_e_machine = Header::CLASS::ELF32;
break;
}
default:
{
from_e_machine = Header::CLASS::NONE;
break;
}
}
}
if (from_e_machine != Header::CLASS::NONE &&
from_ei_class != Header::CLASS::NONE)
{
if (from_e_machine == from_ei_class) {
return {from_ei_class, machine};
}
if (file_type == Header::FILE_TYPE::REL) {
return {from_ei_class, machine};
}
if (machine == ARCH::X86_64 && from_ei_class == Header::CLASS::ELF32) {
LIEF_DEBUG("ELF32-X86_64: x32");
return {from_ei_class, machine};
}
if (machine == ARCH::AARCH64 && from_ei_class == Header::CLASS::ELF32) {
LIEF_DEBUG("ELF32-arm64: arm64ilp32");
return {from_ei_class, machine};
}
LIEF_WARN("ELF class from machine type ('{}') does not match ELF class from "
"e_ident ('{}'). The binary may have been modified.",
to_string(from_e_machine), to_string(from_ei_class));
// Prioritize Elf_Ehdr.e_machine as it is
// this value that is used by the kernel.
return {from_e_machine, machine};
}
if (from_e_machine != Header::CLASS::NONE) {
return {from_e_machine, machine};
}
return {from_ei_class, machine};
}
ok_error_t Parser::init() {
if (stream_ == nullptr) {
LIEF_ERR("Stream not properly initialized");
return make_error_code(lief_errors::parsing_error);
}
binary_->should_swap_ = should_swap();
binary_->original_size_ = stream_->size();
binary_->pagesize_ = config_.page_size;
auto strm = DataHandler::Handler::from_stream(stream_);
if (strm == nullptr) {
LIEF_ERR("Unsupported stream type for the ELF DataHandler");
return make_error_code(lief_errors::not_supported);
}
binary_->datahandler_ = std::move(strm);
auto res_ident = stream_->peek<Header::identity_t>();
if (!res_ident) {
LIEF_ERR("Failed to read ELF identity");
return make_error_code(res_ident.error());
}
LIEF_DEBUG("Should swap: {}", should_swap());
stream_->set_endian_swap(should_swap());
Target elf_target = determine_elf_target(*stream_);
binary_->type_ = elf_target.clazz;
switch (elf_target.clazz) {
case Header::CLASS::ELF32:
{
if (elf_target.arch == ARCH::X86_64) {
return parse_binary<details::ELF32_x32>();
}
if (elf_target.arch == ARCH::AARCH64) {
return parse_binary<details::ELF32_arm64>();
}
return parse_binary<details::ELF32>();
}
case Header::CLASS::ELF64: return parse_binary<details::ELF64>();
case Header::CLASS::NONE:
{
LIEF_ERR("Failed to determine ELF class ({})",
static_cast<size_t>(binary_->type_));
return make_error_code(lief_errors::corrupted);
}
}
return ok();
}
std::unique_ptr<Binary> Parser::parse(const std::string& filename,
const ParserConfig& conf) {
if (!is_elf(filename)) {
return nullptr;
}
Parser parser{filename, conf};
parser.init();
return std::move(parser.binary_);
}
std::unique_ptr<Binary> Parser::parse(const std::vector<uint8_t>& data,
const ParserConfig& conf) {
if (!is_elf(data)) {
return nullptr;
}
Parser parser{data, conf};
parser.init();
return std::move(parser.binary_);
}
std::unique_ptr<Binary> Parser::parse(std::unique_ptr<BinaryStream> stream,
const ParserConfig& conf) {
if (stream == nullptr) {
return nullptr;
}
if (!is_elf(*stream)) {
return nullptr;
}
Parser parser{std::move(stream), conf};
parser.init();
return std::move(parser.binary_);
}
std::unique_ptr<Binary> Parser::parse_from_memory(uintptr_t address,
const ParserConfig& conf) {
static constexpr uint64_t MAX_MEM_IMAGE = 6_GB;
MemoryStream stream(address, 0x4000);
auto elf_info = get_info(stream);
if (!elf_info) {
return nullptr;
}
uint64_t vsize = elf_info->vsize();
if (vsize == 0) {
return nullptr;
}
if (vsize > MAX_MEM_IMAGE) {
LIEF_WARN("In-memory ELF image size ({:#x}) is too large, limiting to {:#x}",
vsize, MAX_MEM_IMAGE);
vsize = MAX_MEM_IMAGE;
}
return parse_from_memory(address, static_cast<size_t>(vsize), conf);
}
std::unique_ptr<Binary> Parser::parse_from_memory(uintptr_t address, size_t size,
const ParserConfig& conf) {
auto stream = std::make_unique<MemoryStream>(address, size);
if (!is_elf(*stream)) {
return nullptr;
}
Parser parser{std::move(stream), conf};
parser.init();
return std::move(parser.binary_);
}
std::unique_ptr<Binary> Parser::parse_from_dump(const std::string& filepath,
uint64_t addr,
const ParserConfig& conf) {
auto stream = VectorStream::from_file(filepath);
if (!stream) {
return nullptr;
}
return parse_from_dump(std::make_unique<VectorStream>(std::move(*stream)), addr,
conf);
}
std::unique_ptr<Binary> Parser::parse_from_dump(BinaryStream& stream,
uint64_t addr,
const ParserConfig& conf) {
return parse(std::make_unique<DumpStream>(addr, stream), conf);
}
std::unique_ptr<Binary>
Parser::parse_from_dump(std::unique_ptr<BinaryStream> stream, uint64_t addr,
const ParserConfig& conf) {
if (stream == nullptr) {
return nullptr;
}
return parse(std::make_unique<DumpStream>(addr, std::move(stream)), conf);
}
ok_error_t Parser::parse_symbol_version(uint64_t symbol_version_offset) {
LIEF_DEBUG("Parsing symbol version");
LIEF_DEBUG("Symbol version offset: {:#x}", symbol_version_offset);
const auto nb_entries = static_cast<uint32_t>(binary_->dynamic_symbols_.size());
stream_->setpos(symbol_version_offset);
for (size_t i = 0; i < nb_entries; ++i) {
auto val = stream_->read<uint16_t>();
if (!val) {
break;
}
binary_->symbol_version_table_.emplace_back(
std::make_unique<SymbolVersion>(*val)
);
}
return ok();
}
result<uint64_t>
Parser::get_dynamic_string_table_from_segments(BinaryStream* stream) const {
const ARCH arch = binary_->header().machine_type();
if (const DynamicEntry* dt_str = binary_->get(DynamicEntry::TAG::STRTAB)) {
return binary_->virtual_address_to_offset(dt_str->value());
}
if (stream != nullptr) {
size_t count = 0;
ScopedStream scope(*stream);
while (*scope) {
if (++count > Parser::NB_MAX_DYNAMIC_ENTRIES) {
break;
}
if (binary_->type_ == Header::CLASS::ELF32) {
auto dt = scope->read<details::Elf32_Dyn>();
if (!dt) {
break;
}
if (DynamicEntry::from_value(dt->d_tag, arch) == DynamicEntry::TAG::STRTAB)
{
return binary_->virtual_address_to_offset(dt->d_un.d_val);
}
} else {
auto dt = scope->read<details::Elf64_Dyn>();
if (!dt) {
break;
}
if (DynamicEntry::from_value(dt->d_tag, arch) == DynamicEntry::TAG::STRTAB)
{
return binary_->virtual_address_to_offset(dt->d_un.d_val);
}
}
}
}
Segment* dyn_segment = binary_->get(Segment::TYPE::DYNAMIC);
if (dyn_segment == nullptr) {
return 0;
}
const uint64_t offset = dyn_segment->file_offset();
const uint64_t size = dyn_segment->physical_size();
stream_->setpos(offset);
if (binary_->type_ == Header::CLASS::ELF32) {
size_t nb_entries = size / sizeof(details::Elf32_Dyn);
for (size_t i = 0; i < nb_entries; ++i) {
auto res = stream_->read<details::Elf32_Dyn>();
if (!res) {
LIEF_ERR("Failed to read dynamic entry #{}", i);
return 0;
}
auto dt = *res;
if (DynamicEntry::from_value(dt.d_tag, arch) == DynamicEntry::TAG::STRTAB) {
return binary_->virtual_address_to_offset(dt.d_un.d_val);
}
}
} else {
size_t nb_entries = size / sizeof(details::Elf64_Dyn);
for (size_t i = 0; i < nb_entries; ++i) {
auto res = stream_->read<details::Elf64_Dyn>();
if (!res) {
LIEF_ERR("Failed to read dynamic entry #{}", i);
return 0;
}
const auto dt = *res;
if (DynamicEntry::from_value(dt.d_tag, arch) == DynamicEntry::TAG::STRTAB) {
return binary_->virtual_address_to_offset(dt.d_un.d_val);
}
}
}
return 0;
}
uint64_t Parser::get_dynamic_string_table_from_sections() const {
// Find Dynamic string section
auto it_dynamic_string_section =
std::find_if(binary_->sections_.begin(), binary_->sections_.end(),
[](const std::unique_ptr<Section>& section) {
return section->name() == ".dynstr" &&
section->type() == Section::TYPE::STRTAB;
});
if (it_dynamic_string_section == binary_->sections_.end()) {
return 0;
}
return (*it_dynamic_string_section)->file_offset();
}
uint64_t Parser::get_dynamic_string_table(BinaryStream* stream) const {
if (auto res = get_dynamic_string_table_from_segments(stream)) {
return *res;
}
return get_dynamic_string_table_from_sections();
}
void Parser::link_symbol_version() {
if (binary_->dynamic_symbols_.size() == binary_->symbol_version_table_.size()) {
for (size_t i = 0; i < binary_->dynamic_symbols_.size(); ++i) {
binary_->dynamic_symbols_[i]->symbol_version_ =
binary_->symbol_version_table_[i].get();
}
}
}
ok_error_t Parser::parse_symbol_sysv_hash(uint64_t offset) {
LIEF_DEBUG("Parsing SYSV hash table");
auto sysvhash = std::make_unique<SysvHash>();
stream_->setpos(offset);
auto res_nbucket = stream_->read<uint32_t>();
if (!res_nbucket) {
LIEF_ERR("Failed to read bucket count");
return make_error_code(lief_errors::read_error);
}
auto res_nchains = stream_->read<uint32_t>();
if (!res_nchains) {
LIEF_ERR("Failed to read chain count");
return make_error_code(lief_errors::read_error);
}
const auto nbuckets = std::min<uint32_t>(*res_nbucket, Parser::NB_MAX_BUCKETS);
const auto nchain = std::min<uint32_t>(*res_nchains, Parser::NB_MAX_CHAINS);
sysvhash->buckets_.reserve(nbuckets);
for (size_t i = 0; i < nbuckets; ++i) {
if (auto bucket = stream_->read<uint32_t>()) {
sysvhash->buckets_.push_back(*bucket);
} else {
LIEF_ERR("Failed to read bucket #{}", i);
break;
}
}
sysvhash->chains_.reserve(nchain);
for (size_t i = 0; i < nchain; ++i) {
if (auto chain = stream_->read<uint32_t>()) {
sysvhash->chains_.push_back(*chain);
} else {
LIEF_ERR("Failed to read chain #{}", i);
break;
}
}
binary_->sysv_hash_ = std::move(sysvhash);
binary_->sizing_info_->hash = stream_->pos() - offset;
return ok();
}
ok_error_t Parser::parse_notes(uint64_t offset, uint64_t size) {
static constexpr auto ERROR_THRESHOLD = 6;
LIEF_DEBUG("Parsing note segment");
stream_->setpos(offset);
uint64_t last_offset = offset + size;
size_t error_count = 0;
if (!*stream_) {
return make_error_code(lief_errors::read_error);
}
while (*stream_ && stream_->pos() < last_offset) {
const auto current_pos = static_cast<int64_t>(stream_->pos());
const Section* sec = binary_->section_from_offset(current_pos);
std::string sec_name = sec != nullptr ? sec->name() : "";
std::unique_ptr<Note> note =
Note::create(*stream_, std::move(sec_name), binary_->header().file_type(),
binary_->header().machine_type(),
binary_->header().identity_class());
if (note != nullptr) {
const auto it_note =
std::find_if(binary_->notes_.begin(), binary_->notes_.end(),
[¬e](const std::unique_ptr<Note>& n) {
return *n == *note;
});
if (it_note == binary_->notes_.end()) { // Not already present
binary_->notes_.push_back(std::move(note));
}
} else {
LIEF_WARN("Failed to parse note");
++error_count;
}
if (error_count > ERROR_THRESHOLD) {
LIEF_ERR("Too many errors while parsing notes");
return make_error_code(lief_errors::corrupted);
}
if (static_cast<int64_t>(stream_->pos()) <= current_pos) {
return make_error_code(lief_errors::corrupted);
}
}
return ok();
}
ok_error_t Parser::parse_overlay() {
const uint64_t last_offset = binary_->eof_offset();
if (last_offset > stream_->size()) {
return ok();
}
const uint64_t overlay_size = stream_->size() - last_offset;
if (overlay_size == 0) {
return ok();
}
LIEF_INFO("Overlay detected at {:#x} ({} bytes)", last_offset, overlay_size);
if (!stream_->peek_data(binary_->overlay_, last_offset, overlay_size)) {
LIEF_WARN("Failed to read overlay data");
return make_error_code(lief_errors::read_error);
}
return ok();
}
bool Parser::check_section_in_segment(const Section& section,
const Segment& segment) {
if (section.virtual_address() > 0) {
const uint64_t seg_vend = segment.virtual_address() + segment.virtual_size();
return segment.virtual_address() <= section.virtual_address() &&
section.virtual_address() + section.size() <= seg_vend;
}
if (section.file_offset() > 0) {
const uint64_t seg_end = segment.file_offset() + segment.physical_size();
return segment.file_offset() <= section.file_offset() &&
section.file_offset() + section.size() <= seg_end;
}
return false;
}
ok_error_t Parser::link_symbol_section(Symbol& sym) {
const uint16_t sec_idx = sym.section_idx();
if (sec_idx == Symbol::SECTION_INDEX::ABS ||
sec_idx == Symbol::SECTION_INDEX::UNDEF)
{
// Nothing to bind
return ok();
}
auto it_section = sections_idx_.find(sec_idx);
if (it_section == sections_idx_.end()) {
return make_error_code(lief_errors::corrupted);
}
sym.section_ = it_section->second;
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("Symbol index #{} out of range for relocation: {}", idx,
to_string(R));
return false;
}
R.symbol_ = binary_->dynamic_symbols_[idx].get();
return true;
}
Relocation& Parser::insert_relocation(std::unique_ptr<Relocation> R) {
R->binary_ = binary_.get();
binary_->relocations_.push_back(std::move(R));
return *binary_->relocations_.back();
}
}