动态添加/编辑远程进程的环境变量
动态添加/编辑远程进程的环境变量

引言
在许多情况下,有必要为测试和调试目的设置应用程序特定的环境变量。这类变量是应用程序独有的,并在应用程序退出时失效。例如,假设你有一个渲染应用程序,其中渲染路径是通过环境变量指定的。有时可能需要更改这些变量进行测试,或者在某些情况下可能需要添加或删除现有变量。在大多数情况下,应用程序特定的变量是通过在设置好必要变量后通过脚本文件启动,或者通过一个启动器应用程序来控制(环境变量是从父进程继承的)。然而,动态更新变量仍然超出我们的能力范围。
在本文中,我将介绍一个简单的工具,可以动态地为系统中的任何进程设置环境变量。该工具不会修改系统的任何设置或用户环境变量。相反,它会添加/更新特定进程的环境变量。
该工具支持以下操作
- 添加新变量
- 替换现有变量
- 追加到现有变量(以“;”分隔)
- 删除变量
如何操作?
要添加/替换变量,只需在相应的文本框中选择一个进程或输入一个有效的进程ID。然后按“设置”按钮。如果要添加到现有变量,请勾选“添加到现有”。要删除变量,请输入有效的变量名,并将值文本框留空。确保取消勾选“添加到现有”复选框。在幕后,让我们看看我是如何实现的。很简单,如下所示:
- 创建具有用户输入内容和选项的共享内存
- 将我们自己的DLL注入到目标进程
- 在DLL主函数中,打开共享内存,读取所需数据,最后设置变量
- 从DLL返回
- 现在DLL不必保留在进程中,可以将其弹出。
为什么注入?
Windows提供的API只能与当前进程一起工作。这些API无法与外部进程一起工作。如果DLL被注入到特定进程中,后续代码将在该特定进程的上下文中执行。
如何注入DLL?
有几种注入DLL的方法。请参阅Windows via C/C++(或其早期版本)以了解更多关于不同的DLL注入方法。在这个程序中,我使用了远程线程。我相信下面的代码比语言更能说明问题。无论如何,基本技术是将LoadLibrary
设置为线程例程,以便在远程进程中加载DLL。DLL文件的名称必须是目标进程本身可访问的。为此,我们必须使用虚拟内存API。请参阅代码。
// Function to inject the library
void CDLLInjector::InjectLibW( DWORD dwProcessId )
{
HANDLE hProcess= NULL; // Process handle
PWSTR pszLibFileRemote = NULL;
HANDLE hRemoteThread = NULL;
__try
{
hProcess = OpenProcess(
PROCESS_QUERY_INFORMATION |
PROCESS_CREATE_THREAD |
PROCESS_VM_OPERATION |
PROCESS_VM_WRITE, // For CreateRemoteThread
FALSE, dwProcessId);
if( !hProcess )
{
AfxMessageBox( _T("Failed to update selected process") );
__leave;
}
WCHAR szFilePath[MAX_PATH];
GetModuleFileNameW( NULL, szFilePath, MAX_PATH );
// Remove file name of the string
PathRemoveFileSpecW( szFilePath );
// Append the DLL file which is there in the same directory of exe
LPCWSTR pszLib = L"\\SetEnvLib.dll";
// Append the string
wcscat_s( szFilePath, MAX_PATH, pszLib );
int cch = 1 + lstrlenW(szFilePath);
int cb = cch * sizeof(WCHAR);
// Allocate space in the remote process for the pathname
pszLibFileRemote = (PWSTR) VirtualAllocEx( hProcess,
NULL, cb,
MEM_COMMIT, PAGE_READWRITE);
if ( pszLibFileRemote == NULL)
{
AfxMessageBox( _T("Unable to allocate memory") );
return;
}
// Copy the DLL's pathname to the remote process' address space
if (!WriteProcessMemory(hProcess, pszLibFileRemote,
(PVOID) szFilePath, cb, NULL))
{
AfxMessageBox( _T( "Failed to write" ));
return;
};
// Create remote thread and inject the library
hRemoteThread = CreateRemoteThread( hProcess, NULL, 0,
(LPTHREAD_START_ROUTINE)LoadLibraryW,
pszLibFileRemote, NULL,NULL );
if( !hRemoteThread )
{
AfxMessageBox( _T("Failed to update selected process") );
__leave;
}
WaitForSingleObject( hRemoteThread, INFINITE );
AfxMessageBox( _T("Successfully Set values"));
}
__finally // Do the cleanup
{
// Free the remote memory that contained the DLL's pathname
if (pszLibFileRemote != NULL)
VirtualFreeEx(hProcess, pszLibFileRemote, 0, MEM_RELEASE);
if ( hRemoteThread != NULL)
CloseHandle(hRemoteThread);
if ( hProcess != NULL)
CloseHandle(hProcess);
}
}
如何弹出注入的DLL?
完成任务后,让我们的DLL成为目标进程的负担是不好的。我们以与加载相同的方式卸载库。使用FreeLibrary
API创建远程线程,并传递SetEnvVarLIb.dll的模块基地址。为此,我们可以使用Toolhelp
库API。如果我们创建目标进程的模块句柄快照,就可以获得我们注入的DLL的基地址。请参阅下面的代码了解更多详情。
// Eject the library loaded to remote process
BOOL CDLLInjector::EjectLibW( DWORD dwProcessID )
{
BOOL bOk = FALSE; // Assume that the function fails
HANDLE hthSnapshot = NULL;
HANDLE hProcess = NULL, hThread = NULL;
__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 bFound = FALSE;
BOOL bMoreMods = Module32FirstW(hthSnapshot, &me);
// Iterate through all the loaded modules
for (; bMoreMods; bMoreMods = Module32NextW(hthSnapshot, &me))
{
bFound = (_wcsicmp(me.szModule, L"SetEnvLib.dll" ) == 0) ||
(_wcsicmp(me.szExePath, L"SetEnvLib.dll" ) == 0);
if (bFound) break;
}
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 == NULL) __leave;
// Get the address of FreeLibrary in Kernel32.dll
PTHREAD_START_ROUTINE pfnThreadRtn = (PTHREAD_START_ROUTINE)
GetProcAddress
(GetModuleHandle(TEXT("Kernel32")), "FreeLibrary");
if (pfnThreadRtn == NULL) __leave;
// Create a remote thread that calls FreeLibrary()
hThread = CreateRemoteThread(hProcess, NULL, 0,
pfnThreadRtn, me.modBaseAddr, 0, NULL);
if (hThread == NULL) __leave;
// Wait for the remote thread to terminate
WaitForSingleObject(hThread, INFINITE);
bOk = TRUE; // Everything executed successfully
}
__finally
{
if (hthSnapshot != NULL)
CloseHandle(hthSnapshot);
if (hThread != NULL)
CloseHandle(hThread);
if (hProcess != NULL)
CloseHandle(hProcess);
}
return bOk;
}
在注入之前准备共享内存
好的,现在你已经了解了注入和弹出的基本知识。在注入代码之前,我们必须在共享内存中准备好数据。在我们的例子中,有3件事需要共享给DLL。
- 变量
- 值
- 用于指定是否添加到现有变量的标志
以下结构将帮助我们保存所需的信息
// Structure to share data
#pragma pack( push )
#pragma pack( 4 )
struct SharedData
{
BOOL bAddToExisting;
WCHAR strVariable[1024];
WCHAR strValue[1024];
SharedData()
{
ZeroMemory( strVariable, sizeof( strVariable ));
ZeroMemory( strValue, sizeof( strValue ));
bAddToExisting = TRUE;
}
};
为了使DLL和我们的注入应用程序中的结构大小相同,我们显式设置了打包值。否则,它可能会受到项目设置或pragma pack预处理器的影响。现在创建并将数据写入共享内存。为了保持一致性,我们始终使用UNICODE字符集。CDLLInjector::SetEnvironmentVariable
函数接受CString
值,这些值根据项目设置定义字符集。在内部,如果项目设置不是UNICODE,则函数会将ANSI字符串转换为UNICODE,并调用执行实际任务的UNICODE函数。
// Update the user entered data to sharememory
BOOL CDLLInjector::CreateAndCopyToShareMem
( LPCWSTR lpVarName, LPCWSTR lpVarVal, BOOL bAddToExisting )
{
SharedData stData;
int nLenVar = wcslen( lpVarName );
if ( 0 == nLenVar || nLenVar >= _countof( stData.strVariable ))
{
AfxMessageBox( _T("Variable length is too high.
Currently supports only 1024 chars" ));
return FALSE;
}
LPWSTR pBuf;
// prepare data for copying
wcscpy_s( stData.strVariable, _countof( stData.strVariable), lpVarName );
wcscpy_s( stData.strValue, _countof( stData.strValue), lpVarVal );
stData.bAddToExisting = bAddToExisting;
m_hMapFile = CreateFileMapping( INVALID_HANDLE_VALUE, NULL,
PAGE_READWRITE, 0, sizeof(stData),
SHAREMEM_NAME );
if ( m_hMapFile == NULL)
{
MessageBox(0, _T("Could not create file mapping object"),
_T("Error"), MB_OK | MB_ICONERROR );
return FALSE;
}
pBuf = (LPWSTR) MapViewOfFile( m_hMapFile, FILE_MAP_ALL_ACCESS,
0, 0, sizeof( stData ));
if ( pBuf == NULL)
{
MessageBox(0, _T("Could not map view of file"),
_T( "Error" ), MB_OK | MB_ICONERROR );
CloseHandle(m_hMapFile);
m_hMapFile = 0;
return FALSE;
}
// Copy the data
CopyMemory((PVOID)pBuf, &stData, sizeof( stData ));
UnmapViewOfFile(pBuf);
return TRUE;
}
注入到远程进程后会发生什么?
现在主应用程序已经完成。现在我们可以开始分析被注入的DLL。它只是打开共享内存,并将其读入我们之前讨论过的共享结构。请参阅代码。
// Function which reads and updates shared memory
BOOL UpdateEnvVar()
{
HANDLE hMapFile = 0;
SharedData* pShared = 0;
hMapFile = OpenFileMapping( FILE_MAP_READ,
FALSE, SHAREMEM_NAME ); // name of mapping object
if (hMapFile == NULL)
{
OutputDebugString(TEXT("Could not open file mapping object"));
return FALSE;
}
pShared = (SharedData*) MapViewOfFile(hMapFile, // handle to map object
FILE_MAP_READ, 0, 0, sizeof( SharedData ));
if (pShared == NULL)
{
OutputDebugString(TEXT("Could not map view of file"));
return FALSE;
}
if( !pShared->bAddToExisting )
{
if( wcslen( pShared->strValue ))
{
SetEnvironmentVariableW( pShared->strVariable,
pShared->strValue);
}
else
{
// Delete variable
SetEnvironmentVariableW( pShared->strVariable, NULL );
}
}
else
{
// Get the required size
const DWORD dwReturn = GetEnvironmentVariable
( pShared->strVariable, 0, 0 );
const DWORD dwErr = GetLastError();
if( 0 == dwReturn &&
ERROR_ENVVAR_NOT_FOUND == dwErr ) // Variable not found
{
// Set the new one
SetEnvironmentVariableW( pShared->strVariable,
pShared->strValue);
}
else if( dwReturn > 0 )
{
WCHAR* pstrExisting = new WCHAR[1024];
if( 0 == GetEnvironmentVariable( pShared->strVariable,
pstrExisting, dwReturn ) &&
GetLastError() == 0 )
{
std::wstring strNew( pstrExisting );
strNew += L";";
strNew += pShared->strValue;
SetEnvironmentVariableW
( pShared->strVariable, strNew.c_str());
}
}
}
if( pShared )
UnmapViewOfFile(pShared);
if( hMapFile )
CloseHandle(hMapFile);
return TRUE;
}
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
{
TCHAR buff[MAX_PATH] = { 0 };
_stprintf_s( buff, _T( "Attached Process: %d" ),
GetCurrentProcessId());
OutputDebugString( buff );
UpdateEnvVar();
}
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
限制
支持的最大变量长度为1024,值也为相同的字符数。
如何验证?
- 使用Process Explorer
- 我的另一个工具 - 读取进程环境变量
历史
- 2008年12月30日 - 初始版本