diff --git a/README.md b/README.md index 1222f9e0..45b65b4c 100644 --- a/README.md +++ b/README.md @@ -19,8 +19,8 @@ Reverse engineering and analysis of Call of Duty: Black Ops 3. Very experimental - [x] Disable Integrity Checks - [x] Demonware Emulation - [ ] Disable Anti-Debugging Mechanisms (probably never gonna happen cause who needs that if you have printf debugging) +- [x] Process wrapper - [x] P2P multiplayer -- [ ] Process hollowing loader - [ ] Dedicated Servers ## Disclaimer diff --git a/premake5.lua b/premake5.lua index de3babc7..cbb1d3c5 100644 --- a/premake5.lua +++ b/premake5.lua @@ -285,10 +285,10 @@ project "common" dependencies.imports() project "client" - kind "SharedLib" + kind "WindowedApp" language "C++" - targetname "d3d11" + targetname "boiii" pchheader "std_include.hpp" pchsource "src/client/std_include.cpp" @@ -311,6 +311,18 @@ project "client" dependencies.imports() +project "tlsdll" + kind "SharedLib" + language "C++" + + files {"./src/tlsdll/**.rc", "./src/tlsdll/**.hpp", "./src/tlsdll/**.cpp", "./src/tlsdll/resources/**.*"} + + includedirs {"./src/tlsdll", "%{prj.location}/src"} + + links {"common"} + + resincludedirs {"$(ProjectDir)src"} + project "runner" kind "WindowedApp" diff --git a/src/client/component/arxan.cpp b/src/client/component/arxan.cpp index 06de10a1..3df2be7d 100644 --- a/src/client/component/arxan.cpp +++ b/src/client/component/arxan.cpp @@ -384,7 +384,7 @@ namespace arxan { continue; } - + if (!loaded) { memcpy(buffers[i], func, sizeof(buffer)); @@ -698,14 +698,11 @@ namespace arxan class component final : public component_interface { public: - component() + void pre_start() override { auto* dll_characteristics = &utils::nt::library().get_optional_header()->DllCharacteristics; utils::hook::set(dll_characteristics, *dll_characteristics | IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE); - } - void pre_start() override - { disable_tls_callbacks(); restore_debug_functions(); diff --git a/src/client/game/symbols.hpp b/src/client/game/symbols.hpp index f9ddb7ac..5d979adf 100644 --- a/src/client/game/symbols.hpp +++ b/src/client/game/symbols.hpp @@ -18,13 +18,13 @@ namespace game WEAK symbol Cbuf_AddText{0x1420EC8B0}; WEAK symbol Cmd_AddCommandInternal{ - 0x1420ED530_g + 0x1420ED530 }; WEAK symbol Con_GetTextCopy{0x14133A7D0}; // DB WEAK symbol DB_LoadXAssets{ - 0x1414236A0_g + 0x1414236A0 }; // Live @@ -50,12 +50,12 @@ namespace game WEAK symbol Dvar_GetDebugName{0x1422BDCB0}; WEAK symbol Dvar_GetString{0x1422BFFF0}; WEAK symbol Dvar_SetFromStringByName{ - 0x1422C7F60_g + 0x1422C7F60 }; // Rendering WEAK symbol R_AddCmdDrawText{ - 0x141CD98D0_g + 0x141CD98D0 }; // Variables diff --git a/src/client/loader/component_loader.cpp b/src/client/loader/component_loader.cpp index c163fd8e..ff3ef7a3 100644 --- a/src/client/loader/component_loader.cpp +++ b/src/client/loader/component_loader.cpp @@ -138,8 +138,16 @@ std::vector>& component_loader::get_compone size_t get_base() { - static auto base = size_t(utils::nt::library{}.get_ptr()); - assert(base && "Failed to resolve base"); + static auto base = [] + { + const utils::nt::library host{}; + if(!host || host == utils::nt::library::get_by_address(get_base)) + { + throw std::runtime_error("Invalid host application"); + } + + return size_t(host.get_ptr()); + }(); return base; } diff --git a/src/client/loader/loader.cpp b/src/client/loader/loader.cpp new file mode 100644 index 00000000..7d26f253 --- /dev/null +++ b/src/client/loader/loader.cpp @@ -0,0 +1,118 @@ +#include +#include "loader.hpp" +#include "tls.hpp" + +#include +#include + +namespace loader +{ + namespace + { + void load_imports(const utils::nt::library& target) + { + const auto* const import_directory = &target.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(target.get_ptr() + descriptor-> + OriginalFirstThunk); + auto* address_table_entry = reinterpret_cast(target.get_ptr() + descriptor->FirstThunk); + + if (!descriptor->OriginalFirstThunk) + { + name_table_entry = reinterpret_cast(target.get_ptr() + descriptor->FirstThunk); + } + + while (*name_table_entry) + { + FARPROC function = nullptr; + std::string function_name; + const char* function_procname; + + if (IMAGE_SNAP_BY_ORDINAL(*name_table_entry)) + { + function_name = "#" + std::to_string(IMAGE_ORDINAL(*name_table_entry)); + function_procname = MAKEINTRESOURCEA(IMAGE_ORDINAL(*name_table_entry)); + } + else + { + const auto* import = PIMAGE_IMPORT_BY_NAME(target.get_ptr() + *name_table_entry); + function_name = import->Name; + function_procname = function_name.data(); + } + + auto library = utils::nt::library::load(name); + if (library) + { + function = GetProcAddress(library, function_procname); + } + + if (!function) + { + throw std::runtime_error(utils::string::va("Unable to load import '%s' from library '%s'", + function_name.data(), name.data())); + } + + utils::hook::set(address_table_entry, reinterpret_cast(function)); + + name_table_entry++; + address_table_entry++; + } + + descriptor++; + } + } + + void load_tls(const utils::nt::library& target) + { + if (target.get_optional_header()->DataDirectory[IMAGE_DIRECTORY_ENTRY_TLS].Size) + { + auto* target_tls = tls::allocate_tls_index(); + const auto* const source_tls = reinterpret_cast(target.get_ptr() + target. + get_optional_header() + ->DataDirectory[IMAGE_DIRECTORY_ENTRY_TLS].VirtualAddress); + + const auto tls_size = source_tls->EndAddressOfRawData - source_tls->StartAddressOfRawData; + const auto tls_index = *reinterpret_cast(target_tls->AddressOfIndex); + utils::hook::set(source_tls->AddressOfIndex, tls_index); + + if (target_tls->AddressOfCallBacks) + { + utils::hook::set(target_tls->AddressOfCallBacks, nullptr); + } + + DWORD old_protect; + VirtualProtect(PVOID(target_tls->StartAddressOfRawData), + source_tls->EndAddressOfRawData - source_tls->StartAddressOfRawData, PAGE_READWRITE, + &old_protect); + + auto* const tls_base = *reinterpret_cast(__readgsqword(0x58) + 8ull * tls_index); + std::memmove(tls_base, PVOID(source_tls->StartAddressOfRawData), tls_size); + std::memmove(PVOID(target_tls->StartAddressOfRawData), PVOID(source_tls->StartAddressOfRawData), + tls_size); + + VirtualProtect(target_tls, sizeof(*target_tls), PAGE_READWRITE, &old_protect); + *target_tls = *source_tls; + } + } + } + + utils::nt::library load_binary(const std::string& filename) + { + const auto target = utils::nt::library::load(filename); + if (!target) + { + throw std::runtime_error{"Failed to map binary!"}; + } + + load_imports(target); + load_tls(target); + + return target; + } +} diff --git a/src/client/loader/loader.hpp b/src/client/loader/loader.hpp new file mode 100644 index 00000000..783fa972 --- /dev/null +++ b/src/client/loader/loader.hpp @@ -0,0 +1,7 @@ +#pragma once +#include + +namespace loader +{ + utils::nt::library load_binary(const std::string& filename); +} diff --git a/src/client/loader/tls.cpp b/src/client/loader/tls.cpp new file mode 100644 index 00000000..147ae550 --- /dev/null +++ b/src/client/loader/tls.cpp @@ -0,0 +1,42 @@ +#include +#include "tls.hpp" + +#include +#include + +#include "resource.hpp" + +namespace tls +{ + namespace + { + utils::binary_resource tls_dll_file(TLS_DLL, "boiii-tlsdll.dll"); + } + + PIMAGE_TLS_DIRECTORY allocate_tls_index() + { + static auto already_allocated = false; + if (already_allocated) + { + throw std::runtime_error("Currently only a single allocation is supported!"); + } + + already_allocated = true; + + const auto dll_path = tls_dll_file.get_extracted_file(); + const auto tls_dll = utils::nt::library::load(dll_path); + if (!tls_dll) + { + throw std::runtime_error("Failed to load TLS DLL"); + } + + const auto tls_dir_entry = tls_dll.get_optional_header() + ->DataDirectory[IMAGE_DIRECTORY_ENTRY_TLS].VirtualAddress; + if (!tls_dir_entry) + { + throw std::runtime_error("TLS DLL is invalid"); + } + + return reinterpret_cast(tls_dll.get_ptr() + tls_dir_entry); + } +} diff --git a/src/client/loader/tls.hpp b/src/client/loader/tls.hpp new file mode 100644 index 00000000..1b7c68d2 --- /dev/null +++ b/src/client/loader/tls.hpp @@ -0,0 +1,6 @@ +#pragma once + +namespace tls +{ + PIMAGE_TLS_DIRECTORY allocate_tls_index(); +} diff --git a/src/client/main.cpp b/src/client/main.cpp index 44d16e84..2e02395f 100644 --- a/src/client/main.cpp +++ b/src/client/main.cpp @@ -1,6 +1,7 @@ #include #include "loader/component_loader.hpp" +#include "loader/loader.hpp" #include #include @@ -11,6 +12,7 @@ namespace { + volatile bool g_call_tls_callbacks = false; std::pair g_original_import{}; DECLSPEC_NORETURN void WINAPI exit_hook(const uint32_t code) @@ -84,8 +86,61 @@ namespace utils::io::remove_file(game_path.generic_string()); } - bool run() + PIMAGE_TLS_CALLBACK* get_tls_callbacks() { + const utils::nt::library game{}; + const auto& entry = game.get_optional_header()->DataDirectory[IMAGE_DIRECTORY_ENTRY_TLS]; + if (!entry.VirtualAddress || !entry.Size) + { + return nullptr; + } + + const auto* tls_dir = reinterpret_cast(game.get_ptr() + entry.VirtualAddress); + return reinterpret_cast(tls_dir->AddressOfCallBacks); + } + + void run_tls_callbacks(const DWORD reason) + { + if (!g_call_tls_callbacks) + { + return; + } + + auto* callback = get_tls_callbacks(); + while (callback && *callback) + { + (*callback)(GetModuleHandleA(nullptr), reason, nullptr); + ++callback; + } + } + + [[maybe_unused]] thread_local struct tls_runner + { + tls_runner() + { + run_tls_callbacks(DLL_THREAD_ATTACH); + } + + ~tls_runner() + { + run_tls_callbacks(DLL_THREAD_DETACH); + } + } tls_runner; + + FARPROC load_process(const std::string& procname) + { + const auto proc = loader::load_binary(procname); + + auto* const peb = reinterpret_cast(__readgsqword(0x60)); + peb->Reserved3[1] = proc.get_ptr(); + static_assert(offsetof(PEB, Reserved3[1]) == 0x10); + + return FARPROC(proc.get_ptr() + proc.get_relative_entry_point()); + } + + int main() + { + FARPROC entry_point{}; srand(uint32_t(time(nullptr)) ^ ~(GetTickCount() * GetCurrentProcessId())); { @@ -100,12 +155,19 @@ namespace try { - patch_imports(); remove_crash_file(); + entry_point = load_process("BlackOps3.exe"); + if (!entry_point) + { + throw std::runtime_error("Unable to load binary into memory"); + } + + patch_imports(); + if (!component_loader::pre_start()) { - return false; + return 1; } premature_shutdown = false; @@ -113,141 +175,17 @@ namespace catch (std::exception& e) { MessageBoxA(nullptr, e.what(), "ERROR", MB_ICONERROR); - return false; + return 1; } } - return true; - } - - class patch - { - public: - patch() = default; - - patch(void* source, void* target) - : source_(source) - { - memcpy(this->data_, source, sizeof(this->data_)); - utils::hook::jump(this->source_, target, true, true); - } - - ~patch() - { - if (source_) - { - utils::hook::copy(this->source_, this->data_, sizeof(this->data_)); - } - } - - patch(patch&& obj) noexcept - : patch() - { - this->operator=(std::move(obj)); - } - - patch& operator=(patch&& obj) noexcept - { - if (this != &obj) - { - this->~patch(); - - this->source_ = obj.source_; - memcpy(this->data_, obj.data_, sizeof(this->data_)); - - obj.source_ = nullptr; - } - - return *this; - } - - private: - void* source_{nullptr}; - uint8_t data_[15]{}; - }; - - std::vector initialization_hooks{}; - - uint8_t* get_entry_point() - { - const utils::nt::library game{}; - return game.get_ptr() + game.get_optional_header()->AddressOfEntryPoint; - } - - std::vector get_tls_callbacks() - { - const utils::nt::library game{}; - const auto& entry = game.get_optional_header()->DataDirectory[IMAGE_DIRECTORY_ENTRY_TLS]; - if (!entry.VirtualAddress || !entry.Size) - { - return {}; - } - - const auto* tls_dir = reinterpret_cast(game.get_ptr() + entry.VirtualAddress); - auto* callback = reinterpret_cast(tls_dir->AddressOfCallBacks); - - std::vector addresses{}; - while (callback && *callback) - { - addresses.emplace_back(*callback); - ++callback; - } - - return addresses; - } - - int patch_main() - { - if (!run()) - { - return 1; - } - - initialization_hooks.clear(); - return reinterpret_cast(get_entry_point())(); - } - - void nullsub() - { - } - - void patch_entry_point() - { - initialization_hooks.emplace_back(get_entry_point(), patch_main); - - for (auto* tls_callback : get_tls_callbacks()) - { - initialization_hooks.emplace_back(tls_callback, nullsub); - } + g_call_tls_callbacks = true; + return static_cast(entry_point()); } } -BOOL WINAPI DllMain(HINSTANCE, const DWORD reason, LPVOID) + +int __stdcall WinMain(HINSTANCE, HINSTANCE, PSTR, int) { - if (reason == DLL_PROCESS_ATTACH) - { - patch_entry_point(); - } - - return TRUE; -} - -extern "C" __declspec(dllexport) -HRESULT D3D11CreateDevice(void* adapter, const uint64_t driver_type, - const HMODULE software, const UINT flags, - const void* p_feature_levels, const UINT feature_levels, - const UINT sdk_version, void** device, void* feature_level, - void** immediate_context) -{ - static auto func = [] - { - char dir[MAX_PATH]{0}; - GetSystemDirectoryA(dir, sizeof(dir)); - - const auto d3d11 = utils::nt::library::load(dir + "/d3d11.dll"s); - return d3d11.get_proc("D3D11CreateDevice"); - }(); - - return func(adapter, driver_type, software, flags, p_feature_levels, feature_levels, sdk_version, device, - feature_level, immediate_context); + return main(); } diff --git a/src/client/resource.hpp b/src/client/resource.hpp index 3dc82d00..d40cdb81 100644 --- a/src/client/resource.hpp +++ b/src/client/resource.hpp @@ -13,3 +13,4 @@ #define DW_QOSCONFIG 307 #define RUNNER 308 +#define TLS_DLL 309 diff --git a/src/client/resource.rc b/src/client/resource.rc index 30076075..d1c0aa9a 100644 --- a/src/client/resource.rc +++ b/src/client/resource.rc @@ -108,6 +108,12 @@ RUNNER RCDATA "../../build/bin/x64/Debug/runner.exe" RUNNER RCDATA "../../build/bin/x64/Release/runner.exe" #endif +#ifdef _DEBUG +TLS_DLL RCDATA "../../build/bin/x64/Debug/tlsdll.dll" +#else +TLS_DLL RCDATA "../../build/bin/x64/Release/tlsdll.dll" +#endif + #endif // English (United States) resources ///////////////////////////////////////////////////////////////////////////// diff --git a/src/tlsdll/dllmain.cpp b/src/tlsdll/dllmain.cpp new file mode 100644 index 00000000..56d31a66 --- /dev/null +++ b/src/tlsdll/dllmain.cpp @@ -0,0 +1,7 @@ +#define TLS_PAYLOAD_SIZE 0x2000 +thread_local char tls_data[TLS_PAYLOAD_SIZE]; + +__declspec(dllexport) void* get_tls_data() +{ + return &tls_data[0]; +} diff --git a/src/tlsdll/resource.rc b/src/tlsdll/resource.rc new file mode 100644 index 00000000..2a778f5f --- /dev/null +++ b/src/tlsdll/resource.rc @@ -0,0 +1,100 @@ +// Microsoft Visual C++ generated resource script. +// +#pragma code_page(65001) + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "windows.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "#include ""windows.h""\r\n" + "\0" +END + +2 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,0,0,0 + PRODUCTVERSION 1,0,0,0 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x40004L + FILETYPE VFT_DLL + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "CompanyName", "X Labs" + VALUE "FileDescription", "TLS index allocation dll" + VALUE "FileVersion", "1.0.0.0" + VALUE "InternalName", "TLS DLL" + VALUE "LegalCopyright", "All rights reserved." + VALUE "OriginalFilename", "tlsdll.dll" + VALUE "ProductName", "tlsdll" + VALUE "ProductVersion", "1.0.0.0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + +///////////////////////////////////////////////////////////////////////////// +// +// Binary Data +// + +// Nothing here + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED +