Last active
May 20, 2020 18:05
-
-
Save wbenny/479f4bf3f7853a404d9341d0e7237f8e to your computer and use it in GitHub Desktop.
VS_VERSION_INFO parser
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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