#include "resources.h" #include #include #include #include #include namespace raven { std::vector read_resources(PEDirectory directory, std::span bytes, uint32_t image_size, std::vector> *read) { if (!directory.address && !directory.size) return {}; if (!directory.address || !directory.size || bytes.size() != directory.size || directory.address > image_size || directory.size > image_size - directory.address) throw std::runtime_error("invalid PE resource directory extent"); const auto range = [&](size_t offset, size_t size) { if (offset > bytes.size() || size > bytes.size() - offset) throw std::runtime_error("PE resource metadata exceeds directory"); if (read) read->emplace_back(offset, size); }; const auto number = [&](size_t offset, size_t width) { range(offset, width); uint32_t value = 0; for (size_t index = 0; index < width; ++index) value |= uint32_t(bytes[offset + index]) << (index * 8); return value; }; const auto key = [&](uint32_t value) -> ResourceKey { if (!(value & 0x80000000U)) return value; const size_t offset = value & 0x7fffffffU; if (offset % 2) throw std::runtime_error("unaligned PE resource name"); const auto count = number(offset, 2); range(offset + 2, size_t(count) * 2); std::u16string text; text.reserve(count); for (size_t index = 0; index < count; ++index) text += static_cast(number(offset + 2 + index * 2, 2)); return text; }; std::vector resources; std::set visited; size_t entries = 0; size_t result_bytes = 0; std::function walk; walk = [&](uint32_t offset, unsigned depth, ResourceKey type, ResourceKey name) { if (offset % 4 || !visited.insert(offset).second) throw std::runtime_error("unaligned or reused PE resource directory"); range(offset, 16); const auto named = number(size_t(offset) + 12, 2); const auto count = named + number(size_t(offset) + 14, 2); range(size_t(offset) + 16, size_t(count) * 8); // Every entry takes eight bytes of the directory, so the directory's own size bounds them; large resource libraries hold // hundreds of thousands. if (entries + count > bytes.size() / 8) throw std::runtime_error("PE resource entries exceed policy limit"); entries += count; std::optional previous; for (size_t index = 0; index < count; ++index) { const size_t position = size_t(offset) + 16 + index * 8; const auto raw_name = number(position, 4); if (bool(raw_name & 0x80000000U) != (index < named)) throw std::runtime_error("PE resource name and ID partitions differ"); auto identity = key(raw_name); if (previous && previous->index() == identity.index() && !(*previous < identity)) throw std::runtime_error("PE resource entries are unordered or duplicated"); previous = identity; const auto child = number(position + 4, 4); if (depth < 2) { if (!(child & 0x80000000U)) throw std::runtime_error("PE resource tree ends before language"); walk(child & 0x7fffffffU, depth + 1, depth == 0 ? identity : type, depth == 1 ? identity : name); continue; } if ((child & 0x80000000U) || !std::holds_alternative(identity) || child % 4) throw std::runtime_error("invalid PE resource language leaf"); range(child, 16); const auto data = number(child, 4); const auto size = number(size_t(child) + 4, 4); if (data > image_size || size > image_size - data) throw std::runtime_error("PE resource payload exceeds image"); const auto key_size = [](const ResourceKey &value) { const auto *text = std::get_if(&value); return text ? text->size() * 2 : size_t(0); }; const size_t record_size = sizeof(PEResource) + key_size(type) + key_size(name); if (record_size > 64 * 1024 * 1024 - result_bytes) throw std::runtime_error("PE resource identities exceed policy limit"); result_bytes += record_size; resources.push_back({type, name, std::get(identity), directory.address + child, data, size, number(size_t(child) + 8, 4)}); } }; walk(0, 0, uint32_t(0), uint32_t(0)); return resources; } std::vector PEImage::resources() const { const auto directory = directories[2]; if (!directory.address && !directory.size) return {}; auto result = read_resources(directory, file_span(directory.address, directory.size), image_size); for (const auto &resource : result) file_span(resource.data_address, resource.size); return result; } }