Skip to content

Max Hirschberger and Ogulcan Ugur

sensepost.com • September 22, 2026

Process Parameter Poisoning (P 3 ) is an attack technique we developed that is used to inject code in foreign processes, without triggering typical detection mechanisms.

Its ability to fly under the radar has been tested against four market leading Endpoint Detection and Response (EDR) solutions. Code injection succeeded in all cases and no alerts were created, even though the EDRs were configured to detect, block and remediate.

Our implementation of the technique is published on GitHub:

After publication, X-C3LL showed the same primitive was previously presented by modexp in a now deleted post .

Typical Process Injection and Involved System APIs

Attackers want to make their activities look less suspicious. With process injection, attackers are able to perform their activities from a different process that is more trusted or expected to be performing the specific activity, thus lowering suspicion.

The following are typical steps for injecting code into another process:

The attacker searches for and opens a target process or starts a new process (via OpenProcess / NtOpenProcess or CreateProcess / NtCreateProcess ).

Memory for the malicious code is allocated in the target process (via VirtualAllocEx or NtAllocateVirtualMemory ).

The malicious code is written to the new allocation (via WriteProcessMemory / NtWriteVirtualMemory ).

Memory access protection is configured to allow executing the malicious code (via VirtualProtectEx / NtProtectVirtualMemory ).

A new thread is started in the target process that runs the malicious code (via CreateRemoteThread / NtCreateThreadEx ).

Further injection techniques include but are not limited to the following:

Thread Hijacking: Instead of creating a new thread, an existing one is redirected (via NtSetContextThread )

Early-Bird APC-Injection: Utilises Asynchronous Procedure Calls (APCs) to redirect the execution of an existing thread (via NtQueueApcThread)

Dirty Vanity: Abuses the Windows API RtlCreateProcessReflection , that implements process forking.

In our testing, we observed that most EDRs focus on specific telemetry to detect process injection. EDRs primarily monitor the usage of WriteProcessMemory and VirtualAllocEx , as well as their underlying kernel system calls NtWriteVirtualMemory , NtAllocateVirtualMemory and NtAllocateVirtualMemoryEx .

Based on this observation, we asked ourselves the following question: How can malicious code be transferred into a target process and made executable without using WriteProcessMemory and VirtualAllocEx ? The answer to this question lies in the process startup parameters that are transferred from one process to another at any new process creation. By using these to transfer malicious code, both WriteProcessMemory and VirtualAllocEx are avoided entirely, raising less suspicion.

Technical Foundation of Required Windows Internals

Process Creation API and Startup Paramters

Windows provides the API function CreateProcessW for creating new processes, shown in Listing 1. The first three of its parameters lpCommandLine , lpEnvironment and lpStartupInfo are relevant for the described injection technique, since they are used to transfer data to the new process.

Listing 1: Definition of CreateProcessW Windows API Function

The lpCommandLine parameter specifies the command line for the new process. It is limited to a maximum of 32,767 unicode characters, including the unicode null-terminator. For the unicode variant, it is necessary to provide a string that the function can write to. If a constant string is supplied, any write attempts made by the API function result in a memory access violation. If the value is NULL, the processes command line will be taken from the lpApplicationName parameter. If lpApplicationName is NULL, it has to be provided in the lpCommandLine field and is limited to MAX_PATH characters.

The lpEnvironment parameter provides a list of environment variables to the process. If the value is NULL, the environment of the creating process will be used. The list of environment variables consists of successive null-terminated strings of the format NAME=VALUE with another null-terminator at the end.

The lpStartupInfo parameter has a structure shown in Listing 2 with fields such as window station, desktop, standard input and output handles as well as fields that configure the main window of the new process. According to Microsoft documentation, the field lpReserved is reserved for internal use with no further documentation. Through analysis with the WinDbg debugger, it was possible to relate this parameter to the ShellInfo variable of the type UNICODE_STRING in the new process.

Listing 2: Layout of STARTUPINFOW data structure

The Process Environment Block

At the creation of a new process, all supplied process parameters are written to the Process Environment Block (PEB). The PEB is a data structure that is present in all processes and unique to each process. The parameters can be accessed within the ProcessParameters member of the type RTL_USER_PROCESS_PARAMETERS . Besides the process parameters, this structure also includes further runtime information, such as a list of loaded modules. The structure of the PEB and relevant process parameters in the RTL_USER_PROCESS_PARAMETERS is shown in Listing 3 and Listing 4.

Listing 3: Layout of PEB data structure

Listing 4: Layout of RTL_USER_PROCESS_PARAMETERS data structure

Figure 1 below shows the ShellInfo parameter with the poison value supplied in the lpReserved field for the STARTUPINFOW structure. Furthermore, Figure 2 shows the controlled command line within the System Informer tool.

Figure 1: Poisoned ShellInfo Parameter

Figure 2: Poisoned Command Line

Process Parameter Poisoning (P 3 )

Starting a Process with a Poisoned Parameter

Since there are multiple parameters that may be used to copy the malicious code, the wrapper function in Listing 5 below creates a process with the poison argument supplied to the chosen process parameter. Figure 3 demonstrates running the implemented injector, and choosing which parameter to poison. Furthermore, any value can be provided for the target application that will be used in lpApplication , with the user prompt shown in Figure 4.

Listing 5: Implementation of CreateProcessWithPoison

This function implements three distinct parameter choices:

ShellInfo Injection: Places the poison in the lpReserved field of the lpStartupInfo parameter that will be copied to the ShellInfo variable in the PEB

Environment Injection: Places the poison in the lpEnvironment parameter with the CREATE_UNICODE_ENVIRONMENT flag

CommandLine Injection: Places the poison in the lpCommandLine parameter of CreateProcessW

Figure 3: Selection of Poisonable Parameter

Figure 4: Selection of the Targeted Application Executable

Locating the Injected Data in the New Process

After successful process creation, the injected data can be found via the PEB structure. The following three steps are required to locate the poison in the new process.

First up, the starting address of the PEB structure is determined by calling NtQueryInformationProcess . NtQueryInformationProcess retrieves the data structure PROCESS_BASIC_INFORMATION when called with the information class ProcessBasicInformation . And within the structure PROCESS_BASIC_INFORMATION , the address of the PEB can be found in the PebBaseAddress field. This step is shown in Listing 6.

Listing 6: First Step of Locating the Injected Data

up, the PEB structure is read by calling NtReadVirtualMemoryEx with the starting address of the structure that was retrieved in the first step. The implementation of the second step is shown below in Listing 7.

Listing 7: Second Step of Locating the Injected Data

After reading the PEB, the field ProcessParameters contains the starting address of the RTL_USER_PROCESS_PARAMETERS structure in the new process. In the third step, this structure is also read from the new process. The pointers to the injected data are within this structure. The third step is shown below in Listing 8.

Extracted Entities

Attack Types (1)

Platforms (1)