winternl

cybersecurity & programming

Full Fat Shellcode

GLP-1s need not apply.

There may be situations where you wish to conditionally run 32-bit or 64-bit shellcode from the same codebase. One such scenario is if you are bootstrapping some code into an ILONLY assembly.

Below is valid x86 and x86-64 code that can be used for that purpose.

xor eax, eax

db 048h
db 0ffh
db 0c0h

test eax, eax
jnz lbl_64

lbl_32:
  int 3
  
lbl_64:
  int 3

The interesting bit is how the sequence 0x48, 0xff, 0xc0 is encoded differently between the two architectures.

On x86 it is encoded to:

dec eax
inc eax

And on x86-64 it gets encoded to:

[rex] inc rax

There are alternative variations to this trick, but I feel this iteration strikes a good balance of simplicity and readability.

Additionally, here’s a Python utility function to join x86 and x86-64 shellcode together using this trick.

def create_fat_loader(shellcode_32: bytes, shellcode_64: bytes) -> bytes:

    stub: bytearray = bytearray()
    stub += b"\x31\xC0\x48\xFF\xC0\x85\xC0\x0F\x85"

    offset_patch_64_jne: int = len(stub)
    stub += b"\x00\x00\x00\x00"

    stub.extend(shellcode_32)
    
    offset_64: bytes = len(shellcode_32).to_bytes(length=4, byteorder='little', signed=False)
    stub.extend(shellcode_64)

    stub[offset_patch_64_jne:offset_patch_64_jne + 4] = offset_64

    return bytes(stub)