Writing an Evasive .NET Shellcode Loader

slashcrypto1 pts0 comments

Writing an Evasive .NET Shellcode Loader | slashsec

The basics<br>Let's assume that you have the fanciest and stealthiest C2 shellcode that has ever existed. If it's running, every EDR is blind to it, and you can do whatever your heart desires. That's nice, but how do you get it running?<br>This is where a loader comes into play. Even if you have the best shellcode, your loader needs to carry it to execution just as stealthily as the commands executed by the shellcode. Otherwise, all the time you invested in the shellcode is wasted if your loader is detected by the EDR.<br>So, the most basic thing to do if you want to write a loader is to allocate some memory, copy the shellcode into said memory, mark it as executable, and call the memory address of the shellcode. After you compile your code, you'll get an executable file. Besides the pretty awful loading method (VirtualAlloc → memcpy → VirtualProtect → (void*)hMemory()), any competent EDR will start to scream and alert everyone because:<br>Your executable is probably not signed → big red flag<br>The file was never seen before anywhere (not even across EDR tenants; yes, EDR tenants most likely share this info internally, at least via file hashes) → small red flag<br>And other compiler-related red flags, which will be the topic of another post<br>All these red flags increase the likelihood that your executable will be flagged at least as suspicious, if not malicious.<br>So, how do we solve this conundrum? Well, most obviously, we don't use a self-compiled EXE, not unless we find ourselves in the unlikely position of having a valid Extended Validation code signing certificate.<br>Okay, so we can't use an EXE, but we still need to run our code, so we need some kind of executable, and that's how we get to dynamically linked libraries (DLLs for short). Although we can run a DLL directly via rundll32.exe, it's not recommended. The more elegant way is to let the DLL be loaded by a (hopefully) trusted, signed, and clean EXE. And this is the idea behind DLL Sideloading.<br>We look for a trusted executable that tries to load a (non-existent) DLL, and we place our DLL at the expected path with our loader in it. Although EDRs do see that a specific function was executed from within the DLL, they tend to care less about it, especially if the execution originates from a high-reputation EXE.<br>.NET<br>Okay, so can we do the same trick (placing a DLL at a specific path) and execute code in the context of a .NET (aka managed) binary? Well, no, or at least not as easily as in the case of unmanaged binaries. The main reasons are strong-name and Authenticode signing. dotSec has a two-part blog post (Part 1 and Part 2) about how these work and how this can still be bypassed in specific circumstances.<br>But there is an easier solution, and it's called AppDomain Manager Hijacking. In the .NET world, developers can define so-called AppDomains. At a high level, these are isolated environments within a managed process, and the AppDomain Manager, as the name suggests, manages what is loaded and executed where.<br>The nice thing about the AppDomain Manager is that, if configured, it pretty much halts the execution of the original assembly until it's done initializing the environment. This means that we don't have to analyze which functions are called from which DLL, and we don't have to wait for them to be called to run our malicious code.<br>The other main benefit of AppDomain Managers is that we can configure them for any compiled and signed EXE without modifying it. The only real requirement is that our malicious DLL is strong-name signed, but that only verifies that the DLL was not tampered with, not whether the DLL originates from a known publisher.<br>To define a custom AppDomain manager, we create a new class that extends the AppDomainManager class and override its InitializeNewDomain method.<br>public sealed class AppDomainManagerLoader: AppDomainManager {<br>public override void InitializeNewDomain(AppDomainSetup appDomainInfo) {<br>// TODO your loader logic comes here<br>Why even bother with .NET if I can just write a native loader?<br>The main reason is that some initial access methods require a .NET application, such as ClickOnce. The other reason is that if we'd like to use .NET-based tools through the C2 implant, the .NET framework has to be loaded. Loading the Common Language Runtime (CLR) into a native process might trigger detection by the EDR, as this is not expected behavior for a normal native process.<br>How to configure AppDomain Managers<br>There are two main ways to configure an AppDomain Manager: either via a configuration file or via environment variables. Upon starting a .NET EXE, the system looks for a config file with the name .config in the same directory. If we create this file with the following content, we can instruct the application to load our DLL and start initializing the AppDomain Manager.

At this point we are basically done: if we never return from the initialization, the EXE will wait indefinitely, and we...

loader shellcode appdomain executable file manager

Related Articles