Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Injector.sln.DotSettings
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
<s:Boolean x:Key="/Default/UserDictionary/Words/=HMODULE/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=LUID/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=MBCS/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Nefarius/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=wstr/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=wstring/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
84 changes: 53 additions & 31 deletions Injector/Injector.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#include <Windows.h>
#include <TlHelp32.h>
#include <Psapi.h>
#include <Shlwapi.h>
#include <malloc.h>
#include <Tchar.h>

Expand Down Expand Up @@ -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<HMODULE> Modules;
DWORD SizeNeeded = 0;
Expand All @@ -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];
Expand All @@ -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<BYTE*>(Module);
}
return nullptr;
Expand Down Expand Up @@ -236,42 +248,52 @@ 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<TCHAR> LoaderPath(MAX_PATH);
if (!GetModuleFileName(Self,&LoaderPath[0],static_cast<DWORD>(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<TCHAR> LoaderPath(MAX_PATH);
if (!GetModuleFileName(NULL, &LoaderPath[0], static_cast<DWORD>(LoaderPath.size())) ||
GetLastError() == ERROR_INSUFFICIENT_BUFFER)
throw std::runtime_error("Could not get path to loader.");

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
Expand Down
30 changes: 8 additions & 22 deletions Injector/Main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
#include <Windows.h>
#include <tchar.h>
#include <TlHelp32.h>
#include <Shlwapi.h>

// C++ Standard Library
#include <iostream>
Expand All @@ -33,15 +32,15 @@ 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
std::tcout << _T("Injector x64 [Version ") << VerNum << _T("]") << std::endl;
#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;

Expand Down Expand Up @@ -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);
Expand All @@ -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;
Expand Down
Loading