Load binaries as diffs

This commit is contained in:
momo5502 2018-12-26 16:28:16 +01:00
parent 1ed00dcaf9
commit 48ee9476e1
27 changed files with 1168 additions and 91 deletions

View File

@ -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

View File

@ -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)

View File

@ -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));

View File

@ -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_;

View File

@ -0,0 +1,120 @@
#include <std_include.hpp>
#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<const unsigned char*>(data.data());
const auto old_data = reinterpret_cast<const unsigned char*>(base.data());
std::vector<unsigned char> 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<char*>(diff.data()), diff.size());
result.append(reinterpret_cast<const char*>(&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<const unsigned long long*>(diff.data() + diff.size() - sizeof(unsigned long long));
std::string binary;
binary.resize(size_t(*size));
const auto new_data = reinterpret_cast<unsigned char*>(binary.data());
const auto old_data = reinterpret_cast<const unsigned char*>(base.data());
const auto diff_data = reinterpret_cast<const unsigned char*>(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);
}
}

View File

@ -0,0 +1,8 @@
#pragma once
#include "launcher/launcher.hpp"
namespace binary_loader
{
void create();
std::string load(launcher::mode mode);
}

View File

@ -1,13 +1,16 @@
#include <std_include.hpp>
#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<LPVOID*>(__readfsdword(0x2C));
const auto tls_base = *reinterpret_cast<LPVOID*>(__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::function<FARPROC(const std::string&,
this->import_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<uintptr_t>(function);

View File

@ -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<FARPROC(const std::string&, const std::string&)> 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;

View File

@ -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();
}

View File

@ -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

Binary file not shown.

Binary file not shown.

View File

@ -12,6 +12,15 @@
#include <objidl.h>
#include <gdiplus.h>
// min and max is required by gdi, therefore NOMINMAX won't work
#ifdef max
#undef max
#endif
#ifdef min
#undef min
#endif
#include <map>
#include <vector>
#include <mutex>
@ -19,6 +28,11 @@
#include <fstream>
#include <filesystem>
#include <zlib.h>
#include <diff.h>
#include <patch.h>
#include <tomcrypt.h>
#pragma warning(pop)
using namespace std::literals;

73
src/utils/compression.cpp Normal file
View File

@ -0,0 +1,73 @@
#include <std_include.hpp>
#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<char>(length);
if (compress2(reinterpret_cast<Bytef*>(buffer), &length,
reinterpret_cast<Bytef*>(const_cast<char*>(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<uint8_t>(CHUNK);
auto data_ptr = data.data();
do
{
stream.avail_in = std::min(static_cast<size_t>(CHUNK), data.size() - (data_ptr - data.data()));
stream.next_in = reinterpret_cast<const uint8_t*>(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<const char*>(dest), CHUNK - stream.avail_out);
}
while (stream.avail_out == 0);
}
while (ret != Z_STREAM_END);
inflateEnd(&stream);
return buffer;
}
}
}

16
src/utils/compression.hpp Normal file
View File

@ -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);
};
};
}

277
src/utils/cryptography.cpp Normal file
View File

@ -0,0 +1,277 @@
#include <std_include.hpp>
#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<char*>(buffer), length);
}
return {};
}
void ecc::key::set(const std::string& pub_key_buffer)
{
this->free();
if (ecc_ansi_x963_import(reinterpret_cast<const uint8_t*>(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<const uint8_t*>(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<char*>(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<const uint8_t*>(message.data()), message.size(), buffer, &length, nullptr,
find_prng("sprng"), key.get());
return std::string(reinterpret_cast<char*>(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<const uint8_t*>(signature.data()), signature.size(),
reinterpret_cast<const uint8_t*>(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<const uint8_t*>(iv.data()), reinterpret_cast<const uint8_t*>(key.data()),
key.size(), 0, &cbc);
cbc_encrypt(reinterpret_cast<const uint8_t*>(data.data()),
reinterpret_cast<uint8_t*>(const_cast<char*>(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<const uint8_t*>(iv.data()), reinterpret_cast<const uint8_t*>(key.data()),
key.size(), 0, &cbc);
cbc_decrypt(reinterpret_cast<const uint8_t*>(data.data()),
reinterpret_cast<uint8_t*>(const_cast<char*>(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<const uint8_t*>(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<char*>(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<const uint8_t*>(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<char*>(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<const uint8_t*>(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<char*>(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<const uint8_t*>(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<char*>(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;
}
}
}

View File

@ -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);
};
}
}

View File

@ -69,7 +69,8 @@ namespace utils
this->place_ = place;
this->stub_ = stub;
this->original_ = static_cast<char*>(this->place_) + 5 + *reinterpret_cast<DWORD*>((static_cast<char*>(this->place_) + 1));
this->original_ = static_cast<char*>(this->place_) + 5 + *reinterpret_cast<DWORD*>((static_cast<char*>(this->
place_) + 1));
return this;
}
@ -92,9 +93,11 @@ namespace utils
*code = static_cast<char>(this->use_jump_ ? 0xE9 : 0xE8);
*reinterpret_cast<size_t*>(code + 1) = reinterpret_cast<size_t>(this->stub_) - (reinterpret_cast<size_t>(this->place_) + 5);
*reinterpret_cast<size_t*>(code + 1) = reinterpret_cast<size_t>(this->stub_) - (reinterpret_cast<size_t>(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_));

View File

@ -18,9 +18,17 @@ namespace utils
std::function<void(char*)> callback;
};
signature(void* start, const size_t length) : start_(start), length_(length) {}
signature(const DWORD start, const size_t length) : signature(reinterpret_cast<void*>(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<void*>(start), length)
{
}
signature() : signature(0x400000, 0x800000)
{
}
void process();
void add(const container& container);
@ -31,14 +39,33 @@ namespace utils
std::vector<container> 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<void*>(stub), use_jump) {}
hook(const DWORD place, void* stub, const bool use_jump = true) : hook(reinterpret_cast<void*>(place), stub, use_jump) {}
hook(const DWORD place, const DWORD stub, const bool use_jump = true) : hook(reinterpret_cast<void*>(place), reinterpret_cast<void*>(stub), use_jump) {}
hook(const DWORD place, void(*stub)(), const bool use_jump = true) : hook(reinterpret_cast<void*>(place), reinterpret_cast<void*>(stub), use_jump) {}
hook(void* place, void (*stub)(), const bool use_jump = true) : hook(
place, reinterpret_cast<void*>(stub), use_jump)
{
}
hook(const DWORD place, void* stub, const bool use_jump = true) : hook(
reinterpret_cast<void*>(place), stub, use_jump)
{
}
hook(const DWORD place, const DWORD stub, const bool use_jump = true) : hook(
reinterpret_cast<void*>(place), reinterpret_cast<void*>(stub), use_jump)
{
}
hook(const DWORD place, void (*stub)(), const bool use_jump = true) : hook(
reinterpret_cast<void*>(place), reinterpret_cast<void*>(stub), use_jump)
{
}
hook(const hook&) = delete;
hook(const hook&&) = delete;
@ -57,7 +84,8 @@ namespace utils
static void nop(void* place, size_t length);
static void nop(DWORD place, size_t length);
template <typename T> static void set(void* place, T value)
template <typename T>
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 <typename T> static void set(const DWORD place, T value)
template <typename T>
static void set(const DWORD place, T value)
{
return set<T>(reinterpret_cast<void*>(place), value);
}

110
src/utils/io.cpp Normal file
View File

@ -0,0 +1,110 @@
#include <std_include.hpp>
#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<uint32_t>(size));
stream.read(const_cast<char*>(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<size_t>(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<std::string> list_files(const std::string& directory)
{
std::vector<std::string> files;
for (auto& file : std::experimental::filesystem::directory_iterator(directory))
{
files.push_back(file.path().generic_string());
}
return files;
}
}
}

17
src/utils/io.hpp Normal file
View File

@ -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<std::string> list_files(const std::string& directory);
}
}

142
src/utils/memory.cpp Normal file
View File

@ -0,0 +1,142 @@
#include <std_include.hpp>
#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<void*>(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<char>(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<void*>(data));
}
bool memory::is_set(const void* mem, const char chr, const size_t length)
{
const auto mem_arr = reinterpret_cast<const char*>(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_;
}
}

71
src/utils/memory.hpp Normal file
View File

@ -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 <typename T>
inline T* allocate()
{
return this->allocate_array<T>(1);
}
template <typename T>
inline T* allocate_array(const size_t count = 1)
{
return static_cast<T*>(this->allocate(count * sizeof(T)));
}
bool empty() const;
char* duplicate_string(const std::string& string);
private:
std::mutex mutex_;
std::vector<void*> pool_;
};
static void* allocate(size_t length);
template <typename T>
static inline T* allocate()
{
return allocate_array<T>(1);
}
template <typename T>
static inline T* allocate_array(const size_t count = 1)
{
return static_cast<T*>(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_;
};
}

View File

@ -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
@ -158,14 +159,17 @@ namespace utils
auto* header = this->get_optional_header();
if (!header) return nullptr;
auto* import_descriptor = reinterpret_cast<PIMAGE_IMPORT_DESCRIPTOR>(this->get_ptr() + header->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
auto* import_descriptor = reinterpret_cast<PIMAGE_IMPORT_DESCRIPTOR>(this->get_ptr() + header->DataDirectory
[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
while (import_descriptor->Name)
{
if (!_stricmp(reinterpret_cast<char*>(this->get_ptr() + import_descriptor->Name), module_name.data()))
{
auto* original_thunk_data = reinterpret_cast<PIMAGE_THUNK_DATA>(import_descriptor->OriginalFirstThunk + this->get_ptr());
auto* thunk_data = reinterpret_cast<PIMAGE_THUNK_DATA>(import_descriptor->FirstThunk + this->get_ptr());
auto* original_thunk_data = reinterpret_cast<PIMAGE_THUNK_DATA>(import_descriptor->
OriginalFirstThunk + this->get_ptr());
auto* thunk_data = reinterpret_cast<PIMAGE_THUNK_DATA>(import_descriptor->FirstThunk + this->
get_ptr());
while (original_thunk_data->u1.AddressOfData)
{
@ -173,7 +177,8 @@ namespace utils
if (ordinal_number > 0xFFFF) continue;
if (GetProcAddress(other_module.module_, reinterpret_cast<char*>(ordinal_number)) == target_function)
if (GetProcAddress(other_module.module_, reinterpret_cast<char*>(ordinal_number)) ==
target_function)
{
return reinterpret_cast<void**>(&thunk_data->u1.Function);
}

View File

@ -14,7 +14,9 @@ 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;

38
src/utils/string.cpp Normal file
View File

@ -0,0 +1,38 @@
#include <std_include.hpp>
#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;
}
}
}

81
src/utils/string.hpp Normal file
View File

@ -0,0 +1,81 @@
#pragma once
#include "memory.hpp"
namespace utils
{
namespace string
{
template <size_t Buffers, size_t MinBufferSize>
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<char>(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 = " ");
}
}