diff --git a/.clangd b/.clangd new file mode 100644 index 0000000..ae36ee2 --- /dev/null +++ b/.clangd @@ -0,0 +1,6 @@ +CompileFlags: + BuiltinHeaders: QueryDriver + CompilationDatabase: . + Remove: + - 'external:I*' + - 'external:W*' diff --git a/CMakeLists.txt b/CMakeLists.txt index 0649652..8e312ff 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -31,4 +31,5 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib") set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib") find_package(Qt6 REQUIRED COMPONENTS Widgets Core Gui Core5Compat QUIET) -add_subdirectory(framework) \ No newline at end of file +add_subdirectory(framework) +add_subdirectory(injector) \ No newline at end of file diff --git a/injector/CMakeLists.txt b/injector/CMakeLists.txt new file mode 100644 index 0000000..5319366 --- /dev/null +++ b/injector/CMakeLists.txt @@ -0,0 +1,34 @@ +include(FetchContent) +FetchContent_Declare( + argparse + GIT_REPOSITORY https://github.com/p-ranav/argparse.git +) +FetchContent_MakeAvailable(argparse) + + +### --- sources --- ### +file(GLOB_RECURSE INJECTOR_SOURCES CONFIGURE_DEPENDS + "${CMAKE_CURRENT_SOURCE_DIR}/src/*.c" + "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cxx" + "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp" +) +file(GLOB_RECURSE INJECTOR_HEADERS CONFIGURE_DEPENDS + "${CMAKE_CURRENT_SOURCE_DIR}/src/*.h" + "${CMAKE_CURRENT_SOURCE_DIR}/src/*.hpp" +) + +list(FILTER INJECTOR_SOURCES EXCLUDE REGEX "/(out|build|cmake-build-|CMakeFiles)/") +list(REMOVE_ITEM INJECTOR_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/src/main.cpp") + +add_library(libtoonboom_injector STATIC ${INJECTOR_SOURCES} ${INJECTOR_HEADERS}) +target_compile_options(libtoonboom_injector PRIVATE "/EHsc") +target_include_directories(libtoonboom_injector PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src") + + +add_executable(toon_boom_injector "${CMAKE_CURRENT_SOURCE_DIR}/src/main.cpp") +target_link_libraries(toon_boom_injector PRIVATE libtoonboom_injector) +target_link_libraries(toon_boom_injector PRIVATE argparse) +target_compile_options(toon_boom_injector PRIVATE "/EHsc") + + diff --git a/injector/src/finder.cpp b/injector/src/finder.cpp new file mode 100644 index 0000000..e52e095 --- /dev/null +++ b/injector/src/finder.cpp @@ -0,0 +1,73 @@ +#include "finder.h" +#include + +namespace fs = std::filesystem; + +std::vector> findToonBoomVersions() { + std::string programFiles("C:\\Program Files\\Toon Boom Animation\\"); + std::string programFilesX86("C:\\Program Files (x86)\\Toon Boom Animation\\"); + + std::vector> versions; + try { + for (const auto &entry : fs::directory_iterator(programFiles)) { + auto subEntries = findSubEntries(entry); + versions.insert(versions.end(), subEntries.begin(), subEntries.end()); + } + } catch (const std::filesystem::filesystem_error &e) { + } + try { + for (const auto &entry : fs::directory_iterator(programFilesX86)) { + auto subEntries = findSubEntries(entry); + versions.insert(versions.end(), subEntries.begin(), subEntries.end()); + } + } catch (const std::filesystem::filesystem_error &e) { + std::cerr << "Error: " << e.what() << std::endl; + } + + return versions; +} + +std::vector> +findSubEntries(const std::filesystem::directory_entry &entry) { + std::vector> versions; + std::set exeNames = {"StoryboardPro.exe", "HarmonyPremium.exe", + "HarmonyAdvanced.exe", + "HarmonyEssentials.exe"}; + if (fs::is_directory(entry.path())) { + if (entry.path().filename().string().find("Toon Boom") != + std::string::npos) { + for (const auto &subEntry : + fs::recursive_directory_iterator(entry.path())) { + try { + if (fs::is_regular_file(subEntry.path()) && + exeNames.contains(subEntry.path().filename().string())) { + versions.push_back(std::make_pair(entry.path().filename().string(), subEntry)); + } + } catch (const std::filesystem::filesystem_error &e) { + continue; + } + } + } + } + return versions; +} + +DWORD GetProcessIdByName(const std::string& processName) { + DWORD pid = 0; + HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if (INVALID_HANDLE_VALUE == hSnapshot) return 0; + + PROCESSENTRY32 pe = { sizeof(pe) }; + if (Process32First(hSnapshot, &pe)) { + do { + // std::cout << "Process name: " << pe.szExeFile << std::endl; + if (processName == std::string(pe.szExeFile)) { + pid = pe.th32ProcessID; + break; + } + } while (Process32Next(hSnapshot, &pe)); + } + + CloseHandle(hSnapshot); + return pid; +} \ No newline at end of file diff --git a/injector/src/finder.h b/injector/src/finder.h new file mode 100644 index 0000000..b632899 --- /dev/null +++ b/injector/src/finder.h @@ -0,0 +1,14 @@ +#pragma once +#include +#include +#include +#include +#include +#include + + +std::vector> findToonBoomVersions(); + +std::vector> findSubEntries(const std::filesystem::directory_entry& entry); + +DWORD GetProcessIdByName(const std::string& processName); \ No newline at end of file diff --git a/injector/src/main.cpp b/injector/src/main.cpp new file mode 100644 index 0000000..a974e14 --- /dev/null +++ b/injector/src/main.cpp @@ -0,0 +1,176 @@ +#include "./finder.h" +#include +#include +#include + + +argparse::ArgumentParser *&createProgram(int argc, char *argv[]) { + static auto program = new argparse::ArgumentParser("tb-injector-cli.exe"); + program->set_prefix_chars("-/"); + program->set_assign_chars(":="); + program->add_argument("-h", "--help") + .help("show help message and exit") + .implicit_value(true); + program->add_argument("-v", "--debug") + .help("log program's stdout and stderr to the given file") + .default_value(""); + program->add_argument("-p", "--program") + .help("path to a Toon Boom program") + .default_value("") + .nargs(0, 1); + program->add_argument("-i", "--dep") + .help("path to an additional dll to copy into program's install dir") + .default_value>({}) + .append(); + program->add_argument("-d", "--dll").append().help("path to a dll to inject"); + + return program; +} + +void copyDll(const std::string &dllPath, + const std::filesystem::directory_entry &entry, bool isDebug, + bool isDep) { + auto absPath = std::filesystem::absolute(dllPath); + if (!std::filesystem::exists(absPath)) { + std::cerr << "[warning] dll path " << absPath << " does not exist" + << std::endl; + return; + } + std::filesystem::copy(absPath, + entry.path().parent_path() / + std::filesystem::path(dllPath).filename().string(), + std::filesystem::copy_options::overwrite_existing); + if (isDebug) { + std::cout << "Copied " << (isDep ? "dependency dll" : "dll") << " at " + << absPath << " to " + << entry.path().parent_path() / + std::filesystem::path(dllPath).filename().string() + << std::endl; + } +} + +int main(int argc, char *argv[]) { + auto args = createProgram(argc, argv); + try { + args->parse_args(argc, argv); + } catch (const std::exception &e) { + std::cerr << "Error: " << e.what() << std::endl; + std::cerr << args; + return 1; + } + if (args->present("-h")) { + std::cout << args->help().str() << std::endl; + return 0; + } + + bool isDebug = false; + std::string logFile = ""; + std::filesystem::directory_entry entry; + + if (args->get("-v") != "") { + logFile = args->get("-v"); + std::cout << "Logging to " << (logFile == "-" ? "console" : logFile) + << std::endl; + } + + std::string program = args->get("-p"); + if (program == "" || !std::filesystem::exists(program)) { + std::vector> + versions = findToonBoomVersions(); + bool isValidOption = false; + std::cout + << "The following Toon Boom software was detected on your system: " + << std::endl; + for (int i = 0; i < versions.size(); i++) { + std::cout << "\t" << "#" << i + 1 << ": " << versions[i].first + << std::endl; + } + while (!isValidOption) { + std::cout << "Please pick a number between 1 and " << versions.size() + << ": "; + int choice; + std::cin >> choice; + if (choice >= 1 && choice <= versions.size()) { + isValidOption = true; + program = versions[choice - 1].second.path().filename().string(); + entry = versions[choice - 1].second; + } + } + } else { + entry = std::filesystem::directory_entry(program); + } + if (isDebug) { + std::cout << "Target executable: " << program << std::endl; + } + auto dllPaths = args->get>("-d"); + if (dllPaths.size() == 0) { + std::cout << "must provide at least one dll path" << std::endl; + return 1; + } + + auto dllDeps = args->get>("-i"); + for (auto dllPath : dllPaths) { + copyDll(dllPath, entry, isDebug, false); + } + for (auto dllPath : dllDeps) { + copyDll(dllPath, entry, isDebug, true); + } + STARTUPINFO si; + PROCESS_INFORMATION pi; + ZeroMemory(&si, sizeof(si)); + ZeroMemory(&pi, sizeof(pi)); + SECURITY_ATTRIBUTES sattr; + ZeroMemory(&sattr, sizeof(sattr)); + sattr.bInheritHandle = FALSE; + si.cb = sizeof(si); + if (!CreateProcess(NULL, entry.path().string().data(), &sattr, NULL, FALSE, + CREATE_SUSPENDED | CREATE_NO_WINDOW, NULL, + entry.path().parent_path().string().data(), &si, &pi)) { + std::cerr << "Failed to create process" << std::endl; + return 1; + } + std::cout << "Target process ID: " << pi.dwProcessId << std::endl; + HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pi.dwProcessId); + + HMODULE hKernel32 = GetModuleHandleA("kernel32.dll"); + FARPROC hLoadLibraryA = GetProcAddress(hKernel32, "LoadLibraryA"); + for (auto dllPath : dllPaths) { + auto realPath = (std::filesystem::path(program).parent_path().string() + "/" + std::filesystem::absolute(dllPath).filename().string()); + + LPVOID remoteBuffer = + VirtualAllocEx(hProcess, NULL, dllPath.size() + 1, + MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); + if (remoteBuffer == NULL) { + std::cerr << "Failed to allocate memory in target process" << std::endl; + return 1; + } + if (!WriteProcessMemory(hProcess, remoteBuffer, realPath.data(), + realPath.size() + 1, NULL)) { + std::cerr << "Failed to write process memory" << std::endl; + CloseHandle(hProcess); + return 1; + } + DWORD remoteTID; + HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, + (LPTHREAD_START_ROUTINE)hLoadLibraryA, + remoteBuffer, 0, &remoteTID); + if (hThread == NULL) { + std::cerr << "Failed to create remote thread" << std::endl; + VirtualFreeEx(hProcess, remoteBuffer, 0, MEM_RELEASE); + CloseHandle(hProcess); + return 1; + } + std::cout << "Remote thread ID: " << remoteTID << std::endl; + WaitForSingleObject(hThread, INFINITE); + DWORD exitCode; + GetExitCodeThread(hThread, &exitCode); + std::cout << "Exit code: " << exitCode << std::endl; + CloseHandle(hThread); + VirtualFreeEx(hProcess, remoteBuffer, 0, MEM_RELEASE); + } + ResumeThread(pi.hThread); + CloseHandle(hProcess); + std::cout << "Congratulations!!! you have been injected :3" << std::endl; + + return 0; +} \ No newline at end of file diff --git a/injector/src/stub.cpp b/injector/src/stub.cpp new file mode 100644 index 0000000..e69de29