diff --git a/deps/premake/libHDiffPatch.lua b/deps/premake/libHDiffPatch.lua index d429887..0904c7e 100644 --- a/deps/premake/libHDiffPatch.lua +++ b/deps/premake/libHDiffPatch.lua @@ -13,7 +13,7 @@ end function libHDiffPatch.includes() includedirs { path.join(libHDiffPatch.source, "HDiff"), - path.join(libHDiffPatch.source, "HDiff"), + path.join(libHDiffPatch.source, "HPatch"), } end diff --git a/src/game/game.cpp b/src/game/game.cpp index 721c815..a050c7f 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -9,21 +9,21 @@ namespace game } - launcher::mode mode = launcher::mode::NONE; + launcher::mode mode = launcher::mode::none; bool is_mp() { - return mode == launcher::mode::MULTIPLAYER; + return mode == launcher::mode::multiplayer; } bool is_sp() { - return mode == launcher::mode::SINGLEPLAYER; + return mode == launcher::mode::singleplayer; } bool is_dedi() { - return mode == launcher::mode::SERVER; + return mode == launcher::mode::server; } void initialize(const launcher::mode _mode) diff --git a/src/launcher/launcher.cpp b/src/launcher/launcher.cpp index 62bffac..312e0b4 100644 --- a/src/launcher/launcher.cpp +++ b/src/launcher/launcher.cpp @@ -9,8 +9,8 @@ launcher::launcher() : window_("Open-IW5", 615, 300), image_sp_(IMAGE_SP), image this->image_sp_.set_size({100, 100}); this->image_mp_.set_size({100, 100}); - this->image_sp_.set_click_listener(std::bind(&launcher::select_mode, this, mode::SINGLEPLAYER)); - this->image_mp_.set_click_listener(std::bind(&launcher::select_mode, this, mode::MULTIPLAYER)); + this->image_sp_.set_click_listener(std::bind(&launcher::select_mode, this, mode::singleplayer)); + this->image_mp_.set_click_listener(std::bind(&launcher::select_mode, this, mode::multiplayer)); this->window_.set_callback(std::bind(&launcher::handler, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); diff --git a/src/launcher/launcher.hpp b/src/launcher/launcher.hpp index a3e3d87..ff25d08 100644 --- a/src/launcher/launcher.hpp +++ b/src/launcher/launcher.hpp @@ -7,10 +7,10 @@ class launcher final public: enum mode { - NONE, - SINGLEPLAYER, - MULTIPLAYER, - SERVER, + none, + singleplayer, + multiplayer, + server, }; launcher(); @@ -18,7 +18,7 @@ public: mode run() const; private: - mode mode_ = NONE; + mode mode_ = none; window window_; diff --git a/src/loader/binary_loader.cpp b/src/loader/binary_loader.cpp new file mode 100644 index 0000000..cd3dcbe --- /dev/null +++ b/src/loader/binary_loader.cpp @@ -0,0 +1,120 @@ +#include +#include "binary_loader.hpp" +#include "utils/nt.hpp" +#include "utils/io.hpp" +#include "utils/cryptography.hpp" +#include "utils/string.hpp" +#include "utils/compression.hpp" + +#define DEDI_HASH "F271C305117B79242E254E9F64BD5AA2993CAC8E57975243EBD44CD576418D20" + +namespace binary_loader +{ + std::string load_resource(const int id) + { + const auto res = FindResource(::utils::nt::module(), MAKEINTRESOURCE(id), RT_RCDATA); + if (!res) return {}; + + const auto handle = LoadResource(nullptr, res); + if (!handle) return {}; + + return std::string(LPSTR(LockResource(handle)), SizeofResource(nullptr, res)); + } + + std::string load_delta(const launcher::mode mode) + { + if (mode == launcher::mode::singleplayer) + { + return load_resource(BINARY_SP); + } + + if (mode == launcher::mode::multiplayer) + { + return load_resource(BINARY_MP); + } + + return {}; + } + + std::string load_base(bool verify = true) + { + std::string data; + if (!utils::io::read_file("iw5mp_server.exe", &data)) + { + throw std::runtime_error("Unable to load iw5mp_server.exe"); + } + + if (verify && utils::cryptography::sha256::compute(data, true) != DEDI_HASH) + { + throw std::runtime_error("Your iw5mp_server.exe is incompatible with this client."); + } + + return data; + } + + void create_for_file(const std::string& file, const std::string& base) + { + std::string data; + if (!utils::io::read_file(file, &data)) + { + throw std::runtime_error(utils::string::va("Unable to load file %s!", file.data())); + } + + const auto new_data = reinterpret_cast(data.data()); + const auto old_data = reinterpret_cast(base.data()); + + std::vector diff; + create_diff(new_data, new_data + data.size(), old_data, old_data + base.size(), diff); + + const unsigned long long size = data.size(); + + std::string result(reinterpret_cast(diff.data()), diff.size()); + result.append(reinterpret_cast(&size), sizeof(size)); + result = utils::compression::zlib::compress(result); + + utils::io::write_file(file + ".diff", result); + } + + void create() + { + const auto base = load_base(false); + + utils::io::write_file("hash.txt", utils::cryptography::sha256::compute(base, true)); + + create_for_file("iw5sp.exe", base); + create_for_file("iw5mp.exe", base); + } + + std::string build_binary(const std::string& base, const std::string& diff) + { + const auto* size = reinterpret_cast(diff.data() + diff.size() - sizeof(unsigned long long)); + + std::string binary; + binary.resize(size_t(*size)); + + const auto new_data = reinterpret_cast(binary.data()); + const auto old_data = reinterpret_cast(base.data()); + const auto diff_data = reinterpret_cast(diff.data()); + + if (patch(new_data, new_data + binary.size(), old_data, old_data + base.size(), diff_data, + diff_data + diff.size() - sizeof(*size)) == hpatch_FALSE || binary.empty()) + { + throw std::runtime_error("Unable to create binary from patch!"); + } + + return binary; + } + + std::string load(const launcher::mode mode) + { + auto base = load_base(); + if (mode == launcher::mode::server) + { + return base; + } + + auto delta = load_delta(mode); + delta = utils::compression::zlib::decompress(delta); + return build_binary(base, delta); + } +} diff --git a/src/loader/binary_loader.hpp b/src/loader/binary_loader.hpp new file mode 100644 index 0000000..de069d8 --- /dev/null +++ b/src/loader/binary_loader.hpp @@ -0,0 +1,8 @@ +#pragma once +#include "launcher/launcher.hpp" + +namespace binary_loader +{ + void create(); + std::string load(launcher::mode mode); +} diff --git a/src/loader/loader.cpp b/src/loader/loader.cpp index 421955e..db3a2ce 100644 --- a/src/loader/loader.cpp +++ b/src/loader/loader.cpp @@ -1,13 +1,16 @@ #include #include "loader.hpp" +#include "binary_loader.hpp" +#include "utils/string.hpp" loader::loader(const launcher::mode mode) : mode_(mode) { + } FARPROC loader::load(const utils::nt::module& module) const { - const auto buffer = this->load_binary(); + const auto buffer = binary_loader::load(this->mode_); if (buffer.empty()) return nullptr; utils::nt::module source(HMODULE(buffer.data())); @@ -33,7 +36,7 @@ FARPROC loader::load(const utils::nt::module& module) const VirtualProtect(PVOID(target_tls->StartAddressOfRawData), source_tls->EndAddressOfRawData - source_tls->StartAddressOfRawData, PAGE_READWRITE, &old_protect); - const LPVOID tls_base = *reinterpret_cast(__readfsdword(0x2C)); + const auto tls_base = *reinterpret_cast(__readfsdword(0x2C)); std::memmove(tls_base, PVOID(source_tls->StartAddressOfRawData), source_tls->EndAddressOfRawData - source_tls->StartAddressOfRawData); std::memmove(PVOID(target_tls->StartAddressOfRawData), PVOID(source_tls->StartAddressOfRawData), @@ -58,32 +61,6 @@ void loader::set_import_resolver(const std::functionimport_resolver_ = resolver; } -std::string loader::load_binary() const -{ - if (this->mode_ == launcher::mode::SINGLEPLAYER) - { - return loader::load_resource(BINARY_SP); - } - - if (this->mode_ == launcher::mode::MULTIPLAYER) - { - return loader::load_resource(BINARY_MP); - } - - return {}; -} - -std::string loader::load_resource(const int id) -{ - const auto res = FindResource(::utils::nt::module(), MAKEINTRESOURCE(id), RT_RCDATA); - if (!res) return {}; - - const auto handle = LoadResource(nullptr, res); - if (!handle) return {}; - - return std::string(LPSTR(LockResource(handle)), SizeofResource(nullptr, res)); -} - void loader::load_section(const utils::nt::module& target, const utils::nt::module& source, IMAGE_SECTION_HEADER* section) { @@ -92,13 +69,12 @@ void loader::load_section(const utils::nt::module& target, const utils::nt::modu if (PBYTE(target_ptr) >= (target.get_ptr() + BINARY_PAYLOAD_SIZE)) { - MessageBoxA(nullptr, "Section exceeds the binary payload size, please increase it!", nullptr, MB_ICONERROR); - TerminateProcess(GetCurrentProcess(), 1); + throw std::runtime_error("Section exceeds the binary payload size, please increase it!"); } if (section->SizeOfRawData > 0) { - const auto size_of_data = min(section->SizeOfRawData, section->Misc.VirtualSize); + const auto size_of_data = std::min(section->SizeOfRawData, section->Misc.VirtualSize); std::memmove(target_ptr, source_ptr, size_of_data); DWORD old_protect; @@ -166,10 +142,7 @@ void loader::load_imports(const utils::nt::module& target, const utils::nt::modu if (!function) { - auto error = "Unable to load import '"s + function_name + "' from module '"s + name + "'"s; - - MessageBoxA(nullptr, error.data(), nullptr, MB_ICONERROR); - TerminateProcess(GetCurrentProcess(), 1); + throw std::runtime_error(utils::string::va("Unable to load import '%s' from module '%s'", function_name.data(), name.data())); } *address_table_entry = reinterpret_cast(function); diff --git a/src/loader/loader.hpp b/src/loader/loader.hpp index 6c4a4e0..a57b726 100644 --- a/src/loader/loader.hpp +++ b/src/loader/loader.hpp @@ -5,7 +5,7 @@ class loader final { public: - explicit loader(launcher::mode mode); + loader(launcher::mode mode); FARPROC load(const utils::nt::module& module) const; @@ -15,9 +15,6 @@ private: launcher::mode mode_; std::function import_resolver_; - std::string load_binary() const; - static std::string load_resource(const int id); - static void load_section(const utils::nt::module& target, const utils::nt::module& source, IMAGE_SECTION_HEADER* section); void load_sections(const utils::nt::module& target, const utils::nt::module& source) const; void load_imports(const utils::nt::module& target, const utils::nt::module& source) const; diff --git a/src/main.cpp b/src/main.cpp index 5e3ecc6..1b964ed 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3,6 +3,9 @@ #include "loader/loader.hpp" #include "loader/module_loader.hpp" #include "game/game.hpp" +#include "loader/binary_loader.hpp" + +//#define GENERATE_DIFFS void exit_hook(const int code) { @@ -14,11 +17,17 @@ int CALLBACK WinMain(HINSTANCE /*hInstance*/, HINSTANCE /*hPrevInstance*/, LPSTR { FARPROC entry_point = nullptr; + try { +#ifdef GENERATE_DIFFS + binary_loader::create(); + return 0; +#endif + launcher launcher; const auto mode = launcher.run(); - if (mode == launcher::mode::NONE) return 0; + if (mode == launcher::mode::none) return 0; loader loader(mode); loader.set_import_resolver([](const std::string& module, const std::string& function) -> FARPROC @@ -36,11 +45,16 @@ int CALLBACK WinMain(HINSTANCE /*hInstance*/, HINSTANCE /*hPrevInstance*/, LPSTR }); entry_point = loader.load({}); - if (!entry_point) return 1; + if (!entry_point) throw std::runtime_error("Unable to load inject binary into memory"); game::initialize(mode); module_loader::post_load(); } + catch (std::exception e) + { + MessageBoxA(nullptr, e.what(), "ERROR", MB_ICONERROR); + return 1; + } return entry_point(); } diff --git a/src/resource.rc b/src/resource.rc index a29d513..cfd0da9 100644 --- a/src/resource.rc +++ b/src/resource.rc @@ -89,8 +89,8 @@ END 102 ICON "resources/icon.ico" IMAGE_SP BITMAP "resources/singleplayer.bmp" IMAGE_MP BITMAP "resources/multiplayer.bmp" -BINARY_SP RCDATA "resources/iw5sp.exe" -BINARY_MP RCDATA "resources/iw5mp.exe" +BINARY_SP RCDATA "resources/iw5sp.exe.diff" +BINARY_MP RCDATA "resources/iw5mp.exe.diff" #endif // English (United States) resources diff --git a/src/resources/iw5mp.exe.diff b/src/resources/iw5mp.exe.diff new file mode 100644 index 0000000..68ce39a Binary files /dev/null and b/src/resources/iw5mp.exe.diff differ diff --git a/src/resources/iw5sp.exe.diff b/src/resources/iw5sp.exe.diff new file mode 100644 index 0000000..66834a7 Binary files /dev/null and b/src/resources/iw5sp.exe.diff differ diff --git a/src/std_include.hpp b/src/std_include.hpp index 4579ff5..b1561a6 100644 --- a/src/std_include.hpp +++ b/src/std_include.hpp @@ -12,6 +12,15 @@ #include #include +// min and max is required by gdi, therefore NOMINMAX won't work +#ifdef max +#undef max +#endif + +#ifdef min +#undef min +#endif + #include #include #include @@ -19,6 +28,11 @@ #include #include +#include +#include +#include +#include + #pragma warning(pop) using namespace std::literals; diff --git a/src/utils/compression.cpp b/src/utils/compression.cpp new file mode 100644 index 0000000..ce507bf --- /dev/null +++ b/src/utils/compression.cpp @@ -0,0 +1,73 @@ +#include +#include "memory.hpp" +#include "compression.hpp" + +namespace utils +{ + namespace compression + { + std::string zlib::compress(const std::string& data) + { + memory::allocator allocator; + unsigned long length = (data.size() * 2); + if (!length) length = 2; + + if (length < 100) length *= 10; + + const auto buffer = allocator.allocate_array(length); + if (compress2(reinterpret_cast(buffer), &length, + reinterpret_cast(const_cast(data.data())), data.size(), + Z_BEST_COMPRESSION) != Z_OK) + { + return {}; + } + + return std::string(buffer, length); + } + + std::string zlib::decompress(const std::string& data) + { + z_stream stream; + ZeroMemory(&stream, sizeof(stream)); + std::string buffer; + + if (inflateInit(&stream) != Z_OK) + { + return {}; + } + + int ret; + memory::allocator allocator; + + const auto dest = allocator.allocate_array(CHUNK); + auto data_ptr = data.data(); + + do + { + stream.avail_in = std::min(static_cast(CHUNK), data.size() - (data_ptr - data.data())); + stream.next_in = reinterpret_cast(data_ptr); + data_ptr += stream.avail_in; + + do + { + stream.avail_out = CHUNK; + stream.next_out = dest; + + ret = inflate(&stream, Z_NO_FLUSH); + if (ret != Z_OK && ret != Z_STREAM_END) + { + inflateEnd(&stream); + return {}; + } + + buffer.append(reinterpret_cast(dest), CHUNK - stream.avail_out); + } + while (stream.avail_out == 0); + } + while (ret != Z_STREAM_END); + + inflateEnd(&stream); + return buffer; + } + } +} diff --git a/src/utils/compression.hpp b/src/utils/compression.hpp new file mode 100644 index 0000000..834c8ba --- /dev/null +++ b/src/utils/compression.hpp @@ -0,0 +1,16 @@ +#pragma once + +#define CHUNK 16384 + +namespace utils +{ + namespace compression + { + class zlib final + { + public: + static std::string compress(const std::string& data); + static std::string decompress(const std::string& data); + }; + }; +} diff --git a/src/utils/cryptography.cpp b/src/utils/cryptography.cpp new file mode 100644 index 0000000..55c0626 --- /dev/null +++ b/src/utils/cryptography.cpp @@ -0,0 +1,277 @@ +#include +#include "string.hpp" +#include "cryptography.hpp" + +/// http://www.opensource.apple.com/source/CommonCrypto/CommonCrypto-55010/Source/libtomcrypt/doc/libTomCryptDoc.pdf + +namespace utils +{ + namespace cryptography + { + ecc::key::key() + { + ZeroMemory(&this->key_storage_, sizeof(this->key_storage_)); + } + + ecc::key::~key() + { + this->free(); + } + + bool ecc::key::is_valid() const + { + return (!memory::is_set(&this->key_storage_, 0, sizeof(this->key_storage_))); + } + + ecc_key* ecc::key::get() + { + return &this->key_storage_; + } + + std::string ecc::key::get_public_key() const + { + uint8_t buffer[512] = {0}; + DWORD length = sizeof(buffer); + + if (ecc_ansi_x963_export(&this->key_storage_, buffer, &length) == CRYPT_OK) + { + return std::string(reinterpret_cast(buffer), length); + } + + return {}; + } + + void ecc::key::set(const std::string& pub_key_buffer) + { + this->free(); + + if (ecc_ansi_x963_import(reinterpret_cast(pub_key_buffer.data()), pub_key_buffer.size(), + &this->key_storage_) != CRYPT_OK) + { + ZeroMemory(&this->key_storage_, sizeof(this->key_storage_)); + } + } + + void ecc::key::deserialize(const std::string& key) + { + this->free(); + + if (ecc_import(reinterpret_cast(key.data()), key.size(), &this->key_storage_) != CRYPT_OK) + { + ZeroMemory(&this->key_storage_, sizeof(this->key_storage_)); + } + } + + std::string ecc::key::serialize(const int type) const + { + uint8_t buffer[4096] = {0}; + DWORD length = sizeof(buffer); + + if (ecc_export(buffer, &length, type, &this->key_storage_) == CRYPT_OK) + { + return std::string(reinterpret_cast(buffer), length); + } + + return ""; + } + + void ecc::key::free() + { + if (this->is_valid()) + { + ecc_free(&this->key_storage_); + } + + ZeroMemory(&this->key_storage_, sizeof(this->key_storage_)); + } + + bool ecc::key::operator==(key& key) const + { + return (this->is_valid() && key.is_valid() && this->serialize(PK_PUBLIC) == key.serialize(PK_PUBLIC)); + } + + ecc::key ecc::generate_key(const int bits) + { + key key; + + ltc_mp = ltm_desc; + register_prng(&sprng_desc); + ecc_make_key(nullptr, find_prng("sprng"), bits / 8, key.get()); + + return key; + } + + std::string ecc::sign_message(key key, const std::string& message) + { + if (!key.is_valid()) return ""; + + uint8_t buffer[512]; + DWORD length = sizeof(buffer); + + ltc_mp = ltm_desc; + register_prng(&sprng_desc); + ecc_sign_hash(reinterpret_cast(message.data()), message.size(), buffer, &length, nullptr, + find_prng("sprng"), key.get()); + + return std::string(reinterpret_cast(buffer), length); + } + + bool ecc::verify_message(key key, const std::string& message, const std::string& signature) + { + if (!key.is_valid()) return false; + + ltc_mp = ltm_desc; + + auto result = 0; + return ( ecc_verify_hash(reinterpret_cast(signature.data()), signature.size(), + reinterpret_cast(message.data()), message.size(), &result, key.get()) == CRYPT_OK && result != 0); + } + + std::string des3::encrypt(const std::string& data, const std::string& iv, const std::string& key) + { + initialize(); + + std::string enc_data; + enc_data.resize(data.size()); + + symmetric_CBC cbc; + const auto des3 = find_cipher("3des"); + + cbc_start(des3, reinterpret_cast(iv.data()), reinterpret_cast(key.data()), + key.size(), 0, &cbc); + cbc_encrypt(reinterpret_cast(data.data()), + reinterpret_cast(const_cast(enc_data.data())), data.size(), &cbc); + cbc_done(&cbc); + + return enc_data; + } + + std::string des3::decrypt(const std::string& data, const std::string& iv, const std::string& key) + { + initialize(); + + std::string dec_data; + dec_data.resize(data.size()); + + symmetric_CBC cbc; + const auto des3 = find_cipher("3des"); + + cbc_start(des3, reinterpret_cast(iv.data()), reinterpret_cast(key.data()), + key.size(), 0, &cbc); + cbc_decrypt(reinterpret_cast(data.data()), + reinterpret_cast(const_cast(dec_data.data())), data.size(), &cbc); + cbc_done(&cbc); + + return dec_data; + } + + void des3::initialize() + { + static auto initialized = false; + if (initialized) return; + initialized = true; + + register_cipher(&des3_desc); + } + + std::string tiger::compute(const std::string& data, const bool hex) + { + return compute(reinterpret_cast(data.data()), data.size(), hex); + } + + std::string tiger::compute(const uint8_t* data, const size_t length, const bool hex) + { + uint8_t buffer[24] = {0}; + + hash_state state; + tiger_init(&state); + tiger_process(&state, data, length); + tiger_done(&state, buffer); + + std::string hash(reinterpret_cast(buffer), sizeof(buffer)); + if (!hex) return hash; + + return string::dump_hex(hash, ""); + } + + std::string sha1::compute(const std::string& data, const bool hex) + { + return compute(reinterpret_cast(data.data()), data.size(), hex); + } + + std::string sha1::compute(const uint8_t* data, size_t length, const bool hex) + { + uint8_t buffer[20] = {0}; + + hash_state state; + sha1_init(&state); + sha1_process(&state, data, length); + sha1_done(&state, buffer); + + std::string hash(reinterpret_cast(buffer), sizeof(buffer)); + if (!hex) return hash; + + return string::dump_hex(hash, ""); + } + + std::string sha256::compute(const std::string& data, bool hex) + { + return compute(reinterpret_cast(data.data()), data.size(), hex); + } + + std::string sha256::compute(const uint8_t* data, size_t length, bool hex) + { + uint8_t buffer[32] = {0}; + + hash_state state; + sha256_init(&state); + sha256_process(&state, data, length); + sha256_done(&state, buffer); + + std::string hash(reinterpret_cast(buffer), sizeof(buffer)); + if (!hex) return hash; + + return string::dump_hex(hash, ""); + } + + std::string sha512::compute(const std::string& data, bool hex) + { + return compute(reinterpret_cast(data.data()), data.size(), hex); + } + + std::string sha512::compute(const uint8_t* data, size_t length, bool hex) + { + uint8_t buffer[64] = {0}; + + hash_state state; + sha512_init(&state); + sha512_process(&state, data, length); + sha512_done(&state, buffer); + + std::string hash(reinterpret_cast(buffer), sizeof(buffer)); + if (!hex) return hash; + + return string::dump_hex(hash, ""); + } + + unsigned int jenkins_one_at_a_time::compute(const std::string& data) + { + return compute(data.data(), data.size()); + } + + unsigned int jenkins_one_at_a_time::compute(const char* key, size_t len) + { + unsigned int hash, i; + for (hash = i = 0; i < len; ++i) + { + hash += key[i]; + hash += (hash << 10); + hash ^= (hash >> 6); + } + hash += (hash << 3); + hash ^= (hash >> 11); + hash += (hash << 15); + return hash; + } + } +} diff --git a/src/utils/cryptography.hpp b/src/utils/cryptography.hpp new file mode 100644 index 0000000..e70499a --- /dev/null +++ b/src/utils/cryptography.hpp @@ -0,0 +1,87 @@ +#pragma once +#include "memory.hpp" + +namespace utils +{ + namespace cryptography + { + class ecc final + { + public: + class key final + { + public: + key(); + ~key(); + + bool is_valid() const; + + ecc_key* get(); + + std::string get_public_key() const; + + void set(const std::string& pub_key_buffer); + + void deserialize(const std::string& key); + + std::string serialize(const int type = PK_PRIVATE) const; + + void free(); + + bool operator==(key& key) const; + + private: + ecc_key key_storage_{}; + }; + + static key generate_key(int bits); + static std::string sign_message(key key, const std::string& message); + static bool verify_message(key key, const std::string& message, const std::string& signature); + }; + + class des3 final + { + public: + static std::string encrypt(const std::string& data, const std::string& iv, const std::string& key); + static std::string decrypt(const std::string& data, const std::string& iv, const std::string& key); + + private: + static void initialize(); + }; + + class tiger final + { + public: + static std::string compute(const std::string& data, bool hex = false); + static std::string compute(const uint8_t* data, size_t length, bool hex = false); + }; + + class sha1 final + { + public: + static std::string compute(const std::string& data, bool hex = false); + static std::string compute(const uint8_t* data, size_t length, bool hex = false); + }; + + class sha256 final + { + public: + static std::string compute(const std::string& data, bool hex = false); + static std::string compute(const uint8_t* data, size_t length, bool hex = false); + }; + + class sha512 final + { + public: + static std::string compute(const std::string& data, bool hex = false); + static std::string compute(const uint8_t* data, size_t length, bool hex = false); + }; + + class jenkins_one_at_a_time final + { + public: + static unsigned int compute(const std::string& data); + static unsigned int compute(const char* key, size_t len); + }; + } +} diff --git a/src/utils/hook.cpp b/src/utils/hook.cpp index f966364..a486f9e 100644 --- a/src/utils/hook.cpp +++ b/src/utils/hook.cpp @@ -23,7 +23,7 @@ namespace utils unsigned int j; for (j = 0; j < strlen(container->mask); ++j) { - if (container->mask[j] != '?' &&container->signature[j] != address[j]) + if (container->mask[j] != '?' && container->signature[j] != address[j]) { break; } @@ -50,7 +50,7 @@ namespace utils } } - hook* hook::initialize(const DWORD place, void(*stub)(), const bool use_jump) + hook* hook::initialize(const DWORD place, void (*stub)(), const bool use_jump) { return this->initialize(place, reinterpret_cast(stub), use_jump); } @@ -69,7 +69,8 @@ namespace utils this->place_ = place; this->stub_ = stub; - this->original_ = static_cast(this->place_) + 5 + *reinterpret_cast((static_cast(this->place_) + 1)); + this->original_ = static_cast(this->place_) + 5 + *reinterpret_cast((static_cast(this-> + place_) + 1)); return this; } @@ -92,9 +93,11 @@ namespace utils *code = static_cast(this->use_jump_ ? 0xE9 : 0xE8); - *reinterpret_cast(code + 1) = reinterpret_cast(this->stub_) - (reinterpret_cast(this->place_) + 5); + *reinterpret_cast(code + 1) = reinterpret_cast(this->stub_) - (reinterpret_cast(this-> + place_) + 5); - if (unprotect && !keep_unprotected) VirtualProtect(this->place_, sizeof(this->buffer_), this->protection_, &this->protection_); + if (unprotect && !keep_unprotected) VirtualProtect(this->place_, sizeof(this->buffer_), this->protection_, + &this->protection_); FlushInstructionCache(GetCurrentProcess(), this->place_, sizeof(this->buffer_)); diff --git a/src/utils/hook.hpp b/src/utils/hook.hpp index 5b40650..fc3b3c4 100644 --- a/src/utils/hook.hpp +++ b/src/utils/hook.hpp @@ -18,9 +18,17 @@ namespace utils std::function callback; }; - signature(void* start, const size_t length) : start_(start), length_(length) {} - signature(const DWORD start, const size_t length) : signature(reinterpret_cast(start), length) {} - signature() : signature(0x400000, 0x800000) {} + signature(void* start, const size_t length) : start_(start), length_(length) + { + } + + signature(const DWORD start, const size_t length) : signature(reinterpret_cast(start), length) + { + } + + signature() : signature(0x400000, 0x800000) + { + } void process(); void add(const container& container); @@ -31,14 +39,33 @@ namespace utils std::vector signatures_; }; - hook() : initialized_(false), installed_(false), place_(nullptr), stub_(nullptr), original_(nullptr), use_jump_(false), protection_(0) { ZeroMemory(this->buffer_, sizeof(this->buffer_)); } + hook() : initialized_(false), installed_(false), place_(nullptr), stub_(nullptr), original_(nullptr), + use_jump_(false), protection_(0) + { + ZeroMemory(this->buffer_, sizeof(this->buffer_)); + } hook(void* place, void* stub, const bool use_jump = true) : hook() { this->initialize(place, stub, use_jump); } - hook(void* place, void(*stub)(), const bool use_jump = true) : hook(place, reinterpret_cast(stub), use_jump) {} - hook(const DWORD place, void* stub, const bool use_jump = true) : hook(reinterpret_cast(place), stub, use_jump) {} - hook(const DWORD place, const DWORD stub, const bool use_jump = true) : hook(reinterpret_cast(place), reinterpret_cast(stub), use_jump) {} - hook(const DWORD place, void(*stub)(), const bool use_jump = true) : hook(reinterpret_cast(place), reinterpret_cast(stub), use_jump) {} + hook(void* place, void (*stub)(), const bool use_jump = true) : hook( + place, reinterpret_cast(stub), use_jump) + { + } + + hook(const DWORD place, void* stub, const bool use_jump = true) : hook( + reinterpret_cast(place), stub, use_jump) + { + } + + hook(const DWORD place, const DWORD stub, const bool use_jump = true) : hook( + reinterpret_cast(place), reinterpret_cast(stub), use_jump) + { + } + + hook(const DWORD place, void (*stub)(), const bool use_jump = true) : hook( + reinterpret_cast(place), reinterpret_cast(stub), use_jump) + { + } hook(const hook&) = delete; hook(const hook&&) = delete; @@ -47,7 +74,7 @@ namespace utils hook* initialize(void* place, void* stub, bool use_jump = true); hook* initialize(DWORD place, void* stub, bool use_jump = true); - hook* initialize(DWORD place, void(*stub)(), bool use_jump = true); // For lambdas + hook* initialize(DWORD place, void (*stub)(), bool use_jump = true); // For lambdas hook* install(bool unprotect = true, bool keep_unprotected = false); hook* uninstall(bool unprotect = true); @@ -57,7 +84,8 @@ namespace utils static void nop(void* place, size_t length); static void nop(DWORD place, size_t length); - template static void set(void* place, T value) + template + static void set(void* place, T value) { DWORD old_protect; VirtualProtect(place, sizeof(T), PAGE_EXECUTE_READWRITE, &old_protect); @@ -68,7 +96,8 @@ namespace utils FlushInstructionCache(GetCurrentProcess(), place, sizeof(T)); } - template static void set(const DWORD place, T value) + template + static void set(const DWORD place, T value) { return set(reinterpret_cast(place), value); } diff --git a/src/utils/io.cpp b/src/utils/io.cpp new file mode 100644 index 0000000..6bf5d7a --- /dev/null +++ b/src/utils/io.cpp @@ -0,0 +1,110 @@ +#include +#include "io.hpp" + +namespace utils +{ + namespace io + { + bool file_exists(const std::string& file) + { + return std::ifstream(file).good(); + } + + bool write_file(const std::string& file, const std::string& data, const bool append) + { + const auto pos = file.find_last_of("/\\"); + if (pos != std::string::npos) + { + create_directory(file.substr(0, pos)); + } + + std::ofstream stream( + file, std::ios::binary | std::ofstream::out | (append ? std::ofstream::app : std::ofstream::out)); + + if (stream.is_open()) + { + stream.write(data.data(), data.size()); + stream.close(); + return true; + } + + return false; + } + + std::string read_file(const std::string& file) + { + std::string data; + read_file(file, &data); + return data; + } + + bool read_file(const std::string& file, std::string* data) + { + if (!data) return false; + data->clear(); + + if (file_exists(file)) + { + std::ifstream stream(file, std::ios::binary); + if (!stream.is_open()) return false; + + stream.seekg(0, std::ios::end); + const std::streamsize size = stream.tellg(); + stream.seekg(0, std::ios::beg); + + if (size > -1) + { + data->resize(static_cast(size)); + stream.read(const_cast(data->data()), size); + stream.close(); + return true; + } + } + + return false; + } + + size_t file_size(const std::string& file) + { + if (file_exists(file)) + { + std::ifstream stream(file, std::ios::binary); + + if (stream.good()) + { + stream.seekg(0, std::ios::end); + return static_cast(stream.tellg()); + } + } + + return 0; + } + + bool create_directory(const std::string& directory) + { + return std::experimental::filesystem::create_directories(directory); + } + + bool directory_exists(const std::string& directory) + { + return std::experimental::filesystem::is_directory(directory); + } + + bool directory_is_empty(const std::string& directory) + { + return std::experimental::filesystem::is_empty(directory); + } + + std::vector list_files(const std::string& directory) + { + std::vector files; + + for (auto& file : std::experimental::filesystem::directory_iterator(directory)) + { + files.push_back(file.path().generic_string()); + } + + return files; + } + } +} diff --git a/src/utils/io.hpp b/src/utils/io.hpp new file mode 100644 index 0000000..0dc5a56 --- /dev/null +++ b/src/utils/io.hpp @@ -0,0 +1,17 @@ +#pragma once + +namespace utils +{ + namespace io + { + bool file_exists(const std::string& file); + bool write_file(const std::string& file, const std::string& data, bool append = false); + bool read_file(const std::string& file, std::string* data); + std::string read_file(const std::string& file); + size_t file_size(const std::string& file); + bool create_directory(const std::string& directory); + bool directory_exists(const std::string& directory); + bool directory_is_empty(const std::string& directory); + std::vector list_files(const std::string& directory); + } +} diff --git a/src/utils/memory.cpp b/src/utils/memory.cpp new file mode 100644 index 0000000..419e666 --- /dev/null +++ b/src/utils/memory.cpp @@ -0,0 +1,142 @@ +#include +#include "memory.hpp" + +namespace utils +{ + memory::allocator memory::mem_allocator_; + + memory::allocator::~allocator() + { + this->clear(); + } + + void memory::allocator::clear() + { + std::lock_guard _(this->mutex_); + + for (auto& data : this->pool_) + { + memory::free(data); + } + + this->pool_.clear(); + } + + void memory::allocator::free(void* data) + { + std::lock_guard _(this->mutex_); + + const auto j = std::find(this->pool_.begin(), this->pool_.end(), data); + if (j != this->pool_.end()) + { + memory::free(data); + this->pool_.erase(j); + } + } + + void memory::allocator::free(const void* data) + { + this->free(const_cast(data)); + } + + void* memory::allocator::allocate(const size_t length) + { + std::lock_guard _(this->mutex_); + + const auto data = memory::allocate(length); + this->pool_.push_back(data); + return data; + } + + bool memory::allocator::empty() const + { + return this->pool_.empty(); + } + + char* memory::allocator::duplicate_string(const std::string& string) + { + std::lock_guard _(this->mutex_); + + const auto data = memory::duplicate_string(string); + this->pool_.push_back(data); + return data; + } + + void* memory::allocate(const size_t length) + { + const auto data = calloc(length, 1); + assert(data != nullptr); + return data; + } + + char* memory::duplicate_string(const std::string& string) + { + const auto new_string = allocate_array(string.size() + 1); + std::memcpy(new_string, string.data(), string.size()); + return new_string; + } + + void memory::free(void* data) + { + if (data) + { + ::free(data); + } + } + + void memory::free(const void* data) + { + free(const_cast(data)); + } + + bool memory::is_set(const void* mem, const char chr, const size_t length) + { + const auto mem_arr = reinterpret_cast(mem); + + for (size_t i = 0; i < length; ++i) + { + if (mem_arr[i] != chr) + { + return false; + } + } + + return true; + } + + bool memory::is_bad_read_ptr(const void* ptr) + { + MEMORY_BASIC_INFORMATION mbi = {}; + if (VirtualQuery(ptr, &mbi, sizeof(mbi))) + { + const DWORD mask = (PAGE_READONLY | PAGE_READWRITE | PAGE_WRITECOPY | PAGE_EXECUTE_READ | + PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY); + auto b = !(mbi.Protect & mask); + // check the page is not a guard page + if (mbi.Protect & (PAGE_GUARD | PAGE_NOACCESS)) b = true; + + return b; + } + return true; + } + + bool memory::is_bad_code_ptr(const void* ptr) + { + MEMORY_BASIC_INFORMATION mbi = {}; + if (VirtualQuery(ptr, &mbi, sizeof(mbi))) + { + const DWORD mask = (PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY); + auto b = !(mbi.Protect & mask); + // check the page is not a guard page + if (mbi.Protect & (PAGE_GUARD | PAGE_NOACCESS)) b = true; + + return b; + } + return true; + } + + memory::allocator* memory::get_allocator() + { + return &memory::mem_allocator_; + } +} diff --git a/src/utils/memory.hpp b/src/utils/memory.hpp new file mode 100644 index 0000000..06b35ed --- /dev/null +++ b/src/utils/memory.hpp @@ -0,0 +1,71 @@ +#pragma once + +namespace utils +{ + class memory final + { + public: + class allocator final + { + public: + ~allocator(); + + void clear(); + + void free(void* data); + + void free(const void* data); + + void* allocate(const size_t length); + + template + inline T* allocate() + { + return this->allocate_array(1); + } + + template + inline T* allocate_array(const size_t count = 1) + { + return static_cast(this->allocate(count * sizeof(T))); + } + + bool empty() const; + + char* duplicate_string(const std::string& string); + + private: + std::mutex mutex_; + std::vector pool_; + }; + + static void* allocate(size_t length); + + template + static inline T* allocate() + { + return allocate_array(1); + } + + template + static inline T* allocate_array(const size_t count = 1) + { + return static_cast(allocate(count * sizeof(T))); + } + + static char* duplicate_string(const std::string& string); + + static void free(void* data); + static void free(const void* data); + + static bool is_set(const void* mem, char chr, size_t length); + + static bool is_bad_read_ptr(const void* ptr); + static bool is_bad_code_ptr(const void* ptr); + + static allocator* get_allocator(); + + private: + static allocator mem_allocator_; + }; +} diff --git a/src/utils/nt.cpp b/src/utils/nt.cpp index f38b652..ebea371 100644 --- a/src/utils/nt.cpp +++ b/src/utils/nt.cpp @@ -32,7 +32,7 @@ namespace utils this->module_ = handle; } - bool module::operator==(const module &obj) const + bool module::operator==(const module& obj) const { return this->module_ == obj.module_; } @@ -90,7 +90,8 @@ namespace utils if (!this->is_valid()) return; DWORD protection; - VirtualProtect(this->get_ptr(), this->get_optional_header()->SizeOfImage, PAGE_EXECUTE_READWRITE, &protection); + VirtualProtect(this->get_ptr(), this->get_optional_header()->SizeOfImage, PAGE_EXECUTE_READWRITE, + &protection); } size_t module::get_relative_entry_point() const @@ -125,7 +126,7 @@ namespace utils { if (!this->is_valid()) return ""; - char name[MAX_PATH] = { 0 }; + char name[MAX_PATH] = {0}; GetModuleFileNameA(this->module_, name, sizeof name); return name; @@ -158,22 +159,26 @@ namespace utils auto* header = this->get_optional_header(); if (!header) return nullptr; - auto* import_descriptor = reinterpret_cast(this->get_ptr() + header->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress); + auto* import_descriptor = reinterpret_cast(this->get_ptr() + header->DataDirectory + [IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress); while (import_descriptor->Name) { if (!_stricmp(reinterpret_cast(this->get_ptr() + import_descriptor->Name), module_name.data())) { - auto* original_thunk_data = reinterpret_cast(import_descriptor->OriginalFirstThunk + this->get_ptr()); - auto* thunk_data = reinterpret_cast(import_descriptor->FirstThunk + this->get_ptr()); + auto* original_thunk_data = reinterpret_cast(import_descriptor-> + OriginalFirstThunk + this->get_ptr()); + auto* thunk_data = reinterpret_cast(import_descriptor->FirstThunk + this-> + get_ptr()); - while(original_thunk_data->u1.AddressOfData) + while (original_thunk_data->u1.AddressOfData) { const size_t ordinal_number = original_thunk_data->u1.AddressOfData & 0xFFFFFFF; if (ordinal_number > 0xFFFF) continue; - if (GetProcAddress(other_module.module_, reinterpret_cast(ordinal_number)) == target_function) + if (GetProcAddress(other_module.module_, reinterpret_cast(ordinal_number)) == + target_function) { return reinterpret_cast(&thunk_data->u1.Function); } diff --git a/src/utils/nt.hpp b/src/utils/nt.hpp index 3195d67..9c02860 100644 --- a/src/utils/nt.hpp +++ b/src/utils/nt.hpp @@ -14,10 +14,12 @@ namespace utils explicit module(const std::string& name); explicit module(HMODULE handle); - module(const module& a) : module_(a.module_) {} + module(const module& a) : module_(a.module_) + { + } - bool operator!=(const module &obj) const { return !(*this == obj); }; - bool operator==(const module &obj) const; + bool operator!=(const module& obj) const { return !(*this == obj); }; + bool operator==(const module& obj) const; operator bool() const; operator HMODULE() const; @@ -48,26 +50,26 @@ namespace utils return reinterpret_cast(this->get_proc(process)); } - template - T invoke(const std::string& process, Args... args) + template + T invoke(const std::string& process, Args ... args) { - auto method = this->get(process); + auto method = this->get(process); if (method) return method(args...); return T(); } - template - T invoke_pascal(const std::string& process, Args... args) + template + T invoke_pascal(const std::string& process, Args ... args) { - auto method = this->get(process); + auto method = this->get(process); if (method) return method(args...); return T(); } - template - T invoke_this(const std::string& process, void* thisPtr, Args... args) + template + T invoke_this(const std::string& process, void* thisPtr, Args ... args) { - auto method = this->get(thisPtr, process); + auto method = this->get(thisPtr, process); if (method) return method(args...); return T(); } diff --git a/src/utils/string.cpp b/src/utils/string.cpp new file mode 100644 index 0000000..faad6f4 --- /dev/null +++ b/src/utils/string.cpp @@ -0,0 +1,38 @@ +#include +#include "string.hpp" + +namespace utils +{ + namespace string + { + const char* va(const char* fmt, ...) + { + static thread_local va_provider<8, 256> provider; + + va_list ap; + va_start(ap, fmt); + + const char* result = provider.get(fmt, ap); + + va_end(ap); + return result; + } + + std::string dump_hex(const std::string& data, const std::string& separator) + { + std::string result; + + for (unsigned int i = 0; i < data.size(); ++i) + { + if (i > 0) + { + result.append(separator); + } + + result.append(va("%02X", data[i] & 0xFF)); + } + + return result; + } + } +} diff --git a/src/utils/string.hpp b/src/utils/string.hpp new file mode 100644 index 0000000..a3f7b10 --- /dev/null +++ b/src/utils/string.hpp @@ -0,0 +1,81 @@ +#pragma once +#include "memory.hpp" + +namespace utils +{ + namespace string + { + template + class va_provider final + { + public: + static_assert(Buffers != 0 && MinBufferSize != 0, "Buffers and MinBufferSize mustn't be 0"); + + va_provider() : current_buffer_(0) + { + } + + char* get(const char* format, const va_list ap) + { + ++this->current_buffer_ %= ARRAYSIZE(this->string_pool_); + auto entry = &this->string_pool_[this->current_buffer_]; + + if (!entry->size || !entry->buffer) + { + throw std::runtime_error("String pool not initialized"); + } + + while (true) + { + const int res = vsnprintf_s(entry->buffer, entry->size, _TRUNCATE, format, ap); + if (res > 0) break; // Success + if (res == 0) return nullptr; // Error + + entry->double_size(); + } + + return entry->buffer; + } + + private: + class entry final + { + public: + explicit entry(size_t _size = MinBufferSize) : size(_size), buffer(nullptr) + { + if (this->size < MinBufferSize) this->size = MinBufferSize; + this->allocate(); + } + + ~entry() + { + if (this->buffer) memory::get_allocator()->free(this->buffer); + this->size = 0; + this->buffer = nullptr; + } + + void allocate() + { + if (this->buffer) memory::get_allocator()->free(this->buffer); + this->buffer = memory::get_allocator()->allocate_array(this->size + 1); + } + + void double_size() + { + this->size *= 2; + this->allocate(); + } + + size_t size; + char* buffer; + }; + + size_t current_buffer_; + entry string_pool_[Buffers]; + }; + + const char* va(const char* fmt, ...); + + std::string dump_hex(const std::string& data, const std::string& separator = " "); + } +}