/* 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 "LIEF/Visitor.hpp"
#include "LIEF/utils.hpp"
#include "LIEF/PE/resources/langs.hpp"
#include "LIEF/PE/ResourcesManager.hpp"
#include "LIEF/PE/ResourceNode.hpp"
#include "LIEF/PE/ResourceData.hpp"
#include "LIEF/PE/ResourceDirectory.hpp"
#include "PE/Structures.hpp"
#include "LIEF/BinaryStream/SpanStream.hpp"
#include "LIEF/iostream.hpp"
#include "frozen.hpp"
#include "logging.hpp"
#include "internal_utils.hpp"
#include "fmt_formatter.hpp"
FMT_FORMATTER(LIEF::PE::ResourcesManager::TYPE, LIEF::PE::to_string);
namespace LIEF::PE {
static constexpr auto RESOURCE_TYPES = {
ResourcesManager::TYPE::CURSOR, ResourcesManager::TYPE::BITMAP,
ResourcesManager::TYPE::ICON, ResourcesManager::TYPE::MENU,
ResourcesManager::TYPE::DIALOG, ResourcesManager::TYPE::STRING,
ResourcesManager::TYPE::FONTDIR, ResourcesManager::TYPE::FONT,
ResourcesManager::TYPE::ACCELERATOR, ResourcesManager::TYPE::RCDATA,
ResourcesManager::TYPE::MESSAGETABLE, ResourcesManager::TYPE::GROUP_CURSOR,
ResourcesManager::TYPE::GROUP_ICON, ResourcesManager::TYPE::VERSION,
ResourcesManager::TYPE::DLGINCLUDE, ResourcesManager::TYPE::PLUGPLAY,
ResourcesManager::TYPE::VXD, ResourcesManager::TYPE::ANICURSOR,
ResourcesManager::TYPE::ANIICON, ResourcesManager::TYPE::HTML,
ResourcesManager::TYPE::MANIFEST,
};
std::string ResourcesManager::string_entry_t::string_u8() const {
return u16tou8(string);
}
const ResourceNode*
ResourcesManager::get_node_type(ResourcesManager::TYPE type) const {
ResourceNode::it_childs nodes = resources_->childs();
const auto it_node =
std::find_if(nodes.begin(), nodes.end(), [type](const ResourceNode& node) {
return TYPE(node.id()) == type;
});
if (it_node == nodes.end()) {
return nullptr;
}
return &*it_node;
}
std::vector<ResourcesManager::TYPE> ResourcesManager::get_types() const {
std::vector<TYPE> types;
for (const ResourceNode& node : resources_->childs()) {
const auto it = std::find_if(RESOURCE_TYPES.begin(), RESOURCE_TYPES.end(),
[&node](TYPE t) { return t == TYPE(node.id()); });
if (it != RESOURCE_TYPES.end()) {
types.push_back(*it);
}
}
return types;
}
std::string ResourcesManager::manifest() const {
const ResourceNode* root_node = get_node_type(TYPE::MANIFEST);
if (root_node == nullptr) {
return "";
}
const auto* data_node =
root_node->safe_get_at(0).safe_get_at(0).cast<ResourceData>();
if (data_node == nullptr) {
LIEF_WARN("Corrupted manifest node");
return "";
}
span<const uint8_t> content = data_node->content();
return std::string{content.begin(), content.end()};
}
void ResourcesManager::manifest(const std::string& manifest) {
ResourceNode* manifest_node = get_node_type(TYPE::MANIFEST);
if (manifest_node == nullptr) {
auto L1 = std::make_unique<ResourceDirectory>((int)TYPE::MANIFEST);
auto L2 = std::make_unique<ResourceDirectory>(1);
auto L3 = std::make_unique<ResourceData>(manifest);
(*resources_)
.add_child(std::move(L1))
.add_child(std::move(L2))
.add_child(std::move(L3));
manifest_node = get_node_type(TYPE::MANIFEST);
}
if (manifest_node == nullptr) {
LIEF_WARN("Corrupted manifest node");
return;
}
auto* data_node =
manifest_node->safe_get_at(0).safe_get_at(0).cast<ResourceData>();
if (data_node == nullptr) {
LIEF_WARN("Corrupted manifest node");
return;
}
data_node->content(manifest);
return;
}
std::vector<ResourceVersion> ResourcesManager::version() const {
std::vector<ResourceVersion> result;
const ResourceNode* root_node = get_node_type(TYPE::VERSION);
if (root_node == nullptr) {
return result;
}
for (const ResourceNode& child_l1 : root_node->childs()) {
if (!child_l1.is_directory()) {
continue;
}
for (const ResourceNode& child_l3 : child_l1.childs()) {
const auto* data_node = child_l3.cast<ResourceData>();
if (data_node == nullptr) {
continue;
}
if (auto info = ResourceVersion::parse(*data_node)) {
result.push_back(std::move(*info));
}
}
}
return result;
}
ResourcesManager::it_const_icons ResourcesManager::icons() const {
std::vector<ResourceIcon> icons;
const ResourceNode* root_icon = get_node_type(TYPE::ICON);
const ResourceNode* root_grp_icon = get_node_type(TYPE::GROUP_ICON);
if (root_icon == nullptr) {
LIEF_ERR("Missing '{}' entry", to_string(TYPE::ICON));
return icons;
}
if (root_grp_icon == nullptr) {
LIEF_ERR("Missing '{}' entry", to_string(TYPE::GROUP_ICON));
return icons;
}
for (const ResourceNode& grp_icon_lvl2 : root_grp_icon->childs()) {
for (const ResourceNode& grp_icon_lvl3 : grp_icon_lvl2.childs()) {
const auto* icon_group_node = grp_icon_lvl3.cast<ResourceData>();
if (icon_group_node == nullptr) {
LIEF_WARN("Expected data node for node id {}", grp_icon_lvl3.id());
continue;
}
const uint32_t id = icon_group_node->id();
span<const uint8_t> icon_group_content = icon_group_node->content();
if (icon_group_content.empty()) {
LIEF_INFO("Group icon is empty");
continue;
}
SpanStream stream(icon_group_content);
details::pe_resource_icon_dir group_icon_header{};
if (auto res = stream.read<details::pe_resource_icon_dir>()) {
group_icon_header = *res;
} else {
LIEF_WARN("Failed to read GRPICONDIR for resource node id {}", id);
continue;
}
LIEF_DEBUG("GRPICONDIR.idType: {}", group_icon_header.type);
LIEF_DEBUG("GRPICONDIR.idCount: {}", group_icon_header.count);
// Some checks
if (group_icon_header.type != 1) {
LIEF_ERR("Group icon type should be 1 (got {})", group_icon_header.type);
return icons;
}
for (size_t i = 0; i < group_icon_header.count; ++i) {
details::pe_resource_icon_group entry{};
if (auto res = stream.read<details::pe_resource_icon_group>()) {
entry = *res;
} else {
LIEF_WARN("Failed to read GRPICONDIR.idEntries[{}]", i);
break;
}
ResourceIcon icon = entry;
icon.lang_ = lang_from_id(grp_icon_lvl3.id());
icon.sublang_ = sublang_from_id(grp_icon_lvl3.id());
// Find the icon in the RESOURCE_TYPES::ICON tree that matches entry.ID
ResourceNode::it_const_childs sub_nodes_icons = root_icon->childs();
const auto it =
std::find_if(sub_nodes_icons.begin(), sub_nodes_icons.end(),
[&entry](const ResourceNode& node) {
return node.id() == entry.ID;
});
if (it == sub_nodes_icons.end()) {
LIEF_WARN("Icon with id {:d} not found", entry.ID);
continue;
}
ResourceNode::it_childs icons_childs = it->childs();
if (icons_childs.empty()) {
LIEF_WARN("Resource nodes appear corrupted");
continue;
}
const ResourceNode& icon_node = icons_childs[0];
if (!icon_node.is_data()) {
LIEF_WARN("Expected data node for node id {}", icon_node.id());
continue;
}
span<const uint8_t> pixels =
static_cast<const ResourceData&>(icon_node).content();
icon.pixels_ = std::vector<uint8_t>(pixels.begin(), pixels.end());
icons.push_back(std::move(icon));
}
}
}
return icons;
}
void ResourcesManager::add_icon(const ResourceIcon& icon) {
// Root
// |
// +--+--- DIR/ICON (ID: 3)
// | |
// | +---+-- DIR / ID: 1
// | |
// | +------ DATA / ID: 1033 (LANG) ---> Pixels <-----------+
// . |
// . |
// | |
// +---+-- DIR/ICON_GROUP |
// | | |
// | +---+-- DIR / ID: 1 |
// | | |
// | +------ DATA / ID: 1033 (LANG) ---> pe_resource_icon_group
ResourceNode* icon_node = this->get_node_type(TYPE::ICON);
ResourceNode* icon_grp_node = this->get_node_type(TYPE::GROUP_ICON);
if (icon_node == nullptr) {
LIEF_DEBUG("Missing 'ICON' node: creating");
auto icon_dir = std::make_unique<ResourceDirectory>((int)TYPE::ICON);
auto icon_id = std::make_unique<ResourceDirectory>(1);
icon_dir->add_child(std::move(icon_id));
icon_node = &resources_->add_child(std::move(icon_dir));
}
if (icon_grp_node == nullptr) {
LIEF_DEBUG("Missing 'GROUP_ICON' node: creating");
vector_iostream ios;
ios.reserve(sizeof(details::pe_resource_icon_dir) +
sizeof(details::pe_icon_header));
ios
// pe_resource_icon_dir
.write<uint16_t>(/*reserved*/ 0)
.write<uint16_t>(/*type*/ 1)
.write<uint16_t>(/*count*/ 0);
std::vector<uint8_t> raw_header;
ios.move(raw_header);
auto icon_grp_dir = std::make_unique<ResourceDirectory>((int)TYPE::GROUP_ICON);
auto group = std::make_unique<ResourceDirectory>(0);
auto header_node = std::make_unique<ResourceData>(std::move(raw_header));
group->add_child(std::move(header_node));
icon_grp_dir->add_child(*group);
icon_grp_node = &resources_->add_child(std::move(icon_grp_dir));
}
auto new_id = static_cast<uint16_t>(icon.id());
if ((int32_t)icon.id() < 0) {
new_id = icon_node->childs().size() + 1;
}
auto* icon_group_data =
icon_grp_node->safe_get_at(0).safe_get_at(0).cast<ResourceData>();
auto* icon_dir = icon_node->safe_get_at(0).cast<ResourceDirectory>();
if (icon_group_data == nullptr) {
LIEF_ERR("Icon group headers data node not found");
return;
}
if (icon_dir == nullptr) {
LIEF_ERR("Icon headers data node not found");
return;
}
vector_iostream icon_group = icon_group_data->edit();
if (icon_group.size() < /* reserved + type + count */ 3 * sizeof(uint16_t)) {
LIEF_ERR("Icon group header too small");
return;
}
++icon_group.edit_as<details::pe_resource_icon_dir>()->count;
icon_group.seek_end()
.write<uint8_t>(icon.width())
.write<uint8_t>(icon.height())
.write<uint8_t>(icon.color_count())
.write<uint8_t>(icon.reserved())
.write<uint16_t>(icon.planes())
.write<uint16_t>(icon.bit_count())
.write<uint16_t>(icon.size())
.write<uint16_t>(new_id);
auto pixel_node = std::make_unique<ResourceData>(as_vector(icon.pixels()));
pixel_node->id(static_cast<int>(icon.sublang()) << 10 |
static_cast<int>(icon.lang()));
icon_dir->add_child(std::move(pixel_node));
}
void ResourcesManager::change_icon(const ResourceIcon& original,
const ResourceIcon& newone) {
ResourceNode* icon_node = get_node_type(TYPE::ICON);
ResourceNode* icon_grp_node = get_node_type(TYPE::GROUP_ICON);
if (icon_node == nullptr) {
LIEF_ERR("Missing 'ICON' node");
return;
}
if (icon_grp_node == nullptr) {
LIEF_ERR("Missing 'GROUP_ICON' node");
return;
}
// Update group in which the icon is registered
for (ResourceNode& grp_icon_lvl2 : icon_grp_node->childs()) {
for (ResourceNode& grp_icon_lvl3 : grp_icon_lvl2.childs()) {
auto* icon_group_node = grp_icon_lvl3.cast<ResourceData>();
if (icon_group_node == nullptr) {
LIEF_WARN("Corrupted resource group icon");
continue;
}
vector_iostream editor = icon_group_node->edit();
SpanStream stream = icon_group_node->content();
/* reserved */ stream.read<uint16_t>().value_or(0);
/* type */ stream.read<uint16_t>().value_or(0);
size_t count = stream.read<uint16_t>().value_or(0);
for (size_t i = 0; i < count; ++i) {
const size_t pos = stream.pos();
auto res = stream.read<details::pe_resource_icon_group>();
if (!res) {
LIEF_DEBUG("Icon #{} is corrupted");
return;
}
if (res->ID != original.id()) {
continue;
}
auto* icon_header = editor.edit_as<details::pe_resource_icon_group>(pos);
icon_header->width = newone.width();
icon_header->height = newone.height();
icon_header->color_count = newone.color_count();
icon_header->reserved = newone.reserved();
icon_header->planes = newone.planes();
icon_header->bit_count = newone.bit_count();
icon_header->size = newone.size();
}
}
}
// Then the header
icon_node->delete_child(original.id());
ResourceDirectory new_icon_dir_node(newone.id());
ResourceData new_icon_data_node(as_vector(newone.pixels()));
new_icon_data_node.id((int)encode_lang(newone.lang(), newone.sublang()));
new_icon_dir_node.add_child(new_icon_data_node);
icon_node->add_child(new_icon_dir_node);
}
ResourcesManager::it_const_dialogs ResourcesManager::dialogs() const {
static std::mutex DIALOG_CACHE_MU;
std::scoped_lock lock(DIALOG_CACHE_MU);
dialogs_.clear();
const ResourceNode* dialog_node = get_node_type(TYPE::DIALOG);
if (dialog_node == nullptr) {
return dialogs_;
}
const auto* dialog_dir = dialog_node->cast<ResourceDirectory>();
if (dialog_dir == nullptr) {
LIEF_INFO("Expected directory node for dialog node");
return dialogs_;
}
auto nodes = dialog_dir->childs();
for (size_t i = 0; i < nodes.size(); ++i) {
const auto* dialog = nodes[i].cast<ResourceDirectory>();
if (dialog == nullptr) {
continue;
}
auto langs = dialog->childs();
for (size_t j = 0; j < langs.size(); ++j) {
const auto* data_node = langs[j].cast<ResourceData>();
if (data_node == nullptr) {
LIEF_INFO("Expected data node for child #{}->{}", i, j);
continue;
}
dialogs_t lang_dialog = ResourceDialog::parse(*data_node);
if (lang_dialog.empty()) {
continue;
}
std::move(lang_dialog.begin(), lang_dialog.end(),
std::back_inserter(dialogs_));
}
}
return dialogs_;
}
ResourcesManager::strings_table_t ResourcesManager::string_table() const {
std::vector<string_entry_t> string_table;
const ResourceNode* root_node = get_node_type(TYPE::STRING);
if (root_node == nullptr) {
return string_table;
}
for (const ResourceNode& child_l1 : root_node->childs()) {
for (const ResourceNode& child_l2 : child_l1.childs()) {
const auto* data_node = child_l2.cast<ResourceData>();
if (data_node == nullptr) {
LIEF_WARN("Expected data node for string node id {}", child_l2.id());
continue;
}
span<const uint8_t> content = data_node->content();
if (content.empty()) {
LIEF_ERR("String table content is empty");
continue;
}
SpanStream stream(content);
for (size_t i = 0; i < 16; ++i) {
if (!stream) {
break;
}
auto slen = stream.read<uint16_t>();
if (!slen) {
break;
}
if (*slen == 0) {
continue;
}
std::vector<char16_t> buffer;
if (!stream.read_objects(buffer, *slen)) {
break;
}
std::u16string str(buffer.begin(), buffer.end());
uint32_t id = (child_l1.id() - 1) << 4 | i;
string_table.push_back(string_entry_t{std::move(str), id});
}
}
}
return string_table;
}
std::vector<std::string> ResourcesManager::html() const {
const ResourceNode* root_node = get_node_type(TYPE::HTML);
if (root_node == nullptr) {
LIEF_ERR("Missing '{}' entry", to_string(TYPE::HTML));
return {};
}
std::vector<std::string> html;
for (const ResourceNode& child_l1 : root_node->childs()) {
for (const ResourceNode& child_l2 : child_l1.childs()) {
if (!child_l2.is_data()) {
LIEF_ERR("HTML node corrupted");
continue;
}
const auto& html_node = static_cast<const ResourceData&>(child_l2);
span<const uint8_t> content = html_node.content();
if (content.empty()) {
LIEF_ERR("HTML content is empty");
continue;
}
html.emplace_back(content.begin(), content.end());
}
}
return html;
}
ResourcesManager::it_const_accelerators ResourcesManager::accelerator() const {
std::vector<ResourceAccelerator> accelerator;
const ResourceNode* root_node = get_node_type(TYPE::ACCELERATOR);
if (root_node == nullptr) {
LIEF_ERR("Missing '{}' entry", to_string(TYPE::ACCELERATOR));
return accelerator;
}
for (const ResourceNode& child_l1 : root_node->childs()) {
for (const ResourceNode& child_l2 : child_l1.childs()) {
const auto* accelerator_node = child_l2.cast<ResourceData>();
if (accelerator_node == nullptr) {
LIEF_ERR("Expected data node for node id {}", child_l2.id());
continue;
}
span<const uint8_t> content = accelerator_node->content();
if (content.empty()) {
LIEF_INFO("Accelerator content is empty");
continue;
}
SpanStream stream(content);
while (stream) {
auto res_entry = stream.read<details::pe_resource_acceltableentry>();
if (!res_entry) {
LIEF_ERR("Failed to read pe_resource_acceltableentry");
break;
}
accelerator.emplace_back(*res_entry);
}
if (!accelerator.empty()) {
ResourceAccelerator& acc = accelerator.back();
if (!acc.has(ResourceAccelerator::FLAGS::END)) {
LIEF_ERR("Possibly corrupted accelerator resources");
}
}
}
}
return accelerator;
}
std::string ResourcesManager::print(uint32_t depth) const {
std::ostringstream oss;
uint32_t current_depth = 0;
print_tree(*resources_, oss, current_depth, depth);
return oss.str();
}
void ResourcesManager::print_tree(const ResourceNode& node,
std::ostringstream& output,
uint32_t current_depth, uint32_t max_depth,
const ResourceNode* /*parent*/,
std::string header, bool is_last) const {
static constexpr auto ELBOW = "└──";
static constexpr auto PIPE = "│ ";
static constexpr auto TEE = "├──";
static constexpr auto BLANK = " ";
if (max_depth > 0 && max_depth < current_depth) {
return;
}
std::string type = node.is_directory() ? "Directory" : "Data";
std::string info =
fmt::format("{} ID: {:04d} ({:#06x})", type, node.id(), node.id());
if (node.has_name()) {
info += fmt::format(" name: {}", u16tou8(node.name()));
} else if (std::string ty = to_string(TYPE(node.id()));
ty != "UNKNOWN" && node.depth() == 1)
{
info += fmt::format(" type: {}", ty);
} else if (current_depth == 3) {
uint32_t lang = lang_from_id(node.id());
uint32_t sub_lang = sublang_from_id(node.id());
info += fmt::format(" Lang: {:#04x} / Sublang: {:#04x}", lang, sub_lang);
}
if (const auto* data = node.cast<ResourceData>()) {
const size_t size = data->content().size();
info += fmt::format(" length={} ({:#08x}), offset: {:#06x}", size, size,
data->offset());
}
if (const auto* dir = node.cast<ResourceDirectory>()) {
info += fmt::format(" children={}", dir->childs().size());
}
output << fmt::format("{}{} {}\n", header, is_last ? ELBOW : TEE, info);
if (const auto* data = node.cast<ResourceData>()) {
std::string hex_content;
std::string str_content;
span<const uint8_t> content = data->content();
size_t size = std::min<size_t>(content.size(), 20);
hex_content.reserve(2 * size);
for (size_t i = 0; i < size; ++i) {
hex_content += fmt::format("{:02x}", content[i]);
if (i != (size - 1)) {
hex_content += ':';
}
if (char c = (char)content[i]; is_printable(c)) {
str_content += c;
} else {
str_content += '.';
}
}
output << fmt::format("{}{}{} Hex: {}\n", header, BLANK, TEE, hex_content);
output << fmt::format("{}{}{} Str: {}\n", header, BLANK, ELBOW, str_content);
}
auto children = node.childs();
for (size_t i = 0; i < children.size(); ++i) {
print_tree(children[i], output, current_depth + 1, max_depth, &node,
header + (is_last ? BLANK : PIPE), i == (children.size() - 1));
}
}
void ResourcesManager::accept(Visitor& visitor) const {
visitor.visit(*this);
}
std::ostream& operator<<(std::ostream& os, const ResourcesManager& rsrc) {
os << rsrc.print(3) << '\n';
std::vector<ResourcesManager::TYPE> types = rsrc.get_types();
if (!types.empty()) {
os << fmt::format("Types: {}\n", types);
}
if (const std::string& manifest = rsrc.manifest(); !manifest.empty()) {
os << fmt::format("Manifest:\n{}\n", manifest);
}
for (const ResourceVersion& version : rsrc.version()) {
os << version << '\n';
}
const auto& icons = rsrc.icons();
for (size_t i = 0; i < icons.size(); ++i) {
os << fmt::format("Icon #{:02d}:\n{}\n", i, to_string(icons[i]));
}
const auto& dialogs = rsrc.dialogs();
for (size_t i = 0; i < dialogs.size(); ++i) {
os << fmt::format("Dialog #{:02d}:\n{}\n", i, to_string(dialogs[i]));
}
const auto& str_table = rsrc.string_table();
for (size_t i = 0; i < str_table.size(); ++i) {
const ResourcesManager::string_entry_t& entry = str_table[i];
os << fmt::format("StringTable[{}]: {}", i, LIEF::to_string(entry));
}
return os;
}
const char* to_string(ResourcesManager::TYPE type) {
#define ENTRY(X) std::pair(ResourcesManager::TYPE::X, #X)
STRING_MAP enums2str{
ENTRY(CURSOR), ENTRY(BITMAP), ENTRY(ICON),
ENTRY(MENU), ENTRY(DIALOG), ENTRY(STRING),
ENTRY(FONTDIR), ENTRY(FONT), ENTRY(ACCELERATOR),
ENTRY(RCDATA), ENTRY(MESSAGETABLE), ENTRY(GROUP_CURSOR),
ENTRY(GROUP_ICON), ENTRY(VERSION), ENTRY(DLGINCLUDE),
ENTRY(PLUGPLAY), ENTRY(VXD), ENTRY(ANICURSOR),
ENTRY(ANIICON), ENTRY(HTML), ENTRY(MANIFEST),
};
#undef ENTRY
if (auto it = enums2str.find(type); it != enums2str.end()) {
return it->second;
}
return "UNKNOWN";
}
} // namespace LIEF::PE