INSIGHTS | May 14, 2025

Breaking Patterns: Rethinking Assumptions in Code Execution and Injection

Breaking Patterns

Security solutions like Endpoint Detection and Response (EDRs) and behavioural detection engines rely heavily on identifying known patterns of suspicious activity. For example, API calls executed in a specific order, such as VirtualAllocEx, WriteProcessMemory, and CreateRemoteThread are often indicators suspicious behaviour.

In this post, we’ll explore two techniques that break traditional patterns:

  • Self-Injection – Overwriting a method in your own process memory from within a process running a .NET Framework-managed executable.
  • Indirect DLL Path Injection – Exploiting the Windows GUI system to implant payloads in another process without direct memory writes.

Self-Injection – Code Execution Without a Loader

Traditional shellcode loaders follow a well-trodden API sequence:

VirtualAlloc → Write shellcode → Change memory protection → Execute

In a .NET application, methods are not immediately compiled to native code. Instead, the JIT compiler translates Common Intermediate Language (CIL) into native machine code the first time a method is invoked. This native code is stored in memory regions allocated on the heap, which are already marked as executable (and often writable, as well). This characteristic makes the JIT-generated method bodies ideal targets for self-modifying behavior.

To determine the access permissions of the memory region holding the compiled code, we inspected the address where the function pointer of the JIT-compiled method points across various .NET runtime environments. Specifically, in .NET 8 (net8.0) and .NET 7 (net7.0), the memory was marked as read and execute (RX), reflecting modern security practices that avoid writable executable memory. However, in .NET 6 (net6.0), .NET 5 (net5.0), .NET Core 3.1 (netcoreapp3.1), and .NET Framework 4.8.1, the memory was marked as read, write, and execute (RWX), indicating a more permissive configuration. This comparison highlights a trend toward stricter memory protections in newer versions of .NET.

Runtime VersionMemory Access Permissions
.NET Framework 4.8.1RWX
.NET Core 3.1RWX
.NET 5RWX
.NET 6RWX
.NET 7RX
.NET 8RX

To exploit this, a method (such as a placeholder Test method) is compiled by the JIT, and its function pointer is retrieved via reflection. The shellcode is decrypted and copied directly over the compiled method using Marshal.Copy. When the method is called, the process executes the injected shellcode instead of the original .NET instructions.

The following screenshot shows an example of what this exploitation might look like in a program.

This program screenshot shows how an attacker would execute the shellcode when calling the Test() method. First, we force compilation of the Test() method. Then we retrieve the shellcode and use it to overwrite the compiled method. At the end, when Test() is called, the shellcode is executed instead of the Test method.

Note that in this example, there is no need to call VirtualAlloc or VirtualProtect, nor modify memory protection attributes. The memory containing JIT-compiled methods is already suitable for code execution, and writing into it directly bypasses many of the behavioral IOCs typically monitored by security tools.

This technique takes advantage of the .NET runtime’s own execution model, operating entirely within expected memory regions and avoiding any calls that would raise suspicion in traditional endpoint detection systems. While not impervious to all detection, it highlights how abusing trusted execution paths can erode the reliability of conventional heuristics.

Indirect DLL Path Injection

While this post will demonstrate the following technique using window renaming, the core idea is not tied to that mechanism—any method that causes a controllable string to be stored in another process’s memory can be leveraged similarly.

The Trouble with Traditional Process Injection

Process injection is a well-known tactic in the offensive security arsenal, often used by malware and red teamers alike to execute code in the context of another process. One of the most common forms is DLL injection using the following steps:

  1. Allocate memory in the remote process with VirtualAllocEx.
  2. Write the DLL path into the allocated memory with WriteProcessMemory.
  3. Call CreateRemoteThread to run LoadLibrary.

However, these steps produce well-known Indicators of Compromise (IOCs):

  • Use of VirtualAllocEx and WriteProcessMemory on a remote process
  • API call patterns: OpenProcess → VirtualAllocEx → WriteProcessMemory → CreateRemoteThread

Window Renaming as a Covert Channel

To avoid these behavioral signatures, we can use an alternative that removes the need for explicit memory allocation or memory writing in the remote process.

The Core Idea

Instead of using VirtualAllocEx and WriteProcessMemory, we leverage existing window handles owned by the target process.

Step-by-Step Logic

  1. Enumerate Windows: Use EnumWindows to find window handles belonging to the target process.
  2. Rename a Window: Modify the window’s title. The new title will be our DLL path.
  3. Memory Side Effect: When the title is set, Windows internally stores this string in the process’s address space—effectively writing our DLL path without WriteProcessMemory.
  4. Scan Process Memory: Read the remote process memory to find our DLL path string using ReadProcessMemory. The title of the window should be easy to identify but this will not be the case if a different indirect method is used.
  5. Invoke LoadLibrary: Use CreateRemoteThread to execute LoadLibraryA with the found address.

Benefits of the indirect injection technique:

  • No VirtualAllocEx – we’re not allocating memory in the remote process.
  • No WriteProcessMemory- we’re not directly writing to remote memory.
  • No unusual memory protection flags or RWX pages.
  • The only “invasive” step is CreateRemoteThread, but even that can be replaced with more stealthy execution methods (e.g., APC, thread execution hijacking, etc.).
  • The window renaming APIs (SetWindowText, SetClassLongPtr) are rarely monitored by EDRs.
  • Looks more like GUI interaction than code injection.

Conclusion

This injection method sidesteps common behavioral IOCs by co-opting the windowing system as a covert memory channel. While not bulletproof, it challenges the assumptions many EDRs rely on and demonstrates how creative thinking can yield new evasion paths.