7 - SHELLCODE ENCRYPTION / DECRYPTION
1 - INTRODUCTION
The goal of this exercise is to implement encryption techniques on a shellcode with the purpose of evading Antivirus (AV) and Intrusion Detection Systems (IDS).
One of the most important concepts about Cryptography is to avoid the so called Security Through Obscurity (STO), meaning the usage of secrecy on the design of security. A system relying on STO may have security due to the unknown of its inherent flaws, so allegedly it would be difficult for the attackers to find them. However, general practice and academy institutions agree that the truth of the matter is exactly the contrary: the robustness of any security measure increases as it can be proved and tested publicly by the professional community. In this regard, the National Institute of Standards and Technology (NIST) in the United States specifically discourages the use of STO: "System security should not depend on the secrecy of the implementation or its components." In other words, according to Kerckhoffs' doctrine (XIXth century) "the security of a system should depend on its key, not on its design remaining obscure."
For this reason, instead of trying to develop my own cryptographic methods, I have preferred to rely on a well known cryptographic algorithm (Advanced Encryption Standard, AES) and a well tested cryptographic library (Libgcrypt).
AES, aka Rijndael, was selected by the NIST and adopted by the US government in 2001 as one of the most preferred encryption algorithm of electronic data. Based on a the design principle substitution - permutation network, uses a fixed block size of 128 bits, and possible key size of 128, 192 or 256 bits. AES is a symmetric criptographic algorithm, so same key must be used for encryption and encryption.
Libgcrypt is a general purpose cryptographic library based on the code from GnuPG. Libgcrypt provides a high level interface using an extensible and flexible API for all cryptograhic building blocks: symmetric ciphers, hash algorithms and public key algorithms. Libgcrypt works on most POSIX systems. It’s Free Software, so anybody can use, modify, and redistribute it under the terms of the GNU Lesser General Public License.
https://gnupg.org/documentation/manuals/gcrypt/
2 - ORIGINAL SHELLCODE
https://gnupg.org/documentation/manuals/gcrypt/
2 - ORIGINAL SHELLCODE
- The original shellcode used in this assignment is very simple, and it will be used as a proof of concept to demonstrate the process of encryption and decryption of a common shellcode.
- Let's have a look to the assembly code of original.nasm:
global _start
section .text
_start:
xor eax,eax
mov al,0x4
xor ebx,ebx
mov bl,0x1
xor edx,edx
push edx
push 0x0a202020
push 0x54454e2e
push 0x45425554
push 0x59544952
push 0x55434553
push 0x2e575757
mov ecx,esp
mov dl,0x18
int 0x80
xor eax,eax
mov al,0x1
int 0x80
- Indeed, the message sounds familiar to the SLAE community ... :)
- Finally, a shellcode is extracted from original.nasm:
- This shellcode will be used along this exercise to be encrypted and decrypted, and finally executed.
3 - ENCRYPTION STEP BY STEP
- Both the encrypting and decrypting programs will be written in language C.
- Starting with the header files, <gcrypt.h> must be included because all interfaces (data types and functions) of the library are defined there:
#include <gcrypt.h>
- Then, some macros are defined that will be used along the whole program. Cipher AES and Counter mode are determined, and also the sizes for key and block of operation:
#define algo GCRY_CIPHER_AES128
#define mode GCRY_CIPHER_MODE_CTR
#define KEY_LENGTH 16
#define BLOCK_LENGTH 16
uint8_t shellcode[] = "\x31\xc0\xb0\x04\x31\xdb\xb3\x01\x31\xd2\x52\x68\x20\x20\x20\x0a\x68\x2e\x4e\x45\x54\x68\x54\x55\x42\x45\x68\x52\x49\x54\x59\x68\x53\x45\x43\x55\x68\x57\x57\x57\x2e\x89\xe1\xb2\x18\xcd\x80\x31\xc0\xb0\x01\xcd\x80";
- The variable for encriptedShellcode is defined, but left blank for the moment:
uint8_t encriptedShellcode[] = " ";
- The key is introduced, consisting of 16 Bytes. Same key will be used later for decryption, because AES is a symmetric crytographic algorithm:
const char *key = "0123456789abcdef";
- Initialization Vectors (IVs) are used as a fixed-size input to cryptographic primitives. Usually IVs are random, what helps to avoid the repetition of patterns. In this way distinct ciphertexts are produced even when the same plaintext being encrypted multiple times and independently with the same key. However, in this case I have chosen a simple one for easiness of the exercise. Anyway, same IV should be used later for decryption:
- Counter mode of operation allows to use a stream cipher, generating a keystream block by encrypting successive values of a "counter". The counter can be any function which produces a sequence which is guaranteed not to repeat for a long time, although an increment by one is usual:
uint8_t ctr[] = "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\xf0\x10";
- A function for encryption is created:
static void encryption(int algo, size_t size, uint8_t *encriptedShellcode)
- To use a cipher algorithm with libgcrypt a "handle" must be allocated:
gcry_cipher_hd_t handle;
- Creating the required context for the "handle":
gcry_cipher_open(&handle, algo, mode, 0);
- The key is introduced:
gcry_cipher_setkey(handle, key, KEY_LENGTH );
- The IV is set:
gcry_cipher_setiv(handle, IV, BLOCK_LENGTH);
- The counter is set:
gcry_cipher_setctr(handle, ctr, BLOCK_LENGTH);
- Encryption is performed:
gcry_cipher_encrypt(handle, encriptedShellcode, size, shellcode, size);
- Finally, the "handle" is released:
gcry_cipher_close(handle);
- Inside main() the encryption function is called and encriptedShellcode returned:
encryption(algo, size, encriptedShellcode);
- The output encriptedShellcode is printed:
printf("Encrypted shellcode = ");
while(i<size){
printf("\\x%02x", encriptedShellcode[i]);
i++;
}
- The whole program encryption_AES_128.c:
#include <stdio.h>
#include <string.h>
#include <gcrypt.h>
#include <stdint.h>
#define algo GCRY_CIPHER_AES128
#define mode GCRY_CIPHER_MODE_CTR
#define KEY_LENGTH 16
#define BLOCK_LENGTH 16
uint8_t shellcode[] = "\x31\xc0\xb0\x04\x31\xdb\xb3\x01\x31\xd2\x52\x68\x20\x20\x20\x0a\x68\x2e\x4e\x45\x54\x68\x54\x55\x42\x45\x68\x52\x49\x54\x59\x68\x53\x45\x43\x55\x68\x57\x57\x57\x2e\x89\xe1\xb2\x18\xcd\x80\x31\xc0\xb0\x01\xcd\x80";
uint8_t encriptedShellcode[] = "";
const char *key = "0123456789abcdef";
uint8_t IV[] = "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\xf0\x10";
uint8_t ctr[] = "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\xf0\x10";
static void encryption(int algo, size_t size, uint8_t *encriptedShellcode){
gcry_cipher_hd_t handle;
gcry_cipher_open(&handle, algo, mode, 0);
gcry_cipher_setkey(handle, key, KEY_LENGTH );
gcry_cipher_setiv(handle, IV, BLOCK_LENGTH);
gcry_cipher_setctr(handle, ctr, BLOCK_LENGTH);
gcry_cipher_encrypt(handle, encriptedShellcode, size, shellcode, size);
gcry_cipher_close(handle);
}
int main(void){
int i=0;
int size = strlen(shellcode);
encryption(algo, size, encriptedShellcode);
printf("\n");
printf("Encrypted shellcode = ");
while(i<size){
printf("\\x%02x", encriptedShellcode[i]);
i++;
}
printf("\n\n");
return 0;
}
- Compiling with option -lgcryp the program encryption_AES_128.c:
- Executing encryption_AES_128.c, output is the result of encrypting the original shellcode:
4 - DECRYPTION STEP BY STEP
- Because the decryption program shares many instructions with the encryption one, I will just discuss the differences between them.
- First of all, the last output shellcode is inserted as the encriptedShellcode[]:
uint8_t encriptedShellcode[] = "\x8f\x06\x94\x69\xa4\xe9\xc4\x03\x4a\xdb\x3d\x28\x2d\x02\x77\xaa\x45\x41\x38\x94\x1d\x45\x83\xa1\xba\x16\x60\x3b\xb4\x19\x9f\xfb\x23\x39\x53\x87\xce\x69\x9c\x79\x2e\x12\xf2\xd4\x52\x9e\xfb\xe7\xbe\x5a\x03\x9e\x93";
- gcry_cipher_decrypt must be used in this case, being the input parameter encriptedShellcode and the output decriptedShellcode:
gcry_cipher_decrypt(handle, decriptedShellcode, size, encriptedShellcode, size);
- Once decriptedShellcode is achieved it can be printed, to check whether it matches the original one:
printf("Decrypted shellcode = ");
while(i<size){
printf("\\x%02x", decriptedShellcode[i]);
i++;
}
- Finally, decriptedShellcode is executed:
int (*ret)() = (int(*)())decriptedShellcode;
ret();
#include <stdio.h>
#include <string.h>
#include <gcrypt.h>
#include <stdint.h>
#define algo GCRY_CIPHER_AES128
#define mode GCRY_CIPHER_MODE_CTR
#define KEY_LENGTH 16
#define BLOCK_LENGTH 16
uint8_t encriptedShellcode[] = "\x8f\x06\x94\x69\xa4\xe9\xc4\x03\x4a\xdb\x3d\x28\x2d\x02\x77\xaa\x45\x41\x38\x94\x1d\x45\x83\xa1\xba\x16\x60\x3b\xb4\x19\x9f\xfb\x23\x39\x53\x87\xce\x69\x9c\x79\x2e\x12\xf2\xd4\x52\x9e\xfb\xe7\xbe\x5a\x03\x9e\x93";
uint8_t decriptedShellcode[] = "";
const char *key = "0123456789abcdef";
uint8_t IV[] = "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\xf0\x10";
uint8_t ctr[] = "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\xf0\x10";
static void decryption(int algo, size_t size, uint8_t *decriptedShellcode){
gcry_cipher_hd_t handle;
gcry_cipher_open(&handle, algo, mode, 0);
gcry_cipher_setkey(handle, key, KEY_LENGTH);
gcry_cipher_setiv(handle, IV, BLOCK_LENGTH);
gcry_cipher_setctr(handle, ctr, BLOCK_LENGTH);
gcry_cipher_decrypt(handle, decriptedShellcode, size, encriptedShellcode, size);
gcry_cipher_close(handle);
}
int main(void){
int i=0;
int size = strlen(encriptedShellcode);
decryption(algo, size, decriptedShellcode);
printf("\n");
printf("Decrypted shellcode = ");
while(i<size){
printf("\\x%02x", decriptedShellcode[i]);
i++;
}
printf("\n\n");
int (*ret)() = (int(*)())decriptedShellcode;
ret();
printf("\n\n");
return 0;
}
- The execution is successful because the original shellcode is correctly decrypted and the output matches the expected message WWW.SECURITYTUBE.NET