├── .github └── workflows │ ├── debug.yml │ └── release.yml ├── .gitignore ├── .gitmodules ├── README.md ├── gen_proj.bat ├── premake5.lua └── src ├── Common.cpp ├── Common.hpp ├── Injector.hpp ├── Logger.cpp ├── Logger.hpp ├── Remote.hpp ├── Settings.cpp ├── Settings.hpp ├── Singleton.hpp ├── Util.hpp └── main.cpp /.github/workflows/debug.yml: -------------------------------------------------------------------------------- 1 | name: Debug Build 2 | 3 | on: 4 | push: 5 | branches: 6 | - '**' 7 | pull_request: 8 | jobs: 9 | debug: 10 | runs-on: windows-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | with: 14 | submodules: recursive 15 | 16 | - name: Setup premake 17 | uses: abel0b/setup-premake@v2 18 | with: 19 | version: "5.0.0-beta1" 20 | 21 | - name: Add msbuild to PATH 22 | uses: microsoft/setup-msbuild@v1.1 23 | 24 | - name: Generate premake5 project 25 | run: premake5 vs2019 26 | shell: bash 27 | 28 | - name: Build 32bit and 64bit debug executables 29 | run: | 30 | msbuild /p:Configuration=Debug /p:Platform=x64 Injector.sln 31 | msbuild /p:Configuration=Debug /p:Platform=Win32 Injector.sln 32 | 33 | - name: Debug Build 34 | uses: actions/upload-artifact@v2 35 | with: 36 | name: binaries 37 | path: | 38 | bin/Debug/*/*.exe 39 | bin/Debug/*/*.map 40 | bin/Debug/*/*.pdb -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release Build 2 | 3 | on: 4 | push: 5 | tags: 6 | - '**' 7 | jobs: 8 | release: 9 | runs-on: windows-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | with: 13 | submodules: recursive 14 | 15 | - name: Setup premake 16 | uses: abel0b/setup-premake@v2 17 | with: 18 | version: "5.0.0-beta1" 19 | 20 | - name: Add msbuild to PATH 21 | uses: microsoft/setup-msbuild@v1.1 22 | 23 | - name: Generate premake5 project 24 | run: premake5 vs2019 25 | shell: bash 26 | 27 | - name: Build 32bit and 64bit release executables 28 | run: | 29 | msbuild /p:Configuration=Release /p:Platform=x64 Injector.sln 30 | msbuild /p:Configuration=Release /p:Platform=Win32 Injector.sln 31 | 32 | - name: Rename 32bit build executable 33 | run: ren Injector.exe Injector_x32.exe 34 | working-directory: bin/Release/x32 35 | 36 | - name: Release 37 | uses: softprops/action-gh-release@v1 38 | with: 39 | body: "" 40 | draft: true 41 | files: | 42 | bin/Release/x64/Injector.exe 43 | bin/Release/x32/Injector_x32.exe 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vs/ 2 | bin/ 3 | *.sln 4 | *.vcxproj 5 | *.vcxproj.filters 6 | *.vcxproj.user 7 | 8 | # Ignore auto generated files 9 | *.log 10 | *.json -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "vendor/json"] 2 | path = vendor/json 3 | url = git@github.com:nlohmann/json.git 4 | [submodule "vendor/http_request"] 5 | path = vendor/http_request 6 | url = git@github.com:elnormous/HTTPRequest.git 7 | [submodule "vendor/g3log"] 8 | path = vendor/g3log 9 | url = https://github.com/KjellKod/g3log.git 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Injector 2 | 3 | An injector that fetches version.json from a webserver, downloads a dll and injects it into the game. 4 | 5 | ## Setup 6 | 7 | To setup the build environment, run the following commands in your preferred terminal. 8 | 9 | ```bash 10 | git clone https://github.com/Yimura/Injector.git --recurse 11 | cd Injector 12 | premake5 vs2019 13 | ``` 14 | 15 | Get premake5 from [here](https://premake.github.io/download/). 16 | 17 | Then afterwards open Injector.sln and start coding. 18 | 19 | ## How to use 20 | 21 | This injector has 2 ways of functioning: 22 | - Remote mode (webserver required) 23 | - Local mode (inject without webserver) 24 | 25 | ### Setup Remote Server 26 | 27 | If you only wish to use the local features just ignore this step and skip to Injector settings. 28 | 29 | Requirements: 30 | - Webserver 31 | 32 | In the root of your webserver create a file called `version.json`, this file will tell our Injector what to do and if it should download a dll. 33 | 34 | ```js 35 | { 36 | "file": "menu.dll", // path to the dll file on the webserver starting from the root / is prefixed in the binary 37 | "version": "0.1.3", // human readable version number 38 | "version_machine": 13 // version number that the injector will compare against 39 | } 40 | ``` 41 | 42 | ### Injector Settings 43 | 44 | If you run the injector for the first time and no `settings.json` is present in the folder from which you executed the Injector it will create an example file. 45 | 46 | ```js 47 | { 48 | "application_mode": 0, 49 | "injection_direction": 0, 50 | "local": { 51 | "file": "./some_dll.dll" 52 | }, 53 | "target_application": "wordpad.exe", 54 | "web_server": { 55 | "dll_provider": "http://example.com", 56 | "file": "latest.dll", 57 | "version": -1 58 | } 59 | } 60 | ``` 61 | 62 | |Name|Description|Value| 63 | |--|--|--| 64 | |`application_mode` | If the injector runs locally or with a remote server. | `WEB_SERVER=0` & `LOCAL=1` | 65 | |`target_application`| The process name of the application to inject into.| `string`| 66 | |`local.file`|The local path to a DLL.|`string`| 67 | |`web_server.dll_provider`| Web Server origin. | `string` | 68 | |`web_server.file`|The file name to save the DLL to locally.|`string`| 69 | |`web_server.version`|Machine version integer to compare with the remote version.|`int`| 70 | 71 | ``` 72 | injection_direction is currently not implemented, but still exists for future API. 73 | ``` 74 | 75 | That's it. 76 | -------------------------------------------------------------------------------- /gen_proj.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | DEL Injector.sln /Q 3 | DEL Injector\Injector.vcxproj /Q 4 | DEL Injector\Injector.vcxproj.filters /Q 5 | DEL Injector\Injector.vcxproj.user /Q 6 | 7 | premake5 vs2019 8 | IF %ERRORLEVEL% NEQ 0 ( 9 | PAUSE 10 | ) -------------------------------------------------------------------------------- /premake5.lua: -------------------------------------------------------------------------------- 1 | function DeclareCommon() 2 | staticruntime "Off" 3 | floatingpoint "Fast" 4 | vectorextensions "AVX2" 5 | 6 | -- versions 7 | systemversion "10.0" 8 | toolset "v143" 9 | cppdialect "C++20" 10 | 11 | -- build output options 12 | outputdir = "%{cfg.buildcfg}/%{cfg.platform}" -- "Release/x32" / "Release/x64" / "Debug/x32" / "Debug/x64" 13 | targetdir ("bin/" .. outputdir) 14 | objdir ("bin/int/" .. outputdir .. "/%{prj.name}") 15 | libdirs ("bin/" .. outputdir .. "/%{prj.name}") 16 | 17 | -- 18 | defines { 19 | "_CRT_SECURE_NO_WARNINGS", -- removes pesky warnings to use Microsoft "safer" variants of certain std:: functions 20 | "NOMINMAX", -- excludes certain MINMAX macros 21 | "WIN32_LEAN_AND_MEAN", -- excludes certain includes that we do not require from Windows.h 22 | } 23 | 24 | filter "configurations:Debug" 25 | defines { "_DEBUG" } 26 | end 27 | 28 | function does_file_exist(file) 29 | local f = io.open(file,"r") 30 | if f ~= nil then 31 | io.close(f) 32 | 33 | return true 34 | end 35 | 36 | return false 37 | end 38 | 39 | workspace "Injector" 40 | startproject "Injector" 41 | 42 | configurations { "Debug", "Release" } 43 | platforms { "x64", "x32" } 44 | 45 | project "g3log" 46 | location "vendor/%{prj.name}" 47 | kind "StaticLib" 48 | language "C++" 49 | 50 | includedirs { 51 | "vendor/%{prj.name}/src" 52 | } 53 | 54 | if not does_file_exist("vendor\\g3log\\src\\g3log\\generated_definitions.hpp") then 55 | file = io.open("vendor\\g3log\\src\\g3log\\generated_definitions.hpp", "w") 56 | 57 | if file == nil then 58 | premake.error("Failed to location g3log vendor directory, did you pull with --recurse?") 59 | end 60 | 61 | file:write("#pragma once") 62 | end 63 | 64 | files { 65 | "vendor/%{prj.name}/src/**.hpp", 66 | "vendor/%{prj.name}/src/**.cpp" 67 | } 68 | 69 | removefiles { 70 | "vendor/%{prj.name}/src/crashhandler_unix.cpp" 71 | } 72 | 73 | DeclareCommon() 74 | 75 | project "Injector" 76 | location "src/" 77 | kind "ConsoleApp" 78 | language "C++" 79 | 80 | includedirs { 81 | "vendor/g3log/src", 82 | "vendor/http_request/include", 83 | "vendor/json/single_include", 84 | "src/" 85 | } 86 | 87 | PrecompiledHeaderInclude = "Common.hpp" 88 | PrecompiledHeaderSource = "src/Common.cpp" 89 | 90 | files { 91 | "src/**.hpp", 92 | "src/**.cpp" 93 | } 94 | 95 | libdirs { 96 | "bin/lib" 97 | } 98 | 99 | links { 100 | "g3log", 101 | "Psapi", 102 | "ws2_32" 103 | } 104 | 105 | pchheader "%{PrecompiledHeaderInclude}" 106 | pchsource "%{PrecompiledHeaderSource}" 107 | 108 | DeclareCommon() 109 | 110 | flags { 111 | "LinkTimeOptimization", 112 | "Maps", 113 | "NoImportLib", 114 | "MultiProcessorCompile" 115 | } 116 | 117 | filter "configurations:Debug" 118 | flags { } 119 | symbols "On" 120 | editandcontinue "Off" 121 | defines { "DEBUG", "CHANGE_G3LOG_DEBUG_TO_DBUG" } 122 | 123 | filter "configurations:Release" 124 | flags { "NoManifest" } 125 | defines { "RELEASE", "NDEBUG" } 126 | optimize "speed" 127 | 128 | filter "configurations:Dist" 129 | flags { "FatalWarnings", "NoManifest" } 130 | defines { "DIST", "NDEBUG" } 131 | optimize "speed" 132 | -------------------------------------------------------------------------------- /src/Common.cpp: -------------------------------------------------------------------------------- 1 | #include "Common.hpp" -------------------------------------------------------------------------------- /src/Common.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | 14 | #include 15 | #include 16 | 17 | #include "Settings.hpp" 18 | 19 | inline Settings& g = Settings::Get(); 20 | 21 | using namespace std::chrono_literals; -------------------------------------------------------------------------------- /src/Injector.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Common.hpp" 3 | #include "Logger.hpp" 4 | #include "Util.hpp" 5 | #include 6 | #include 7 | 8 | namespace Injector 9 | { 10 | inline void* AllocateDllPath(const HANDLE processHandle, const std::string_view dllFile) 11 | { 12 | const size_t size = ::strlen(dllFile.data()) + 1; 13 | 14 | return VirtualAllocEx(processHandle, nullptr, size, MEM_COMMIT, PAGE_EXECUTE); 15 | } 16 | 17 | inline HANDLE LoadLibraryRemotely(const HANDLE processHandle, void* alloc) 18 | { 19 | const auto hMod = GetModuleHandleA("kernel32.dll"); 20 | if (!hMod) return INVALID_HANDLE_VALUE; 21 | 22 | const auto loadLibrary = (LPTHREAD_START_ROUTINE)GetProcAddress(hMod, "LoadLibraryA"); 23 | return CreateRemoteThread(processHandle, nullptr, 0, loadLibrary, alloc, 0, nullptr); 24 | } 25 | 26 | inline int GetProcessId(std::string_view processName) 27 | { 28 | auto* target = Util::CharToWchar(processName.data()); 29 | 30 | const auto hToolHelpSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); 31 | PROCESSENTRY32W processEntry32{}; 32 | processEntry32.dwSize = sizeof(PROCESSENTRY32W); 33 | 34 | if (hToolHelpSnapshot == INVALID_HANDLE_VALUE) 35 | { 36 | LOG(WARNING) << "CreateToolhelp32Snapshot returned INVALID_HANDLE_VALUE, unable to proceed."; 37 | 38 | return -1; 39 | } 40 | 41 | int processId = -1; 42 | for ( 43 | bool foundProcess = Process32FirstW(hToolHelpSnapshot, &processEntry32); 44 | foundProcess; 45 | foundProcess = Process32NextW(hToolHelpSnapshot, &processEntry32)) 46 | { 47 | if (!wcscmp(processEntry32.szExeFile, target)) 48 | { 49 | processId = processEntry32.th32ProcessID; 50 | 51 | break; 52 | } 53 | } 54 | 55 | CloseHandle(hToolHelpSnapshot); 56 | return processId; 57 | } 58 | 59 | inline bool OpenProcess(const int processId, HANDLE& processHandle) 60 | { 61 | processHandle = ::OpenProcess(PROCESS_ALL_ACCESS, false, processId); 62 | 63 | return processHandle != INVALID_HANDLE_VALUE; 64 | } 65 | 66 | inline bool WriteToTargetProcess(HANDLE processHandle, void* alloc, std::string_view dllFile) 67 | { 68 | const size_t size = ::strlen(dllFile.data()) + 1; 69 | return WriteProcessMemory(processHandle, alloc, dllFile.data(), size, nullptr); 70 | } 71 | }; 72 | -------------------------------------------------------------------------------- /src/Logger.cpp: -------------------------------------------------------------------------------- 1 | #include "Common.hpp" 2 | #include "Logger.hpp" 3 | 4 | struct LogSink 5 | { 6 | void Callback(g3::LogMessageMover log) 7 | { 8 | Logger::GetInstance().m_ConsoleOut << log.get().toString(LogSink::FormatConsole) << std::flush; 9 | Logger::GetInstance().m_FileOut << log.get().toString(LogSink::FormatFile) << std::flush; 10 | } 11 | 12 | static LogColor GetColor(const LEVELS& level) 13 | { 14 | switch (level.value) 15 | { 16 | case g3::kDebugValue: 17 | return LogColor::BLUE; 18 | case g3::kInfoValue: 19 | return LogColor::GREEN; 20 | case g3::kWarningValue: 21 | return LogColor::YELLOW; 22 | } 23 | return g3::internal::wasFatal(level) ? LogColor::RED : LogColor::WHITE; 24 | } 25 | 26 | static std::string FormatConsole(const g3::LogMessage& msg) 27 | { 28 | LogColor color = LogSink::GetColor(msg._level); 29 | std::stringstream out; 30 | 31 | out 32 | << "[" << msg.timestamp("%H:%M:%S") << "]" 33 | << ADD_COLOR_TO_STREAM(color) 34 | << "[" << std::setw(7) << msg.level() << "/" 35 | << msg.file() << ":" << msg.line() << "]" 36 | << RESET_STREAM_COLOR 37 | << ": "; 38 | 39 | return out.str(); 40 | } 41 | static std::string FormatFile(const g3::LogMessage& msg) 42 | { 43 | LogColor color = LogSink::GetColor(msg._level); 44 | std::stringstream out; 45 | 46 | out 47 | << "[" << msg.timestamp("%H:%M:%S") << "]" 48 | << "[" << std::setw(7) << msg.level() << "/" 49 | << msg.file() << ":" << msg.line() << "]" 50 | << ": "; 51 | 52 | return out.str(); 53 | } 54 | }; 55 | 56 | void Logger::DestroyImpl() 57 | { 58 | m_Worker->removeAllSinks(); 59 | m_Worker.reset(); 60 | 61 | m_ConsoleOut.close(); 62 | m_FileOut.close(); 63 | } 64 | 65 | void Logger::InitImpl() 66 | { 67 | m_Worker = g3::LogWorker::createLogWorker(); 68 | 69 | #ifdef _WIN32 70 | const auto consoleHandle = GetStdHandle(STD_OUTPUT_HANDLE); 71 | 72 | DWORD consoleMode; 73 | GetConsoleMode(consoleHandle, &consoleMode); 74 | 75 | consoleMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN; 76 | SetConsoleMode(consoleHandle, consoleMode); 77 | #endif 78 | 79 | m_ConsoleOut.open("CONOUT$", std::ios::out | std::ios::app); 80 | m_FileOut.open("./cout.log", std::ios::out | std::ios::trunc); 81 | 82 | m_Worker->addSink(std::make_unique(), &LogSink::Callback); 83 | g3::initializeLogging(m_Worker.get()); 84 | } 85 | -------------------------------------------------------------------------------- /src/Logger.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Common.hpp" 3 | #include 4 | #include 5 | #include "Singleton.hpp" 6 | 7 | #define ADD_COLOR_TO_STREAM(color) "\x1b[" << int(color) << "m" 8 | #define RESET_STREAM_COLOR "\x1b[" << int(LogColor::RESET) << "m" 9 | enum class LogColor 10 | { 11 | RESET, 12 | WHITE = 97, 13 | CYAN = 36, 14 | MAGENTA = 35, 15 | BLUE = 34, 16 | GREEN = 32, 17 | YELLOW = 33, 18 | RED = 31, 19 | BLACK = 30 20 | }; 21 | 22 | struct LogSink; 23 | class Logger final : public Singleton 24 | { 25 | public: 26 | ~Logger() = default; 27 | Logger(const Logger&) = default; 28 | Logger(Logger&&) noexcept = default; 29 | Logger& operator=(const Logger&) = default; 30 | Logger& operator=(Logger&&) noexcept = default; 31 | 32 | static void Destroy() { GetInstance().DestroyImpl(); } 33 | static void Init() { GetInstance().InitImpl(); } 34 | 35 | private: 36 | Logger() = default; 37 | 38 | void DestroyImpl(); 39 | void InitImpl(); 40 | 41 | private: 42 | friend class Singleton; 43 | friend struct LogSink; 44 | 45 | std::ofstream m_ConsoleOut; 46 | std::ofstream m_FileOut; 47 | 48 | std::unique_ptr m_Worker; 49 | }; -------------------------------------------------------------------------------- /src/Remote.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Common.hpp" 3 | #include "Logger.hpp" 4 | 5 | namespace Remote 6 | { 7 | struct VersionInfo 8 | { 9 | const std::string m_Path; 10 | const std::string m_Version; 11 | const int m_VersionMachine; 12 | const bool m_Valid; 13 | }; 14 | 15 | inline VersionInfo GetVersionInfo(const std::string_view dllProvider) 16 | { 17 | const auto url = std::format("{}/version.json", dllProvider.data()); 18 | 19 | try 20 | { 21 | http::Request req(url); 22 | http::Response res = req.send("GET", "", {}, 2500ms); 23 | 24 | nlohmann::json j = nlohmann::json::parse(res.body.begin(), res.body.end()); 25 | 26 | return { j["file"], j["version"], j["version_machine"], true }; 27 | } 28 | catch (const std::exception&) 29 | { 30 | LOG(WARNING) << "Failed to get DLL version info, is the host down?"; 31 | } 32 | 33 | return {}; 34 | } 35 | 36 | inline bool DownloadBinary(const std::string_view fileUrl, const std::filesystem::path& location) 37 | { 38 | std::ofstream file(location, std::ios::binary | std::ios::trunc); 39 | 40 | try 41 | { 42 | http::Request req(fileUrl.data()); 43 | http::Response res = req.send("GET", "", {}, 2500ms); 44 | 45 | std::ostream_iterator outputIter(file); 46 | std::ranges::copy(res.body.begin(), res.body.end(), outputIter); 47 | } 48 | catch (const std::exception&) 49 | { 50 | LOG(INFO) << "Failed to download binary, is the host down?"; 51 | 52 | file.close(); 53 | 54 | return false; 55 | } 56 | file.close(); 57 | 58 | return true; 59 | } 60 | } -------------------------------------------------------------------------------- /src/Settings.cpp: -------------------------------------------------------------------------------- 1 | #include "Common.hpp" 2 | #include "Logger.hpp" 3 | #include "Settings.hpp" 4 | 5 | bool Settings::DeepCompare(nlohmann::json& a, const nlohmann::json& b, const bool compareValues) 6 | { 7 | bool wasModified = false; 8 | 9 | for (auto& e : b.items()) 10 | { 11 | if (const auto& key = e.key(); a.count(key) == 0 || (compareValues && a[key] != e.value())) 12 | { 13 | a[key] = e.value(); 14 | 15 | wasModified = true; 16 | } 17 | else if (a[key].is_structured() && e.value().is_structured()) 18 | { 19 | if (DeepCompare(a[key], e.value(), compareValues)) 20 | wasModified = true; 21 | } 22 | else if (!a[key].is_structured() && e.value().is_structured()) { 23 | a[key] = e.value(); 24 | 25 | wasModified = true; 26 | } 27 | } 28 | 29 | return wasModified; 30 | } 31 | 32 | void Settings::LoadImpl() 33 | { 34 | m_DefaultOptions = ToJsonImpl(); 35 | 36 | std::ifstream file(m_FilePath); 37 | 38 | if (!file.is_open()) 39 | { 40 | SaveImpl(); 41 | 42 | file.open(m_FilePath); 43 | } 44 | 45 | nlohmann::json j; 46 | try 47 | { 48 | file >> j; 49 | } 50 | catch (const std::exception& ex) 51 | { 52 | LOG(WARNING) << "Failed to load settings:\n" << ex.what(); 53 | 54 | SaveImpl(); 55 | 56 | return LoadImpl(); 57 | } 58 | 59 | const bool shouldSave = DeepCompare(j, m_DefaultOptions); 60 | 61 | FromJsonImpl(j); 62 | 63 | if (shouldSave) 64 | { 65 | LOG(INFO) << "Updated settings..."; 66 | 67 | SaveImpl(); 68 | } 69 | } 70 | 71 | void Settings::SaveImpl() const 72 | { 73 | std::ofstream file(m_FilePath, std::ios::out | std::ios::trunc); 74 | file << ToJsonImpl().dump(4); 75 | file.close(); 76 | } 77 | 78 | void Settings::FromJsonImpl(const nlohmann::json& j) 79 | { 80 | m_ApplicationMode = (ApplicationMode)j["application_mode"]; 81 | m_InjectionMode = (InjectionDirection)j["injection_direction"]; 82 | m_Target = j["target_application"]; 83 | 84 | m_Local.m_File = j["local"]["file"]; 85 | 86 | m_WebServer.m_DllProvider = j["web_server"]["dll_provider"]; 87 | m_WebServer.m_File = j["web_server"]["file"]; 88 | m_WebServer.m_VersionMachine = j["web_server"]["version"]; 89 | } 90 | 91 | nlohmann::json Settings::ToJsonImpl() const 92 | { 93 | return { 94 | { "application_mode", (int)m_ApplicationMode }, 95 | { "injection_direction", (int)m_InjectionMode }, 96 | { "target_application", m_Target }, 97 | { "local", 98 | { 99 | {"file", m_Local.m_File } 100 | }, 101 | }, 102 | { "web_server", 103 | { 104 | { "dll_provider", m_WebServer.m_DllProvider }, 105 | { "file", m_WebServer.m_File }, 106 | { "version", m_WebServer.m_VersionMachine } 107 | } 108 | } 109 | }; 110 | } -------------------------------------------------------------------------------- /src/Settings.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Common.hpp" 3 | #include "Singleton.hpp" 4 | 5 | enum class ApplicationMode 6 | { 7 | WEB_SERVER, 8 | LOCAL 9 | }; 10 | 11 | enum class InjectionDirection 12 | { 13 | ATTACH, 14 | DETACH 15 | }; 16 | 17 | class Settings final : public Singleton 18 | { 19 | public: 20 | ~Settings() = default; 21 | Settings(const Settings&) = default; 22 | Settings(Settings&&) noexcept = default; 23 | Settings& operator=(const Settings&) = default; 24 | Settings& operator=(Settings&&) noexcept = default; 25 | 26 | static bool DeepCompare(nlohmann::json& a, const nlohmann::json& b, const bool compareValues = false); 27 | static Settings& Get() { return GetInstance(); } 28 | static void Load() { GetInstance().LoadImpl(); } 29 | static void Save() { GetInstance().SaveImpl(); } 30 | 31 | static void FromJson(const nlohmann::json& j) { GetInstance().FromJsonImpl(j); } 32 | [[nodiscard]] static nlohmann::json ToJson() { return GetInstance().ToJsonImpl(); } 33 | 34 | public: 35 | ApplicationMode m_ApplicationMode = ApplicationMode::WEB_SERVER; 36 | InjectionDirection m_InjectionMode = InjectionDirection::ATTACH; 37 | std::string m_Target = "wordpad.exe"; 38 | 39 | struct Local 40 | { 41 | std::string m_File = "./some_dll.dll"; 42 | } m_Local; 43 | 44 | struct WebServer 45 | { 46 | std::string m_DllProvider = "http://example.com"; 47 | std::string m_File = "latest.dll"; 48 | int m_VersionMachine = 010; 49 | } m_WebServer; 50 | 51 | private: 52 | Settings() = default; 53 | 54 | void LoadImpl(); 55 | void SaveImpl() const; 56 | 57 | void FromJsonImpl(const nlohmann::json& j); 58 | [[nodiscard]] nlohmann::json ToJsonImpl() const; 59 | 60 | private: 61 | friend class Singleton; 62 | 63 | nlohmann::json m_DefaultOptions; 64 | 65 | std::filesystem::path m_FilePath = "./settings.json"; 66 | }; 67 | -------------------------------------------------------------------------------- /src/Singleton.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | template 4 | class Singleton 5 | { 6 | public: 7 | virtual ~Singleton() = default; 8 | Singleton(const Singleton & other) = delete; 9 | Singleton(Singleton && other) = delete; 10 | Singleton& operator=(const Singleton & other) = delete; 11 | Singleton& operator=(Singleton && other) = delete; 12 | 13 | protected: 14 | static T& GetInstance() 15 | { 16 | static T instance{}; 17 | return instance; 18 | } 19 | 20 | Singleton() = default; 21 | }; 22 | -------------------------------------------------------------------------------- /src/Util.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Common.hpp" 3 | 4 | enum class DllValidityError 5 | { 6 | VALID, 7 | ACCESS_FAILURE, 8 | TOO_SMALL, 9 | ALLOCATION_FAILURE, 10 | NOT_A_DLL, 11 | INVALID_PLATFORM 12 | }; 13 | 14 | namespace Util 15 | { 16 | inline const wchar_t* CharToWchar(const char* a) 17 | { 18 | const size_t size = ::strlen(a) + 1; 19 | auto* wchar = new wchar_t[size]; 20 | mbstowcs(wchar, a, size); 21 | 22 | return wchar; 23 | } 24 | 25 | inline DllValidityError CheckIfFileIsValidDll(const std::filesystem::path& dllFile) 26 | { 27 | std::ifstream fileStream(dllFile, std::ios::binary | std::ios::ate); 28 | 29 | if (fileStream.fail()) 30 | { 31 | fileStream.close(); 32 | 33 | return DllValidityError::ACCESS_FAILURE; 34 | } 35 | 36 | const auto fileSize = fileStream.tellg(); 37 | if (fileSize < 0x1000) 38 | { 39 | fileStream.close(); 40 | 41 | return DllValidityError::TOO_SMALL; 42 | } 43 | 44 | auto* pSrcData = new uint8_t[static_cast(fileSize)]; 45 | if (!pSrcData) 46 | { 47 | fileStream.close(); 48 | 49 | return DllValidityError::ALLOCATION_FAILURE; 50 | } 51 | 52 | fileStream.seekg(0, std::ios::beg); 53 | fileStream.read(reinterpret_cast(pSrcData), fileSize); 54 | fileStream.close(); 55 | 56 | if (reinterpret_cast(pSrcData)->e_magic != 0x5A4D) 57 | { 58 | delete[] pSrcData; 59 | 60 | return DllValidityError::NOT_A_DLL; 61 | } 62 | 63 | const auto* pOldNtHeader = reinterpret_cast(pSrcData + reinterpret_cast(pSrcData)->e_lfanew); 64 | const auto* pOldFileHeader = &pOldNtHeader->FileHeader; 65 | 66 | #ifdef _WIN64 67 | if (pOldFileHeader->Machine != IMAGE_FILE_MACHINE_AMD64) 68 | { 69 | delete[] pSrcData; 70 | delete pOldFileHeader; 71 | delete pOldNtHeader; 72 | 73 | return DllValidityError::INVALID_PLATFORM; 74 | } 75 | #else 76 | if (pOldFileHeader->Machine != IMAGE_FILE_MACHINE_I386) 77 | { 78 | delete[] pSrcData; 79 | delete pOldFileHeader; 80 | delete pOldNtHeader; 81 | 82 | return DllValidityError::INVALID_PLATFORM; 83 | } 84 | #endif 85 | 86 | return DllValidityError::VALID; 87 | } 88 | } -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include "Common.hpp" 2 | #include "Injector.hpp" 3 | #include "Logger.hpp" 4 | #include "Remote.hpp" 5 | #include "Settings.hpp" 6 | 7 | int main(int argc, const char** argv) 8 | { 9 | Logger::Init(); 10 | Settings::Load(); 11 | LOG(INFO) << "Loaded settings."; 12 | 13 | std::filesystem::path dllFile; 14 | const std::string_view targetApplication = g.m_Target; 15 | 16 | switch (g.m_ApplicationMode) 17 | { 18 | case ApplicationMode::LOCAL: 19 | dllFile = g.m_Local.m_File; 20 | 21 | break; 22 | case ApplicationMode::WEB_SERVER: 23 | dllFile = g.m_WebServer.m_File; 24 | 25 | const auto versionInfo = Remote::GetVersionInfo(g.m_WebServer.m_DllProvider); 26 | if (!versionInfo.m_Valid) 27 | { 28 | LOG(WARNING) << "Host did not return valid version data, does it have a version.json?"; 29 | 30 | return 1; 31 | } 32 | 33 | if (!std::filesystem::exists(dllFile) || g.m_WebServer.m_VersionMachine < versionInfo.m_VersionMachine) 34 | { 35 | if (!Remote::DownloadBinary( 36 | std::format("{}/{}", g.m_WebServer.m_DllProvider, versionInfo.m_Path), 37 | dllFile)) 38 | { 39 | return 1; 40 | } 41 | 42 | LOG(INFO) << "Downloaded new DLL from remote, new DLL version is " << versionInfo.m_Version; 43 | } 44 | 45 | g.m_WebServer.m_VersionMachine = versionInfo.m_VersionMachine; 46 | Settings::Save(); 47 | LOG(INFO) << "Saved new version to disk."; 48 | 49 | break; 50 | } 51 | 52 | if (!std::filesystem::exists(dllFile)) 53 | { 54 | LOG(WARNING) << "Unable to find DLL file '" << dllFile << "', make sure it exists."; 55 | 56 | return 1; 57 | } 58 | 59 | if (!dllFile.is_absolute()) 60 | dllFile = std::filesystem::absolute(dllFile); 61 | LOG(INFO) << "Starting injection for " << dllFile.filename().string(); 62 | 63 | switch (Util::CheckIfFileIsValidDll(dllFile)) 64 | { 65 | case DllValidityError::ACCESS_FAILURE: 66 | LOG(WARNING) << "Failed to access DLL on disk."; 67 | 68 | return 1; 69 | case DllValidityError::TOO_SMALL: 70 | LOG(WARNING) << "DLL file seems inconceivably small, request to inject ignored."; 71 | 72 | return 1; 73 | case DllValidityError::ALLOCATION_FAILURE: 74 | LOG(WARNING) << "Failed to allocate memory when checking DLL file."; 75 | 76 | return 1; 77 | case DllValidityError::NOT_A_DLL: 78 | LOG(WARNING) << "The file given does not appear to be a valid DLL."; 79 | 80 | return 1; 81 | case DllValidityError::INVALID_PLATFORM: 82 | LOG(WARNING) << "The DLL given did not match the target platform the injector."; 83 | 84 | return 1; 85 | case DllValidityError::VALID: 86 | LOG(INFO) << "DLL seems valid, proceeding with injection."; 87 | 88 | break; 89 | } 90 | 91 | const int processId = Injector::GetProcessId(targetApplication.data()); 92 | if (processId == -1) 93 | { 94 | LOG(WARNING) << "Unable to find target application " << targetApplication << ", is it running?"; 95 | 96 | return 1; 97 | } 98 | LOG(INFO) << "Found target application " << targetApplication << " with process id #" << processId; 99 | 100 | HANDLE processHandle; 101 | if (!Injector::OpenProcess(processId, processHandle)) 102 | { 103 | LOG(WARNING) << "Failed to open target with necessary rights to inject."; 104 | 105 | return 1; 106 | } 107 | 108 | switch (g.m_InjectionMode) 109 | { 110 | case InjectionDirection::ATTACH: 111 | LOG(INFO) << "Attempting LoadLibrary of DLL..."; 112 | 113 | void* alloc; 114 | if (alloc = Injector::AllocateDllPath(processHandle, dllFile.string()); !alloc) 115 | { 116 | LOG(WARNING) << "Failed to allocate DLL file path to target process."; 117 | 118 | return 1; 119 | } 120 | 121 | if (!Injector::WriteToTargetProcess(processHandle, alloc, dllFile.string())) 122 | { 123 | LOG(WARNING) << "Failed to write DLL file path to target process."; 124 | 125 | return 1; 126 | } 127 | 128 | if (!Injector::LoadLibraryRemotely(processHandle, alloc)) 129 | { 130 | LOG(WARNING) << "Failed to start LoadLibrary call with remote thread."; 131 | 132 | return 1; 133 | } 134 | break; 135 | } 136 | LOG(INFO) << "Successfully sent instructions to target process!"; 137 | 138 | Logger::Destroy(); 139 | 140 | return 0; 141 | } 142 | --------------------------------------------------------------------------------