Add binary loader

This commit is contained in:
momo5502 2018-12-23 22:15:32 +01:00
parent 22bd1aad3f
commit dfc1037bd2
13 changed files with 586 additions and 25 deletions

View File

@ -17,6 +17,15 @@ workspace "open-mw3"
staticruntime "On" staticruntime "On"
warnings "Extra" warnings "Extra"
flags {
"StaticRuntime",
"NoIncrementalLink",
"NoEditAndContinue",
"NoMinimalRebuild",
"MultiProcessorCompile",
"No64BitChecks"
}
configuration "windows" configuration "windows"
defines { defines {
"_WINDOWS", "_WINDOWS",
@ -31,7 +40,6 @@ workspace "open-mw3"
} }
flags { flags {
"MultiProcessorCompile",
"LinkTimeOptimization", "LinkTimeOptimization",
"FatalCompileWarnings", "FatalCompileWarnings",
} }
@ -44,10 +52,6 @@ workspace "open-mw3"
"_DEBUG", "_DEBUG",
} }
flags {
"MultiProcessorCompile",
}
configuration {} configuration {}
project "open-mw3" project "open-mw3"
@ -57,6 +61,10 @@ workspace "open-mw3"
pchheader "std_include.hpp" pchheader "std_include.hpp"
pchsource "src/std_include.cpp" pchsource "src/std_include.cpp"
linkoptions "/IGNORE:4254 /DYNAMICBASE:NO /SAFESEH:NO /LARGEADDRESSAWARE"
linkoptions "/LAST:.zdata"
files { files {
"./src/**.rc", "./src/**.rc",
"./src/**.hpp", "./src/**.hpp",

View File

@ -111,7 +111,7 @@ void launcher::paint() const
EndPaint(this->window_, &ps); EndPaint(this->window_, &ps);
} }
void launcher::mouse_move(LPARAM l_param) void launcher::mouse_move(const LPARAM l_param)
{ {
this->mouse_.x = GET_X_LPARAM(l_param); this->mouse_.x = GET_X_LPARAM(l_param);
this->mouse_.y = GET_Y_LPARAM(l_param); this->mouse_.y = GET_Y_LPARAM(l_param);

View File

@ -17,7 +17,7 @@ window::window(const std::string& title, const int width, const int height)
this->wc_.hCursor = LoadCursor(nullptr, IDC_ARROW); this->wc_.hCursor = LoadCursor(nullptr, IDC_ARROW);
this->wc_.hIcon = LoadIcon(handle, MAKEINTRESOURCE(102)); this->wc_.hIcon = LoadIcon(handle, MAKEINTRESOURCE(102));
this->wc_.hIconSm = this->wc_.hIcon; this->wc_.hIconSm = this->wc_.hIcon;
this->wc_.hbrBackground = CreateSolidBrush(RGB(35, 35, 35)); //HBRUSH(COLOR_WINDOW); this->wc_.hbrBackground = CreateSolidBrush(RGB(35, 35, 35));
this->wc_.lpszClassName = L"omw3_window"; this->wc_.lpszClassName = L"omw3_window";
RegisterClassEx(&this->wc_); RegisterClassEx(&this->wc_);

187
src/loader/loader.cpp Normal file
View File

@ -0,0 +1,187 @@
#include <std_include.hpp>
#include "loader.hpp"
loader::loader(const launcher::mode mode) : mode_(mode)
{
}
void loader::patch()
{
}
FARPROC loader::load(const utils::nt::module& module) const
{
const auto buffer = this->load_binary();
if (buffer.empty()) return nullptr;
utils::nt::module source(HMODULE(buffer.data()));
if (!source) return nullptr;
this->load_sections(module, source);
this->load_imports(module, source);
if (source.get_optional_header()->DataDirectory[IMAGE_DIRECTORY_ENTRY_TLS].Size)
{
const IMAGE_TLS_DIRECTORY* target_tls = reinterpret_cast<PIMAGE_TLS_DIRECTORY>(module.get_ptr() + module
.get_optional_header()
->DataDirectory
[IMAGE_DIRECTORY_ENTRY_TLS].VirtualAddress);
const IMAGE_TLS_DIRECTORY* source_tls = reinterpret_cast<PIMAGE_TLS_DIRECTORY>(module.get_ptr() + source
.get_optional_header()
->DataDirectory
[IMAGE_DIRECTORY_ENTRY_TLS].VirtualAddress);
*reinterpret_cast<DWORD*>(source_tls->AddressOfIndex) = 0;
DWORD old_protect;
VirtualProtect(PVOID(target_tls->StartAddressOfRawData),
source_tls->EndAddressOfRawData - source_tls->StartAddressOfRawData, PAGE_READWRITE, &old_protect);
const LPVOID 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),
source_tls->EndAddressOfRawData - source_tls->StartAddressOfRawData);
}
DWORD oldProtect;
VirtualProtect(module.get_nt_headers(), 0x1000, PAGE_EXECUTE_READWRITE, &oldProtect);
module.get_optional_header()->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT] = source
.get_optional_header()->DataDirectory[
IMAGE_DIRECTORY_ENTRY_IMPORT];
std::memmove(module.get_nt_headers(), source.get_nt_headers(),
sizeof(IMAGE_NT_HEADERS) + (source.get_nt_headers()->FileHeader.NumberOfSections * (sizeof(
IMAGE_SECTION_HEADER))));
return FARPROC(module.get_ptr() + source.get_relative_entry_point());
}
void loader::set_import_resolver(const std::function<FARPROC(const std::string&, const std::string&)>& resolver)
{
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)
{
void* target_ptr = target.get_ptr() + section->VirtualAddress;
const void* source_ptr = source.get_ptr() + section->PointerToRawData;
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);
}
if (section->SizeOfRawData > 0)
{
const auto size_of_data = min(section->SizeOfRawData, section->Misc.VirtualSize);
std::memmove(target_ptr, source_ptr, size_of_data);
DWORD old_protect;
VirtualProtect(target_ptr, size_of_data, PAGE_EXECUTE_READWRITE, &old_protect);
}
}
void loader::load_sections(const utils::nt::module& target, const utils::nt::module& source) const
{
for (auto& section : source.get_section_headers())
{
this->load_section(target, source, section);
}
}
void loader::load_imports(const utils::nt::module& target, const utils::nt::module& source) const
{
IMAGE_DATA_DIRECTORY* import_directory = &source.get_optional_header()->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
auto descriptor = PIMAGE_IMPORT_DESCRIPTOR(target.get_ptr() + import_directory->VirtualAddress);
while (descriptor->Name)
{
std::string name = LPSTR(target.get_ptr() + descriptor->Name);
auto name_table_entry = reinterpret_cast<uintptr_t*>(target.get_ptr() + descriptor->OriginalFirstThunk);
auto address_table_entry = reinterpret_cast<uintptr_t*>(target.get_ptr() + descriptor->FirstThunk);
if (!descriptor->OriginalFirstThunk)
{
name_table_entry = reinterpret_cast<uintptr_t*>(target.get_ptr() + descriptor->FirstThunk);
}
while (*name_table_entry)
{
FARPROC function = nullptr;
std::string function_name;
// is this an ordinal-only import?
if (IMAGE_SNAP_BY_ORDINAL(*name_table_entry))
{
auto module = utils::nt::module::load(name);
if (module)
{
function = GetProcAddress(module, MAKEINTRESOURCEA(IMAGE_ORDINAL(*name_table_entry)));
}
function_name = "#" + std::to_string(IMAGE_ORDINAL(*name_table_entry));
}
else
{
auto import = PIMAGE_IMPORT_BY_NAME(target.get_ptr() + *name_table_entry);
function_name = import->Name;
if (this->import_resolver_) function = this->import_resolver_(name, function_name);
if (!function)
{
auto module = utils::nt::module::load(name);
if (module)
{
function = GetProcAddress(module, function_name.data());
}
}
}
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);
}
*address_table_entry = reinterpret_cast<uintptr_t>(function);
name_table_entry++;
address_table_entry++;
}
descriptor++;
}
}

26
src/loader/loader.hpp Normal file
View File

@ -0,0 +1,26 @@
#pragma once
#include "utils/nt.hpp"
#include "launcher/launcher.hpp"
class loader final
{
public:
explicit loader(launcher::mode mode);
void patch();
FARPROC load(const utils::nt::module& module) const;
void set_import_resolver(const std::function<FARPROC(const std::string&, const std::string&)>& resolver);
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;
};

10
src/loader/module.hpp Normal file
View File

@ -0,0 +1,10 @@
#pragma once
class module
{
public:
virtual ~module() {};
virtual void pre_load() {}
virtual void post_load() {}
virtual void pre_destroy() {}
};

View File

@ -1,23 +1,37 @@
#include <std_include.hpp> #include <std_include.hpp>
#include "launcher/launcher.hpp" #include "launcher/launcher.hpp"
#include "loader/loader.hpp"
int CALLBACK WinMain(HINSTANCE /*hInstance*/, HINSTANCE /*hPrevInstance*/, LPSTR /*lpCmdLine*/, int /*nCmdShow*/) int CALLBACK WinMain(HINSTANCE /*hInstance*/, HINSTANCE /*hPrevInstance*/, LPSTR /*lpCmdLine*/, int /*nCmdShow*/)
{ {
FARPROC entry_point = nullptr;
{
launcher launcher; launcher launcher;
const auto mode = launcher.run(); const auto mode = launcher.run();
if(mode == launcher::mode::NONE) if (mode == launcher::mode::NONE) return 0;
loader loader(mode);
loader.set_import_resolver([](const std::string& module, const std::string& function) -> FARPROC
{ {
return 0; if (module == "steam_api.dll")
{
return utils::nt::module().get_proc<FARPROC>(function);
} }
else if(mode == launcher::mode::SINGLEPLAYER) else if (function == "ExitProcess")
{ {
OutputDebugStringA("\n\nSINGLEPLAYER\n\n"); return FARPROC(exit);
}
else if(mode == launcher::mode::MULTIPLAYER)
{
MessageBoxA(nullptr, "Multiplayer not supported yet!", "ERROR", MB_ICONEXCLAMATION);
} }
return 0; return nullptr;
});
entry_point = loader.load({});
if (!entry_point) return 1;
loader.patch();
}
return entry_point();
} }

View File

@ -2,3 +2,5 @@
#define IMAGE_SP 300 #define IMAGE_SP 300
#define IMAGE_MP 301 #define IMAGE_MP 301
#define BINARY_SP 302
#define BINARY_MP 303

View File

@ -89,6 +89,8 @@ END
102 ICON "resources/icon.ico" 102 ICON "resources/icon.ico"
IMAGE_SP BITMAP "resources/singleplayer.bmp" IMAGE_SP BITMAP "resources/singleplayer.bmp"
IMAGE_MP BITMAP "resources/multiplayer.bmp" IMAGE_MP BITMAP "resources/multiplayer.bmp"
BINARY_SP RCDATA "resources/iw5sp.exe"
BINARY_MP RCDATA "resources/iw5mp.exe"
#endif // English (United States) resources #endif // English (United States) resources

View File

@ -1 +1,18 @@
#include <std_include.hpp> #include <std_include.hpp>
#pragma comment(linker, "/merge:.data=.cld")
#pragma comment(linker, "/merge:.rdata=.clr")
#pragma comment(linker, "/merge:.cl=.zdata")
#pragma comment(linker, "/merge:.text=.zdata")
#pragma comment(linker, "/section:.zdata,re")
#pragma comment(linker, "/base:0x400000")
__declspec(thread) char tls_data[0x2000];
#pragma bss_seg(".cdummy")
char dummy_seg[BINARY_PAYLOAD_SIZE];
char stub_seg[0x100000];
#pragma data_seg(".zdata")
char zdata[200] = { 1 };

View File

@ -1,5 +1,7 @@
#pragma once #pragma once
#define BINARY_PAYLOAD_SIZE 0x0A000000
#pragma warning(push) #pragma warning(push)
#pragma warning(disable: 4458) #pragma warning(disable: 4458)
@ -14,6 +16,8 @@
#include <vector> #include <vector>
#include <mutex> #include <mutex>
#include <thread> #include <thread>
#include <fstream>
#include <filesystem>
#pragma warning(pop) #pragma warning(pop)

202
src/utils/nt.cpp Normal file
View File

@ -0,0 +1,202 @@
#include <std_include.hpp>
#include "nt.hpp"
namespace utils
{
namespace nt
{
module module::load(const std::string& name)
{
return module(LoadLibraryA(name.data()));
}
module module::get_by_address(void* address)
{
HMODULE handle = nullptr;
GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, reinterpret_cast<LPCSTR>(address), &handle);
return module(handle);
}
module::module()
{
this->module_ = GetModuleHandleA(nullptr);
}
module::module(const std::string& name)
{
this->module_ = GetModuleHandleA(name.data());
}
module::module(const HMODULE handle)
{
this->module_ = handle;
}
bool module::operator==(const module &obj) const
{
return this->module_ == obj.module_;
}
module::operator bool() const
{
return this->is_valid();
}
module::operator HMODULE() const
{
return this->get_handle();
}
PIMAGE_NT_HEADERS module::get_nt_headers() const
{
if (!this->is_valid()) return nullptr;
return reinterpret_cast<PIMAGE_NT_HEADERS>(this->get_ptr() + this->get_dos_header()->e_lfanew);
}
PIMAGE_DOS_HEADER module::get_dos_header() const
{
return reinterpret_cast<PIMAGE_DOS_HEADER>(this->get_ptr());
}
PIMAGE_OPTIONAL_HEADER module::get_optional_header() const
{
if (!this->is_valid()) return nullptr;
return &this->get_nt_headers()->OptionalHeader;
}
std::vector<PIMAGE_SECTION_HEADER> module::get_section_headers() const
{
std::vector<PIMAGE_SECTION_HEADER> headers;
auto nt_headers = this->get_nt_headers();
auto section = IMAGE_FIRST_SECTION(nt_headers);
for (uint16_t i = 0; i < nt_headers->FileHeader.NumberOfSections; ++i, ++section)
{
if (section) headers.push_back(section);
else OutputDebugStringA("There was an invalid section :O");
}
return headers;
}
std::uint8_t* module::get_ptr() const
{
return reinterpret_cast<std::uint8_t*>(this->module_);
}
void module::unprotect() const
{
if (!this->is_valid()) return;
DWORD protection;
VirtualProtect(this->get_ptr(), this->get_optional_header()->SizeOfImage, PAGE_EXECUTE_READWRITE, &protection);
}
size_t module::get_relative_entry_point() const
{
if (!this->is_valid()) return 0;
return this->get_nt_headers()->OptionalHeader.AddressOfEntryPoint;
}
void* module::get_entry_point() const
{
if (!this->is_valid()) return nullptr;
return this->get_ptr() + this->get_relative_entry_point();
}
bool module::is_valid() const
{
return this->module_ != nullptr && this->get_dos_header()->e_magic == IMAGE_DOS_SIGNATURE;
}
std::string module::get_name() const
{
if (!this->is_valid()) return "";
auto path = this->get_path();
const auto pos = path.find_last_of("/\\");
if (pos == std::string::npos) return path;
return path.substr(pos + 1);
}
std::string module::get_path() const
{
if (!this->is_valid()) return "";
char name[MAX_PATH] = { 0 };
GetModuleFileNameA(this->module_, name, sizeof name);
return name;
}
void module::free()
{
if (this->is_valid())
{
FreeLibrary(this->module_);
this->module_ = nullptr;
}
}
HMODULE module::get_handle() const
{
return this->module_;
}
void** module::get_iat_entry(const std::string& module_name, const std::string& proc_name) const
{
if (!this->is_valid()) return nullptr;
module other_module(module_name);
if (!other_module.is_valid()) return nullptr;
const auto target_function = other_module.get_proc<void*>(proc_name);
if (!target_function) return nullptr;
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);
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());
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<char*>(ordinal_number)) == target_function)
{
return reinterpret_cast<void**>(&thunk_data->u1.Function);
}
++original_thunk_data;
++thunk_data;
}
//break;
}
++import_descriptor;
}
return nullptr;
}
void raise_hard_exception()
{
int data = false;
utils::nt::module ntdll("ntdll.dll");
ntdll.invoke_pascal<void>("RtlAdjustPrivilege", 19, true, false, &data);
ntdll.invoke_pascal<void>("NtRaiseHardError", 0xC000007B, 0, nullptr, nullptr, 6, &data);
}
}
}

89
src/utils/nt.hpp Normal file
View File

@ -0,0 +1,89 @@
#pragma once
namespace utils
{
namespace nt
{
class module final
{
public:
static module load(const std::string& name);
static module get_by_address(void* address);
module();
explicit module(const std::string& name);
explicit module(HMODULE handle);
module(const module& a) : module_(a.module_) {}
bool operator!=(const module &obj) const { return !(*this == obj); };
bool operator==(const module &obj) const;
operator bool() const;
operator HMODULE() const;
void unprotect() const;
void* get_entry_point() const;
size_t get_relative_entry_point() const;
bool is_valid() const;
std::string get_name() const;
std::string get_path() const;
std::uint8_t* get_ptr() const;
void free();
HMODULE get_handle() const;
template <typename T>
T get_proc(const std::string& process)
{
if (!this->is_valid()) T{};
return reinterpret_cast<T>(GetProcAddress(this->module_, process.data()));
}
template <typename T>
std::function<T> get(const std::string& process)
{
if (!this->is_valid()) std::function<T>();
return reinterpret_cast<T*>(this->get_proc<void*>(process));
}
template<typename T, typename... Args>
T invoke(const std::string& process, Args... args)
{
auto method = this->get<T(__cdecl)(Args...)>(process);
if (method) return method(args...);
return T();
}
template<typename T, typename... Args>
T invoke_pascal(const std::string& process, Args... args)
{
auto method = this->get<T(__stdcall)(Args...)>(process);
if (method) return method(args...);
return T();
}
template<typename T, typename... Args>
T invoke_this(const std::string& process, void* thisPtr, Args... args)
{
auto method = this->get<T(__thiscall)(void*,Args...)>(thisPtr, process);
if (method) return method(args...);
return T();
}
std::vector<PIMAGE_SECTION_HEADER> get_section_headers() const;
PIMAGE_NT_HEADERS get_nt_headers() const;
PIMAGE_DOS_HEADER get_dos_header() const;
PIMAGE_OPTIONAL_HEADER get_optional_header() const;
void** get_iat_entry(const std::string& module_name, const std::string& proc_name) const;
private:
HMODULE module_;
};
void raise_hard_exception();
}
}