Skip to content

Instantly share code, notes, and snippets.

@wbenny
Last active May 20, 2020 18:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save wbenny/479f4bf3f7853a404d9341d0e7237f8e to your computer and use it in GitHub Desktop.
Save wbenny/479f4bf3f7853a404d9341d0e7237f8e to your computer and use it in GitHub Desktop.
VS_VERSION_INFO parser
#define _CRT_SECURE_NO_WARNINGS
#define _SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING
#define _SILENCE_CXX20_CODECVT_FACETS_DEPRECATION_WARNING
#define NOMINMAX
#include <cassert>
#include <cstddef>
#include <cstdlib>
#include <algorithm>
#include <codecvt>
#include <iostream>
#include <locale>
#include <memory>
#include <span>
#include <string>
#include <string_view>
#include <vector>
#undef assert
#define assert(...)
//
// Notes:
// - We define our own vs_char_t, which will be wchar_t on Windows and
// char16_t on the rest (wchar_t on linux has 32 bits).
// - There is to_string() & to_vs_string() for easy manipulation with these strings.
// - vs_codecvt must be here because destructor of std::codecvt can be protected
// (happens to be a case on linux). See: https://stackoverflow.com/questions/41744559/is-this-a-bug-of-gcc
// - vs_char_traits::find() shenanigans in vs_enumerator::pointer_to_value() is
// nothing more than safe and simple strnlen_s/wcsnlen_s. Note that std::char_traits::length()
// doesn't have parameter for specifying how long is the buffer (if it's not null-terminated,
// it can crash on invalid memory access).
// - If you don't like it and will always operate on Windows, it's easy to clean the code:
// * replace vs_char_t with wchar_t
// * get rid off the codecvt, because printf can operate with wchar_t
// - The enumerator itself
// * doesn't allocate memory
// * doesn't produce exceptions
// * hopefully shouldn't crash - it was very briefly tested with AFL
//
#ifdef _WIN32
using vs_char_t = wchar_t;
#else
using vs_char_t = char16_t;
#endif
using vs_string = std::basic_string<vs_char_t>;
using vs_string_view = std::basic_string_view<vs_char_t>;
using vs_char_traits = std::char_traits<vs_char_t>;
template<class I, class E, class S>
struct vs_codecvt : std::codecvt<I, E, S>
{
~vs_codecvt()
{ }
};
std::wstring_convert<vs_codecvt<vs_char_t, char, std::mbstate_t>, vs_char_t> convert;
std::string to_string(vs_string_view value)
{
try
{
return convert.to_bytes(vs_string{ value });
}
catch (const std::range_error& exception)
{
(void)(exception);
return {};
}
}
vs_string to_vs_string(std::string_view value)
{
try
{
return convert.from_bytes(std::string{ value });
}
catch (const std::range_error& exception)
{
(void)(exception);
return {};
}
}
//////////////////////////////////////////////////////////////////////////
struct vs_fixed_file_info_t
{
uint32_t signature; /* e.g. 0xfeef04bd */
uint32_t struc_version; /* e.g. 0x00000042 = "0.42" */
uint32_t file_version_ms; /* e.g. 0x00030075 = "3.75" */
uint32_t file_version_ls; /* e.g. 0x00000031 = "0.31" */
uint32_t product_version_ms; /* e.g. 0x00030010 = "3.10" */
uint32_t product_version_ls; /* e.g. 0x00000031 = "0.31" */
uint32_t file_flags_mask; /* = 0x3F for version "0.42" */
uint32_t file_flags; /* e.g. VFF_DEBUG | VFF_PRERELEASE */
uint32_t file_os; /* e.g. VOS_DOS_WINDOWS16 */
uint32_t file_type; /* e.g. VFT_DRIVER */
uint32_t file_subtype; /* e.g. VFT2_DRV_KEYBOARD */
uint32_t file_date_ms; /* e.g. 0 */
uint32_t file_date_ls; /* e.g. 0 */
};
struct vs_block_t
{
uint16_t length;
uint16_t value_length;
uint16_t type;
vs_char_t key[1];
// vs_fixed_file_info_t value;
// uint16_t children[1];
};
static inline auto vs_align_offset(size_t offset, size_t alignment = sizeof(uint32_t)) noexcept
{
return static_cast<ptrdiff_t>((offset + (alignment - 1)) & ~(alignment - 1));
}
template <typename T = const vs_block_t>
static inline auto vs_align(const void* ptr, size_t offset, size_t alignment = sizeof(uint32_t)) noexcept
{
return reinterpret_cast<T*>(reinterpret_cast<uintptr_t>(ptr) + vs_align_offset(offset, alignment));
}
class vs_enumerator
{
public:
struct iterator
{
friend class vs_enumerator;
using iterator_category = std::forward_iterator_tag;
using value_type = vs_enumerator;
using difference_type = ptrdiff_t;
using pointer = value_type;
using reference = value_type;
iterator() noexcept = default;
iterator(const iterator& other) noexcept = default;
iterator(iterator&& other) noexcept = default;
iterator& operator=(const iterator& other) noexcept = default;
iterator& operator=(iterator&& other) noexcept = default;
iterator& operator++( ) noexcept { increment(); return *this; }
iterator operator++(int) noexcept { auto tmp = *this; increment(); return tmp; }
bool operator==(const iterator& other) const noexcept { return table_ == other.table_; }
bool operator!=(const iterator& other) const noexcept { return table_ != other.table_; }
reference operator*() const noexcept { return dereference(); }
pointer operator->() const noexcept { return dereference(); }
private:
iterator(const vs_block_t* table, const vs_block_t* last) noexcept
: table_{ table }
, last_{ last }
{}
auto peek_next_unsafe() const noexcept -> const vs_block_t*
{
return reinterpret_cast<const vs_block_t*>(vs_align(table_, table_->length));
}
auto peek_next() const noexcept -> const vs_block_t*
{
auto next = peek_next_unsafe();
if (next > last_) { assert(0 && "out of range"); next = last_; }
if (next <= table_) { assert(0 && "out of range"); next = last_; }
return next;
}
void increment() noexcept
{
table_ = peek_next();
}
auto dereference() const noexcept -> value_type
{
return {
table_,
peek_next()
};
}
const vs_block_t* table_;
const vs_block_t* last_;
};
vs_enumerator() noexcept
: table_{ nullptr }
, last_{ nullptr }
{}
vs_enumerator(const vs_block_t* table, const vs_block_t* last) noexcept
: table_{ table }
, last_{ last }
{}
vs_enumerator(const vs_enumerator& other) noexcept = default;
vs_enumerator(vs_enumerator&& other) noexcept = default;
vs_enumerator& operator=(const vs_enumerator& other) noexcept = default;
vs_enumerator& operator=(vs_enumerator&& other) noexcept = default;
auto begin() const noexcept -> iterator
{
return iterator{ table_, last_ };
}
auto end() const noexcept -> iterator
{
return iterator{ last_, last_ };
}
auto key() const noexcept -> vs_string_view
{
return table_->key;
}
auto children() const noexcept -> vs_enumerator
{
if (pointer_to_end() > last_)
{
assert(0 && "out of range");
return vs_enumerator{};
}
return {
static_cast<const vs_block_t*>(pointer_to_children()),
static_cast<const vs_block_t*>(pointer_to_end())
};
}
auto value() const noexcept -> vs_enumerator
{
if (pointer_to_children() > last_)
{
assert(0 && "out of range");
return vs_enumerator{};
}
return {
static_cast<const vs_block_t*>(pointer_to_value()),
static_cast<const vs_block_t*>(pointer_to_children())
};
}
auto value_as_var() const noexcept -> std::span<const std::pair<uint16_t, uint16_t>>
{
auto count = value_size() / sizeof(uint32_t);
if (!count) { assert(0 && "out of range"); return {}; }
return { reinterpret_cast<const std::pair<uint16_t, uint16_t>*>(pointer_to_value()), count };
}
auto value_as_string() const noexcept -> vs_string_view
{
if (!value_size()) { assert(0 && "out of range"); return {}; }
return { static_cast<const vs_char_t*>(pointer_to_value()), value_size() - 1 };
}
auto value_as_fixed_file_info() const noexcept -> const vs_fixed_file_info_t*
{
if (value_size() < sizeof(vs_fixed_file_info_t)) { assert(0 && "out of range"); return {}; }
return static_cast<const vs_fixed_file_info_t*>(pointer_to_value());
}
auto is_valid() const noexcept -> bool
{
return table_ != nullptr && last_ != nullptr;
}
auto pointer_to_value() const noexcept -> const void*
{
const auto max_key_length = offset_from_end(&table_->key);
const auto null_terminator = vs_char_traits::find(table_->key, max_key_length / sizeof(vs_char_t), vs_char_t{});
const auto key_length = null_terminator ? (null_terminator - table_->key) * sizeof(vs_char_t) : max_key_length;
const auto value_offset = offsetof(vs_block_t, key) + key_length + (key_length < max_key_length ? sizeof(vs_char_t) : 0);
return vs_align(table_, value_offset);
}
auto pointer_to_children() const noexcept -> const void*
{
return vs_align(pointer_to_value(), value_size());
}
auto pointer_to_end() const noexcept -> const void*
{
return vs_align(table_, table_->length);
}
auto value_size_unsafe() const noexcept -> size_t
{
return table_->value_length;
}
auto value_size() const noexcept -> size_t
{
return std::min(value_size_unsafe(), offset_from_end(pointer_to_value()));
}
auto offset_from_end(const void* p) const noexcept -> uintptr_t
{
return reinterpret_cast<uintptr_t>(last_) >= reinterpret_cast<uintptr_t>(p)
? reinterpret_cast<uintptr_t>(last_) - reinterpret_cast<uintptr_t>(p)
: 0;
}
private:
const vs_block_t* table_;
const vs_block_t* last_;
};
inline auto make_vs_enumerator(const void* buffer, size_t size) -> vs_enumerator
{
return vs_enumerator{
static_cast<const vs_block_t*>(buffer),
reinterpret_cast<const vs_block_t*>(reinterpret_cast<uintptr_t>(buffer) + size)
};
}
//////////////////////////////////////////////////////////////////////////
void process_buffer(const void* buffer, size_t size) noexcept
{
auto version_info = make_vs_enumerator(buffer, size);
auto version_info_key = version_info.key();
assert(version_info_key == to_vs_string("VS_VERSION_INFO"));
auto fixed_file_info = version_info.value_as_fixed_file_info();
assert(fixed_file_info->signature == 0xfeef04bd);
if (fixed_file_info == nullptr)
{
return;
}
std::cout << std::hex;
std::cout << "fixed_file_info (signature: " << fixed_file_info->signature << ")" << std::endl;
std::cout << " - struc_version : " << fixed_file_info->struc_version << std::endl;
std::cout << " - file_version_ms : " << fixed_file_info->file_version_ms << std::endl;
std::cout << " - file_version_ls : " << fixed_file_info->file_version_ls << std::endl;
std::cout << " - product_version_ms : " << fixed_file_info->product_version_ms << std::endl;
std::cout << " - product_version_ls : " << fixed_file_info->product_version_ls << std::endl;
std::cout << " - file_flags_mask : " << fixed_file_info->file_flags_mask << std::endl;
std::cout << " - file_flags : " << fixed_file_info->file_flags << std::endl;
std::cout << " - file_os : " << fixed_file_info->file_os << std::endl;
std::cout << " - file_type : " << fixed_file_info->file_type << std::endl;
std::cout << " - file_subtype : " << fixed_file_info->file_subtype << std::endl;
std::cout << " - file_date_ms : " << fixed_file_info->file_date_ms << std::endl;
std::cout << " - file_date_ls : " << fixed_file_info->file_date_ls << std::endl;
std::cout << std::endl;
for (auto file_info : version_info.children())
{
auto file_info_key = file_info.key();
std::cout << "'" << to_string(file_info_key) << "'" << std::endl;
if (file_info_key == to_vs_string("StringFileInfo"))
{
for (auto block : file_info.children())
{
auto block_key = block.key();
std::cout << " - block: '" << to_string(block_key) << "'" << std::endl;
for (auto item : block.children())
{
auto item_key = item.key();
auto item_value = item.value_as_string();
std::cout << " - item: '" << to_string(item_key) << "' -> '" << to_string(item_value) << "'" << std::endl;
item_value = item_value;
}
}
}
else if (file_info_key == to_vs_string("VarFileInfo"))
{
for (auto item : file_info.children())
{
auto item_key = item.key();
std::cout << " - var: '" << to_string(item_key) << "'" << std::endl;
for (auto var : item.value_as_var())
{
auto [var1, var2] = var;
std::cout << " - (" << var1 << ", " << var2 << ")" << std::endl;
}
}
}
else
{
assert("Invalid" && 0);
}
}
}
void process_file(const char* path) noexcept
{
FILE* f;
f = fopen(path, "rb");
if (!f)
{
return;
}
fseek(f, 0, SEEK_END);
auto file_size = ftell(f);
fseek(f, 0, SEEK_SET);
if (file_size > 100 * 1024 * 1024)
{
return;
}
std::vector<std::byte> version_info_data;
version_info_data.resize(file_size);
fread(version_info_data.data(), 1, file_size, f);
fclose(f);
process_buffer(version_info_data.data(), version_info_data.size());
}
#ifdef _WIN32
#include <filesystem>
#include <windows.h>
#include <winternl.h>
#define RESOURCE_TYPE_LEVEL 0
#define RESOURCE_NAME_LEVEL 1
#define RESOURCE_LANGUAGE_LEVEL 2
#define RESOURCE_DATA_LEVEL 3
typedef struct _LDR_RESOURCE_INFO
{
ULONG_PTR Type;
ULONG_PTR Name;
ULONG_PTR Language;
} LDR_RESOURCE_INFO, *PLDR_RESOURCE_INFO;
EXTERN_C
NTSYSAPI
NTSTATUS
NTAPI
LdrFindResource_U(
_In_ PVOID DllHandle,
_In_ PLDR_RESOURCE_INFO ResourceInfo,
_In_ ULONG Level,
_Out_ PIMAGE_RESOURCE_DATA_ENTRY *ResourceDataEntry
);
EXTERN_C
NTSYSAPI
NTSTATUS
NTAPI
LdrAccessResource(
_In_ PVOID DllHandle,
_In_ PIMAGE_RESOURCE_DATA_ENTRY ResourceDataEntry,
_Out_opt_ PVOID *ResourceBuffer,
_Out_opt_ ULONG *ResourceLength
);
void process_resource(LPCTSTR module_name)
{
HMODULE ModuleBase = LoadLibraryEx(module_name, NULL, LOAD_LIBRARY_AS_DATAFILE);
NTSTATUS Status;
LDR_RESOURCE_INFO ResourceInfo;
ResourceInfo.Type = (ULONG_PTR)(RT_VERSION);
ResourceInfo.Name = 1;
ResourceInfo.Language = 1033;
PIMAGE_RESOURCE_DATA_ENTRY ResourceDataEntry;
Status = LdrFindResource_U(ModuleBase,
&ResourceInfo,
RESOURCE_DATA_LEVEL,
&ResourceDataEntry);
if (!NT_SUCCESS(Status))
{
return;
}
PVOID ResourceBuffer;
ULONG ResourceLength;
Status = LdrAccessResource(ModuleBase,
ResourceDataEntry,
&ResourceBuffer,
&ResourceLength);
if (!NT_SUCCESS(Status))
{
return;
}
process_buffer(ResourceBuffer, ResourceLength);
FreeLibrary(ModuleBase);
}
#endif
int main(int argc, char* argv[])
{
const char* filename = nullptr;
if (argc > 1)
{
filename = argv[1];
}
#ifdef _WIN32
# ifdef _DEBUG
{
//
// Test on ntdll.dll in debug mode.
//
LPCTSTR module_name = TEXT("ntdll.dll");
std::cout << "Processing module" << std::endl;
process_resource(module_name);
}
# endif
#endif
if (filename)
{
process_file(filename);
return EXIT_SUCCESS;
}
return EXIT_SUCCESS;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment