65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.96/5 (18投票s)

2008年12月30日

CPOL

4分钟阅读

viewsIcon

70424

downloadIcon

2139

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

DynamicEnvVar_src

引言

在许多情况下,有必要为测试和调试目的设置应用程序特定的环境变量。这类变量是应用程序独有的,并在应用程序退出时失效。例如,假设你有一个渲染应用程序,其中渲染路径是通过环境变量指定的。有时可能需要更改这些变量进行测试,或者在某些情况下可能需要添加或删除现有变量。在大多数情况下,应用程序特定的变量是通过在设置好必要变量后通过脚本文件启动,或者通过一个启动器应用程序来控制(环境变量是从父进程继承的)。然而,动态更新变量仍然超出我们的能力范围。

在本文中,我将介绍一个简单的工具,可以动态地为系统中的任何进程设置环境变量。该工具不会修改系统的任何设置或用户环境变量。相反,它会添加/更新特定进程的环境变量。

该工具支持以下操作

  1. 添加新变量
  2. 替换现有变量
  3. 追加到现有变量(以“;”分隔)
  4. 删除变量

如何操作?

要添加/替换变量,只需在相应的文本框中选择一个进程或输入一个有效的进程ID。然后按“设置”按钮。如果要添加到现有变量,请勾选“添加到现有”。要删除变量,请输入有效的变量名,并将值文本框留空。确保取消勾选“添加到现有”复选框。在幕后,让我们看看我是如何实现的。很简单,如下所示:

  1. 创建具有用户输入内容和选项的共享内存
  2. 将我们自己的DLL注入到目标进程
  3. 在DLL主函数中,打开共享内存,读取所需数据,最后设置变量
  4. 从DLL返回
  5. 现在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。

  1. 变量
  2. 用于指定是否添加到现有变量的标志

以下结构将帮助我们保存所需的信息

// 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,值也为相同的字符数。

如何验证?

  1. 使用Process Explorer
  2. 我的另一个工具 - 读取进程环境变量

历史

  • 2008年12月30日 - 初始版本
© . All rights reserved.