/* 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 <iterator>
#include <memory>
#include <string>
#include <numeric>
#include "logging.hpp"
#include "LIEF/BinaryStream/SpanStream.hpp"
#include "LIEF/BinaryStream/MemoryStream.hpp"
#include "LIEF/BinaryStream/DumpStream.hpp"
#include "LIEF/BinaryStream/VectorStream.hpp"
#include "LIEF/PE/signature/Signature.hpp"
#include "LIEF/PE/signature/SignatureParser.hpp"
#include "LIEF/PE/Binary.hpp"
#include "LIEF/PE/DataDirectory.hpp"
#include "LIEF/PE/EnumToString.hpp"
#include "LIEF/PE/Export.hpp"
#include "LIEF/PE/ExportEntry.hpp"
#include "LIEF/PE/Parser.hpp"
#include "LIEF/PE/debug/CodeViewPDB.hpp"
#include "LIEF/PE/debug/Pogo.hpp"
#include "LIEF/PE/debug/Repro.hpp"
#include "LIEF/PE/debug/PogoEntry.hpp"
#include "LIEF/PE/debug/PDBChecksum.hpp"
#include "LIEF/PE/debug/VCFeature.hpp"
#include "LIEF/PE/debug/FPO.hpp"
#include "LIEF/PE/debug/ExDllCharacteristics.hpp"
#include "LIEF/PE/Relocation.hpp"
#include "LIEF/PE/RelocationEntry.hpp"
#include "LIEF/PE/ResourceData.hpp"
#include "LIEF/PE/ResourceDirectory.hpp"
#include "LIEF/PE/ResourceNode.hpp"
#include "LIEF/PE/RichHeader.hpp"
#include "LIEF/PE/Section.hpp"
#include "LIEF/PE/TLS.hpp"
#include "LIEF/PE/utils.hpp"
#include "LIEF/PE/exceptions_info/RuntimeFunctionX64.hpp"
#include "LIEF/COFF/Symbol.hpp"
#include "LIEF/COFF/AuxiliarySymbol.hpp"
#include "internal_utils.hpp"
#include "overflow_check.hpp"
#include "Parser.tcc"
namespace LIEF::PE {
Parser::~Parser() = default;
Parser::Parser() = default;
Parser::Parser(const std::string& file) :
LIEF::Parser{file} {
if (auto stream = VectorStream::from_file(file)) {
stream_ = std::make_unique<VectorStream>(std::move(*stream));
} else {
LIEF_ERR("Failed to create stream");
}
}
Parser::Parser(std::vector<uint8_t> data) :
Parser{std::make_unique<VectorStream>(std::move(data))} {}
Parser::Parser(std::unique_ptr<BinaryStream> stream) :
stream_{std::move(stream)} {}
ok_error_t Parser::init(const ParserConfig& config) {
stream_->setpos(0);
auto type = get_type_from_stream(*stream_);
if (!type) {
LIEF_ERR("Failed to determine PE type");
return make_error_code(lief_errors::parsing_error);
}
type_ = type.value();
binary_ = std::make_unique<Binary>();
binary_->type_ = type_;
binary_->original_size_ = stream_->size();
config_ = config;
return type_ == PE_TYPE::PE32 ? parse<details::PE32>() : parse<details::PE64>();
}
ok_error_t Parser::parse_dos_stub() {
const DosHeader& dos_header = binary_->dos_header();
if (dos_header.addressof_new_exeheader() < sizeof(details::pe_dos_header)) {
LIEF_ERR("Corrupted new exe header address");
return make_error_code(lief_errors::corrupted);
}
const uint64_t sizeof_dos_stub =
dos_header.addressof_new_exeheader() - sizeof(details::pe_dos_header);
LIEF_DEBUG("DOS stub: @{:#x}:{:#x}", sizeof(details::pe_dos_header),
sizeof_dos_stub);
const uint64_t dos_stub_offset = sizeof(details::pe_dos_header);
if (!stream_->peek_data(binary_->dos_stub_, dos_stub_offset, sizeof_dos_stub)) {
LIEF_ERR("DOS stub corrupted");
return make_error_code(lief_errors::read_error);
}
return ok();
}
ok_error_t Parser::parse_rich_header() {
LIEF_DEBUG("Parsing rich header");
span<const uint8_t> dos_stub = binary_->dos_stub();
const SpanStream stream(dos_stub);
const auto* it_rich = std::search(std::begin(dos_stub), std::end(dos_stub),
std::begin(RichHeader::RICH_MAGIC),
std::end(RichHeader::RICH_MAGIC));
if (it_rich == std::end(dos_stub)) {
LIEF_DEBUG("Rich header not found");
return ok();
}
auto rich_header = std::make_unique<RichHeader>();
const uint64_t end_offset_rich_header = std::distance(dos_stub.begin(), it_rich);
LIEF_DEBUG("Offset to rich header: {:#x}", end_offset_rich_header);
if (auto res_xor_key = stream.peek<uint32_t>(end_offset_rich_header +
sizeof(RichHeader::RICH_MAGIC)))
{
rich_header->key(*res_xor_key);
} else {
return make_error_code(lief_errors::read_error);
}
const uint32_t xor_key = rich_header->key();
LIEF_DEBUG("XOR key: {:#x}", xor_key);
int64_t curent_offset = end_offset_rich_header - sizeof(RichHeader::RICH_MAGIC);
std::vector<uint32_t> values;
values.reserve(dos_stub.size() / sizeof(uint32_t));
uint32_t count = 0;
uint32_t value = 0;
while (curent_offset > 0 && stream.pos() < stream.size()) {
if (auto res_count = stream.peek<uint32_t>(curent_offset)) {
count = *res_count ^ xor_key;
} else {
break;
}
curent_offset -= sizeof(uint32_t);
if (auto res_value = stream.peek<uint32_t>(curent_offset)) {
value = *res_value ^ xor_key;
} else {
break;
}
curent_offset -= sizeof(uint32_t);
if (value == 0 && count == 0) { // Skip padding entry
continue;
}
if (value == RichHeader::DANS_MAGIC_NUMBER ||
count == RichHeader::DANS_MAGIC_NUMBER)
{
break;
}
const uint16_t build_number = value & 0xFFFF;
const uint16_t id = (value >> 16) & 0xFFFF;
LIEF_DEBUG("ID: {:#06x}", id);
LIEF_DEBUG("Build Number: {:#06x}", build_number);
LIEF_DEBUG("Count: {:d}", count);
rich_header->add_entry(id, build_number, count);
}
binary_->rich_header_ = std::move(rich_header);
return ok();
}
ok_error_t Parser::read_section_content(const details::pe_section& raw_sec,
uint32_t index, Section& section) {
uint32_t numberof_sections = binary_->header().numberof_sections();
uint32_t size_to_read = 0;
const uint32_t offset = raw_sec.PointerToRawData;
const bool is_memory_view = stream_->is_memory_view();
if (is_memory_view) {
size_to_read = raw_sec.VirtualSize;
} else {
size_to_read = raw_sec.VirtualSize > 0 ?
std::min(raw_sec.VirtualSize,
raw_sec.SizeOfRawData) : // According to Corkami
raw_sec.SizeOfRawData;
}
if (size_to_read == 0) {
return ok();
}
const uint64_t peek_target =
is_memory_view ? section.virtual_address() : (uint64_t)offset;
if (peek_target < stream_->size() &&
(peek_target + size_to_read) > stream_->size())
{
size_to_read = static_cast<uint32_t>(stream_->size() - peek_target);
}
if (size_to_read > Parser::MAX_DATA_SIZE) {
LIEF_WARN("Section '{}' data too large ({:#x})", section.name(), size_to_read);
return make_error_code(lief_errors::data_too_large);
}
if (!stream_->peek_data(section.content_, peek_target, size_to_read,
section.virtual_address()))
{
LIEF_ERR("Corrupted section #{:d} ({})", index, section.name());
}
uint64_t padding_size = 0;
if (size_to_read <= section.size()) {
padding_size = section.size() - size_to_read;
}
const uint64_t padding_offset = (uint64_t)offset + size_to_read;
// Treat content between two sections (that is not wrapped in a section) as
// 'padding'
uint64_t hole_size = 0;
if (!is_memory_view && index < numberof_sections - 1) {
// As we *read* at the beginning of the loop, the cursor is already on the next
// one
auto res_next_section = stream_->peek<details::pe_section>();
if (!res_next_section) {
LIEF_ERR("Failed to read section #{}", index + 1);
} else {
const details::pe_section& next_section = *res_next_section;
const uint64_t sec_offset = next_section.PointerToRawData;
if (padding_offset + padding_size < sec_offset) {
hole_size = sec_offset - (padding_offset + padding_size);
}
}
}
uint64_t padding_to_read = padding_size + hole_size;
if (!is_memory_view && padding_to_read > MAX_PADDING_SIZE) {
LIEF_WARN("Padding of section '{}' too large, "
"limiting to {} bytes",
section.name(), Parser::MAX_PADDING_SIZE);
padding_to_read = MAX_PADDING_SIZE;
}
if (is_memory_view) {
padding_to_read = 0;
}
if (!stream_->peek_data(section.padding_, padding_offset, padding_to_read)) {
LIEF_ERR("Failed to read padding of section '{}'", section.name());
}
return ok();
}
ok_error_t Parser::parse_sections() {
static constexpr size_t NB_MAX_SECTIONS = 1000;
static constexpr uint32_t SENTINEL = std::numeric_limits<uint32_t>::max();
LIEF_DEBUG("Parsing sections");
const uint32_t pe_header_off = binary_->dos_header().addressof_new_exeheader();
const uint32_t opt_header_off = pe_header_off + sizeof(details::pe_header);
const uint32_t sections_offset =
opt_header_off + binary_->header().sizeof_optional_header();
uint32_t first_section_offset = SENTINEL;
uint32_t numberof_sections = binary_->header().numberof_sections();
if (numberof_sections > NB_MAX_SECTIONS) {
LIEF_ERR("PE binary has {} sections, exceeding limit of {}. "
"Only the first {} will be parsed",
numberof_sections, NB_MAX_SECTIONS, NB_MAX_SECTIONS);
numberof_sections = NB_MAX_SECTIONS;
}
stream_->setpos(sections_offset);
for (size_t i = 0; i < numberof_sections; ++i) {
details::pe_section raw_sec{};
if (auto res = stream_->read<details::pe_section>()) {
raw_sec = *res;
} else {
LIEF_ERR("Failed to read section at {:#x}", stream_->pos());
break;
}
auto section = std::make_unique<Section>(raw_sec);
const uint32_t offset = raw_sec.PointerToRawData;
if (offset > 0) {
first_section_offset = std::min(first_section_offset, offset);
}
read_section_content(raw_sec, i, *section);
if (const std::string& name = section->name();
name.size() > 1 && name[0] == '/')
{
char* endptr = nullptr;
uint32_t offset = std::strtol(name.c_str() + 1, &endptr, /*base=*/10);
if (COFF::String* coff_str = binary_->find_coff_string(offset)) {
section->coff_string_ = coff_str;
}
}
binary_->sections_.push_back(std::move(section));
}
// At this point we can bind the binary to the stream (if needed)
stream_->bind_binary(*binary_);
const uint32_t last_section_header_offset =
sections_offset + numberof_sections * sizeof(details::pe_section);
if (first_section_offset == SENTINEL ||
first_section_offset < last_section_header_offset)
{
binary_->available_sections_space_ = -1;
LIEF_DEBUG("No usable section header padding (first section offset: {:#x})",
first_section_offset);
return ok();
}
const size_t padding_size = first_section_offset - last_section_header_offset;
if (!stream_->peek_data(binary_->section_offset_padding_,
last_section_header_offset, padding_size))
{
LIEF_ERR("Failed to read section header padding");
}
binary_->available_sections_space_ =
(int32_t)(padding_size / sizeof(details::pe_section)) - 1;
LIEF_DEBUG("Number of sections that could be added: #{:d}",
binary_->available_sections_space_);
return ok();
}
ok_error_t Parser::parse_relocations() {
static constexpr size_t MAX_RELOCATION_ENTRIES = 100000;
LIEF_DEBUG("Parsing relocations");
const DataDirectory* reloc_dir = binary_->relocation_dir();
const Header::MACHINE_TYPES arch = binary_->header().machine();
if (reloc_dir == nullptr) {
return make_error_code(lief_errors::not_found);
}
const uint32_t offset = binary_->rva_to_offset(reloc_dir->RVA());
const uint32_t max_size = reloc_dir->size();
const uint32_t max_offset = offset + max_size;
auto res_relocation_headers =
stream_->peek<details::pe_base_relocation_block>(offset);
if (!res_relocation_headers) {
return make_error_code(lief_errors::read_error);
}
uint32_t current_offset = offset;
while (res_relocation_headers && current_offset < max_offset &&
res_relocation_headers->PageRVA != 0)
{
const details::pe_base_relocation_block& raw_struct = *res_relocation_headers;
auto relocation = std::make_unique<Relocation>(raw_struct);
if (raw_struct.BlockSize < sizeof(details::pe_base_relocation_block)) {
LIEF_ERR("Corrupted relocation: BlockSize too small ({})",
raw_struct.BlockSize);
break;
}
if (raw_struct.BlockSize > binary_->optional_header().sizeof_image()) {
LIEF_ERR("Corrupted relocation: BlockSize exceeds binary virtual size: "
"{0} ({0:#10x})",
raw_struct.BlockSize);
break;
}
size_t numberof_entries =
(raw_struct.BlockSize - sizeof(details::pe_base_relocation_block)) /
sizeof(uint16_t);
if (numberof_entries > MAX_RELOCATION_ENTRIES) {
LIEF_WARN("Relocation entry count ({}) exceeds limit ({}), "
"only parsing first {}",
numberof_entries, MAX_RELOCATION_ENTRIES, MAX_RELOCATION_ENTRIES);
numberof_entries = MAX_RELOCATION_ENTRIES;
}
stream_->setpos(current_offset + sizeof(details::pe_base_relocation_block));
for (size_t i = 0; i < numberof_entries; ++i) {
auto res_entry = stream_->read<uint16_t>();
if (!res_entry) {
LIEF_ERR("Failed to parse relocation entry #{}", i);
break;
}
uint16_t data = *res_entry;
uint16_t pos = RelocationEntry::get_position(data);
RelocationEntry::BASE_TYPES ty = RelocationEntry::type_from_data(arch, data);
auto entry = std::make_unique<RelocationEntry>(pos, ty);
entry->relocation_ = relocation.get();
relocation->entries_.push_back(std::move(entry));
}
binary_->relocations_.push_back(std::move(relocation));
current_offset += raw_struct.BlockSize;
res_relocation_headers =
stream_->peek<details::pe_base_relocation_block>(current_offset);
}
return ok();
}
ok_error_t Parser::parse_resources() {
LIEF_DEBUG("Parsing resources");
const DataDirectory* res_dir = binary_->rsrc_dir();
if (res_dir == nullptr) {
return make_error_code(lief_errors::not_found);
}
const uint32_t resources_rva = res_dir->RVA();
LIEF_DEBUG("Resources RVA: {:#06x}", resources_rva);
const uint32_t offset = binary_->rva_to_offset(resources_rva);
LIEF_DEBUG("Resources Offset: {:#06x}", offset);
ScopedStream scoped(*stream_, offset);
binary_->resources_ = ResourceNode::parse(*scoped, *binary_);
if (binary_->resources_ == nullptr) {
LIEF_WARN("Failed to parse resource tree");
return make_error_code(lief_errors::read_error);
}
return ok();
}
ok_error_t Parser::parse_string_table() {
// PE is using the "Symbol16" format
static constexpr auto SYMBOL16_SZ = 18;
if (stream_->is_memory_view()) {
return ok();
}
const Header& hdr = binary_->header();
if (hdr.pointerto_symbol_table() == 0) {
return ok();
}
LIEF_DEBUG("Parsing string table");
const uint64_t string_tbl_offset =
static_cast<uint64_t>(hdr.pointerto_symbol_table()) +
static_cast<uint64_t>(hdr.numberof_symbols()) * SYMBOL16_SZ;
if (string_tbl_offset + sizeof(uint32_t) > stream_->size()) {
LIEF_DEBUG("COFF string table offset ({:#x}) is out of bounds",
string_tbl_offset);
return ok();
}
LIEF_DEBUG("String table offset: {:#010x}", string_tbl_offset);
stream_->setpos(string_tbl_offset);
auto table_sz = stream_->read<uint32_t>();
if (!table_sz) {
return make_error_code(table_sz.error());
}
if (*table_sz <= 4) {
return ok();
}
std::vector<uint8_t> buffer;
if (auto is_ok = stream_->read_data(buffer, *table_sz - 4); !is_ok) {
return make_error_code(is_ok.error());
}
SpanStream string_strm(buffer);
while (string_strm) {
size_t pos = string_strm.pos() + 4;
auto str = string_strm.read_string();
if (!str) {
break;
}
LIEF_DEBUG("string[{:#08x}]: {}", pos, *str);
memoize(COFF::String(pos, std::move(*str)));
}
LIEF_DEBUG("#{} strings found", binary_->strings_table_.size());
return ok();
}
ok_error_t Parser::parse_symbols() {
LIEF_DEBUG("Parsing symbols");
const Header& hdr = binary_->header();
if (stream_->is_memory_view()) {
return ok();
}
if (hdr.pointerto_symbol_table() == 0 || hdr.numberof_symbols() == 0) {
return ok();
}
const uint32_t nb_symbols = hdr.numberof_symbols();
const uint32_t symtab_off = hdr.pointerto_symbol_table();
stream_->setpos(symtab_off);
COFF::Symbol::parsing_context_t ctx{/*.find_string =*/[this](uint32_t offset) {
return this->find_coff_string(offset);
},
/*is_bigobj=*/false};
for (size_t idx = 0; idx < nb_symbols;) {
std::unique_ptr<COFF::Symbol> sym = COFF::Symbol::parse(ctx, *stream_, &idx);
if (sym == nullptr) {
LIEF_ERR("Failed to parse COFF symbol #{}", idx);
break;
}
binary_->symbols_.push_back(std::move(sym));
}
return ok();
}
span<uint8_t> get_payload(Binary& bin, const details::pe_debug& dbg,
Section*& sec) {
if (dbg.SizeOfData == 0) {
return {};
}
LIEF_DEBUG("Payload RVA: {:#08x}", dbg.AddressOfRawData);
LIEF_DEBUG("Payload offset: {:#08x}", dbg.PointerToRawData);
LIEF_DEBUG("Payload size: {:#08x}", dbg.SizeOfData);
sec = bin.section_from_offset(dbg.PointerToRawData);
if (sec == nullptr) {
sec = bin.section_from_rva(dbg.AddressOfRawData);
}
// Can be in the "overlay" area
if (sec == nullptr && !bin.overlay().empty() &&
bin.overlay_offset() <= dbg.PointerToRawData)
{
if (dbg.PointerToRawData == 0) {
return {};
}
span<uint8_t> overlay = bin.overlay();
int32_t delta = (int32_t)dbg.PointerToRawData - (int32_t)bin.overlay_offset();
if (delta < 0 || (uint32_t)delta >= overlay.size()) {
return {};
}
if (check_overflow<uint64_t>((uint32_t)delta, dbg.SizeOfData, overlay.size()))
{
return {};
}
return overlay.subspan((uint32_t)delta, dbg.SizeOfData);
}
if (sec == nullptr) {
LIEF_WARN("Section not found for debug payload at offset "
"{:#010x}, VA {:#010x}",
dbg.PointerToRawData, dbg.AddressOfRawData);
return {};
}
return Debug::get_payload(*sec, dbg);
}
ok_error_t Parser::parse_debug() {
LIEF_DEBUG("Parsing debug directory");
DataDirectory* dir = binary_->debug_dir();
if (dir == nullptr) {
return ok();
}
if (dir->RVA() == 0 || dir->size() == 0) {
return ok();
}
const uint32_t debug_rva = dir->RVA();
uint32_t debug_off = binary_->rva_to_offset(debug_rva);
const uint32_t debug_sz = dir->size();
const uint32_t debug_end = debug_off + debug_sz;
if (debug_sz == 0) {
return ok();
}
stream_->setpos(debug_off);
while (stream_->pos() < debug_end) {
auto res = stream_->read<details::pe_debug>();
if (!res) {
break;
}
Section* sec = nullptr;
span<uint8_t> payload = get_payload(*binary_, *res, sec);
const auto type = static_cast<Debug::TYPES>(res->Type);
LIEF_DEBUG("Type is: {}", to_string(type));
switch (type) {
case Debug::TYPES::CODEVIEW:
{
if (std::unique_ptr<Debug> cv = parse_code_view(*res, sec, payload)) {
binary_->debug_.push_back(std::move(cv));
} else {
LIEF_WARN("Failed to parse CodeView debug entry");
}
break;
}
case Debug::TYPES::POGO:
{
if (std::unique_ptr<Debug> pogo = parse_pogo(*res, sec, payload)) {
binary_->debug_.push_back(std::move(pogo));
} else {
LIEF_WARN("Failed to parse POGO debug entry");
}
break;
}
case Debug::TYPES::REPRO:
{
if (std::unique_ptr<Debug> repro = parse_repro(*res, sec, payload)) {
binary_->debug_.push_back(std::move(repro));
} else {
LIEF_WARN("Failed to parse Repro debug entry");
}
break;
}
case Debug::TYPES::PDBCHECKSUM:
{
if (std::unique_ptr<Debug> checksum =
PDBChecksum::parse(*res, sec, payload))
{
binary_->debug_.push_back(std::move(checksum));
break;
}
LIEF_WARN("Failed to parse PE PDB checksum");
break;
}
case Debug::TYPES::VC_FEATURE:
{
if (std::unique_ptr<Debug> vcfeature =
VCFeature::parse(*res, sec, payload))
{
binary_->debug_.push_back(std::move(vcfeature));
break;
}
LIEF_WARN("Failed to parse PE VC Feature");
break;
}
case Debug::TYPES::EX_DLLCHARACTERISTICS:
{
if (std::unique_ptr<Debug> exdll =
ExDllCharacteristics::parse(*res, sec, payload))
{
binary_->debug_.push_back(std::move(exdll));
break;
}
LIEF_WARN("Failed to parse PE EX_DLLCHARACTERISTICS");
break;
}
case Debug::TYPES::FPO:
{
if (std::unique_ptr<Debug> fpo = FPO::parse(*res, sec, payload)) {
binary_->debug_.push_back(std::move(fpo));
break;
}
LIEF_WARN("Failed to parse PE FPO");
break;
}
default:
{
binary_->debug_.push_back(std::make_unique<Debug>(*res, sec));
break;
}
}
}
return ok();
}
ok_error_t Parser::parse_exceptions() {
if (!config_.parse_exceptions) {
return ok();
}
const DataDirectory* exception_dir = binary_->exceptions_dir();
if (exception_dir == nullptr) {
return ok();
}
if (exception_dir->RVA() == 0 || exception_dir->size() == 0) {
return ok();
}
LIEF_DEBUG("Parsing exceptions [{:#08x}, {:#08x}] ({} bytes)",
exception_dir->RVA(), exception_dir->RVA() + exception_dir->size(),
exception_dir->size());
uint32_t base_offset = binary_->rva_to_offset(exception_dir->RVA());
span<const uint8_t> pdata = exception_dir->content();
if (pdata.empty()) {
LIEF_DEBUG("{}:{}", __FUNCTION__, __LINE__);
return make_error_code(lief_errors::read_error);
}
LIEF_DEBUG("Span size: {}", pdata.size());
LIEF_DEBUG("Section size: {}", exception_dir->section()->sizeof_raw_data());
std::unique_ptr<SpanStream> stream = exception_dir->stream();
if (stream == nullptr) {
LIEF_DEBUG("{}:{}", __FUNCTION__, __LINE__);
return make_error_code(lief_errors::corrupted);
}
[[maybe_unused]] size_t idx = 0;
while (*stream) {
auto ptr = ExceptionInfo::parse(*this, *stream);
if (ptr == nullptr) {
LIEF_INFO("Failed to parse exception info index: {}", idx);
break;
}
ptr->offset(base_offset + ptr->offset());
binary_->exceptions_.push_back(std::move(ptr));
++idx;
}
for (auto& [f, rva] : unresolved_chains_) {
if (auto* func = f->as<RuntimeFunctionX64>()) {
if (auto it = memoize_exception_info_.find(rva);
it == memoize_exception_info_.end())
{
LIEF_DEBUG("RuntimeFunctionX64 {:#08x}: chained info not found at {:#08x}",
func->rva_start(), rva);
continue;
} else {
assert(func->unwind_info() != nullptr);
func->unwind_info()->chained = it->second->as<RuntimeFunctionX64>();
}
}
}
unresolved_chains_.clear();
if (!parse_chpe_exceptions()) {
LIEF_INFO("CHPE exception parsing finished with errors");
}
return ok();
}
ok_error_t Parser::parse_chpe_exceptions() {
const LoadConfiguration* lconf = binary_->load_configuration();
if (lconf == nullptr) {
return ok();
}
const CHPEMetadata* metadata = lconf->chpe_metadata();
if (metadata == nullptr) {
return ok();
}
const auto* arm64 = metadata->as<CHPEMetadataARM64>();
if (arm64 == nullptr) {
return ok();
}
if (arm64->extra_rfe_table() == 0 || arm64->extra_rfe_table_size() == 0) {
return ok();
}
std::unique_ptr<SpanStream> stream =
stream_from_rva(arm64->extra_rfe_table(), arm64->extra_rfe_table_size());
if (stream == nullptr) {
return make_error_code(lief_errors::read_error);
}
uint64_t base_offset = binary_->rva_to_offset(arm64->extra_rfe_table());
Header::MACHINE_TYPES target_arch = bin().header().machine();
switch (target_arch) {
// ARM64EC
case Header::MACHINE_TYPES::AMD64:
target_arch = Header::MACHINE_TYPES::ARM64;
break;
// ARM64X: CHPE is used to refer to ARM64EC binary (which uses an AMD64 machine
// type)
case Header::MACHINE_TYPES::ARM64:
target_arch = Header::MACHINE_TYPES::AMD64;
break;
default: break;
}
[[maybe_unused]] size_t idx = 0;
while (*stream) {
auto ptr = ExceptionInfo::parse(*this, *stream, target_arch);
if (ptr == nullptr) {
LIEF_INFO("Failed to parse exception info index: {}", idx);
break;
}
ptr->offset(base_offset + ptr->offset());
binary_->exceptions_.push_back(std::move(ptr));
++idx;
}
return ok();
}
std::unique_ptr<Debug> Parser::parse_code_view(const details::pe_debug& debug_info,
Section* sec,
span<uint8_t> payload) {
LIEF_DEBUG("Parsing Debug Code View (payload: {} bytes)", payload.size());
SpanStream stream(payload);
auto res_sig = stream.peek<uint32_t>();
if (!res_sig) {
return nullptr;
}
const auto signature = static_cast<CodeView::SIGNATURES>(*res_sig);
auto default_value = std::make_unique<CodeView>(debug_info, signature, sec);
switch (signature) {
case CodeView::SIGNATURES::PDB_70:
{
const auto pdb_s = stream.read<details::pe_pdb_70>();
if (!pdb_s) {
return default_value;
}
auto cv_pdb70 = std::make_unique<CodeViewPDB>(debug_info, *pdb_s, sec);
if (auto fname = stream.read_string()) {
cv_pdb70->filename(std::move(*fname));
}
return cv_pdb70;
}
case CodeView::SIGNATURES::PDB_20:
{
const auto pdb_s = stream.read<details::pe_pdb_20>();
if (!pdb_s) {
return default_value;
}
auto cv_pdb20 = std::make_unique<CodeViewPDB>(debug_info, *pdb_s, sec);
if (auto fname = stream.read_string()) {
cv_pdb20->filename(std::move(*fname));
}
return cv_pdb20;
}
default:
{
LIEF_INFO("CodeView signature '{}' not yet implemented",
to_string(signature));
}
}
return default_value;
}
std::unique_ptr<Debug> Parser::parse_pogo(const details::pe_debug& debug_info,
Section* sec, span<uint8_t> payload) {
LIEF_DEBUG("Parsing POGO");
SpanStream stream(payload);
auto res_sig = stream.read<uint32_t>();
if (!res_sig) {
return nullptr;
}
const auto signature = static_cast<Pogo::SIGNATURES>(*res_sig);
auto pogo = std::make_unique<Pogo>(debug_info, signature, sec);
switch (signature) {
case Pogo::SIGNATURES::ZERO: // zero-signature may contain valid entries
case Pogo::SIGNATURES::LCTG:
case Pogo::SIGNATURES::SPGO:
case Pogo::SIGNATURES::PGI:
case Pogo::SIGNATURES::PGU:
{
while (stream) {
auto raw = stream.read<details::pe_pogo>();
if (!raw) {
break;
}
PogoEntry entry{raw->start_rva, raw->size};
if (auto name = stream.read_string()) {
entry.name(std::move(*name));
}
pogo->add(std::move(entry));
stream.align(4);
}
return pogo;
}
case Pogo::SIGNATURES::UNKNOWN:
default:
{
LIEF_WARN("POGO signature {:#x} not yet implemented", *res_sig);
}
}
return pogo;
}
std::unique_ptr<Debug> Parser::parse_repro(const details::pe_debug& debug_info,
Section* sec, span<uint8_t> payload) {
LIEF_DEBUG("Parsing Debug Repro");
if (payload.empty()) {
return std::make_unique<Repro>(debug_info, sec);
}
SpanStream stream(payload);
auto res_size = stream.read<uint32_t>();
if (!res_size) {
return nullptr;
}
LIEF_DEBUG("Size: {:#x}", *res_size);
std::vector<uint8_t> hash;
if (!stream.read_data(hash, *res_size)) {
LIEF_INFO("Failed to read reproducible build hash");
}
return std::make_unique<Repro>(debug_info, std::move(hash), sec);
}
inline result<uint32_t> address_table_value(BinaryStream& stream,
uint32_t address_table_offset,
size_t i) {
using element_t = uint32_t;
const size_t element_offset = address_table_offset + i * sizeof(element_t);
if (auto res = stream.peek<element_t>(element_offset)) {
return *res;
}
return make_error_code(lief_errors::read_error);
}
inline result<uint16_t> ordinal_table_value(BinaryStream& stream,
uint32_t ordinal_table_offset,
size_t i) {
using element_t = uint16_t;
const size_t element_offset = ordinal_table_offset + i * sizeof(element_t);
if (auto res = stream.peek<element_t>(element_offset)) {
return *res;
}
return make_error_code(lief_errors::read_error);
}
inline result<uint32_t> name_table_value(BinaryStream& stream,
uint32_t name_table_offset, size_t i) {
using element_t = uint32_t;
const size_t element_offset = name_table_offset + i * sizeof(element_t);
if (auto res = stream.peek<element_t>(element_offset)) {
return *res;
}
return make_error_code(lief_errors::read_error);
}
ok_error_t Parser::parse_exports() {
LIEF_DEBUG("Parsing exports");
static constexpr uint32_t NB_ENTRIES_LIMIT = 0x1000000;
static constexpr size_t MAX_EXPORT_NAME_SIZE = 4096; // Because of C++ mangling
struct range_t {
uint32_t start;
uint32_t end;
};
const DataDirectory* export_dir = binary_->export_dir();
if (export_dir == nullptr) {
return make_error_code(lief_errors::not_found);
}
uint32_t exports_rva = export_dir->RVA();
uint32_t exports_size = export_dir->size();
uint32_t exports_offset = binary_->rva_to_offset(exports_rva);
range_t range = {exports_rva, exports_rva + exports_size};
// First Export directory
auto export_dir_tbl =
stream_->peek<details::pe_export_directory_table>(exports_offset);
if (!export_dir_tbl) {
LIEF_WARN("Failed to read export table at {:#x}", exports_offset);
return make_error_code(lief_errors::read_error);
}
auto export_object = std::make_unique<Export>(*export_dir_tbl);
uint32_t name_offset = binary_->rva_to_offset(export_dir_tbl->NameRVA);
if (auto res_name =
stream_->peek_string_at(name_offset, Parser::MAX_DLL_NAME_SIZE))
{
std::string name = *res_name;
if (is_valid_dll_name(name)) {
export_object->name_ = std::move(name);
LIEF_DEBUG("Export name {}@{:#x}", export_object->name_, name_offset);
} else {
if (name.empty()) {
LIEF_DEBUG("Export name is empty");
} else {
LIEF_DEBUG("Invalid export name '{}'", printable_string(name));
}
}
} else {
LIEF_INFO("DLL name appears corrupted");
}
const uint32_t nbof_addr_entries = export_dir_tbl->AddressTableEntries;
const uint32_t nbof_name_ptr = export_dir_tbl->NumberOfNamePointers;
const uint16_t ordinal_base = export_dir_tbl->OrdinalBase;
const uint32_t address_table_offset =
binary_->rva_to_offset(export_dir_tbl->ExportAddressTableRVA);
const uint32_t ordinal_table_offset =
binary_->rva_to_offset(export_dir_tbl->OrdinalTableRVA);
const uint32_t name_table_offset =
binary_->rva_to_offset(export_dir_tbl->NamePointerRVA);
LIEF_DEBUG("Number of entries: {}", nbof_addr_entries);
LIEF_DEBUG("Number of names ptr: {}", nbof_name_ptr);
LIEF_DEBUG("Ordinal Base: {}", ordinal_base);
LIEF_DEBUG("External Range: {:#08x} - {:#08x}", range.start, range.end);
if (nbof_addr_entries > NB_ENTRIES_LIMIT) {
LIEF_WARN("Export.AddressTableEntries is too large ({})", nbof_addr_entries);
return make_error_code(lief_errors::corrupted);
}
if (nbof_name_ptr > NB_ENTRIES_LIMIT) {
LIEF_WARN("Export.NumberOfNamePointers is too large ({})", nbof_name_ptr);
return make_error_code(lief_errors::corrupted);
}
Export::entries_t export_entries;
export_entries.reserve(nbof_addr_entries);
std::set<uint32_t> corrupted_entries; // Ordinal value of corrupted entries
/*
* First, process the Export address table.
* This table is an array of RVAs
*/
for (size_t i = 0; i < nbof_addr_entries; ++i) {
uint32_t addr_value = 0;
if (auto res = address_table_value(*stream_, address_table_offset, i)) {
addr_value = *res;
} else {
LIEF_WARN("Failed to read Export.address_table[{}]", i);
break;
}
LIEF_DEBUG("Export.address_table[{}].addr_value: {:#06x}", i, addr_value);
const uint16_t ordinal = i + ordinal_base;
const bool is_extern = range.start <= addr_value && addr_value < range.end;
const uint32_t address = is_extern ? 0 : addr_value;
auto entry =
std::make_unique<ExportEntry>(address, is_extern, ordinal, addr_value);
if (addr_value == 0) {
corrupted_entries.insert(ordinal);
}
if (is_extern && addr_value > 0) {
uint32_t name_offset = binary_->rva_to_offset(addr_value);
if (auto res = stream_->peek_string_at(name_offset)) {
entry->name_ = std::move(*res);
if (entry->name_.size() > MAX_EXPORT_NAME_SIZE ||
!is_printable(entry->name_))
{
LIEF_INFO("Invalid export name '{}'", printable_string(entry->name_));
entry = std::make_unique<ExportEntry>(address, is_extern, ordinal,
addr_value);
entry->name_.clear();
}
}
}
export_entries.push_back(std::move(entry));
}
for (size_t i = 0; i < nbof_name_ptr; ++i) {
uint16_t ordinal = 0;
if (auto res = ordinal_table_value(*stream_, ordinal_table_offset, i)) {
ordinal = *res;
} else {
LIEF_WARN("Failed to read Export.ordinal_table[{}]", i);
break;
}
if (ordinal >= export_entries.size()) {
LIEF_WARN(
"Ordinal value ordinal_table[{}]: {} out of range for export entries", i,
ordinal
);
break;
}
ExportEntry& entry = *export_entries[ordinal];
if (entry.name_.empty()) {
uint32_t name_offset = 0;
if (auto res = name_table_value(*stream_, name_table_offset, i)) {
name_offset = binary_->rva_to_offset(*res);
} else {
LIEF_WARN("Failed to read Export.name_table[{}]", i);
corrupted_entries.insert(entry.ordinal_);
continue;
}
LIEF_DEBUG("names[{:03d}]: {:#08x}", i, name_offset);
if (auto res = stream_->peek_string_at(name_offset)) {
std::string name = *res;
if (name.empty() || name.size() > MAX_EXPORT_NAME_SIZE) {
if (!name.empty()) {
LIEF_WARN("Invalid export name '{}'", printable_string(name));
}
corrupted_entries.insert(entry.ordinal_);
} else {
entry.name_ = std::move(name);
}
} else {
LIEF_WARN("Failed to read Export.entries[{}].name at {:#x}", i,
name_offset);
corrupted_entries.insert(entry.ordinal_);
}
}
if (entry.is_extern_ && !entry.name_.empty()) {
std::string fwd_str = entry.name_;
std::string function = fwd_str;
std::string library;
// Split on '.'
const size_t dot_pos = fwd_str.find('.');
if (dot_pos != std::string::npos) {
library = fwd_str.substr(0, dot_pos);
function = fwd_str.substr(dot_pos + 1);
if (auto name_rva = name_table_value(*stream_, name_table_offset, i)) {
uint32_t name_offset = binary_->rva_to_offset(*name_rva);
if (auto name = stream_->peek_string_at(name_offset)) {
const bool is_valid = !name->empty() &&
name->size() <= MAX_EXPORT_NAME_SIZE &&
is_printable(*name);
if (is_valid) {
entry.name_ = std::move(*name);
}
}
}
}
entry.set_forward_info(std::move(library), std::move(function));
}
}
for (std::unique_ptr<ExportEntry>& entry : export_entries) {
if (corrupted_entries.count(entry->ordinal()) != 0) {
continue;
}
export_object->max_ordinal_ =
std::max<uint32_t>(export_object->max_ordinal_, entry->ordinal());
export_object->entries_.push_back(std::move(entry));
}
binary_->export_ = std::move(export_object);
return ok();
}
ok_error_t Parser::parse_signature() {
LIEF_DEBUG("Parsing signature");
if (stream_->is_memory_view()) {
return ok();
}
static constexpr size_t SIZEOF_HEADER = 8;
/*** /!\ In this data directory, RVA is used as an **OFFSET** /!\ ****/
/*********************************************************************/
const DataDirectory* cert_dir = binary_->cert_dir();
if (cert_dir == nullptr) {
return make_error_code(lief_errors::not_found);
}
const uint32_t signature_offset = cert_dir->RVA();
const uint32_t signature_size = cert_dir->size();
const uint64_t end_p = signature_offset + signature_size;
LIEF_DEBUG("Signature Offset: {:#06x}", signature_offset);
LIEF_DEBUG("Signature Size: {:#06x}", signature_size);
stream_->setpos(signature_offset);
while (stream_->pos() < end_p) {
const uint64_t current_p = stream_->pos();
uint32_t length = 0;
uint16_t revision = 0;
uint16_t certificate_type = 0;
if (auto res = stream_->read<uint32_t>()) {
length = *res;
} else {
return make_error_code(res.error());
}
if (length <= SIZEOF_HEADER) {
LIEF_WARN("Signature appears corrupted");
break;
}
if (auto res = stream_->read<uint16_t>()) {
revision = *res;
} else {
LIEF_ERR("Failed to parse signature revision");
break;
}
if (auto res = stream_->read<uint16_t>()) {
certificate_type = *res;
} else {
LIEF_ERR("Failed to read certificate type");
break;
}
LIEF_DEBUG("Signature {}r{:#x} ({:#x} bytes)", certificate_type, revision,
length);
std::vector<uint8_t> raw_signature;
if (!stream_->read_data(raw_signature, length - SIZEOF_HEADER)) {
LIEF_INFO("Failed to read {:#x} bytes", length);
break;
}
if (auto sign = SignatureParser::parse(std::move(raw_signature))) {
binary_->signatures_.push_back(std::move(*sign));
} else {
LIEF_INFO("Failed to parse signature");
}
stream_->align(8);
if (stream_->pos() <= current_p) {
break;
}
}
return ok();
}
ok_error_t Parser::parse_overlay() {
LIEF_DEBUG("Parsing Overlay");
if (stream_->is_memory_view()) {
return ok();
}
const uint64_t last_section_offset = std::accumulate(
binary_->sections_.begin(), binary_->sections_.end(), uint64_t{0u},
[](uint64_t offset, const std::unique_ptr<Section>& section) {
return std::max<uint64_t>(section->offset() + section->size(), offset);
}
);
LIEF_DEBUG("Overlay offset: {:#x}", last_section_offset);
if (last_section_offset < stream_->size()) {
const uint64_t overlay_size = stream_->size() - last_section_offset;
LIEF_DEBUG("Overlay size: {:#x}", overlay_size);
if (stream_->peek_data(binary_->overlay_, last_section_offset, overlay_size)) {
binary_->overlay_offset_ = last_section_offset;
}
}
return ok();
}
std::unique_ptr<Binary> Parser::parse(const std::string& filename,
const ParserConfig& conf) {
if (!is_pe(filename)) {
return nullptr;
}
Parser parser{filename};
parser.init(conf);
return std::move(parser.binary_);
}
std::unique_ptr<Binary> Parser::parse(std::vector<uint8_t> data,
const ParserConfig& conf) {
if (!is_pe(data)) {
return nullptr;
}
Parser parser{std::move(data)};
parser.init(conf);
return std::move(parser.binary_);
}
std::unique_ptr<Binary> Parser::parse(const uint8_t* buffer, size_t size,
const ParserConfig& conf) {
auto strm = std::make_unique<SpanStream>(buffer, size);
return parse(std::move(strm), conf);
}
std::unique_ptr<Binary> Parser::parse(std::unique_ptr<BinaryStream> stream,
const ParserConfig& conf) {
if (stream == nullptr) {
return nullptr;
}
if (!is_pe(*stream)) {
return nullptr;
}
Parser parser{std::move(stream)};
parser.init(conf);
return std::move(parser.binary_);
}
bool Parser::is_valid_import_name(const std::string& name) {
if (name.empty() || name.size() > MAX_IMPORT_NAME_SIZE) {
return false;
}
const bool valid_chars =
std::all_of(name.begin(), name.end(), [](char c) { return ::isprint(c); });
return valid_chars;
}
bool Parser::is_valid_dll_name(const std::string& name) {
/// @brief Minimum size for a DLL's name
static constexpr unsigned MIN_DLL_NAME_SIZE = 4;
if (name.size() < MIN_DLL_NAME_SIZE || name.size() > Parser::MAX_DLL_NAME_SIZE) {
return false;
}
if (!is_printable(name)) {
return false;
}
return true;
}
void Parser::memoize(ExceptionInfo& info) {
memoize_exception_info_[info.rva_start()] = &info;
}
void Parser::memoize(COFF::String str) {
const uint32_t offset = str.offset();
memoize_coff_str_[offset] = binary_->strings_table_.size();
binary_->strings_table_.push_back(std::move(str));
}
COFF::String* Parser::find_coff_string(uint32_t offset) const {
if (auto it = memoize_coff_str_.find(offset); it != memoize_coff_str_.end()) {
return &binary_->strings_table_[it->second];
}
return nullptr;
}
std::unique_ptr<SpanStream> Parser::stream_from_rva(uint32_t rva, size_t size) {
Section* sec = bin().section_from_rva(rva);
if (sec == nullptr) {
return std::make_unique<SpanStream>(nullptr, 0);
}
span<const uint8_t> content = sec->content();
if (content.empty()) {
return std::make_unique<SpanStream>(content);
}
int64_t delta = (int64_t)rva - (int64_t)sec->virtual_address();
if (delta < 0) {
return std::make_unique<SpanStream>(nullptr, 0);
}
if ((uint64_t)delta >= content.size() ||
((uint64_t)delta + size) > content.size())
{
return std::make_unique<SpanStream>(nullptr, 0);
}
if (check_overflow<uint64_t>((uint64_t)delta, (uint64_t)size, content.size())) {
return std::make_unique<SpanStream>(nullptr, 0);
}
return std::make_unique<SpanStream>(size > 0 ? content.subspan(delta, size) :
content.subspan(delta));
}
void Parser::record_relocation(uint32_t rva, span<const uint8_t> data) {
uint64_t value = 0;
switch (data.size()) {
case sizeof(uint8_t):
value = *reinterpret_cast<const uint8_t*>(data.data());
break;
case sizeof(uint16_t):
value = *reinterpret_cast<const uint16_t*>(data.data());
break;
case sizeof(uint32_t):
value = *reinterpret_cast<const uint32_t*>(data.data());
break;
case sizeof(uint64_t):
value = *reinterpret_cast<const uint64_t*>(data.data());
break;
default:
LIEF_DEBUG("Error: {}:{}: unsupported size ({})", __FUNCTION__, __LINE__,
data.size());
return;
}
uint32_t offset = bin().rva_to_offset(rva);
LIEF_DEBUG("ARM64X[{:#010x}]: {:#010x} ({} bytes)", rva, value, data.size());
dyn_hdr_relocs_.insert({offset, {data.size(), value}});
}
ok_error_t Parser::record_delta_relocation(uint32_t rva, int64_t delta,
size_t size) {
uint32_t offset = bin().rva_to_offset(rva);
switch (size) {
case sizeof(uint8_t):
{
auto value = stream_->peek<uint8_t>(offset);
if (!value) {
return make_error_code(value.error());
}
LIEF_DEBUG("ARM64X[{:#010x}]: {:#010x} (1 bytes)", rva,
(uint64_t)((int64_t)*value + delta));
dyn_hdr_relocs_.insert({offset,
{size, (uint64_t)((int64_t)*value + delta)}});
return ok();
}
case sizeof(uint16_t):
{
auto value = stream_->peek<uint16_t>(offset);
if (!value) {
return make_error_code(value.error());
}
LIEF_DEBUG("ARM64X[{:#010x}]: {:#010x} (2 bytes)", rva,
(uint64_t)((int64_t)*value + delta));
dyn_hdr_relocs_.insert({offset,
{size, (uint64_t)((int64_t)*value + delta)}});
return ok();
}
case sizeof(uint32_t):
{
auto value = stream_->peek<uint32_t>(offset);
if (!value) {
return make_error_code(value.error());
}
LIEF_DEBUG("ARM64X[{:#010x}]: {:#010x} (4 bytes)", rva,
(uint64_t)((int64_t)*value + delta));
dyn_hdr_relocs_.insert({offset,
{size, (uint64_t)((int64_t)*value + delta)}});
return ok();
}
case sizeof(uint64_t):
{
auto value = stream_->peek<uint64_t>(offset);
if (!value) {
return make_error_code(value.error());
}
LIEF_DEBUG("ARM64X[{:#010x}]: {:#010x} (8 bytes)", rva,
(uint64_t)((int64_t)*value + delta));
dyn_hdr_relocs_.insert({offset,
{size, (uint64_t)((int64_t)*value + delta)}});
return ok();
}
default:
LIEF_DEBUG("Error: {}:{}: unsupported size ({})", __FUNCTION__, __LINE__,
size);
return make_error_code(lief_errors::not_supported);
}
return make_error_code(lief_errors::not_supported);
}
std::unique_ptr<Binary> Parser::parse_from_memory(uintptr_t address,
const ParserConfig& config) {
static constexpr size_t MAX_SIZE = std::numeric_limits<size_t>::max() >> 2;
return parse_from_memory(address, MAX_SIZE, config);
}
std::unique_ptr<Binary> Parser::parse_from_memory(uintptr_t address, size_t size,
const ParserConfig& config) {
auto stream = std::make_unique<MemoryStream>(address, size);
if (!is_pe(*stream)) {
return nullptr;
}
Parser parser{std::move(stream)};
parser.init(config);
return std::move(parser.binary_);
}
std::unique_ptr<Binary> Parser::parse_from_dump(const std::string& filepath,
uint64_t addr,
const ParserConfig& config) {
auto stream = VectorStream::from_file(filepath);
if (!stream) {
return nullptr;
}
return parse_from_dump(std::make_unique<VectorStream>(std::move(*stream)), addr,
config);
}
std::unique_ptr<Binary> Parser::parse_from_dump(BinaryStream& stream,
uint64_t addr,
const ParserConfig& config) {
return parse(std::make_unique<DumpStream>(addr, stream), config);
}
std::unique_ptr<Binary>
Parser::parse_from_dump(std::unique_ptr<BinaryStream> stream, uint64_t addr,
const ParserConfig& config) {
if (stream == nullptr) {
return nullptr;
}
return parse(std::make_unique<DumpStream>(addr, std::move(stream)), config);
}
}