/* 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 <sstream>
#include "LIEF/utils.hpp"
#include "LIEF/Visitor.hpp"
#include "LIEF/PE/ResourceData.hpp"
#include "LIEF/PE/resources/ResourceVersion.hpp"
#include "LIEF/PE/resources/ResourceStringFileInfo.hpp"
#include "LIEF/PE/resources/ResourceVarFileInfo.hpp"
#include "LIEF/BinaryStream/BinaryStream.hpp"
#include "LIEF/BinaryStream/SpanStream.hpp"
#include "logging.hpp"
#include "frozen.hpp"
#include "fmt_formatter.hpp"
FMT_FORMATTER(LIEF::PE::ResourceVersion::fixed_file_info_t::VERSION_OS,
LIEF::PE::to_string);
FMT_FORMATTER(LIEF::PE::ResourceVersion::fixed_file_info_t::FILE_TYPE,
LIEF::PE::to_string);
FMT_FORMATTER(LIEF::PE::ResourceVersion::fixed_file_info_t::FILE_TYPE_DETAILS,
LIEF::PE::to_string);
FMT_FORMATTER(LIEF::PE::ResourceVersion::fixed_file_info_t::FILE_FLAGS,
LIEF::PE::to_string);
namespace LIEF::PE {
static constexpr auto FILE_FLAGS_ARRAY = {
ResourceVersion::fixed_file_info_t::FILE_FLAGS::DEBUG,
ResourceVersion::fixed_file_info_t::FILE_FLAGS::INFO_INFERRED,
ResourceVersion::fixed_file_info_t::FILE_FLAGS::PATCHED,
ResourceVersion::fixed_file_info_t::FILE_FLAGS::PRERELEASE,
ResourceVersion::fixed_file_info_t::FILE_FLAGS::PRIVATEBUILD,
ResourceVersion::fixed_file_info_t::FILE_FLAGS::SPECIALBUILD,
};
// True if the stream points to a StringFileInfo structure
inline bool is_string_file_info(BinaryStream& strm) {
ScopedStream scope(strm);
[[maybe_unused]] auto wLength = scope->read<uint16_t>().value_or(0);
[[maybe_unused]] auto wValueLength = scope->read<uint16_t>().value_or(0);
auto wType = scope->read<uint16_t>().value_or(-1);
auto szKey = scope->read_u16string().value_or(std::u16string());
if (wType != 0 && wType != 1) {
return false;
}
return u16tou8(szKey) == "StringFileInfo";
}
// True if the stream points to a VarFileInfo structure
inline bool is_var_file_info(BinaryStream& strm) {
ScopedStream scope(strm);
[[maybe_unused]] auto wLength = scope->read<uint16_t>().value_or(0);
[[maybe_unused]] auto wValueLength = scope->read<uint16_t>().value_or(0);
auto wType = scope->read<uint16_t>().value_or(-1);
auto szKey = scope->read_u16string().value_or(std::u16string());
if (wType != 0 && wType != 1) {
return false;
}
return u16tou8(szKey) == "VarFileInfo";
}
result<ResourceVersion> ResourceVersion::parse(const ResourceData& node) {
span<const uint8_t> content = node.content();
if (content.empty()) {
return make_error_code(lief_errors::corrupted);
}
return parse(content.data(), content.size());
}
result<ResourceVersion> ResourceVersion::parse(const uint8_t* p, size_t sz) {
SpanStream strm(p, sz);
return parse(strm);
}
result<ResourceVersion> ResourceVersion::parse(BinaryStream& stream) {
// typedef struct {
// WORD wLength;
// WORD wValueLength;
// WORD wType;
// WCHAR szKey;
// WORD Padding1;
// VS_FIXEDFILEINFO Value;
// WORD Padding2;
// WORD Children;
// } VS_VERSIONINFO;
ResourceVersion version;
// The length, in bytes, of the VS_VERSIONINFO structure.
// This length does not include any padding that aligns any subsequent version
// resource data on a 32-bit boundary.
auto wLength = stream.read<uint16_t>();
if (!wLength) {
return make_error_code(wLength.error());
}
// The length, in bytes, of the Value member. This value is zero if there is
// no Value member associated with the current version structure.
auto wValueLength = stream.read<uint16_t>();
if (!wValueLength) {
return make_error_code(wValueLength.error());
}
// The type of data in the version resource. This member is 1 if the version
// resource contains text data and 0 if the version resource contains binary
// data.
auto wType = stream.read<uint16_t>();
if (!wType) {
return make_error_code(wType.error());
}
if (*wType != 0 && *wType != 1) {
LIEF_WARN("VS_VERSIONINFO.wType should be 0 or 1, got {}", *wType);
return make_error_code(lief_errors::corrupted);
}
// The Unicode string L"VS_VERSION_INFO".
auto szKey = stream.read_u16string();
if (!szKey) {
return make_error_code(wType.error());
}
std::string szKey_u8 = u16tou8(*szKey);
if (szKey_u8 != "VS_VERSION_INFO") {
LIEF_WARN("VS_VERSIONINFO.szKey should be 'VS_VERSION_INFO', got {}",
szKey_u8);
return make_error_code(lief_errors::corrupted);
}
// Contains as many zero words as necessary to align the Value member on a 32-bit
// boundary.
stream.align(sizeof(uint32_t));
// Type: VS_FIXEDFILEINFO
// Arbitrary data associated with this VS_VERSIONINFO structure.
// The wValueLength member specifies the length of this member;
// if wValueLength is zero, this member does not exist.
if (*wValueLength > 0) {
std::vector<uint8_t> Value;
if (auto is_ok = stream.read_data(Value, *wValueLength); !is_ok) {
return make_error_code(is_ok.error());
}
SpanStream fixed_strm(Value);
if (auto is_ok = parse_fixed_file_info(version, fixed_strm); !is_ok) {
LIEF_WARN("Failed to parse VS_FIXEDFILEINFO structure");
}
}
// As many zero words as necessary to align the Children member on a
// 32-bit boundary. These bytes are not included in wValueLength.
// This member is optional.
stream.align(sizeof(uint32_t));
version.type(*wType).key(std::move(*szKey));
// Children:
// An array of zero or one StringFileInfo structures, and zero or one
// VarFileInfo structures that are children of the current VS_VERSIONINFO
// structure.
if (auto is_ok = parse_children(version, stream); !is_ok) {
LIEF_WARN("Failed to parse VS_VERSIONINFO children");
}
LIEF_DEBUG("Bytes left: {}", stream.size() - stream.pos());
return version;
}
ok_error_t ResourceVersion::parse_fixed_file_info(ResourceVersion& version,
BinaryStream& stream) {
// typedef struct tagVS_FIXEDFILEINFO {
// DWORD dwSignature;
// DWORD dwStrucVersion;
// DWORD dwFileVersionMS;
// DWORD dwFileVersionLS;
// DWORD dwProductVersionMS;
// DWORD dwProductVersionLS;
// DWORD dwFileFlagsMask;
// DWORD dwFileFlags;
// DWORD dwFileOS;
// DWORD dwFileType;
// DWORD dwFileSubtype;
// DWORD dwFileDateMS;
// DWORD dwFileDateLS;
// } VS_FIXEDFILEINFO;
// Contains the value 0xFEEF04BD. This is used with the szKey member of the
// VS_VERSIONINFO structure when searching a file for the VS_FIXEDFILEINFO
// structure.
auto dwSignature = stream.read<uint32_t>();
if (!dwSignature) {
return make_error_code(dwSignature.error());
}
if (*dwSignature != fixed_file_info_t::SIGNATURE_VALUE) {
LIEF_WARN("VS_FIXEDFILEINFO.dwSignature: expecting {:#010x}, got: {:#010x}",
fixed_file_info_t::SIGNATURE_VALUE, *dwSignature);
}
auto dwStrucVersion = stream.read<uint32_t>();
if (!dwStrucVersion) {
return make_error_code(dwStrucVersion.error());
}
auto dwFileVersionMS = stream.read<uint32_t>();
if (!dwFileVersionMS) {
return make_error_code(dwFileVersionMS.error());
}
auto dwFileVersionLS = stream.read<uint32_t>();
if (!dwFileVersionLS) {
return make_error_code(dwFileVersionLS.error());
}
auto dwProductVersionMS = stream.read<uint32_t>();
if (!dwProductVersionMS) {
return make_error_code(dwProductVersionMS.error());
}
auto dwProductVersionLS = stream.read<uint32_t>();
if (!dwProductVersionLS) {
return make_error_code(dwProductVersionLS.error());
}
auto dwFileFlagsMask = stream.read<uint32_t>();
if (!dwFileFlagsMask) {
return make_error_code(dwFileFlagsMask.error());
}
auto dwFileFlags = stream.read<uint32_t>();
if (!dwFileFlags) {
return make_error_code(dwFileFlags.error());
}
auto dwFileOS = stream.read<uint32_t>();
if (!dwFileOS) {
return make_error_code(dwFileOS.error());
}
auto dwFileType = stream.read<uint32_t>();
if (!dwFileType) {
return make_error_code(dwFileType.error());
}
auto dwFileSubtype = stream.read<uint32_t>();
if (!dwFileSubtype) {
return make_error_code(dwFileSubtype.error());
}
auto dwFileDateMS = stream.read<uint32_t>();
if (!dwFileDateMS) {
return make_error_code(dwFileDateMS.error());
}
auto dwFileDateLS = stream.read<uint32_t>();
if (!dwFileDateLS) {
return make_error_code(dwFileDateLS.error());
}
version.fixed_file_info_.signature = *dwSignature;
version.fixed_file_info_.struct_version = *dwStrucVersion;
version.fixed_file_info_.file_version_ms = *dwFileVersionMS;
version.fixed_file_info_.file_version_ls = *dwFileVersionLS;
version.fixed_file_info_.product_version_ms = *dwProductVersionMS;
version.fixed_file_info_.product_version_ls = *dwProductVersionLS;
version.fixed_file_info_.file_flags_mask = *dwFileFlagsMask;
version.fixed_file_info_.file_flags = *dwFileFlags;
version.fixed_file_info_.file_os = *dwFileOS;
version.fixed_file_info_.file_type = *dwFileType;
version.fixed_file_info_.file_subtype = *dwFileSubtype;
version.fixed_file_info_.file_date_ms = *dwFileDateMS;
version.fixed_file_info_.file_date_ls = *dwFileDateLS;
return ok();
}
inline std::string version_to_str(uint32_t lsb, uint32_t msb) {
return fmt::format("{}-{}-{}-{}", (msb >> 16) & 0xFFFF, msb & 0xFFFF,
(lsb >> 16) & 0xFFFF, lsb & 0xFFFF);
}
std::string ResourceVersion::fixed_file_info_t::to_string() const {
static constexpr auto WIDTH = 20;
std::ostringstream oss;
oss << fmt::format("{:{}}: {:#08x}\n", "Struct Version", WIDTH, struct_version);
oss << fmt::format("{:{}}: {}\n", "File version", WIDTH,
version_to_str(file_version_ls, file_version_ms));
oss << fmt::format("{:{}}: {}\n", "Product version", WIDTH,
version_to_str(product_version_ls, product_version_ms));
if (file_flags != 0) {
oss << fmt::format("{:{}}: {} ({:#010x})\n", "Flags", WIDTH,
fmt::to_string(flags()), file_flags);
}
oss << fmt::format("{:{}}: {} ({:#010x})\n", "File OS", WIDTH,
VERSION_OS(file_os), file_os);
oss << fmt::format("{:{}}: {} ({:#010x})\n", "File Type", WIDTH,
FILE_TYPE(file_type), file_type);
if (file_subtype > 0) {
oss << fmt::format("{:{}}: {} ({:#010x})\n", "File Subtype", WIDTH,
file_type_details(), file_subtype);
}
if (file_date_ms > 0 || file_date_ls > 0) {
uint64_t ts = uint64_t(file_date_ms) << 32 | file_date_ls;
oss << fmt::format("{:{}}: {}\n", "File Date", WIDTH, ts);
}
return oss.str();
}
std::vector<ResourceVersion::fixed_file_info_t::FILE_FLAGS>
ResourceVersion::fixed_file_info_t::flags() const {
std::vector<FILE_FLAGS> out;
std::copy_if(FILE_FLAGS_ARRAY.begin(), FILE_FLAGS_ARRAY.end(),
std::back_inserter(out), [this](FILE_FLAGS c) { return has(c); });
return out;
}
ok_error_t ResourceVersion::parse_children(ResourceVersion& version,
BinaryStream& stream) {
stream.align(sizeof(uint32_t));
/* First Child */ {
auto wLength = stream.peek<uint16_t>();
if (!wLength) {
return make_error_code(wLength.error());
}
std::vector<uint8_t> payload;
if (auto is_ok = stream.read_data(payload, *wLength); !is_ok) {
LIEF_DEBUG("Error: {}:{}", __FUNCTION__, __LINE__);
return make_error_code(is_ok.error());
}
SpanStream child_strm(payload);
if (auto is_ok = parse_child(version, child_strm); !is_ok) {
LIEF_WARN("Failed to parse first child");
}
}
stream.align(sizeof(uint32_t));
/* Second Child */ {
auto wLength = stream.peek<uint16_t>();
if (!wLength) {
return make_error_code(wLength.error());
}
std::vector<uint8_t> payload;
if (auto is_ok = stream.read_data(payload, *wLength); !is_ok) {
LIEF_DEBUG("Error: {}:{}", __FUNCTION__, __LINE__);
return make_error_code(is_ok.error());
}
SpanStream child_strm(payload);
if (auto is_ok = parse_child(version, child_strm); !is_ok) {
LIEF_WARN("Failed to parse second child");
}
}
return ok();
}
ok_error_t ResourceVersion::parse_child(ResourceVersion& version,
BinaryStream& stream) {
if (is_var_file_info(stream)) {
return parse_var_file_info(version, stream);
}
if (is_string_file_info(stream)) {
return parse_str_file_info(version, stream);
}
LIEF_DEBUG("Error: {}:{}", __FUNCTION__, __LINE__);
return make_error_code(lief_errors::conversion_error);
}
ok_error_t ResourceVersion::parse_str_file_info(ResourceVersion& version,
BinaryStream& stream) {
auto info = ResourceStringFileInfo::parse(stream);
if (!info) {
LIEF_DEBUG("Error: {}:{}", __FUNCTION__, __LINE__);
return make_error_code(info.error());
}
version.string_file_info(std::move(*info));
return ok();
}
ok_error_t ResourceVersion::parse_var_file_info(ResourceVersion& version,
BinaryStream& stream) {
auto info = ResourceVarFileInfo::parse(stream);
if (!info) {
LIEF_DEBUG("Error: {}:{}", __FUNCTION__, __LINE__);
return make_error_code(info.error());
}
version.var_file_info(std::move(*info));
return ok();
}
void ResourceVersion::accept(Visitor& visitor) const {
visitor.visit(*this);
}
std::string ResourceVersion::key_u8() const {
return u16tou8(key());
}
std::ostream& operator<<(std::ostream& os, const ResourceVersion& version) {
os << version.file_info();
if (const ResourceStringFileInfo* info = version.string_file_info()) {
os << *info << '\n';
}
if (const ResourceVarFileInfo* info = version.var_file_info()) {
os << *info << '\n';
}
return os;
}
const char* to_string(ResourceVersion::fixed_file_info_t::FILE_FLAGS e) {
#define ENTRY(X) std::pair(ResourceVersion::fixed_file_info_t::FILE_FLAGS::X, #X)
STRING_MAP enums2str{
ENTRY(DEBUG), ENTRY(INFO_INFERRED), ENTRY(PATCHED),
ENTRY(PRERELEASE), ENTRY(PRIVATEBUILD), ENTRY(SPECIALBUILD),
};
#undef ENTRY
if (auto it = enums2str.find(e); it != enums2str.end()) {
return it->second;
}
return "UNKNOWN";
}
const char* to_string(ResourceVersion::fixed_file_info_t::VERSION_OS e) {
#define ENTRY(X) std::pair(ResourceVersion::fixed_file_info_t::VERSION_OS::X, #X)
STRING_MAP enums2str{
ENTRY(DOS_WINDOWS16), ENTRY(DOS_WINDOWS32), ENTRY(NT),
ENTRY(NT_WINDOWS32), ENTRY(OS216), ENTRY(OS216_PM16),
ENTRY(OS232), ENTRY(OS232_PM32), ENTRY(PM16),
ENTRY(PM32), ENTRY(UNKNOWN), ENTRY(WINCE),
ENTRY(WINDOWS16), ENTRY(WINDOWS32),
};
#undef ENTRY
if (auto it = enums2str.find(e); it != enums2str.end()) {
return it->second;
}
return "UNKNOWN";
}
const char* to_string(ResourceVersion::fixed_file_info_t::FILE_TYPE e) {
#define ENTRY(X) std::pair(ResourceVersion::fixed_file_info_t::FILE_TYPE::X, #X)
STRING_MAP enums2str{
ENTRY(UNKNOWN), ENTRY(APP), ENTRY(DLL), ENTRY(DRV),
ENTRY(FONT), ENTRY(STATIC_LIB), ENTRY(VXD),
};
#undef ENTRY
if (auto it = enums2str.find(e); it != enums2str.end()) {
return it->second;
}
return "UNKNOWN";
}
const char* to_string(ResourceVersion::fixed_file_info_t::FILE_TYPE_DETAILS e) {
#define ENTRY(X) \
std::pair(ResourceVersion::fixed_file_info_t::FILE_TYPE_DETAILS::X, #X)
STRING_MAP enums2str{
ENTRY(DRV_COMM), ENTRY(DRV_DISPLAY), ENTRY(DRV_INPUTMETHOD),
ENTRY(DRV_INSTALLABLE), ENTRY(DRV_KEYBOARD), ENTRY(DRV_LANGUAGE),
ENTRY(DRV_MOUSE), ENTRY(DRV_NETWORK), ENTRY(DRV_PRINTER),
ENTRY(DRV_SOUND), ENTRY(DRV_SYSTEM), ENTRY(DRV_VERSIONED_PRINTER),
ENTRY(FONT_RASTER), ENTRY(FONT_TRUETYPE), ENTRY(FONT_VECTOR),
ENTRY(UNKNOWN),
};
#undef ENTRY
if (auto it = enums2str.find(e); it != enums2str.end()) {
return it->second;
}
return "UNKNOWN";
}
}