Zerosalarium

EDR Evasion: Process Injection Without WriteProcessMemory


I. LEAD-IN

Throughout the course of a red-team engagement or a penetration test against a target, there’s a very good chance you’ll need to use remote process injection to execute your payloads. Because this technique is used so frequently, Endpoint Detection and Response (EDRs) monitor the APIs involved very closely.

In this article, I’ll introduce a new approach to remotely injecting code into a process, one that works without relying on the well-known WriteProcessMemory and VirtualAllocEx APIs.

While I was wrestling with a pile of EDR licenses, I discovered that two very capable researchers, Max Hirschberger and Ogulcan Ugur from SensePost.com, had independently come up with a similar idea and published an article on the subject. Their work also led me to research by modexp that explored a closely related approach. In fact, those researchers did a far better job than I did.

However, I wasn’t entirely satisfied with having to pause the process during initialization, or with the unusual formats required for lpCommandLine and lpEnvironment. So I set those approaches aside and moved on to a new injection technique, which I’ll present in the article below.

Find me on X to get the latest pentest and red team tricks that I’ve been researching: Two Seven One Three (@TwoSevenOneT) / X

II. CENTRAL PART

1. An Overview of Remote Process Injection Techniques

Process injection is a critical evasion and persistence technique where an attacker forces a legitimate, trusted Windows process to run arbitrary code on its behalf.

In a classic remote thread injection or PE injection workflow, the injector process must first acquire a handle to a target application (such as explorer.exe or svchost.exe) via OpenProcess and allocate a dedicated buffer within its virtual address space using VirtualAllocEx.

Once the memory is prepared, the attacker calls the WriteProcessMemory API to copy the malicious payload into the remote process’s memory space.

The attacker then uses CreateRemoteThread or another method to create a thread whose RIP points to the newly written memory region containing the shellcode.

Because this cross-process transition naturally bypasses conventional boundary defenses and inherits the host process’s access privileges, Endpoint Detection and Response (EDR) solutions closely monitor and scrutinize WriteProcessMemory through userland hooks and kernel callbacks.

EDR platforms treat cross-process memory modifications as high-severity telemetry events, prompting modern threat actors to continually seek evasive alternatives that can bypass traditional memory-manipulation signatures.

The general formula behind most remote injection techniques is:

[OpenProcess/CreateProcess] + [VirtualAllocEx] + [WriteProcessMemory] + [Some method of creating a thread whose RIP is redirected to the newly written shellcode]

2. Writing Arbitrary Payloads to a Remote Process Using Windows Named Pipes

When you open an interactive console program with a child process named conhost.exe and interact with it by entering commands, where is the content of those commands stored?

The answer: It’s stored somewhere in the program’s memory.

To demonstrate this, I’ll write a small program that creates a child process using CreateProcess and writes data to the child process’s hStdInput.

Code to create and write to console of process

I’ll use the console program nslookup.exe as an example:

name pipe std_in data in child process memory

When I call WriteFile on the child process’s hStdInput, the data being written is stored in the child process’s memory.

The idea, then, is to use the hStdInput named pipe to write a payload into another process instead of calling WriteProcessMemory on the child process.

However, a problem arises: the Windows console interface can display only a limited set of readable characters, while writing to a named pipe is essentially a call to WriteFile. In theory, this means we should be able to write bytes that cannot be displayed by the console to hStdInput. Let’s test this in practice. I have the following binary array:

raw payload to write to child process without using writeprocessmemory

After writing to the named pipe:

raw payload can write to child process without using writeprocessmemory

This confirms that we can write virtually any file we want into the memory of a console process.

3. Remote Process Injection via a Console Named Pipe

Based on the information above, injecting a payload into a remote process without using VirtualAllocEx and WriteProcessMemory requires the following main steps:

  1. Select an interactive console program. I found two programs: netsh.exe and nslookup.exe.
  2. Call CreateProcess and obtain a handle to the child process’s hStdInput.
  3. Write the payload to the child process by calling WriteFile on hStdInput.
  4. Locate the newly written payload in the child process’s memory.
  5. Use VirtualProtectEx to add Execute permissions to the newly identified memory region.
  6. Hijack a thread and redirect its RIP to that address.

When using WriteFile to write to hStdInput, the payload must avoid certain bad characters that have special meaning in the Windows console:

0x0D: Carriage Return (CR), a control character in ASCII and Unicode.

0x0A: Line Feed (LF), a control character.

0x1A: SUB (Substitute), generated by pressing Ctrl+Z and historically used as an End-of-File (EOF) marker.

When generating the payload, we must avoid these characters. Otherwise, the child process will interpret the payload as a command and execute it as a regular command. The result will be “Command not found” or something similar, and the original payload will no longer remain in the process’s memory.

To locate the newly written payload in the child process’s memory, I’ll place a sequence of distinctive characters at the beginning of the payload, which I’ll refer to as a marker. By searching for this marker, we can identify the payload’s location. When redirecting RIP, we need to add the marker’s size to the address where RIP will point:

RIP = marker_addr + sizeof(marker)

I created a proof of concept that performs the six steps above to remotely inject and execute shellcode, as shown below:

InjectSetConsole run success

InjectSetConsole run success process tree

Previous researchers tested this technique against several EDRs, so I won’t include my own test results here.

Demo Video: https://youtu.be/DCUnbj_usPM

4. Defense

Because this console named-pipe injection technique completely eliminates the use of the VirtualAllocEx and WriteProcessMemory APIs, monitoring should focus on the use of VirtualProtectEx against a remote process, along with read and write operations on the named pipe.

III. CLOSURE

Most remote injection techniques used in red-team engagements or penetration tests rely on the VirtualAllocEx and WriteProcessMemory API pair, so EDRs monitor them very closely.

Unlike traditional approaches, console named-pipe injection does not use VirtualAllocEx and WriteProcessMemory. Instead, it takes advantage of read and write operations through a named pipe, along with the way console programs store interactive commands in memory. In addition, this technique offers several other advantages:

  • It does not require CreateProcess to launch the process in a suspended state.
  • It does not cause the child process’s lpCommandLine or lpEnvironment to have unusual formatting.

The characters used in the payload or shellcode are subject to relatively few restrictions, meaning there are fewer bad characters to avoid.

As a result, traditional monitoring methods cannot be used to reliably detect and prevent this technique. Instead, defenders should focus on the memory region where the console program stores its commands, monitor the use of VirtualProtectEx, and track read and write operations on named pipes.

Author of the article: Two Seven One Three



Source link