I have C++ code that updates an environment variable in a TCC process from a C++ program. This code no longer works consistently (sometimes it works, sometimes it doesn't, and I've been unable to identify a pattern, particularly since it appears that it both sometimes fails and sometmes works correctly for exactly the same input data - although failure is the most common result) but has worked fine for several years in previous versions (up through and including version 13; and I tested it again under version 13 immediately prior to making this posting) of TCC.
This is seriously complicated, which can't be helped, and probably only Rex knows/can do anything about it.
So, on to C++ code:
First the structure representing memory that is going to be shared between the TCC process and the running C++ program.
Since the shared memory has to be updated by a DLL this is the class that handles all of the shared memory/DLL interaction.
This is the code that actually (at least attempts to!) set an environment variable in a given process (which is assumed to be a TCC instance, of course):
This is the routine that actually copies the data that is to be the contents of the environment variable in the "remote" TCC instance to shared memory.
This is the code that actually "injects" the DLL ("SetEnvLib.DLL") into the "remote" process (TCC instance). This is the routine that is now (version 14 of TCC) usually failing ("Failed to update selected process!").
For completeness in this missive, this is the routine that "ejects" the DLL injected into the remote process (TCC session).
I have nothing more to say on this subject, but I've probably said more than enough! :)
This is seriously complicated, which can't be helped, and probably only Rex knows/can do anything about it.
So, on to C++ code:
First the structure representing memory that is going to be shared between the TCC process and the running C++ program.
Code:
// Structure to share data between this program and TCC
#pragma pack(push)
#pragma pack(4)
struct SharedData {
BOOL bAddToExisting;
wchar_t strVariable[1024];
wchar_t strValue[1024];
SharedData() {
ZeroMemory(strVariable, sizeof(strVariable));
ZeroMemory(strValue, sizeof(strValue));
bAddToExisting = TRUE;
}
};
#pragma pack(pop)
Since the shared memory has to be updated by a DLL this is the class that handles all of the shared memory/DLL interaction.
Code:
class CDLLInjector {
void FreeShareMem() {
if (m_hMapFile) {
CloseHandle(m_hMapFile);
m_hMapFile = 0;
}
}
bool InjectLibW(DWORD, bool);
bool EjectLibW(DWORD);
HANDLE m_hMapFile;
public:
CDLLInjector();
~CDLLInjector();
bool SetEnvironmentVariable(const DWORD, // Process ID
const char * const, // Variable Name
const char * const, // Variable Value
const bool); // Add To Existing flag
bool CreateAndCopyToShareMem(const char * const, const char * const, const bool);
};
CDLLInjector::CDLLInjector() : m_hMapFile(0) {
}
CDLLInjector::~CDLLInjector() {
}
This is the code that actually (at least attempts to!) set an environment variable in a given process (which is assumed to be a TCC instance, of course):
Code:
bool CDLLInjector::SetEnvironmentVariable(const DWORD dwProcessID,
const char * const strVarName,
const char * const strVarVal,
const bool bAddToExisting = false) {
size_t sztLngthVar = strlen(strVarName);
if (!sztLngthVar)
return FALSE;
CreateAndCopyToShareMem(strVarName, strVarVal, bAddToExisting);
bool bSuccess = InjectLibW(dwProcessID, false);
// If unable to update environment variable and
// new value was not null,,,
if (!bSuccess && strVarVal) {
Number ldTemp;
if (!GetVariableValue("NoSetEnvlibDLL", ldTemp, false)) {
ReformatMessageToErrorStack(0,
"Note: Unable to store the results "
"of the last calculation in an "
"environment variable. Using a "
"\"Variables\" file instead. (You "
"may want to check if "
"\"SetEnvLib.DLL\" is in a "
"directory that is in your path.)");
AddOrUpdateVariable("NoSetEnvlibDLL");
}
bVariableChange = true; // Set to write environment information to a file
}
FreeShareMem();
if (bSuccess)
EjectLibW(dwProcessID);
return bSuccess;
}
This is the routine that actually copies the data that is to be the contents of the environment variable in the "remote" TCC instance to shared memory.
Code:
// Update the user entered data to sharememory
bool CDLLInjector::CreateAndCopyToShareMem(const char * const strVarName,
const char * const strVarVal,
const bool bAddToExisting) {
SharedData stData;
size_t sztLngthVar = strlen(strVarName),
sztLngthVal;
if (!sztLngthVar || sztLngthVar >= (_countof(stData.strVariable) - 1)) {
// Not being done through error stack because total message length
// might be longer than the size of szMessageBuffer
// (But this is highly unlikely to ever occur.)
cerr << "Variable name\n \"" << strVarName <<
"\"\n""is too long. Currently supports a maximum of 1024 chars." << endl;
return FALSE;
}
if (strVarVal)
sztLngthVal = strlen(strVarVal);
LPWSTR pBuf;
size_t sztReturnedValue1,
sztReturnedValue2;
// Prepare data for copying
mbstowcs_s(&sztReturnedValue1, stData.strVariable, _countof(stData.strVariable), strVarName, sztLngthVar);
if (strVarVal)
mbstowcs_s(&sztReturnedValue2, stData.strValue, _countof(stData.strValue), strVarVal, sztLngthVal);
else
*(stData.strValue) = 0;
stData.bAddToExisting = bAddToExisting;
m_hMapFile = CreateFileMappingA(INVALID_HANDLE_VALUE, 0,
PAGE_READWRITE, 0, sizeof(stData),
SHAREMEM_NAME);
if (!m_hMapFile) {
cerr << "Could not create file mapping object" << endl;
return FALSE;
}
pBuf = (LPWSTR) MapViewOfFile(m_hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(stData));
if (!pBuf) {
cerr << "Could not map view of file!" << endl;
CloseHandle(m_hMapFile);
m_hMapFile = 0;
return FALSE;
}
// Copy the data
CopyMemory((PVOID) pBuf, &stData, sizeof(stData));
UnmapViewOfFile(pBuf);
return TRUE;
}
This is the code that actually "injects" the DLL ("SetEnvLib.DLL") into the "remote" process (TCC instance). This is the routine that is now (version 14 of TCC) usually failing ("Failed to update selected process!").
Code:
// Function to inject the library
bool CDLLInjector::InjectLibW(DWORD dwProcessId, bool bDisplayErrors = false) {
HANDLE hProcess = 0; // Process handle
PWSTR wszLibFileRemote = 0;
HANDLE hRemoteThread = 0;
bool bSuccess = true;
__try {
hProcess = OpenProcess(
PROCESS_QUERY_INFORMATION | PROCESS_CREATE_THREAD |
PROCESS_VM_OPERATION | PROCESS_VM_WRITE, // For CreateRemoteThread
FALSE, dwProcessId);
if(!hProcess) {
if (bDisplayErrors)
cerr << "Failed to update selected process!" << endl;
bSuccess = false;
__leave;
}
wchar_t wszModulePath[MAX_PATH];
if (!ExistsInPath(L"SetEnvLib.DLL", wszModulePath, _countof(wszModulePath))) {
if (bDisplayErrors)
cerr << "Error: Unable to determine existence of \"SetEnvLib.DLL\"!" << endl;
bSuccess = false;
__leave;
}
int cb = sizeof(wchar_t) * (wcslen(wszModulePath) + 1);
// Allocate space in the remote process for the pathname
wszLibFileRemote = (PWSTR) VirtualAllocEx(hProcess, 0, cb, MEM_COMMIT, PAGE_READWRITE);
if (!wszLibFileRemote) {
if (bDisplayErrors)
cerr << "Unable to allocate memory!" << endl;
bSuccess = false;
__leave;
}
// Copy the DLL's pathname to the remote process' address space
if (!WriteProcessMemory(hProcess, wszLibFileRemote, (PVOID) wszModulePath, cb, 0)) {
if (bDisplayErrors)
cerr << "Failed to write to Process Memory!" << endl;
bSuccess = false;
__leave;
};
// Create remote thread and inject the library
hRemoteThread = CreateRemoteThread(hProcess, 0, 0,
(LPTHREAD_START_ROUTINE)LoadLibraryW,
wszLibFileRemote, 0, 0);
if(!hRemoteThread) {
cerr << "Failed to update selected process!" << endl;
return false;
}
WaitForSingleObject(hRemoteThread, INFINITE);
}
__finally { // Do the cleanup
// Free the remote memory that contained the DLL's pathname
if (wszLibFileRemote)
VirtualFreeEx(hProcess, wszLibFileRemote, 0, MEM_RELEASE);
if (hRemoteThread)
CloseHandle(hRemoteThread);
if (hProcess)
CloseHandle(hProcess);
}
return bSuccess;
}
For completeness in this missive, this is the routine that "ejects" the DLL injected into the remote process (TCC session).
Code:
// Eject the library loaded to remote process
bool CDLLInjector::EjectLibW(DWORD dwProcessID) {
HANDLE hthSnapshot = 0,
hProcess = 0,
hThread = 0;
bool bOk = false, // Assume that the function fails
bFound = false;
__try {
// Grab a new snapshot of the process
hthSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwProcessID);
if (hthSnapshot == INVALID_HANDLE_VALUE)
__leave;
// Get the HMODULE of the desired library
MODULEENTRY32W me = { sizeof(me) };
BOOL bMoreMods = Module32FirstW(hthSnapshot, &me);
// Iterate through all the loaded modules
while (bMoreMods) {
bFound = (!_wcsicmp(me.szModule, L"SetEnvLib.dll")) ||
(!_wcsicmp(me.szExePath, L"SetEnvLib.dll"));
if (bFound)
break;
bMoreMods = Module32NextW(hthSnapshot, &me);
}
if (!bFound)
__leave;
// Get a handle for the target process.
hProcess = OpenProcess(
PROCESS_QUERY_INFORMATION |
PROCESS_CREATE_THREAD |
PROCESS_VM_OPERATION, // For CreateRemoteThread
FALSE, dwProcessID);
if (!hProcess)
__leave;
// Get the address of FreeLibrary in Kernel32.dll
PTHREAD_START_ROUTINE pfnThreadRtn = (PTHREAD_START_ROUTINE)
GetProcAddress(GetModuleHandleA("Kernel32"), "FreeLibrary");
if (!pfnThreadRtn)
__leave;
// Create a remote thread that calls FreeLibrary()
hThread = CreateRemoteThread(hProcess, 0, 0, pfnThreadRtn, me.modBaseAddr, 0, 0);
if (!hThread)
__leave;
// Wait for the remote thread to terminate
WaitForSingleObject(hThread, INFINITE);
bOk = TRUE; // Everything executed successfully
}
__finally {
if (hthSnapshot)
CloseHandle(hthSnapshot);
if (hThread)
CloseHandle(hThread);
if (hProcess)
CloseHandle(hProcess);
}
return bOk;
}
I have nothing more to say on this subject, but I've probably said more than enough! :)