diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..33a750b --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,34 @@ +cmake_minimum_required(VERSION 3.0) + +set(CMAKE_CXX_STANDARD 20) + +project(libhat) + +if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC") + set_source_files_properties(src/arch/x86/AVX2.cpp PROPERTIES COMPILE_FLAGS "/arch:AVX2") + set_source_files_properties(src/arch/x86/AVX512.cpp PROPERTIES COMPILE_FLAGS "/arch:AVX512") +elseif (CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") + set_source_files_properties(src/arch/x86/AVX2.cpp PROPERTIES COMPILE_FLAGS "-mavx -mavx2 -mbmi") + set_source_files_properties(src/arch/x86/AVX512.cpp PROPERTIES COMPILE_FLAGS "-mavx512f -mavx512bw -mbmi") + set_source_files_properties(src/arch/x86/System.cpp PROPERTIES COMPILE_FLAGS "-mxsave") +endif () + +set(LIBHAT_SRC + src/Process.cpp + src/Scanner.cpp + src/System.cpp + + src/os/Win32.cpp + + src/arch/x86/AVX2.cpp + src/arch/x86/AVX512.cpp + src/arch/x86/System.cpp + + src/arch/arm/Neon.cpp + src/arch/arm/System.cpp) + +add_library(libhat ${LIBHAT_SRC}) + +target_include_directories(libhat PUBLIC + $ + $) diff --git a/README.md b/README.md new file mode 100644 index 0000000..0fb72c2 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# libhat \ No newline at end of file diff --git a/include/libhat.h b/include/libhat.h new file mode 100644 index 0000000..14a363c --- /dev/null +++ b/include/libhat.h @@ -0,0 +1,8 @@ +#pragma once + +#include +#include +#include +#include +#include +#include diff --git a/include/libhat/CompileTime.hpp b/include/libhat/CompileTime.hpp new file mode 100644 index 0000000..ad59573 --- /dev/null +++ b/include/libhat/CompileTime.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include +#include +#include + +namespace hat { + + template + struct string_literal { + constexpr string_literal(const char (&str)[N]) { + std::copy_n(str, N, value); + } + + [[nodiscard]] constexpr const char* c_str() const { + return (const char*) &this->value[0]; + } + + char value[N]; + }; + + static constexpr int atoi(std::string_view str, int base = 10) { + if (base < 2 || base > 36) { + throw std::invalid_argument("Invalid base specified"); + } + + int value = 0; + auto digits = base < 10 ? base : 10; + auto letters = base > 10 ? base - 10 : 0; + + for (char ch : str) { + value *= base; + if (ch >= '0' && ch < '0' + digits) { + value += (ch - '0'); + } else if (ch >= 'A' && ch < 'A' + letters) { + value += (ch - 'A' + 10); + } else if (ch >= 'a' && ch < 'a' + letters) { + value += (ch - 'a' + 10); + } else { + // Throws an exception at runtime AND prevents constexpr evaluation + throw std::invalid_argument("Unexpected character in integer string"); + } + } + return value; + } +} diff --git a/include/libhat/Defines.hpp b/include/libhat/Defines.hpp new file mode 100644 index 0000000..e27d1c0 --- /dev/null +++ b/include/libhat/Defines.hpp @@ -0,0 +1,32 @@ +#pragma once + +// Detect CPU Architecture +#if defined(_M_X64) || defined(__amd64__) || defined(_M_IX86) || defined(__i386__) + #define LIBHAT_X86 + #if defined(_M_X64) || defined(__amd64__) + #define LIBHAT_X86_64 + #endif +#elif defined(_M_ARM64) || defined(__aarch64__) || defined(_M_ARM) || defined(__arm__) + #define LIBHAT_ARM +#else + #error Unsupported Architecture +#endif + +// Detect Operating System +#if defined(_WIN32) + #define LIBHAT_WINDOWS +#else + #error Unsupported Operating System +#endif + +// Macros wrapping intrinsics +#ifdef LIBHAT_X86 + #ifdef LIBHAT_X86_64 + #define LIBHAT_TZCNT64(num) _tzcnt_u64(num) + #define LIBHAT_BLSR64(num) _blsr_u64(num) + #else + #include + #define LIBHAT_TZCNT64(num) std::countl_zero(num) + #define LIBHAT_BLSR64(num) num & (num - 1) + #endif +#endif diff --git a/include/libhat/MemoryProtector.hpp b/include/libhat/MemoryProtector.hpp new file mode 100644 index 0000000..5284ab2 --- /dev/null +++ b/include/libhat/MemoryProtector.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include +#include + +namespace hat { + + enum class protection : uint8_t { + Read = 0b001, + Write = 0b010, + Execute = 0b100 + }; + + constexpr protection operator|(protection lhs, protection rhs) { + using U = std::underlying_type_t; + return static_cast(static_cast(lhs) | static_cast(rhs)); + } + + constexpr protection operator&(protection lhs, protection rhs) { + using U = std::underlying_type_t; + return static_cast(static_cast(lhs) & static_cast(rhs)); + } + + /// RAII wrapper for setting memory protection flags + class memory_protector { + public: + memory_protector(uintptr_t address, size_t size, protection flags); + ~memory_protector(); + private: + uintptr_t address; + size_t size; + uint32_t oldProtection; // Memory protection flags native to Operating System + }; +} diff --git a/include/libhat/Process.hpp b/include/libhat/Process.hpp new file mode 100644 index 0000000..4144400 --- /dev/null +++ b/include/libhat/Process.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include +#include + +namespace hat::process { + + // TODO: Consider using a typedef or class instead? idk + enum class module_t : uintptr_t {}; + + /// Returns the module for the curent process's base executable + auto get_process_module() -> module_t; + + /// Returns a module by its given name in the current process + auto get_module(std::string_view name) -> module_t; + + /// Returns the module located at the specified base address + auto module_at(uintptr_t address) -> module_t; + + /// Returns the module located at the specified base address + auto module_at(std::byte* address) -> module_t; + + /// Returns the complete memory region for the given module. This may include portions which are uncommitted. + auto get_module_data(module_t module) -> std::span; + + /// Returns the memory region for a named section in the given module + auto get_section_data(module_t module, std::string_view name) -> std::span; +} diff --git a/include/libhat/ScanMode.hpp b/include/libhat/ScanMode.hpp new file mode 100644 index 0000000..08b3b12 --- /dev/null +++ b/include/libhat/ScanMode.hpp @@ -0,0 +1,15 @@ +#pragma once + +namespace hat { + + enum class scan_mode { + Search, + FastFirst, + AVX2, + AVX512, + Neon + }; + + template + scan_result find_pattern(std::byte* begin, std::byte* end, signature_view signature); +} diff --git a/include/libhat/Scanner.hpp b/include/libhat/Scanner.hpp new file mode 100644 index 0000000..5158778 --- /dev/null +++ b/include/libhat/Scanner.hpp @@ -0,0 +1,81 @@ +#pragma once + +#include +#include +#include + +#include "Process.hpp" +#include "Signature.hpp" + +namespace hat { + + class scan_result { + using rel_t = int32_t; + public: + constexpr scan_result() : result(nullptr) {} + constexpr scan_result(std::byte* result) : result(result) {} // NOLINT(google-explicit-constructor) + + /// Reads an integer of the specified type located at an offset from the signature result + template + [[nodiscard]] constexpr Int read(size_t offset) const { + return *reinterpret_cast(this->result + offset); + } + + /// Reads an integer of the specified type which represents an index into an array with the given element type + template + [[nodiscard]] constexpr size_t index(size_t offset) const { + return static_cast(read(offset)) / sizeof(ArrayType); + } + + /// Resolve the relative address located at an offset from the signature result + [[nodiscard]] constexpr std::byte* rel(size_t offset) const { + return this->has_result() ? this->result + this->read(offset) + offset + sizeof(rel_t) : nullptr; + } + + [[nodiscard]] constexpr bool has_result() const { + return this->result != nullptr; + } + + [[nodiscard]] constexpr std::byte* operator*() const { + return this->result; + } + + [[nodiscard]] constexpr std::byte* get() const { + return this->result; + } + private: + std::byte* result; + }; + + enum class compiler_type { + MSVC, + MinGW + }; + + /// Gets the VTable address for a class by its mangled name + template + scan_result find_vtable( + const std::string& className, + process::module_t module = process::get_process_module() + ); + + /// Perform a signature scan on the entirety of the process module or a specified module + scan_result find_pattern( + signature_view signature, + process::module_t module = process::get_process_module() + ); + + /// Perform a signature scan on a specific section of the process module or a specified module + scan_result find_pattern( + signature_view signature, + std::string_view section, + process::module_t module = process::get_process_module() + ); + + /// Root implementation of FindPattern + scan_result find_pattern( + std::byte* begin, + std::byte* end, + signature_view signature + ); +} diff --git a/include/libhat/Signature.hpp b/include/libhat/Signature.hpp new file mode 100644 index 0000000..6d438a6 --- /dev/null +++ b/include/libhat/Signature.hpp @@ -0,0 +1,64 @@ +#pragma once + +#include +#include +#include +#include + +#include "CompileTime.hpp" + +namespace hat { + + using signature_element = std::optional; + using signature = std::vector; + using signature_view = std::span; + + template + using fixed_signature = std::array; + + template + inline signature object_to_signature(const T& value) { + auto bytes = reinterpret_cast(&value); + return {bytes, bytes + sizeof(T)}; + } + + /// Convert raw byte storage into a signature + constexpr signature bytes_to_signature(std::span bytes) { + return {bytes.begin(), bytes.end()}; + } + + inline signature string_to_signature(std::string_view str) { + return bytes_to_signature({reinterpret_cast(str.data()), str.size()}); + } + + constexpr signature parse_signature(std::string_view str) { + signature sig{}; + for (const auto& word : str | std::views::split(' ')) { + if (word.empty()) { + continue; + } else if (word[0] == '?') { + if (sig.empty()) { + throw std::invalid_argument("First byte cannot be a wildcard"); + } + sig.emplace_back(std::nullopt); + } else { + const auto sv = std::string_view{word.begin(), word.end()}; + sig.emplace_back(static_cast(atoi(sv, 16) & 0xFF)); + } + } + return sig; + } + + /// Parses a signature string at compile time, and provides a signature_view which exists for the program's lifetime + template + inline signature_view compile_signature() { + static constexpr auto compiled = ([]() consteval -> auto { + const auto sig = parse_signature(str.c_str()); + constexpr auto N = parse_signature(str.c_str()).size(); + fixed_signature arr{}; + std::ranges::move(sig, arr.begin()); + return arr; + })(); + return compiled; + } +} diff --git a/include/libhat/System.hpp b/include/libhat/System.hpp new file mode 100644 index 0000000..68d1550 --- /dev/null +++ b/include/libhat/System.hpp @@ -0,0 +1,14 @@ +#pragma once + +#include "Defines.hpp" + +#if defined(LIBHAT_X86) +#include "../../src/arch/x86/System.hpp" +#elif defined(LIBHAT_ARM) +#include "../../src/arch/arm/System.hpp" +#endif + +namespace hat { + + const system_info& get_system(); +} diff --git a/src/Process.cpp b/src/Process.cpp new file mode 100644 index 0000000..eb48846 --- /dev/null +++ b/src/Process.cpp @@ -0,0 +1,12 @@ +#include + +namespace hat::process { + + module_t module_at(uintptr_t address) { + return module_t{address}; + } + + module_t module_at(std::byte* address) { + return module_at(reinterpret_cast(address)); + } +} \ No newline at end of file diff --git a/src/Scanner.cpp b/src/Scanner.cpp new file mode 100644 index 0000000..676f2d2 --- /dev/null +++ b/src/Scanner.cpp @@ -0,0 +1,146 @@ +#include + +#include +#include + +#include +#include +#include + +namespace hat { + + using namespace hat::process; + + template<> + scan_result find_vtable(const std::string& className, module_t module) { + // Tracing cross-references + // Type Descriptor => Object Locator => VTable + auto sig = string_to_signature(".?AV" + className + "@@"); + + // TODO: Have a better solution for this + // 3rd character may be 'V' for classes and 'U' for structs + sig[3] = {}; + + auto typeDesc = *find_pattern(sig, ".data", module); + if (!typeDesc) { + return nullptr; + } + // 0x10 is the offset from the type descriptor name to the type descriptor header + typeDesc -= 2 * sizeof(void*); + + // The actual xref refers to an offset from the base module + const auto loffset = static_cast(typeDesc - reinterpret_cast(module)); + auto locator = object_to_signature(loffset); + // FIXME: These appear to be the values just for basic classes with single inheritance. We should be using a + // different method to differentiate the object locator from the base class descriptor. + #ifdef LIBHAT_X86_64 + locator.insert(locator.begin(), { + std::byte{0x01}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, // signature + std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, // offset + std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00} // constructor displacement offset + }); + #else + locator.insert(locator.begin(), { + std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, // signature + std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, // offset + std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00} // constructor displacement offset + }); + #endif + const auto objectLocator = *find_pattern(locator, ".rdata", module); + if (!objectLocator) { + return nullptr; + } + + const auto vtable = *find_pattern(object_to_signature(objectLocator), ".data", module); + return vtable ? vtable + sizeof(void*) : nullptr; + } + + template<> + scan_result find_vtable(const std::string& className, module_t module) { + // Tracing cross-references + // Type Descriptor Name => Type Info => VTable + const auto sig = string_to_signature(std::to_string(className.size()) + className + "\0"); + const auto typeName = *find_pattern(sig, ".rdata", module); + if (!typeName) { + return nullptr; + } + auto typeInfo = *find_pattern(object_to_signature(typeName), ".rdata", module); + if (!typeInfo) { + return nullptr; + } + // A single pointer is the offset from the type name pointer to the start of the type info + typeInfo -= sizeof(void*); + + const auto vtable = *find_pattern(object_to_signature(typeInfo), ".rdata", module); + return vtable ? vtable + sizeof(void*) : nullptr; + } + + scan_result find_pattern(signature_view signature, module_t module) { + const auto data = get_module_data(module); + if (data.empty()) { + return nullptr; + } + return find_pattern(std::to_address(data.begin()), std::to_address(data.end()), signature); + } + + scan_result find_pattern(signature_view signature, std::string_view section, module_t module) { + const auto data = get_section_data(module, section); + if (data.empty()) { + return nullptr; + } + return find_pattern(std::to_address(data.begin()), std::to_address(data.end()), signature); + } + + template<> + [[deprecated]] scan_result find_pattern(std::byte* begin, std::byte* end, signature_view signature) { + auto it = std::search( + begin, end, + signature.begin(), signature.end(), + [](auto byte, auto opt) { + return !opt.has_value() || *opt == byte; + }); + return it != end ? it : nullptr; + } + + template<> + scan_result find_pattern(std::byte* begin, std::byte* end, signature_view signature) { + const auto firstByte = *signature[0]; + const auto scanEnd = end - signature.size() + 1; + + for (auto i = begin; i != scanEnd; i++) { + // Use std::find to efficiently find the first byte + i = std::find(std::execution::unseq, i, scanEnd, firstByte); + if (i == scanEnd) { + break; + } + // Compare everything after the first byte + auto match = std::equal(signature.begin() + 1, signature.end(), i + 1, [](auto opt, auto byte) { + return !opt.has_value() || *opt == byte; + }); + if (match) { + return i; + } + } + return nullptr; + } + + scan_result find_pattern(std::byte* begin, std::byte* end, signature_view signature) { + const auto size = signature.size(); +#if defined(LIBHAT_X86) + const auto& ext = get_system().extensions; + if (ext.bmi1 && ext.popcnt) { + if (size <= 65 && ext.avx512) { + return find_pattern(begin, end, signature); + } else if (size <= 33 && ext.avx2) { + return find_pattern(begin, end, signature); + } + } +#elif defined(LIBHAT_ARM) + if (size <= 17) { + return find_pattern(begin, end, signature); + } +#endif + // If none of the vectorized implementations are available/supported, then fallback to scanning per-byte + return find_pattern(begin, end, signature); + } +} diff --git a/src/System.cpp b/src/System.cpp new file mode 100644 index 0000000..0a0e4f9 --- /dev/null +++ b/src/System.cpp @@ -0,0 +1,9 @@ +#include + +namespace hat { + + const system_info system_info::instance{}; + const system_info& get_system() { + return system_info::instance; + } +} \ No newline at end of file diff --git a/src/arch/arm/Neon.cpp b/src/arch/arm/Neon.cpp new file mode 100644 index 0000000..095f7d9 --- /dev/null +++ b/src/arch/arm/Neon.cpp @@ -0,0 +1,29 @@ +#include +#ifdef LIBHAT_ARM + +#include +#include + +namespace hat { + + template<> + scan_result find_pattern(std::byte* begin, std::byte* end, signature_view signature) { + const auto firstByte = vld1q_dup_u8(reinterpret_cast(*signature[0])); + + auto vec = reinterpret_cast(begin); + const auto n = static_cast(end - signature.size() - begin) / sizeof(uint8x16_t); + const auto e = vec + n; + + for (; vec != e; vec++) { + const auto cmp = vceqq_u8(firstByte, *vec); + uint64_t first = vgetq_lane_u64(vreinterpretq_u64_u8(cmp), 0); + uint64_t second = vgetq_lane_u64(vreinterpretq_u64_u8(cmp), 1); + if (first || second) { + // TODO: Extract Mask + } + } + + return find_pattern(begin, end, signature); + } +} +#endif diff --git a/src/arch/arm/System.cpp b/src/arch/arm/System.cpp new file mode 100644 index 0000000..f94fdca --- /dev/null +++ b/src/arch/arm/System.cpp @@ -0,0 +1,9 @@ +#include +#ifdef LIBHAT_ARM + +#include "System.hpp" + +namespace hat { + +} +#endif diff --git a/src/arch/arm/System.hpp b/src/arch/arm/System.hpp new file mode 100644 index 0000000..a2cef6d --- /dev/null +++ b/src/arch/arm/System.hpp @@ -0,0 +1,13 @@ +#pragma once + +namespace hat { + + typedef struct system_info_arm { + system_info_arm(const system_info_arm&) = delete; + system_info_arm& operator=(const system_info_arm&) = delete; + private: + system_info_arm() = default; + friend const system_info_arm& get_system(); + static const system_info_arm instance; + } system_info; +} diff --git a/src/arch/x86/AVX2.cpp b/src/arch/x86/AVX2.cpp new file mode 100644 index 0000000..9dddcf7 --- /dev/null +++ b/src/arch/x86/AVX2.cpp @@ -0,0 +1,55 @@ +#include + +#ifdef LIBHAT_X86 + +#include +#include + +#include + +namespace hat { + + template<> + scan_result find_pattern(std::byte* begin, std::byte* end, signature_view signature) { + // 256 bit vector containing first signature byte repeated + const auto firstByte = _mm256_set1_epi8(static_cast(*signature[0])); + + std::byte byteBuffer[32]{}; // The remaining signature bytes + std::byte maskBuffer[32]{}; // A bitmask for the signature bytes we care about + for (size_t i = 1; i < signature.size(); i++) { + auto e = signature[i]; + if (e.has_value()) { + byteBuffer[i - 1] = *e; + maskBuffer[i - 1] = std::byte{0xFFu}; + } + } + + const auto signatureBytes = _mm256_loadu_si256(reinterpret_cast<__m256i*>(&byteBuffer)); + const auto signatureMask = _mm256_loadu_si256(reinterpret_cast<__m256i*>(&maskBuffer)); + + auto vec = reinterpret_cast<__m256i*>(begin); + const auto n = static_cast(end - signature.size() - begin) / sizeof(__m256i); + const auto e = vec + n; + + for (; vec != e; vec++) { + const auto cmp = _mm256_cmpeq_epi8(firstByte, *vec); + auto mask = static_cast(_mm256_movemask_epi8(cmp)); + while (mask) { + const auto offset = _tzcnt_u32(mask); + const auto i = reinterpret_cast(vec) + offset; + const auto data = _mm256_loadu_si256(reinterpret_cast<__m256i*>(i + 1)); + const auto cmpToSig = _mm256_cmpeq_epi8(signatureBytes, data); + const auto matched = _mm256_testc_si256(cmpToSig, signatureMask); + if (matched) { + return i; + } + mask = _blsr_u32(mask); + } + } + + // Look in remaining bytes that couldn't be grouped into 256 bits + begin = reinterpret_cast(vec); + return find_pattern(begin, end, signature); + } +} +#endif diff --git a/src/arch/x86/AVX512.cpp b/src/arch/x86/AVX512.cpp new file mode 100644 index 0000000..211b8df --- /dev/null +++ b/src/arch/x86/AVX512.cpp @@ -0,0 +1,52 @@ +#include +#ifdef LIBHAT_X86 + +#include +#include + +#include + +namespace hat { + + template<> + scan_result find_pattern(std::byte* begin, std::byte* end, signature_view signature) { + // 512 bit vector containing first signature byte repeated + const auto firstByte = _mm512_set1_epi8(static_cast(*signature[0])); + + std::byte byteBuffer[64]{}; // The remaining signature bytes + uint64_t maskBuffer{}; // A bitmask for the signature bytes we care about + for (size_t i = 1; i < signature.size(); i++) { + auto e = signature[i]; + if (e.has_value()) { + byteBuffer[i - 1] = *e; + maskBuffer |= (1ull << (i - 1)); + } + } + + const auto signatureBytes = _mm512_loadu_si512(&byteBuffer); + const auto signatureMask = _cvtu64_mask64(maskBuffer); + + auto vec = reinterpret_cast<__m512i*>(begin); + const auto n = static_cast(end - signature.size() - begin) / sizeof(__m512i); + const auto e = vec + n; + + for (; vec != e; vec++) { + auto mask = _mm512_cmpeq_epi8_mask(firstByte, *vec); + while (mask) { + const auto offset = LIBHAT_TZCNT64(mask); + const auto i = reinterpret_cast(vec) + offset; + const auto data = _mm512_loadu_si512(i + 1); + const auto invalid = _mm512_mask_cmpneq_epi8_mask(signatureMask, signatureBytes, data); + if (!invalid) { + return i; + } + mask = LIBHAT_BLSR64(mask); + } + } + + // Look in remaining bytes that couldn't be grouped into 512 bits + begin = reinterpret_cast(vec); + return find_pattern(begin, end, signature); + } +} +#endif diff --git a/src/arch/x86/System.cpp b/src/arch/x86/System.cpp new file mode 100644 index 0000000..bfc2731 --- /dev/null +++ b/src/arch/x86/System.cpp @@ -0,0 +1,97 @@ +#include +#ifdef LIBHAT_X86 + +#include "System.hpp" + +#include +#include +#include +#include +#include + +#ifndef _XCR_XFEATURE_ENABLED_MASK + #define _XCR_XFEATURE_ENABLED_MASK 0 +#endif + +namespace hat { + + static constexpr int CPU_BASIC_INFO = 0; + static constexpr int CPU_EXTENDED_INFO = static_cast(0x80000000); + static constexpr int CPU_BRAND_STRING = static_cast(0x80000004); + + system_info_x86::system_info_x86() { + std::array info{}; + std::vector> data{}; + std::vector> extData{}; + + // Gather info + __cpuid(info.data(), CPU_BASIC_INFO); + auto nIds = info[0]; + + char vendor[0xC + 1]{}; + memcpy(vendor, &info[1], sizeof(int)); + memcpy(vendor + 4, &info[3], sizeof(int)); + memcpy(vendor + 8, &info[2], sizeof(int)); + + for (int i = CPU_BASIC_INFO; i <= nIds; i++) { + __cpuidex(info.data(), i, 0); + data.push_back(info); + } + + // Gather extended info + __cpuid(info.data(), CPU_EXTENDED_INFO); + int nExtIds = info[0]; + for (int i = CPU_EXTENDED_INFO; i <= nExtIds; i++) { + __cpuidex(info.data(), i, 0); + extData.push_back(info); + } + + // Read relevant info + std::bitset<32> f_1_ECX_{}; + std::bitset<32> f_1_EDX_{}; + std::bitset<32> f_7_EBX_{}; + if (nIds >= 1) { + f_1_ECX_ = (uint32_t) data[1][2]; + f_1_EDX_ = (uint32_t) data[1][3]; + } + if (nIds >= 7) { + f_7_EBX_ = (uint32_t) data[7][1]; + } + + // Read extended info + char brand[0x40 + 1]{}; + if (nExtIds >= CPU_BRAND_STRING) { + memcpy(brand, extData[2].data(), sizeof(info)); + memcpy(brand + 16, extData[3].data(), sizeof(info)); + memcpy(brand + 32, extData[4].data(), sizeof(info)); + } + + // Check OS capabilities + bool avxsupport = false; + bool avx512support = false; + bool osxsave = f_1_ECX_[27]; + if (osxsave) { + // https://cdrdv2-public.intel.com/671190/253668-sdm-vol-3a.pdf (Page 2-20) + const std::bitset<64> xcr = _xgetbv(_XCR_XFEATURE_ENABLED_MASK); + avxsupport = xcr[1] && xcr[2]; // xmm and ymm + avx512support = avxsupport && xcr[5] && xcr[6] && xcr[7]; // opmask and zmm + } + + this->cpu_vendor = vendor; + this->cpu_brand = brand; + this->extensions = { + .sse = f_1_EDX_[25], + .sse2 = f_1_EDX_[26], + .sse3 = f_1_ECX_[0], + .ssse3 = f_1_ECX_[9], + .sse41 = f_1_ECX_[19], + .sse42 = f_1_ECX_[20], + .avx = f_1_ECX_[28] && avxsupport, + .avx2 = f_7_EBX_[5] && avxsupport, + .avx512 = f_7_EBX_[16] && f_7_EBX_[30] && avx512support, // AVX512F and AVX512BW + .popcnt = f_1_ECX_[23], + .bmi1 = f_7_EBX_[3], + }; + } +} +#endif diff --git a/src/arch/x86/System.hpp b/src/arch/x86/System.hpp new file mode 100644 index 0000000..4bd7905 --- /dev/null +++ b/src/arch/x86/System.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include + +namespace hat { + + typedef struct system_info_x86 { + std::string cpu_vendor; + std::string cpu_brand; + struct { + bool sse; + bool sse2; + bool sse3; + bool ssse3; + bool sse41; + bool sse42; + bool avx; + bool avx2; + bool avx512; + bool popcnt; + bool bmi1; + } extensions; + + system_info_x86(const system_info_x86&) = delete; + system_info_x86& operator=(const system_info_x86&) = delete; + private: + system_info_x86(); + friend const system_info_x86& get_system(); + static const system_info_x86 instance; + } system_info; +} diff --git a/src/os/Win32.cpp b/src/os/Win32.cpp new file mode 100644 index 0000000..02a9324 --- /dev/null +++ b/src/os/Win32.cpp @@ -0,0 +1,84 @@ +#include +#ifdef LIBHAT_WINDOWS + +#include +#include + +#include +#include +#include + +namespace hat { + DWORD ToWinProt(protection flags) { + const bool r = bool(flags & protection::Read); + const bool w = bool(flags & protection::Write); + const bool x = bool(flags & protection::Execute); + + if (x && w) return PAGE_EXECUTE_READWRITE; + if (x && r) return PAGE_EXECUTE_READ; + if (x) return PAGE_EXECUTE; + if (w) return PAGE_READWRITE; + if (r) return PAGE_READONLY; + return PAGE_NOACCESS; + } + + memory_protector::memory_protector(uintptr_t address, size_t size, protection flags) : address(address), size(size) { + VirtualProtect(reinterpret_cast(this->address), this->size, ToWinProt(flags), reinterpret_cast(&this->oldProtection)); + } + + memory_protector::~memory_protector() { + DWORD temp; + VirtualProtect(reinterpret_cast(this->address), this->size, this->oldProtection, &temp); + } +} + +namespace hat::process { + + namespace { + PIMAGE_NT_HEADERS GetNTHeaders(module_t module) { + auto* const scanBytes = reinterpret_cast(module); + auto* const dosHeader = reinterpret_cast(module); + if (dosHeader->e_magic != IMAGE_DOS_SIGNATURE) + return nullptr; + + auto* const ntHeaders = reinterpret_cast(scanBytes + dosHeader->e_lfanew); + if (ntHeaders->Signature != IMAGE_NT_SIGNATURE) + return nullptr; + + return ntHeaders; + } + } + + module_t get_process_module() { + return module_t{reinterpret_cast(GetModuleHandleA(nullptr))}; + } + + std::span get_module_data(module_t module) { + auto* const scanBytes = reinterpret_cast(module); + auto* const ntHeaders = GetNTHeaders(module); + if (!ntHeaders) + return {}; + + const size_t sizeOfImage = ntHeaders->OptionalHeader.SizeOfImage; + return {scanBytes, sizeOfImage}; + } + + std::span get_section_data(module_t module, std::string_view name) { + auto* const scanBytes = reinterpret_cast(module); + auto* const ntHeaders = GetNTHeaders(module); + if (!ntHeaders) + return {}; + + const auto* sectionHeader = IMAGE_FIRST_SECTION(ntHeaders); + for (int i = 0; i < ntHeaders->FileHeader.NumberOfSections; i++, sectionHeader++) { + if (strncmp(name.data(), reinterpret_cast(sectionHeader->Name), 8) == 0) { + return { + scanBytes + sectionHeader->VirtualAddress, + static_cast(sectionHeader->Misc.VirtualSize) + }; + } + } + return {}; + } +} +#endif