> 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/quickstart.md).

# Malware Obfuscation (XOR)

> *<mark style="color:$info;">Obfuscation means Making something harder to understand or confusing</mark>*

{% 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 %}

### **Introduction**

This is the first post of a series which regards development of malicious software. In this series we will explore and try to implement multiple techniques used by malicious applications to execute code, hide from defenses and persist.

Let’s create a C++ application that will run malicious shellcode while trying to not be caught by AV software.

Why C++ and not C# or PowerShell script? Because it’s much more difficult to analyze compiled binary when compared to managed code or script.

The proof of concept code for this can be found along the blog series and will also add on github. If you like my project and post, please feel free to give the repo a star! :)

Today i am writing this complete blog on XOR obfuscation. I will start from the basic meaning, then cover many different types with proper explanation for each one and then go deep into the Multiple techniques used. I will give clear working examples for each and also explain exactly how to detect them in real samples, here everything explained step by step in simple words like i talk to my friends. No short cuts, everything expanded.

### **What Does Obfuscation Mean?**

<figure><img src="/files/fv3ZcS0GBCk4b1IaDOQW" alt="" width="320"><figcaption></figcaption></figure>

Obfuscation is basically the art of making code or data look messy and hard to understand on purpose. In normal programming we write clean code so that other developers can read it easily. But in the malware world or when someone wants to protect their secret stuff, they deliberately turn that clean code into something that looks like garbage. The real logic is still there but hidden behind layers so that antivirus engines, security researchers or even other hackers cannot figure out what is happening just by looking at the binary.

Think of it like this – you have a secret message “download malware from this link”. Instead of keeping it as plain text, you scramble it so that in the exe file it shows only random characters and weird symbols. The program itself knows how to unscramble it at runtime and use the real message. This way signature based antivirus fails because there is no clear string to match. Developers also use obfuscation sometimes to hide API keys or license checks in their commercial software. But 90 percent of the time when you see heavy obfuscation in a binary, it is malware trying to stay alive longer. The main goals are always evasion, size reduction and making analysis take more time.

### **How XOR Obfuscation Works:**

XOR is the king of simple encryption obfuscation. It is a bitwise operation where each byte of data is XORed with a key. The beauty is that doing the same operation again with the same key brings back the original data because A XOR B XOR B equals A again. Malware writers love it because it needs almost zero code, runs super fast, and no external libraries are required.

Usually they use a single byte key like 0xAA or 0x55 but sometimes they make it rolling – the key changes after every byte using addition or multiplication. You will see a small loop in the binary that takes a buffer and does XOR on each byte. In many ransomware samples the config or the C2 address is hidden with XOR.

The code snippet below shows a basic XOR string obfustication in C++:

```cpp
#include <iostream>
#include <windows.h>
#include <vector>
#include <iomanip>

using byte = uint8_t;

void xoronekey(std::vector<uint8_t>& data, uint8_t key) { // Single Key (BYTE) encoding 
	for (size_t i = 0; i < data.size(); i++) {
		data[i] ^= key;
	}
}

void printHex_payload(std::vector<byte>& pPayload) { // Just to print in Hex format
    for (std::size_t i = 0; i < pPayload.size(); i+=16) {
        std::cout << std::hex << std::setfill('0') << std::setw(8) << i << " ";
        for (std::size_t j = 0; j < 16 && (i + j) < pPayload.size(); ++j) {
            std::cout << std::hex << std::setfill('0') << std::setw(2) << static_cast<int>(pPayload[i + j]) << " ";
        }
        std::cout << "\n";
    }
    std::cout << std::dec << std::setfill(' ') << "\n";
}

int main() {

	const std::string msg = "Hello, havox here !!";
	std::vector<uint8_t> pPayload(msg.begin(), msg.end());
	
	std::cout << "[*] Original string: " << msg << std::endl;

	// Single Byte KEY
	uint8_t key = 0x33;
	std::cout << "[*] Original (Plain) in Bytes" << std::endl;
	printHex_payload(pPayload);

	// XOR ENCODING happens using the key definded
	std::cout << "[*] After XOR Encoding (key = 0x33) (Hex Dump): " << std::endl;
	xoronekey(pPayload, key);
	printHex_payload(pPayload);
	std::string Encoded_dec(reinterpret_cast<char*>(pPayload.data()));
	// Prints the String to knowhow that look like after encoding
	std::cout << "[*] String After encode (Char): " << Encoded_dec << "\n" << std::endl;

	// Uses the same function to decode the Encoded Hex Dump
	std::cout << "[*] After XOR Decoding (back to original) (Hex Dump) " << std::endl;
	xoronekey(pPayload, key);
	printHex_payload(pPayload);

	// converting bytes into char using reinterpret_cast to cast the type
	std::string decoded(reinterpret_cast<char*>(pPayload.data()),pPayload.size());  
	std::cout << "[*] Decoded string (Char): " << decoded << "\n";

	return 0;
}	
```

the above code is the simple string obfuscation code done using XOR, which gives you a idea how XOR obfuscation works,&#x20;

and the output look like something like this&#x20;

<figure><img src="/files/3QjDYIC1oWzER0AHsHMN" alt=""><figcaption></figcaption></figure>

sorry for the scribbles, due to MS paint : ), here you gonna see, every byte of the original string is XORed with the same fixed key value (e.g., 0x33). Because XOR is its own inverse, applying the exact same operation again with the same key instantly recovers the original plaintext making it perfect for both encryption and decryption using a single function. In the example below, we take a clear-text message (“Hello, havox here !!”), convert it to a byte vector, XOR each byte with the key, and then XOR it again to demonstrate recovery.

ok, i got you inner voice where can we use this methods and how this piece evade the AV/EDR right, yes now we see how this actually working on payloads, this XOR itself has multiple techniques within it which like the Ramp of increases in complexity of the obfuscation that make hard to understand, this Obfuscation is not only limited piece of payload (code) also applicable to encode whole executable.....

### **Method 1: Single-Byte XOR**

> <mark style="color:purple;">Note :</mark> Before getting in, All of the techniques shown here are commonly used to **obfuscate payloads or encode data (strings, shellcode, or even entire executables)** so the content is harder to inspect and understand. The encoded content is later **loaded into a decoder/de-obfuscation routine or same function** to restore it back to its original form at runtime. Once the payload is back in its real form, it can be executed using different techniques. In many cases, it’s run as **shellcode**, and in more advanced cases it may be launched through methods like **process injection** or **process hollowing**.

XOR (exclusive OR) is a bitwise operation. For each bit:

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

The spooky part? XOR is its own inverse. If you XOR a byte with a key once → encrypted. Do it again with the same key → back to original. No extra decryption logic needed – malware just runs the same loop twice.

The code snippet below shows a basic Single-Byte XOR obfuscation in C++:

```c++
#include <iostream>
#include <iomanip>
#include <vector>
#include <cstdint>  // for uint8_t / byte

using byte = uint8_t;

// Template for simple output (your SMEout)
template <typename T>
void SMEout(const T& PrintIt) {
    std::cout << PrintIt << std::endl;
}

const std::vector<byte> pPayload = {
    0xfc,0x48,0x81,0xe4,0xf0,0xff,0xff,0xff,0xe8,0xd0,0x00,0x00,0x00,0x41,0x51,0x41,0x50,0x52,0x51,
    0x56,0x48,0x31,0xd2,0x65,0x48,0x8b,0x52,0x60,0x3e,0x48,0x8b,0x52,0x18,0x3e,0x48,0x8b,0x52,0x20,
    0x3e,0x48,0x8b,0x72,0x50,0x3e,0x48,0x0f,0xb7,0x4a,0x4a,0x4d,0x31,0xc9,0x48,0x31,0xc0,0xac,0x3c,
    0x61,0x7c,0x02,0x2c,0x20,0x41,0xc1,0xc9,0x0d,0x41,0x01,0xc1,0xe2,0xed,0x52,0x41,0x51,0x3e,0x48,
    0x8b,0x52,0x20,0x3e,0x8b,0x42,0x3c,0x48,0x01,0xd0,0x3e,0x8b,0x80,0x88,0x00,0x00,0x00,0x48,0x85,
    0xc0,0x74,0x6f,0x48,0x01,0xd0,0x50,0x3e,0x8b,0x48,0x18,0x3e,0x44,0x8b,0x40,0x20,0x49,0x01,0xd0,
    0xe3,0x5c,0x48,0xff,0xc9,0x3e,0x41,0x8b,0x34,0x88,0x48,0x01,0xd6,0x4d,0x31,0xc9,0x48,0x31,0xc0,
    0xac,0x41,0xc1,0xc9,0x0d,0x41,0x01,0xc1,0x38,0xe0,0x75,0xf1,0x3e,0x4c,0x03,0x4c,0x24,0x08,0x45,
    0x39,0xd1,0x75,0xd6,0x58,0x3e,0x44,0x8b,0x40,0x24,0x49,0x01,0xd0,0x66,0x3e,0x41,0x8b,0x0c,0x48,
    0x3e,0x44,0x8b,0x40,0x1c,0x49,0x01,0xd0,0x3e,0x41,0x8b,0x04,0x88,0x48,0x01,0xd0,0x41,0x58,0x41,
    0x58,0x5e,0x59,0x5a,0x41,0x58,0x41,0x59,0x41,0x5a,0x48,0x83,0xec,0x20,0x41,0x52,0xff,0xe0,0x58,
    0x41,0x59,0x5a,0x3e,0x48,0x8b,0x12,0xe9,0x49,0xff,0xff,0xff,0x5d,0x3e,0x48,0x8d,0x8d,0x30,0x01,
    0x00,0x00,0x41,0xba,0x4c,0x77,0x26,0x07,0xff,0xd5,0x49,0xc7,0xc1,0x00,0x00,0x00,0x00,0x3e,0x48,
    0x8d,0x95,0x0e,0x01,0x00,0x00,0x3e,0x4c,0x8d,0x85,0x24,0x01,0x00,0x00,0x48,0x31,0xc9,0x41,0xba,
    0x45,0x83,0x56,0x07,0xff,0xd5,0x48,0x31,0xc9,0x41,0xba,0xf0,0xb5,0xa2,0x56,0xff,0xd5,0x48,0x65,
    0x79,0x20,0x6d,0x61,0x6e,0x2e,0x20,0x49,0x74,0x73,0x20,0x6d,0x65,0x20,0x48,0x61,0x76,0x6f,0x78,
    0x00,0x6d,0x65,0x77,0x6f,0x6f,0x20,0x20,0x20,0x20,0x20,0x20,0x00,0x75,0x73,0x65,0x72,0x33,0x32,
    0x2e,0x64,0x6c,0x6c,0x00
}; 

void printHex_payload(const std::vector<byte>& payload, const std::string& label) {
    std::cout << "[*] " << label << " (" << payload.size() << " bytes)\n";
    std::cout << "[*] Payload base address in memory: 0x"
        << std::hex << std::uppercase << std::setfill('0') << std::setw(16)
        << reinterpret_cast<uintptr_t>(payload.data())
        << std::endl;

    for (std::size_t i = 0; i < payload.size(); i += 32) {
        std::cout << std::hex << std::setfill('0') << std::setw(8) << i << "  ";
        for (std::size_t j = 0; j < 32 && (i + j) < payload.size(); ++j) {
            std::cout << std::hex << std::setfill('0') << std::setw(2)
                << static_cast<int>(payload[i + j]) << " ";
        }
        std::cout << "\n";
    }
    std::cout << std::dec << std::setfill(' ') << "\n";
}

void xorSingleByte(std::vector<byte>& data, byte key) {
    for (std::size_t i = 0; i < data.size(); ++i) {
        data[i] ^= key; // here the XOR happens 
    }
}

int main() {
    std::cout << "[*] Starting XOR Single-Byte Obfuscation\n";
    std::cout << "[*] Original const payload (read-only reference)\n";

    // Print original
    printHex_payload(pPayload, "Original Payload (Plain)");

    std::cout << "[*] Creating working copy of payload for modification\n";
    auto payload = pPayload;

    std::cout << "[*] Applying single-byte XOR encryption (key = 0x55)\n";
    byte key = 0x55;
    xorSingleByte(payload, key);

    printHex_payload(payload, "After XOR Single-Byte Encoding");

    std::cout << "[*] Applying same XOR again to decrypt (XOR is symmetric)\n";
    xorSingleByte(payload, key);

    printHex_payload(payload, "After XOR Single-Byte Decoding (back to original)");

    std::cout << "[*] complete - payload successfully obfuscated and restored\n";
    return 0;
}
```

Let's zoom in on just these bytes – the start of shellcode + the end where the clear string lives:

**Original bytes (hex):** fc 48 81 e4 f0 ff ff ff ... (shellcode start) ... 48 65 79 20 6d 61 6e 2e 20 49 74 73 20 6d 65 20 48 61 76 6f 78 00

(That's "Hey man. Its me Havox" followed by a null terminator – the kind of giveaway string malware wants to hide.)

i used often 0x55 (binary 01010101) as a key because it flips a lot of bits and makes the output look very random. Key = 0x55

here the part how encoding happens For every byte in the original payload: <mark style="color:$danger;">**encrypted\_byte = original\_byte XOR 0x55**</mark>

the output something looks like (Here this is the sample Payload example):

```
[*] Starting XOR Single-Byte Obfuscation
[*] Original const payload (read-only reference)
[*] Original Payload (Plain) (328 bytes)
[*] Payload base address in memory: 0x000001E0136BE170

00000000  FC 48 81 E4 F0 FF FF FF E8 D0 00 00 00 41 51 41 50 52 51 56 48 31 D2 65 48 8B 52 60 3E 48 8B 52
00000020  18 3E 48 8B 52 20 3E 48 8B 72 50 3E 48 0F B7 4A 4A 4D 31 C9 48 31 C0 AC 3C 61 7C 02 2C 20 41 C
00000060  74 6F 48 01 D0 50 3E 8B 48 18 3E 44 8B 40 20 49 01 D0 E3 5C 48 FF C9 3E 41 8B 34 88 48 01 D6 4D
00000080  31 C9 48 31 C0 AC 41 C1 C9 0D 41 01 C1 38 E0 75 F1 3E 4C 03 4C 24 08 45 39 D1 75 D6 58 3E 44 8B
000000C0  59 5A 41 58 41 59 41 5A 48 83 EC 20 41 52 FF E0 58 41 59 5A 3E 48 8B 12 E9 49 FF FF FF 5D 3E 48
00000100  85 24 01 00 00 48 31 C9 41 BA 45 83 56 07 FF D5 48 31 C9 41 BA F0 B5 A2 56 FF D5 48 65 79 20 6D
00000140  72 33 32 2E 64 6C 6C 00

[*] Creating working copy of payload for modification
[*] Applying single-byte XOR encryption (key = 0x55)
[*] After XOR Single-Byte Encoding (328 bytes)
[*] Payload base address in memory: 0x000001E0136C35B0

00000000  A9 1D D4 B1 A5 AA AA AA BD 85 55 55 55 14 04 14 05 07 04 03 1D 64 87 30 1D DE 07 35 6B 1D DE 07
00000020  4D 6B 1D DE 07 75 6B 1D DE 27 05 6B 1D 5A E2 1F 1F 18 64 9C 1D 64 95 F9 69 34 29 57 79 75 14 94
00000040  9C 58 14 54 94 B7 B8 07 14 04 6B 1D DE 07 75 6B DE 17 69 1D 54 85 6B DE D5 DD 55 55 55 1D D0 95
00000060  21 3A 1D 54 85 05 6B DE 1D 4D 6B 11 DE 15 75 1C 54 85 B6 09 1D AA 9C 6B 14 DE 61 DD 1D 54 83 18
00000080  64 9C 1D 64 95 F9 14 94 9C 58 14 54 94 6D B5 20 A4 6B 19 56 19 71 5D 10 6C 84 20 83 0D 6B 11 DE
000000E0  D8 D8 65 54 55 55 14 EF 19 22 73 52 AA 80 1C 92 94 55 55 55 55 6B 1D D8 C0 5B 54 55 55 6B 19 D8
00000100  D0 71 54 55 55 1D 64 9C 14 EF 10 D6 03 52 AA 80 1D 64 9C 14 EF A5 E0 F7 03 AA 80 1D 30 2C 75 38
00000120  34 3B 7B 75 1C 21 26 75 38 30 75 1D 34 23 3A 2D 55 38 30 22 3A 3A 75 75 75 75 75 75 55 20 26 30
00000140  27 66 67 7B 31 39 39 55

```

Let's do the first few bytes manually:

* Original: <mark style="color:$warning;">fc</mark> (11111100 in binary) -> <mark style="color:$success;">fc XOR 55</mark> = 11111100 XOR 01010101 = 10101001 → <mark style="color:$warning;">**a9**</mark>
* Original: <mark style="color:$warning;">48</mark> (01001000) -> <mark style="color:$success;">48 XOR 55</mark> = 01001000 XOR 01010101 = 00011101 → <mark style="color:$warning;">**1d**</mark>
* Original: <mark style="color:$warning;">81</mark> (10000001) -> <mark style="color:$success;">81 XOR 55</mark> = 10000001 XOR 01010101 = 11010100 → <mark style="color:$warning;">**d4**</mark>
* Original: <mark style="color:$warning;">e4</mark> (11100100)-> <mark style="color:$success;">e4 XOR 55</mark> = 11100100 XOR 01010101 = 10110001 → <mark style="color:$warning;">**b1**</mark>

Now the readable string part – watch how "Hey man..." disappears:

* 'H' = 48 → 48 XOR 55 = **1d**
* 'e' = 65 → 65 XOR 55 = **30**
* 'y' = 79 → 79 XOR 55 = **2c**
* ' ' (space) = 20 → 20 XOR 55 = **75**
* 'm' = 6d → 6d XOR 55 = **38**
* 'a' = 61 → 61 XOR 55 = **34**
* 'n' = 6e → 6e XOR 55 = **3b**
* '.' = 2e → 2e XOR 55 = **7b**

After single-byte XOR with 0x55, that nice readable "Hey man. Its me Havox" becomes complete garbage like: **1d 30 2c 75 38 34 3b 7b ...**

No more strings.exe hits. No AV signature matches on "Havox" or "user32.dll". The whole thing looks like random data.In real malware this loop is written in assembly with just a few instructions and runs on the entire .data section or a specific buffer.

and by passing the encoded paylaod into the same function

```objective-cpp
void xorSingleByte(std::vector<byte>& data, byte key) {
    for (std::size_t i = 0; i < data.size(); ++i) {
        data[i] ^= key; // here the XOR happens 
    }
}
```

to reverse back to original form where the attackers (like we : ) ) load & execute in memory, cool part is EDR/AV look this as Chunk but it clearly evades those systems..

the reversed Payload will be same as original

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

### **Method 2: Rolling Single-Byte XOR**

We're back with the next level of XOR obfuscation – **Rolling Single-Byte XOR** (also called rolling key or incremental XOR).

This is the upgrade most malware authors make when plain single-byte XOR feels too easy to crack. Instead of using the exact same key for every single byte (which tools brute-force in milliseconds), they make the effective key change slightly for each position in the payload. The code stays tiny, but now brute forcing all possibilities become way harder because there are no longer just 256 keys to try the pattern is dynamic.

The code snippet below shows a basic Rolling-Byte XOR obfuscation in C++:

```c++
#include <iostream>
#include <iomanip>
#include <vector>
#include <cstdint>  // for uint8_t / byte

using byte = uint8_t;

// Your simple output helper
template <typename T>
void SMEout(const T& PrintIt) {
    std::cout << PrintIt << std::endl;
}

const std::vector<byte> pPayload = {
    0xfc,0x48,0x81,0xe4,0xf0,0xff,0xff,0xff,0xe8,0xd0,0x00,0x00,0x00,0x41,0x51,0x41,0x50,0x52,0x51,
    0x56,0x48,0x31,0xd2,0x65,0x48,0x8b,0x52,0x60,0x3e,0x48,0x8b,0x52,0x18,0x3e,0x48,0x8b,0x52,0x20,
    0x3e,0x48,0x8b,0x72,0x50,0x3e,0x48,0x0f,0xb7,0x4a,0x4a,0x4d,0x31,0xc9,0x48,0x31,0xc0,0xac,0x3c,
    0x61,0x7c,0x02,0x2c,0x20,0x41,0xc1,0xc9,0x0d,0x41,0x01,0xc1,0xe2,0xed,0x52,0x41,0x51,0x3e,0x48,
    0x8b,0x52,0x20,0x3e,0x8b,0x42,0x3c,0x48,0x01,0xd0,0x3e,0x8b,0x80,0x88,0x00,0x00,0x00,0x48,0x85,
    0xc0,0x74,0x6f,0x48,0x01,0xd0,0x50,0x3e,0x8b,0x48,0x18,0x3e,0x44,0x8b,0x40,0x20,0x49,0x01,0xd0,
    0xe3,0x5c,0x48,0xff,0xc9,0x3e,0x41,0x8b,0x34,0x88,0x48,0x01,0xd6,0x4d,0x31,0xc9,0x48,0x31,0xc0,
    0xac,0x41,0xc1,0xc9,0x0d,0x41,0x01,0xc1,0x38,0xe0,0x75,0xf1,0x3e,0x4c,0x03,0x4c,0x24,0x08,0x45,
    0x39,0xd1,0x75,0xd6,0x58,0x3e,0x44,0x8b,0x40,0x24,0x49,0x01,0xd0,0x66,0x3e,0x41,0x8b,0x0c,0x48,
    0x3e,0x44,0x8b,0x40,0x1c,0x49,0x01,0xd0,0x3e,0x41,0x8b,0x04,0x88,0x48,0x01,0xd0,0x41,0x58,0x41,
    0x58,0x5e,0x59,0x5a,0x41,0x58,0x41,0x59,0x41,0x5a,0x48,0x83,0xec,0x20,0x41,0x52,0xff,0xe0,0x58,
    0x41,0x59,0x5a,0x3e,0x48,0x8b,0x12,0xe9,0x49,0xff,0xff,0xff,0x5d,0x3e,0x48,0x8d,0x8d,0x30,0x01,
    0x00,0x00,0x41,0xba,0x4c,0x77,0x26,0x07,0xff,0xd5,0x49,0xc7,0xc1,0x00,0x00,0x00,0x00,0x3e,0x48,
    0x8d,0x95,0x0e,0x01,0x00,0x00,0x3e,0x4c,0x8d,0x85,0x24,0x01,0x00,0x00,0x48,0x31,0xc9,0x41,0xba,
    0x45,0x83,0x56,0x07,0xff,0xd5,0x48,0x31,0xc9,0x41,0xba,0xf0,0xb5,0xa2,0x56,0xff,0xd5,0x48,0x65,
    0x79,0x20,0x6d,0x61,0x6e,0x2e,0x20,0x49,0x74,0x73,0x20,0x6d,0x65,0x20,0x48,0x61,0x76,0x6f,0x78,
    0x00,0x6d,0x65,0x77,0x6f,0x6f,0x20,0x20,0x20,0x20,0x20,0x20,0x00,0x75,0x73,0x65,0x72,0x33,0x32,
    0x2e,0x64,0x6c,0x6c,0x00
}; 

// Updated print function (32 bytes per line + nice debug style)
void printHex_payload(const std::vector<byte>& payload, const std::string& label) {
    std::cout << "[*] " << label << " (" << payload.size() << " bytes)\n";
    std::cout << "[*] Payload base address: 0x"
              << std::hex << std::uppercase << std::setfill('0') << std::setw(16)
              << reinterpret_cast<uintptr_t>(payload.data()) << "\n";

    for (std::size_t i = 0; i < payload.size(); i += 32) {
        std::cout << std::hex << std::setfill('0') << std::setw(8) << i << "  ";
        for (std::size_t j = 0; j < 32 && (i + j) < payload.size(); ++j) {
            std::cout << std::hex << std::setfill('0') << std::setw(2)
                      << static_cast<int>(payload[i + j]) << " ";
        }
        std::cout << "\n";
    }
    std::cout << std::dec << std::setfill(' ') << "\n";
}

// Rolling Byte XOR (key + position/index)
void xorRollingByte(std::vector<byte>& data, byte baseKey) {
    for (std::size_t i = 0; i < data.size(); ++i) {
        data[i] ^= static_cast<byte>(baseKey + i);
    }
}

int main() {
    std::cout << "[*] Starting Rolling Byte XOR Demo\n";
    std::cout << "[*] Original shellcode (const reference)\n";

    // Show original
    printHex_payload(pPayload, "Original Payload (Plain)");

    std::cout << "[*] Creating writable copy of payload\n";
    auto payload = pPayload;

    std::cout << "[*] Applying Rolling Byte XOR (base key = 0x55)\n";
    byte key = 0x55;
    xorRollingByte(payload, key);

    printHex_payload(payload, "After Rolling Byte XOR Encoding");

    std::cout << "[*] Applying same rolling XOR again to decrypt (symmetric)\n";
    xorRollingByte(payload, key);

    printHex_payload(payload, "After Rolling Byte XOR Decoding (restored)");

    std::cout << "[*] Demo complete\n";
    return 0;
}
```

This works same as Single-Byte XOR works but this function changes everything:

```cpp
void xorRollingByte(std::vector<BYTE>& data, BYTE key) {
    for (std::size_t i = 0, j = 0; i < data.size(); ++i, ++j) {
        data[i] ^= static_cast<byte>(key + i);
    }
}
```

`The magic line is:`\
`data[i] ^= static_cast(key + i);`

Here, key is the starting/base key (e.g. 0x55), and i is the current byte position (0, 1, 2, 3...). So the effective key for each byte is base\_key + position\_index.

Let's use the same small snippet from before (start of shellcode + the readable "Hey man. Its me Havox" string) and apply rolling XOR with base key **0x55**.

**Original bytes (first few + string part):** fc 48 81 e4 ... 48 65 79 20 6d 61 6e 2e 20 49 74 73 20 6d 65 20 48 61 76 6f 78 00

**Encryption loop in action (key starts at 0x55, adds i each time):**

* Position
* <mark style="color:blue;">i=0</mark>: effective key = <mark style="color:blue;">0x55 + 0</mark> = <mark style="color:blue;">0x55  -></mark> <mark style="color:$warning;">fc</mark> <mark style="color:$danger;">XOR</mark> 55 = <mark style="color:$warning;">**a9**</mark> (same as single-byte)
* <mark style="color:blue;">i=1</mark>: effective key = <mark style="color:blue;">0x55 + 1</mark>  = <mark style="color:blue;">0x56  -></mark><mark style="color:$warning;">48</mark> <mark style="color:$danger;">XOR</mark> 56 = 01001000 XOR 01010110 = **00011110** → <mark style="color:$warning;">**1e**</mark>
* <mark style="color:blue;">i=2</mark>: effective key = <mark style="color:blue;">0x55 + 2</mark> = <mark style="color:blue;">0x57  -></mark> <mark style="color:$warning;">81</mark> <mark style="color:$danger;">XOR</mark> 57 = 10000001 XOR 01010111 = **11010110** → <mark style="color:$warning;">**d6**</mark>
* <mark style="color:blue;">i=3</mark>: effective key = <mark style="color:blue;">0x55 + 3</mark> = <mark style="color:blue;">0x58  -></mark> <mark style="color:$warning;">e4</mark> <mark style="color:$danger;">XOR</mark> 58 = 11100100 XOR 01011000 = **10111100** → <mark style="color:$warning;">**bc**</mark>

Now the string part – watch how it scrambles differently:

* i=... (let's say position of 'H' is some i=100 for example, but exact offset doesn't matter for demo) Effective key = 0x55 + 100 = 0x55 + 0x64 = 0xb9 'H' (48) XOR b9 = **f1** (very different from single-byte's 1d)
* Next byte 'e' (65) at i+1: key = 0xba 65 XOR ba = **df**

The output looks even more random than plain XOR – no repeating patterns you can easily spot in hex view.

**After rolling XOR encryption (first few bytes example):** a9 1e d6 bc ... (and the "Hey man..." part scattered into unpredictable bytes like f1 df 1a 4c ...)

**Decryption:** Run the exact same function again. Since (encrypted XOR (key+i)) XOR (key+i) = original, it flips back perfectly.

* Encrypted a9 at i=0: a9 XOR 55 → fc
* Encrypted 1e at i=1: 1e XOR 56 → 48
* And so on – full original restored in memory at runtime.

the overall output look like :

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

same this also can be reversed by passing into the same function as single Byte XOR :)&#x20;

### **Method 3: Multiple Key-Byte XOR**

We're leveling up again – this time to **Multi-Byte Repeating XOR** (also called Vigenère-style XOR or repeating key XOR).

This is the version malware authors reach for when rolling single-byte still feels too predictable. Instead of one base key + offset, they use a short **key string** (like a password or random bytes) that repeats across the entire payload. It's basically the XOR version of a Vigenère cipher – the key cycles over and over, making the output look even more random and resistant to simple pattern spotting or quick brute-force.

In your code, the function is clean and classic:

```cpp

#include <iostream>
#include <iomanip>
#include <vector>
#include <cstdint>  // for uint8_t / byte

using byte = uint8_t;

// Simple output helper
template <typename T>
void SMEout(const T& PrintIt) {
    std::cout << PrintIt << std::endl;
}

const std::vector<byte> pPayload = {
    0xfc,0x48,0x81,0xe4,0xf0,0xff,0xff,0xff,0xe8,0xd0,0x00,0x00,0x00,0x41,0x51,0x41,0x50,0x52,0x51,
    0x56,0x48,0x31,0xd2,0x65,0x48,0x8b,0x52,0x60,0x3e,0x48,0x8b,0x52,0x18,0x3e,0x48,0x8b,0x52,0x20,
    0x3e,0x48,0x8b,0x72,0x50,0x3e,0x48,0x0f,0xb7,0x4a,0x4a,0x4d,0x31,0xc9,0x48,0x31,0xc0,0xac,0x3c,
    0x61,0x7c,0x02,0x2c,0x20,0x41,0xc1,0xc9,0x0d,0x41,0x01,0xc1,0xe2,0xed,0x52,0x41,0x51,0x3e,0x48,
    0x8b,0x52,0x20,0x3e,0x8b,0x42,0x3c,0x48,0x01,0xd0,0x3e,0x8b,0x80,0x88,0x00,0x00,0x00,0x48,0x85,
    0xc0,0x74,0x6f,0x48,0x01,0xd0,0x50,0x3e,0x8b,0x48,0x18,0x3e,0x44,0x8b,0x40,0x20,0x49,0x01,0xd0,
    0xe3,0x5c,0x48,0xff,0xc9,0x3e,0x41,0x8b,0x34,0x88,0x48,0x01,0xd6,0x4d,0x31,0xc9,0x48,0x31,0xc0,
    0xac,0x41,0xc1,0xc9,0x0d,0x41,0x01,0xc1,0x38,0xe0,0x75,0xf1,0x3e,0x4c,0x03,0x4c,0x24,0x08,0x45,
    0x39,0xd1,0x75,0xd6,0x58,0x3e,0x44,0x8b,0x40,0x24,0x49,0x01,0xd0,0x66,0x3e,0x41,0x8b,0x0c,0x48,
    0x3e,0x44,0x8b,0x40,0x1c,0x49,0x01,0xd0,0x3e,0x41,0x8b,0x04,0x88,0x48,0x01,0xd0,0x41,0x58,0x41,
    0x58,0x5e,0x59,0x5a,0x41,0x58,0x41,0x59,0x41,0x5a,0x48,0x83,0xec,0x20,0x41,0x52,0xff,0xe0,0x58,
    0x41,0x59,0x5a,0x3e,0x48,0x8b,0x12,0xe9,0x49,0xff,0xff,0xff,0x5d,0x3e,0x48,0x8d,0x8d,0x30,0x01,
    0x00,0x00,0x41,0xba,0x4c,0x77,0x26,0x07,0xff,0xd5,0x49,0xc7,0xc1,0x00,0x00,0x00,0x00,0x3e,0x48,
    0x8d,0x95,0x0e,0x01,0x00,0x00,0x3e,0x4c,0x8d,0x85,0x24,0x01,0x00,0x00,0x48,0x31,0xc9,0x41,0xba,
    0x45,0x83,0x56,0x07,0xff,0xd5,0x48,0x31,0xc9,0x41,0xba,0xf0,0xb5,0xa2,0x56,0xff,0xd5,0x48,0x65,
    0x79,0x20,0x6d,0x61,0x6e,0x2e,0x20,0x49,0x74,0x73,0x20,0x6d,0x65,0x20,0x48,0x61,0x76,0x6f,0x78,
    0x00,0x6d,0x65,0x77,0x6f,0x6f,0x20,0x20,0x20,0x20,0x20,0x20,0x00,0x75,0x73,0x65,0x72,0x33,0x32,
    0x2e,0x64,0x6c,0x6c,0x00
}; 

// Print function (32 bytes per line + debug style)
void printHex_payload(const std::vector<byte>& payload, const std::string& label) {
    std::cout << "[*] " << label << " (" << payload.size() << " bytes)\n";
    std::cout << "[*] Payload base address: 0x"
        << std::hex << std::uppercase << std::setfill('0') << std::setw(16)
        << reinterpret_cast<uintptr_t>(payload.data()) << "\n";

    for (std::size_t i = 0; i < payload.size(); i += 32) {
        std::cout << std::hex << std::setfill('0') << std::setw(8) << i << "  ";
        for (std::size_t j = 0; j < 32 && (i + j) < payload.size(); ++j) {
            std::cout << std::hex << std::setfill('0') << std::setw(2)
                << static_cast<int>(payload[i + j]) << " ";
        }
        std::cout << "\n";
    }
    std::cout << std::dec << std::setfill(' ') << "\n";
}

// Multi-Byte Repeating Key XOR (Vigenère-style XOR)
void xorMultiByte(std::vector<byte>& data, const std::vector<byte>& key) {
    if (key.empty()) {
        std::cout << "[!] Warning: Empty key provided - no changes made\n";
        return;
    }

    for (std::size_t i = 0, k = 0; i < data.size(); ++i, ++k) {
        if (k >= key.size()) {
            k = 0;  // Reset key index → repeating cycle
        }
        data[i] ^= key[k];
    }
}

int main() {
    std::cout << "[*] Starting Multi-Byte Repeating Key XOR \n";
    std::cout << "[*] Original shellcode (const reference)\n";

    // Show original
    printHex_payload(pPayload, "Original Payload (Plain)");

    std::cout << "[*] Creating writable copy of payload\n";
    auto payload = pPayload;

    std::cout << "[*] Defining repeating multi-byte key\n";
    std::vector<byte> multiKey = { 0xDE, 0xAD, 0xBE, 0xEF, 0x12, 0x34, 0x56, 0x78 };
    std::cout << "[*] Multi-key length: " << multiKey.size() << " bytes\n";

    std::cout << "[*] Applying Multi-Byte Repeating XOR encryption\n";
    xorMultiByte(payload, multiKey);

    printHex_payload(payload, "After Multi-Byte Repeating XOR Encoding");

    std::cout << "[*] Applying same multi-key XOR again to decrypt (symmetric)\n";
    xorMultiByte(payload, multiKey);

    printHex_payload(payload, "After Multi-Byte Repeating XOR Decoding (restored)");

    std::cout << "[*]  complete\n";
    return 0;
}
```

This actually Multiple array of key which is been XOR sequentially with the Payload make more obfuscated here is the another issue rise where due to variable Key which cause more entropy which makes EDR/AV to suspect as Malicious code../

the output look like :<br>

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

Let's use the same realistic snippet: start of shellcode + the clear string "Hey man. Its me Havox" at the end.

**Original bytes (first few + string part):** fc 48 81 e4 ... 48 65 79 20 6d 61 6e 2e 20 49 74 73 20 6d 65 20 48 61 76 6f 78 00

**The repeating key we'll use (from your code):** DE AD BE EF 12 34 56 78 (That's 8 bytes long – repeats every 8 positions)

**Encryption loop in action:**

* Position i=0: key index k=0 → key byte = DE fc XOR DE = 11111100 XOR 11011110 = **00100010** → **22**
* i=1: k=1 → key = AD 48 XOR AD = 01001000 XOR 10101101 = **11100101** → **e5**
* i=2: k=2 → key = BE 81 XOR BE = 10000001 XOR 10111110 = **00111111** → **3f**
* i=3: k=3 → key = EF e4 XOR EF = 11100100 XOR 11101111 = **00001011** → **0b**
* i=4: k=4 → key = 12 (next byte, say 48) 48 XOR 12 = **5a**
* i=5: k=5 → key = 34 ... and so on

Now the readable string part (assume it starts at some position i=100 for demo – exact offset doesn't change the pattern):

* 'H' (48) at i=100: k = 100 % 8 = 4 → key byte = 12 48 XOR 12 = **5a** (not the same as single-byte's 1d)
* 'e' (65) at i=101: k=5 → key = 34 65 XOR 34 = **51**
* 'y' (79) at i=102: k=6 → key = 56 79 XOR 56 = **2f**
* ' ' (20) at i=103: k=7 → key = 78 20 XOR 78 = **58**
* Next 'm' at i=104: k=0 (cycle back) → key = DE 6d XOR DE = **b3**

The result? The entire "Hey man. Its me Havox" turns into unpredictable bytes like 5a 51 2f 58 b3 ... – no repeating patterns every 1 or 2 bytes like single/rolling XOR. It looks like high-quality random data.

**After multi-byte repeating XOR encryption:** 22 e5 3f 0b 5a ... (and string part scattered into chaos like 5a 51 2f 58 b3 ...)

**Decryption:** Run the exact same function with the same key. The repeating cycle ensures every byte gets XORed with the correct key byte again → original restored perfectly.

* Encrypted 22 at i=0: 22 XOR DE → fc
* Encrypted e5 at i=1: e5 XOR AD → 48
* Encrypted 5a ('H' position): 5a XOR 12 → 48 ('H' returns) And so on – full payload comes back clean in memory.

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

#### Next up: execution flow and a deep dive into XOR—how it works, step by step.


---

# 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/quickstart.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.
