Archive for December, 2008

Article: Dynamically Add/Edit Environment variables of Remote process

screenshot

Introduction

In many situations, it’s necessary to set application specific environment variables for testing and debugging purposes. These kind of variables are exclusive to the application and it will be died when the application exits. For example, suppose if you’ve a rendering application in which the rendering path is specified as environment variable. Sometimes it might be necessary to change these variables for testing or in some situations you may need to add or delete the existing one. In most cases, the application specific variables are controlled launching through script files after setting up necessary variables or by a launcher application (Environment variables are inherited from Parent Process). Still dynamically updating variables is out of our reach. In this article I’m introducing a simple tool to dynamically set environment strings to any process in the system. This tool won’t modify any settings of the system or user environment variables. Instead it add/updates a particular process’s environment variables.

This tool supports the following operations

1. Add a new variable
2. Replace existing variable
3. Append to existing variable (separated by “;”)
4. Deleting a variable

How Do I? 

To Add/Replace a variable just select a process or enter a valid process ID in the corresponding text boxes. Then press “Set” button. If it’s required adding to an existing variable, please check “Add to existing”. To delete a variable enter a valid variable name and leave the value text box as empty. Make sure that you’ve unchecked the “Add to existing” check box. Under the hood OK let’s see how I accomplished this. It’s simple as follows

1. Create a shared memory with user entered content and options
2. Inject our own DLL to the target process
3. In the DLL main, open the share memory and read the required data and finally set the variable
4. Return from DLL.
5. Now the DLL is not necessary to remain in the process, eject it.

 

Why Injecting? 

Windows Provided APIs will be work with current process. These APIs can’t work with a foreign process. If a DLL is injected to a particular process, the further code will be executed in the context of that particular process.
 

How to Inject a DLL? 

There are several methods to inject a DLL. Please see Windows Via C/C++ (or its previous version) to know more about different DLL injection methods. Here in this program I’ve used Remote threads. I hope the following code can explain better than words. Anyway the basic technique is to set LoadLibrary as thread routine to load the DLL in remote process. The name of the DLL file must be accessible from target process itself. For that we’ve to use virtual memory APIs. See the code.
 

// 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);
	}
}

How to Eject an Injected DLL? 

Once you completed the task, it’s not good to make our DLL as a burden to the target process. Again we’re unloads the library in the same way we loaded. Creating Remote thread with FreeLibrary API and passing the module base address of SetEnvVarLIb.dll. For this we can use Toolhelp library APIs. If we create the module handle snapshot of the target process, it’d be able to get the base of address of our injected DLL. See the code below for more details.
 

// 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;
}

Preparing the shared memory before Inject 

OK now you learned about basics of Injecting and Ejecting. Before injecting the code, we’ve to prepare the data in a shared memory. In our case, we’ve 3 things to share to the DLL. 1. Variable 2. Value 3. Flag to specify add to existing or not. The following structure will help us to hold the required information

 

// 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;
	}
};

To keep the size of structure same in both DLL and our injecting application we’ve set the packing values explicitly. Otherwise it might be affected with the project settings or pragma pack pre-processor. Now create and write the data to shared memory. To keep it uniform, we’re always dealing with UNICODE character set. The CDLLInjector::SetEnvironmentVariable function accepts CString values which defines the character set according to the project settings. Internally the function converting ANSI string to UNICODE if the project’s settings is not UNICODE and calling the UNICODE functions which does the actual task. 

// 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;
}

What’s happening after Injecting to remote process? 

Now we’re done with the main application. Now we can start analyzing the DLL being injected. It simply opens the shared memory and read it into the shared structure which we discussed previously. See the code.

// 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;
}

Limitations 

The maximum length of variable is supported 1024 and same number of characters for value.
 

How to verify? 

Use process explorer tool to very it.
 

History

30-Dec-2008 – Initial Version


CreateProcess – Under the hood of command line and application name parameter

If you see the samples from MSDN, or some other sources which is using CreateProcess API, you might have noticed it’s not using the first parameter (pszApplicationName) to specify the application to launch. Instead, the information required to create the process is being specified in the second parameter pszCommandLine. Why it’s like that? Let’s take the following example which is used to create the process.

STARTUPINFO stInfo= {sizeof( stInfo )};
PROCESS_INFORMATION stPInfo;
TCHAR cmdLine[] = TEXT( "NOTEPAD");
if( CreateProcess( NULL, cmdLine, NULL, NULL, FALSE, NULL, NULL,
  	NULL,&stInfo, &stPInfo ))
{
  	_tprintf( TEXT( "Successfully Launched %s"), cmdLine );
}

Let’s dig into the working of command line parameter. By using the above code, you can spawn notepad application. If the first parameter (pszApplicationName) is NULL, CreateProcess API behaves as follows. First it examines and assumes that the first token in the string is the desired process to launch. If the executable file doesn’t have an extension, .exe extension will be applied as default. API will search for the executable file in the following order.

1. If the file name contains full path, it will try to load from the specified path.
2. Directory containing the .exe file of the calling process.
3. Current directory of the calling process ( Current directory can be different from file location)
4. System directory and Windows directory
5. Directories listed in PATH environment variable.

If you’re passing the application name in the first parameter (pszApplicationName) it should definitely contains the extension. It will not put an .exe extension as default. If the path specified is not a fully qualified path, the API will search only in the current directory but not in any other directories as specified earlier.

STARTUPINFO stInfo= {sizeof( stInfo )};
PROCESS_INFORMATION stPInfo;
TCHAR cmdLine[] = TEXT( "WORDPAD Readme.txt");
if( CreateProcess( TEXT( "C:\\Windows\\Notepad.exe" ), cmdLine, NULL, NULL, FALSE, NULL, NULL,
	NULL,&stInfo, &stPInfo ))
{
 	_tprintf( TEXT( "Successfully Launched %s"), cmdLine );
}

What if you specify valid strings in both Application Name and Command line parameters? The application will be launched with the specified exe name in the first parameter and the string in the command line parameters are passed as command line strings for the application. Note that, argv[0] doesn’t contains the application name in the above scenario.

Command line arguments will be argv[0] = “WORDPAD” and argv[1] = “Readme.txt”

The above code actually launches the ReadMe.txt file in Notepad.exe but if you pass the same parameters to your own application, it won’t load “ReadMe.txt” in Notepad. This means that, it’s purely application’s decision to interpret command line arguments. In the case of Notepad, it treats argv[0] as application’s name and the input for filename will be taken from Argv[1] if available. As per the POSIX standard, argv[0] should be equal to the application name. Most of the Windows Applications follows this behavior. (If you remove “WORDPAD” from the cmdLine string, “ReadMe.txt” will not be loaded).

Actually the purpose of Application Name parameter in CreateProcess API is to make it POSIX complaint. The POSIX standard for command line is having following format. Application Name, arg1, arg2, etc. (argv[0] contains application name)

Alternatively, you can pass the parameters to command line arguments as follows to meet the POSIX specification.

CreateProcess( _T( "c:\\MyApp.exe Param1 Param2"), NULL, ... );

  • Translate to yours!

  • Keep this running!

  • The People!

  • If I’ve proven, be connected

  • Recent Posts

  • Pages

  • Copyright © 1996-2010 Reflections of my thoughts..... All rights reserved.