3. Maldev - XOR Encryption - single byte

 

[CODE]

#include <stdio.h>

#include <stdint.h>

uint8_t shellcode[] =

"\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50"

"\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52"

"\x18\x48\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a"

"\x4d\x31\xc9\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41"

"\xc1\xc9\x0d\x41\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52"

"\x20\x8b\x42\x3c\x48\x01\xd0\x8b\x80\x88\x00\x00\x00\x48"

"\x85\xc0\x74\x67\x48\x01\xd0\x50\x8b\x48\x18\x44\x8b\x40"

"\x20\x49\x01\xd0\xe3\x56\x48\xff\xc9\x41\x8b\x34\x88\x48"

"\x01\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41"

"\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c\x24\x08\x45\x39\xd1"

"\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0\x66\x41\x8b\x0c"

"\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04\x88\x48\x01"

"\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59\x41\x5a"

"\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48\x8b"

"\x12\xe9\x57\xff\xff\xff\x5d\x48\xba\x01\x00\x00\x00\x00"

"\x00\x00\x00\x48\x8d\x8d\x01\x01\x00\x00\x41\xba\x31\x8b"

"\x6f\x87\xff\xd5\xbb\xf0\xb5\xa2\x56\x41\xba\xa6\x95\xbd"

"\x9d\xff\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0"

"\x75\x05\xbb\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff"

"\xd5\x63\x61\x6c\x63\x2e\x65\x78\x65\x00";


static void original_shellcode(const uint8_t* buf, size_t len) {

    for (size_t i = 0; i < len; i++) {

        printf("0x%02x ", buf[i]);

        if ((i + 1) % 16 == 0) printf("\n");

    }

    if (len % 16) printf("\n");  // tidy newline

}

static void xor_encryption(uint8_t* buf, size_t len, uint8_t keyE) {

    for (size_t i = 0; i < len; i++) {

        buf[i] ^= keyE;

        printf("0x%02x ", buf[i]);

        if ((i + 1) % 16 == 0) printf("\n");

    }

    if (len % 16) printf("\n");

}

static void xor_decryption(uint8_t* buf, size_t len, uint8_t keyE) {

    for (size_t i = 0; i < len; i++) {

        buf[i] ^= keyE;

        printf("0x%02x ", buf[i]);

        if ((i + 1) % 16 == 0) printf("\n");

    }

    if (len % 16) printf("\n");

}

int main(void) {

    size_t shellcode_length = sizeof shellcode;   // includes trailing NUL

    // Optional: trim trailing zeros

    while (shellcode_length && shellcode[shellcode_length - 1] == 0x00)

        shellcode_length--;

    printf("The size of Shellcode %zu\n", shellcode_length);

    uint8_t key = 0x05;

    printf("\nThe Original shellcode is :\n");

    original_shellcode(shellcode, shellcode_length);

    printf("\nThe Encrypted Shellcode is :\n");

    xor_encryption(shellcode, shellcode_length, key);

    printf("\nThe Decrypted Shellcode is :\n");

    xor_decryption(shellcode, shellcode_length, key);

    return 0;

}



STORY: ----------------------------------------------------

🎬 The Story of Your XOR Program

0) Opening scene — global data is loaded

uint8_t shellcode[] = "\xFC\x48\x83... \x65\x00";
  • This defines a global byte array. It lives in the program’s .data segment.

  • It’s initialized from a C string literal, so the compiler adds a trailing '\0' (NUL) after your bytes.

  • Your literal already ends with \x00, so you’ll have two zeros at the end unless you trim them.

Nothing runs yet; this is set up before main().


1) Main begins — sizing and setup

size_t shellcode_length = sizeof(shellcode); printf("The size of Shellcode %zu\n ", shellcode_length); size_t key = 0x05;
  • sizeof(shellcode) gives the total byte count of the array (including any trailing zeros).

  • You print the length with %zu (correct for size_t).

  • key is the XOR key (0x05). It could be a uint8_t, but size_t works since you only use the low byte.

Tip you can remember: if you don’t want to print/XOR the trailing zeros, you can trim them before the dumps.


2) Phase 1 — “Original” dump (read-only view)

original_shellcode(shellcode, shellcode_length);

Inside:

for (int i = 0; i < shellcode_O_length; i++) { printf("0x%02x ", shellCode_O[i]); if ((i + 1) % 16 == 0) printf("\n"); }
  • You iterate from 0 to length-1 and print each byte as two-digit hex.

  • After every 16 bytes, you print a newline, so the output is tidy rows.

  • This function does not modify the buffer — it’s a pure “view”.

Output now shows the original bytes as they exist in memory.


3) Phase 2 — XOR “encryption” (mutates in place)

xor_encryption(shellcode, shellcode_length, key);

Inside:

for (int i = 0; i < shellcode_O_length; i++) { shellcode_O[i] = shellcode_O[i] ^ keyE; // flip bits in place printf("0x%02x ", shellcode_O[i]); if ((i + 1) % 16 == 0) printf("\n"); }
  • You walk the same buffer and XOR every byte with 0x05.

  • XOR toggles specific bits (bit 0 and bit 2 here), so bytes look “scrambled”.

  • You print as you mutate, so the “Encrypted” block shows the changed bytes.

  • This modifies the global array (no copies). That’s fine for your demo.


4) Phase 3 — XOR “decryption” (restore by symmetry)

xor_decryption(shellcode, shellcode_length, key);

Inside:

for (int i = 0; i < shellcode_O_length; i++) { shellcode_O[i] = shellcode_O[i] ^ keyE; // XOR again with same key printf("0x%02x ", shellcode_O[i]); if ((i + 1) % 16 == 0) printf("\n"); }
  • You apply the same XOR again with the same key.

  • XOR is symmetric: (byte ^ key) ^ key == byte.

  • Result: the buffer returns to its original values, so your “Decrypted” dump should match the “Original” dump (including any trailing zeros you chose to keep).

Credits roll. ✅


🧠 Mental model (what to remember)

  • Arrays decay to pointers when passed to functions — always pass a length alongside the pointer.

  • Use size_t for lengths and indices, %zu to print them; use uint8_t for raw bytes and keys.

  • printf("0x%02x", byte) → two-digit hex with leading zero.

  • Newline every 16: (i + 1) % 16 == 0.

  • String literal adds a NUL; your literal also ends with \x00 → likely two zeros at the end unless you trim.


✅ What your program proves

  • Your dump function prints clean hex rows without altering data.

  • Your XOR function mutates in place and the second pass perfectly restores the original.

  • The outputs confirm the round-trip property of XOR.

If you ever switch to a multi-byte key later, the only change you’ll need is the index: buf[i] ^= key[i % klen];. For a single-byte key, your current code is spot on.


Comments