SLAE A.7 - Crypter/decrypter

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

This seventh and final assignment is to build a crypter for arbitrary shellcode. The crypter stage should take a piece of shellcode and encrypt it some manner so that it cannot be detected by antivirus tools. A corresponding decrypter program should be generated that contains the encrypted payload. When it is run and the correct decryption key is supplied, it should decrypt the shellcode in memory and execute it.

I’ve always wanted to have a play with the libsodium encryption library so I decided to use that and C to implement the crypter and decrypter. The full source code is on GitHub: crypter.c and decrypter.c.

Usage

The easiest way to see how it works is by an example. Here we will encrypt and embed a simple execve /bin/sh shellcode.

  1. The crypter takes the shellcode as raw bytes from stdin, and the encryption key as a command line parameter. (More detail about the encryption below.)
  2. The crypter prints a C header to stdout, containing all of the data that needs to be embedded in the decrypter.
  3. The decrypter is built, taking in this information with #include "encrypted.h"
  4. The decrypter takes the encryption key as a command line parameter. If the key is correct, the shellcode is executed. The ciphertext includes a MAC so it can detect when the decryption was not successful.

Encryption technique

The encryption algorithm used here is the XSalsa20 stream sipher with a Poly1305 MAC to verify the integrity of the ciphertext. Fortunately I don’t have to worry about the details of this. With libsodium I can use their simple crypto_secretbox_easy API to get strong encryption with a low risk of stuffing something up.

The inputs to the encryption/decryption are a nonce, which is a random blob of data, and the symmetric key. I would like to be able to type a password rather than use a long binary key so I use another feature of libsodium: a key derivation function. With this API I can use the well-regarded Argon2 function to convert the password typed on the command line to a suitable symmetric key of the correct size. This KDF also requires a random salt.

To summarise, the crypter process looks like this:

  1. Generate a random salt for the password KDF
  2. Use the supplied password and the salt to generate a key for the encryption
  3. Generate a random nonce for the encryption
  4. Use the key and the nonce to create the ciphertext

The decrypter will have three pieces of information hard-coded within it:

  1. The salt
  2. The nonce
  3. The ciphertext (and its length)

The only missing piece of data is the user supplied password. That must be provided as a command line parameter. The decrypter will do the following:

  1. Use the supplied password and the hard-coded salt to generate the key - if the password was correct, this will be the same key as before
  2. Use the key and the hard-coded nonce to decrypt the hard-coded ciphertext
  3. If decryption succeeds, run the shellcode.

Key derivation

In both the crypter and decrypter, the password is read a command line argument.

char *password = argv[1];

In the crypter only, a random salt of the required size must be generated using a secure random number generator.

/* Create a random salt for the key derivation function */
unsigned char salt[crypto_pwhash_SALTBYTES];
randombytes_buf(salt, sizeof salt);

In both the crypter and decrypter the key derivation function is invoked to get the key.

/* Use argon2 to convert password to a full size key */
unsigned char key[crypto_secretbox_KEYBYTES];
if (crypto_pwhash(key, sizeof key, password, strlen(password), salt,
		crypto_pwhash_OPSLIMIT_INTERACTIVE, crypto_pwhash_MEMLIMIT_INTERACTIVE,
		crypto_pwhash_ALG_DEFAULT) != 0) {
	fprintf(stderr, "Key derivation failed\n");
	exit(1);
}

Encryption

The crypter must prepare a random nonce as an input to the encryption.

/* Create a random nonce for the encryption also */
unsigned char nonce[crypto_secretbox_NONCEBYTES];
randombytes_buf(nonce, sizeof nonce);

The length of the ciphertext can be computed in advance. The ciphertext data will be placed in a buffer on the stack.

/* Now encrypt it into ciphertext */
unsigned long ciphertext_len = crypto_secretbox_MACBYTES + strlen(plaintext);
unsigned char *ciphertext = alloca(ciphertext_len);
crypto_secretbox_easy(ciphertext, (unsigned char *)plaintext, strlen(plaintext), nonce, key);

All the required data is encoded as hex values and printed in the form of a C header file named encrypted.h.

Decryption

The decrypter uses the nonce and derived key and puts the decrypted bytes inside a buffer called decrypted.

unsigned char *decrypted = alloca(ciphertext_len); /* slightly longer than needed */
if (crypto_secretbox_open_easy(decrypted, ciphertext, ciphertext_len, nonce, key) != 0) {
	printf("Decryption failed!\n");
	exit(1);
}

If decryption was successful the shellcode is executed.

/* Execute it */
int (*code)() = (int(*)())decrypted;
code();