diff --git a/Injector.sln.DotSettings b/Injector.sln.DotSettings index a16fae8..53af102 100644 --- a/Injector.sln.DotSettings +++ b/Injector.sln.DotSettings @@ -2,5 +2,6 @@ True True True + True True True \ No newline at end of file diff --git a/Injector/Injector.cpp b/Injector/Injector.cpp index fd4559d..c43bb72 100644 --- a/Injector/Injector.cpp +++ b/Injector/Injector.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include @@ -46,6 +47,19 @@ bool Injector::icompare(const std::wstring& a, const std::wstring& b) const // Check if a module is injected via process handle, and return the base address BYTE* Injector::GetModuleBaseAddress(HANDLE Process, const std::wstring& Path) { + // Canonicalize the search path so it can be compared reliably against + // the canonical paths returned by GetModuleFileNameExW. + WCHAR CanonicalPath[MAX_PATH]; + std::wstring SearchPath = Path; + if (GetFullPathNameW(Path.c_str(), MAX_PATH, CanonicalPath, NULL)) + SearchPath = CanonicalPath; + + // Extract the filename portion for comparison against module base names + std::wstring FileName = SearchPath; + auto LastSep = FileName.rfind(L'\\'); + if (LastSep != std::wstring::npos) + FileName = FileName.substr(LastSep + 1); + // Grab a new snapshot of the process std::vector Modules; DWORD SizeNeeded = 0; @@ -57,7 +71,6 @@ BYTE* Injector::GetModuleBaseAddress(HANDLE Process, const std::wstring& Path) { } while (SizeNeeded > Modules.size() * sizeof(HMODULE)); // Get the HMODULE of the desired library - bool Found = false; for (const auto &Module : Modules) { WCHAR ModuleName[MAX_PATH]; @@ -68,8 +81,7 @@ BYTE* Injector::GetModuleBaseAddress(HANDLE Process, const std::wstring& Path) { // The size of the ExePath buffer, in characters. if (!GetModuleFileNameExW(Process, Module, ExePath, sizeof(ExePath) / sizeof(WCHAR))) throw std::runtime_error("Could not get ExePath."); - Found = (icompare(ModuleName, Path) || icompare(ExePath, Path)); - if (Found) + if (icompare(ModuleName, FileName) || icompare(ExePath, SearchPath)) return reinterpret_cast(Module); } return nullptr; @@ -236,42 +248,54 @@ void Injector::GetSeDebugPrivilege() throw std::runtime_error("Could not adjust token privileges."); } -// Get fully qualified path from module name. Assumes base directory is the -// directory of the currently executing binary. +// Resolve and validate a module path. Handles both relative and absolute paths. +// Relative paths are resolved against the CWD first, then against the injector's +// own directory as a fallback for backward compatibility. std::tstring Injector::GetPath( const std::tstring& ModuleName ) { - // Get handle to self - HMODULE Self = GetModuleHandle(NULL); - - // Get path to loader - std::vector LoaderPath(MAX_PATH); - if (!GetModuleFileName(Self,&LoaderPath[0],static_cast(LoaderPath.size())) || - GetLastError() == ERROR_INSUFFICIENT_BUFFER) - throw std::runtime_error("Could not get path to loader."); - - // Convert path to loader to path to module - std::tstring ModulePath(&LoaderPath[0]); - ModulePath = ModulePath.substr(0, ModulePath.rfind( _T("\\") ) + 1); - ModulePath.append(ModuleName); - - TCHAR FullModulePath[MAX_PATH]; - if (!GetFullPathName(ModulePath.c_str(), sizeof(FullModulePath) / sizeof(TCHAR), FullModulePath, NULL)) + TCHAR FullPath[MAX_PATH]; + + // First: canonicalize the input as-is. For absolute paths this normalizes + // separators and resolves . / .. components. For relative paths this + // resolves against the current working directory. + if (!GetFullPathName(ModuleName.c_str(), MAX_PATH, FullPath, NULL)) throw std::runtime_error("Could not get full path to module."); - ModulePath = std::tstring(&FullModulePath[0]); - // Check path/file is valid - if (GetFileAttributes(ModulePath.c_str()) == INVALID_FILE_ATTRIBUTES) + std::tstring ModulePath(FullPath); + + if (GetFileAttributes(ModulePath.c_str()) != INVALID_FILE_ATTRIBUTES) + return ModulePath; + + // If the file wasn't found and the original input was relative, try + // resolving it relative to the injector executable's directory. + if (PathIsRelative(ModuleName.c_str())) { + std::vector LoaderPath(MAX_PATH); + const DWORD LoaderPathLen = GetModuleFileName(NULL, &LoaderPath[0], static_cast(LoaderPath.size())); + if (LoaderPathLen == 0) + throw std::runtime_error("Could not get path to loader."); + if (LoaderPathLen >= static_cast(LoaderPath.size())) + throw std::runtime_error("Path to loader exceeds MAX_PATH and was truncated."); + + std::tstring LoaderDir(&LoaderPath[0]); + LoaderDir = LoaderDir.substr(0, LoaderDir.rfind(_T("\\")) + 1); + LoaderDir.append(ModuleName); + + if (!GetFullPathName(LoaderDir.c_str(), MAX_PATH, FullPath, NULL)) + throw std::runtime_error("Could not get full path to module."); + + ModulePath = std::tstring(FullPath); + + if (GetFileAttributes(ModulePath.c_str()) != INVALID_FILE_ATTRIBUTES) + return ModulePath; + } + #ifdef _UNICODE - std::string NarrowModulePath(ConvertWideToANSI(ModulePath)); + std::string NarrowModulePath(ConvertWideToANSI(ModulePath)); #else - std::string NarrowModulePath(ModulePath.begin(), ModulePath.end()); + std::string NarrowModulePath(ModulePath.begin(), ModulePath.end()); #endif - throw std::runtime_error("Could not find module. Path: '" + NarrowModulePath + "'."); - } - - // Return module path - return ModulePath; + throw std::runtime_error("Could not find module. Path: '" + NarrowModulePath + "'."); } // Get process ID via name diff --git a/Injector/Main.cpp b/Injector/Main.cpp index 747e6cd..1b27c78 100644 --- a/Injector/Main.cpp +++ b/Injector/Main.cpp @@ -9,7 +9,6 @@ #include #include #include -#include // C++ Standard Library #include @@ -33,7 +32,7 @@ int main(int, char* argv[]) SehGuard Guard; // Injector version number - const std::tstring VerNum(_T("20240218")); + const std::tstring VerNum(_T("20260228")); // Version and copyright output #ifdef _WIN64 @@ -41,7 +40,7 @@ int main(int, char* argv[]) #else std::tcout << _T("Injector x86 [Version ") << VerNum << _T("]") << std::endl; #endif - std::tcout << _T("Copyright (c) 2009 Cypher, 2012-2024 Nefarius. All rights reserved.") << std::endl << std::endl; + std::tcout << _T("Copyright (c) 2009 Cypher, 2012-2026 Nefarius. All rights reserved.") << std::endl << std::endl; argh::parser cmdl; @@ -150,14 +149,7 @@ int main(int, char* argv[]) { for (auto& mod : modules) { - if (PathIsRelative(mod.c_str())) - { - ModulePath = Injector::Get()->GetPath(mod); - } - else - { - ModulePath = mod; - } + ModulePath = Injector::Get()->GetPath(mod); // Inject module Injector::Get()->InjectLib(ProcID, ModulePath); @@ -172,17 +164,11 @@ int main(int, char* argv[]) { for (auto& mod : modules) { - if (PathIsRelative(mod.c_str())) - { - ModulePath = Injector::Get()->GetPath(mod); - } - else - { - ModulePath = mod; - } - - // Eject module - Injector::Get()->EjectLib(ProcID, ModulePath); + // No file-existence check: the module is already loaded in the + // target process and may no longer exist on disk. EjectLib uses + // GetModuleBaseAddress which canonicalizes the path and matches + // against both the module basename and full path. + Injector::Get()->EjectLib(ProcID, mod); // If we get to this point then no exceptions have been thrown so we // assume success. std::tcout << "Successfully ejected module!" << std::endl;