Payload Encryption

Ta cần mã hóa payload để cho không bị phát hiện bởi các security solution. Cụ thể hơn, mã hóa sẽ giúp tránh được các cơ chế phát hiện mã độc dựa trên signature. Hiển nhiên, việc mã hóa sẽ không giúp mã độc tránh bị phát hiện bởi việc phân tích động.

Warning

Việc có nhiều dữ liệu bị mã hóa ở trong một file sẽ khiến cho nó có điểm entropy (là đại lượng đo mức độ ngẫu nhiên hay mức độ hỗn loạn) cao. Điểm entropy cao sẽ làm cho các security solution chú ý. May mắn là chúng ta có cách để giảm thiểu entropy của file.

Ta sẽ tập trung vào 3 thuật toán mã hóa đối xứng là XOR, AES và RC4.

XOR

Là loại thuật toán mã hóa đơn giản và dễ triển khai. Nó nhanh hơn AES và RC4. Ngoài ra, nó không yêu cầu thêm thư viện ngoài cũng như là Windows APIs.

Đặc biệt, ta chỉ cần dùng duy nhất một hàm để mã hóa và giải mã.

XOR Encryption

Đoạn code sau minh họa cho việc mã hóa với thao tác XOR:

/*
	- pShellcode : Base address of the payload to encrypt 
	- sShellcodeSize : The size of the payload 
	- bKey : A single arbitrary byte representing the key for encrypting the payload
*/
VOID XorByOneKey(IN PBYTE pShellcode, IN SIZE_T sShellcodeSize, IN BYTE bKey) {
	for (size_t i = 0; i < sShellcodeSize; i++){
		pShellcode[i] = pShellcode[i] ^ bKey;
	}
}

Có thể thấy, việc mã hóa được thực hiện bằng cách XOR từng byte trong shellcode với 1-byte key.

Securing The Encryption Key

Việc sử dụng khóa có 1 byte thường rất dễ bị brute-force. Đoạn code bên dưới tăng độ khó của việc mã hóa bằng cách cộng thêm biến vòng lặp i vào khóa:

/*
	- pShellcode : Base address of the payload to encrypt 
	- sShellcodeSize : The size of the payload 
	- bKey : A single arbitrary byte representing the key for encrypting the payload
*/
VOID XorByiKeys(IN PBYTE pShellcode, IN SIZE_T sShellcodeSize, IN BYTE bKey) {
	for (size_t i = 0; i < sShellcodeSize; i++) {
		pShellcode[i] = pShellcode[i] ^ (bKey + i);
	}
}

Hoặc, chúng ta cũng có thể sử dụng một chuỗi byte làm khóa. Cụ thể, chúng ta sẽ XOR từng byte trong shellcode với một byte của khóa và tăng dần. Sau khi dùng đến byte cuối cùng của khóa thì quay lại dùng byte đầu tiên:

/*
	- pShellcode : Base address of the payload to encrypt 
	- sShellcodeSize : The size of the payload 
	- bKey : A random array of bytes of specific size
	- sKeySize : The size of the key
*/
VOID XorByInputKey(IN PBYTE pShellcode, IN SIZE_T sShellcodeSize, IN PBYTE bKey, IN SIZE_T sKeySize) {
	for (size_t i = 0, j = 0; i < sShellcodeSize; i++, j++) {
		if (j > sKeySize){
			j = 0;
		}
		pShellcode[i] = pShellcode[i] ^ bKey[j];
	}
}

RC4

Là một stream cipher - loại thuật toán mã hóa bằng cách XOR bản rõ với một chuỗi byte (stream). Chuỗi byte này được tạo ra dựa trên các thuật toán sinh số giả ngẫu nhiên. Có thể nói, tính bảo mật của loại mã hóa này dựa vào cách mà stream được tạo ra.

Seealso

Chúng ta sẽ tập trung vào 3 cách implement thuật toán RC4 chính để mã hóa payload.

Method 1

Đoạn code sau sử dụng implementation ở đây, bao gồm 2 hàm chính là:

  1. rc4Init dùng để khởi tạo Rc4Context mà thực chất là một struct chứa các thông số cần thiết chọ việc mã hóa cũng như là bytestream.

    Cấu trúc Rc4Context:

    typedef struct
    {
    	unsigned int i;
    	unsigned int j;
    	unsigned char s[256];
     
    } Rc4Context;

    Bytestream được lưu ở trong mảng s.

    Hàm rc4Init:

    void rc4Init(Rc4Context* context, const unsigned char* key, size_t length)
    {
    	unsigned int i;
    	unsigned int j;
    	unsigned char temp;
     
    	// Check parameters
    	if (context == NULL || key == NULL)
    		return ERROR_INVALID_PARAMETER;
     
    	// Clear context
    	context->i = 0;
    	context->j = 0;
     
    	// Initialize the S array with identity permutation
    	for (i = 0; i < 256; i++)
    	{
    		context->s[i] = i;
    	}
     
    	// S is then processed for 256 iterations
    	for (i = 0, j = 0; i < 256; i++)
    	{
    		//Randomize the permutations using the supplied key
    		j = (j + context->s[i] + key[i % length]) % 256;
     
    		//Swap the values of S[i] and S[j]
    		temp = context->s[i];
    		context->s[i] = context->s[j];
    		context->s[j] = temp;
    	}
    }

    Có thể thấy, việc xây dựng bytestream được thực hiện bằng cách hoán vị phần tử thứ i trong mảng s với phần tử thứ j. Giá trị j được tính toán dựa trên giá trị j trước đó, giá trị hiện tại của phần tử thứ i và một byte trong khóa.

    Số giả ngẫu nhiên cũng được tính toán dựa trên các giá trị trước đó, bắt đầu bằng một seed cho trước.
  2. Hàm rc4Cipher dùng để mã hóa.

    void rc4Cipher(Rc4Context* context, const unsigned char* input, unsigned char* output, size_t length){
    	unsigned char temp;
     
    	// Restore context
    	unsigned int i = context->i;
    	unsigned int j = context->j;
    	unsigned char* s = context->s;
     
    	// Encryption loop
    	while (length > 0)
    	{
    		// Adjust indices
    		i = (i + 1) % 256;
    		j = (j + s[i]) % 256;
     
    		// Swap the values of S[i] and S[j]
    		temp = s[i];
    		s[i] = s[j];
    		s[j] = temp;
     
    		// Valid input and output?
    		if (input != NULL && output != NULL)
    		{
    			//XOR the input data with the RC4 stream
    			*output = *input ^ s[(s[i] + s[j]) % 256];
     
    			//Increment data pointers
    			input++;
    			output++;
    		}
     
    		// Remaining bytes to process
    		length--;
    	}
     
    	// Save context
    	context->i = i;
    	context->j = j;
    }

Sử dụng hai hàm trên để mã hóa như sau:

// Initialization
Rc4Context ctx = { 0 };
 
// Key used for encryption
unsigned char* key = "maldev123";
rc4Init(&ctx, key, sizeof(key));
 
// Encryption //
// plaintext - The payload to be encrypted
// ciphertext - A buffer that is used to store the outputted encrypted data
rc4Cipher(&ctx, plaintext, ciphertext, sizeof(plaintext));

Tương tự, giải mã như sau:

// Initialization
Rc4Context ctx = { 0 };
 
// Key used to decrypt
unsigned char* key = "maldev123";
rc4Init(&ctx, key, sizeof(key));
 
// Decryption //
// ciphertext - Encrypted payload to be decrypted
// plaintext - A buffer that is used to store the outputted plaintext data
rc4Cipher(&ctx, ciphertext, plaintext, sizeof(ciphertext));
RC4 Encryption - Method 2

Method 2

Cách thứ 2 là sử dụng NativeAPI SystemFunction032 của Microsoft. Hàm này có tốc độ nhanh hơn và có implementation nhỏ hơn.

Seealso

Prototype của SystemFunction032:

 NTSTATUS SystemFunction032
 (
  struct ustring*       data,
  const struct ustring* key
 )

Kiểu ustring là một cấu trúc như sau:

typedef struct
{
	DWORD	Length;         // Size of the data to encrypt/decrypt
	DWORD	MaximumLength;  // Max size of the data to encrypt/decrypt, although often its the same as Length (USTRING.Length = USTRING.MaximumLength = X)
	PVOID	Buffer;         // The base address of the data to encrypt/decrypt
 
} USTRING;

Để sử dụng SystemFunction032, cần lấy ra địa chỉ của nó từ advapi32.dll.

fnSystemFunction032 SystemFunction032 = (fnSystemFunction032) GetProcAddress(LoadLibraryA("Advapi32"), "SystemFunction032");

Đoạn code trên thực hiện 3 việc:

  1. Nạp advapi32.dll sử dụng LoadLibraryA.
  2. Lấy địa chỉ của SystemFunction032 sử dụng GetProcAddress.
  3. Ép kiểu con trỏ trả về của GetProcAddress sang con trỏ hàm fnSystemFunction032.

Con trỏ hàm fnSystemFunction032 được định nghĩa như sau:

typedef NTSTATUS(NTAPI* fnSystemFunction032)(
	struct USTRING* Data,   // Structure of type USTRING that holds information about the buffer to encrypt / decrypt 
	struct USTRING* Key     // Structure of type USTRING that holds information about the key used while encryption / decryption
);

Đoạn code sử dụng SystemFunction032:

typedef struct
{
	DWORD	Length;
	DWORD	MaximumLength;
	PVOID	Buffer;
 
} USTRING;
 
typedef NTSTATUS(NTAPI* fnSystemFunction032)(
	struct USTRING* Data,
	struct USTRING* Key
);
 
/*
Helper function that calls SystemFunction032
* pRc4Key - The RC4 key use to encrypt/decrypt
* pPayloadData - The base address of the buffer to encrypt/decrypt
* dwRc4KeySize - Size of pRc4key (Param 1)
* sPayloadSize - Size of pPayloadData (Param 2)
*/
BOOL Rc4EncryptionViaSystemFunc032(IN PBYTE pRc4Key, IN PBYTE pPayloadData, IN DWORD dwRc4KeySize, IN DWORD sPayloadSize) {
 
	NTSTATUS STATUS	= NULL;
	
	USTRING Data = { 
		.Buffer         = pPayloadData,
		.Length         = sPayloadSize,
		.MaximumLength  = sPayloadSize
	};
 
	USTRING	Key = {
		.Buffer         = pRc4Key,
		.Length         = dwRc4KeySize,
		.MaximumLength  = dwRc4KeySize
	},
 
	fnSystemFunction032 SystemFunction032 = (fnSystemFunction032)GetProcAddress(LoadLibraryA("Advapi32"), "SystemFunction032");
 
	if ((STATUS = SystemFunction032(&Data, &Key)) != 0x0) {
		printf("[!] SystemFunction032 FAILED With Error: 0x%0.8X \n", STATUS);
		return FALSE;
	}
 
	return TRUE;
}

Có thể thấy, Rc4EncryptionViaSystemFunc032 là một hàm wrapper nhận vào 4 đối số, được dùng để khởi tạo cấu trúc của bản rõ và khóa mà sau đó sẽ được truyền vào hàm SystemFunction032.

Method 3

Một cách tương tự với Method 2 là sử dụng hàm SystemFunction033. Về bản chất, 2 hàm này có cùng một địa chỉ trên vùng nhớ nên chúng là như nhau.

Seealso

Encryption/Decryption Key Format

Có một số cách để lưu khóa mã hóa:

// Method 1
unsigned char* key = "maldev123";
 
// Method 2
// This is 'maldev123' represented as an array of hexadecimal bytes
unsigned char key[] = {
	0x6D, 0x61, 0x6C, 0x64, 0x65, 0x76, 0x31, 0x32, 0x33
};
 
// Method 3
// This is 'maldev123' represented in a hex/string form (hexadecimal escape sequence)
unsigned char* key = "\x6D\x61\x64\x65\x76\x31\x32\x33";
 
// Method 4 - better approach (via stack strings)
// This is 'maldev123' represented in an array of chars
unsigned char key[] = {
	'm', 'a', 'l', 'd', 'e', 'v', '1', '2', '3'
};

Warning

Việc gán cứng khóa ở trong source code là không nên do nó có thể dễ dàng bị trích xuất khi mã độc bị phân tích.

AES

Advanced Encryption Standard (AES) là một thuật toán mã hóa đối xứng. Có nhiều loại AES tùy thuộc vào kích thước khóa: AES128, AES192, and AES256 với AES256 sử dụng khóa có độ dài là 256-bit. Ngoài ra, AES còn có thể sử dụng nhiều chế độ mã hóa (modes of operation) chẳng hạn như CBC hoặc GCM. Một số chế độ mã hóa yêu cầu việc sử dụng một thành phần khác gọi là Initizalization Vector (IV), giúp thêm một lớp bảo mật vào quá trình mã hóa.

Thuật toán AES nhận vào 128-bit input và trả về 128-bit output. Cụ thể hơn, dữ liệu đầu vào cần phải là bội số của 128-bit (16-byte). Nếu payload không phải là bội số của 16-byte, ta cần phải thêm vào các byte đệm (padding).

Chúng ta sẽ tập trung vào biến thể AES256 sử dụng khóa có 256-bit (32-byte), 128-bit IV (16-byte) và chế độ mã hóa là CBC. Thuật toán sẽ được triển khai theo 2 cách:

  • Sử dụng thư viện bCrypt mà thực chất là tận dụng các hàm của Windows API.
  • Cách thử 2 là sử dụng tiny-AES-c.

Using WinAPIs (bCrypt Library)

AES Structure

Cấu trúc AES chứa các thông tin cần thiết để thực hiện mã hóa/giải mã:

typedef struct _AES {
 
	PBYTE	pPlainText;         // base address of the plain text data 
	DWORD	dwPlainSize;        // size of the plain text data
 
	PBYTE	pCipherText;        // base address of the encrypted data	
	DWORD	dwCipherSize;       // size of it (this can change from dwPlainSize in case there was padding)
 
	PBYTE	pKey;               // the 32 byte key
	PBYTE	pIv;                // the 16 byte iv
 
} AES, *PAES;

SimpleEncryption Wrapper

Hàm SimpleEncryption nhận vào 6 đối số và được dùng để khởi tạo cấu trúc AES. Sau khi được khởi tạo, nó sẽ gọi hàm InstallAesEncryption để mã hóa.

// Wrapper function for InstallAesEncryption that makes things easier
BOOL SimpleEncryption(IN PVOID pPlainTextData, IN DWORD sPlainTextSize, IN PBYTE pKey, IN PBYTE pIv, OUT PVOID* pCipherTextData, OUT DWORD* sCipherTextSize) {
 
	if (pPlainTextData == NULL || sPlainTextSize == NULL || pKey == NULL || pIv == NULL)
		return FALSE;
	
	// Intializing the struct
	AES Aes = {
		.pKey        = pKey,
		.pIv         = pIv,
		.pPlainText  = pPlainTextData,
		.dwPlainSize = sPlainTextSize
	};
 
	if (!InstallAesEncryption(&Aes)) {
		return FALSE;
	}
 
	// Saving output
	*pCipherTextData = Aes.pCipherText;
	*sCipherTextSize = Aes.dwCipherSize;
 
	return TRUE;
}

Có thể thấy, SimpleEncryption sẽ trả về 2 giá trị là pCipherTextDatasCipherTextSize tương ứng với bản mã và kích thước của nó. Ngoài ra, giá trị trả về của SimpleEncryption sẽ phụ thuộc vào việc mã hóa bằng hàm InstallAesEncryption có thành công hay không.

SimpleDecryption Wrapper

Tương tự, nó cũng nhận vào 6 đối số và gọi hàm InstallAesDecryption để giải mã. Sẽ có 2 giá trị được trả về là pPlainTextDatasPlainTextSize tương ứng với bản rõ và kích thước của nó.

// Wrapper function for InstallAesDecryption that make things easier
BOOL SimpleDecryption(IN PVOID pCipherTextData, IN DWORD sCipherTextSize, IN PBYTE pKey, IN PBYTE pIv, OUT PVOID* pPlainTextData, OUT DWORD* sPlainTextSize) {
 
	if (pCipherTextData == NULL || sCipherTextSize == NULL || pKey == NULL || pIv == NULL)
		return FALSE;
 
	// Intializing the struct
	AES Aes = {
		.pKey          = pKey,
		.pIv           = pIv,
		.pCipherText   = pCipherTextData,
		.dwCipherSize  = sCipherTextSize
	};
 
	if (!InstallAesDecryption(&Aes)) {
		return FALSE;
	}
 
	// Saving output
	*pPlainTextData = Aes.pPlainText;
	*sPlainTextSize = Aes.dwPlainSize;
 
	return TRUE;
}

Giá trị trả về của SimpleDecryption sẽ phụ thuộc vào việc giải mã bằng hàm InstallAesDecryption có thành công hay không.

Cryptographic Next Generation (CNG)

Là một tập các hàm mã hóa của Windows API mà ta sẽ sử dụng để mã hóa và giải mã.

Seealso

InstallAesEncryption Function

Hàm InstallAesEncryption nhận vào một đối số là con trỏ đến cấu trúc AES và sẽ cập nhật các thuộc tính của nó trong quá trình xử lý.

Trước tiên, ta sẽ khai báo một số biến như sau:

BOOL InstallAesEncryption(PAES pAes) {
 
  BOOL                  bSTATE           = TRUE;
  BCRYPT_ALG_HANDLE     hAlgorithm       = NULL;
  BCRYPT_KEY_HANDLE     hKeyHandle       = NULL;
 
  ULONG       		cbResult         = NULL;
  DWORD       		dwBlockSize      = NULL;
  
  DWORD       		cbKeyObject      = NULL;
  PBYTE       		pbKeyObject      = NULL;
 
  PBYTE      		pbCipherText     = NULL;
  DWORD       		cbCipherText     = NULL;
  
  // ...
  
  return bSTATE;
}

Giá trị trả về của hàm này là TRUE hoặc FALSE tùy thuộc vào kết quả của việc mã hóa.

Sau đây là các hàm của thư viện bCrypt mà ta sẽ sử dụng:

  • BCryptOpenAlgorithmProvider: Nạp Cryptographic Next Generation (CNG) provider có kiểu là BCRYPT_AES_ALGORITHM để sử dụng các hàm mã hóa.

    // Intializing "hAlgorithm" as AES algorithm Handle
    STATUS = BCryptOpenAlgorithmProvider(&hAlgorithm, BCRYPT_AES_ALGORITHM, NULL, 0);
    if (!NT_SUCCESS(STATUS)) {
    	printf("[!] BCryptOpenAlgorithmProvider Failed With Error: 0x%0.8X \n", STATUS);
    	bSTATE = FALSE; goto _EndOfFunc;
    }
  • BCryptGetProperty: Gọi hai lần, lần đầu để lấy giá trị của BCRYPT_OBJECT_LENGTH, lần thứ hai để lấy giá trị của BCRYPT_BLOCK_LENGTH.

    // Getting the size of the key object variable pbKeyObject. This is used by the BCryptGenerateSymmetricKey function later 
    STATUS = BCryptGetProperty(hAlgorithm, BCRYPT_OBJECT_LENGTH, (PBYTE)&cbKeyObject, sizeof(DWORD), &cbResult, 0);
    if (!NT_SUCCESS(STATUS)) {
    	printf("[!] BCryptGetProperty[1] Failed With Error: 0x%0.8X \n", STATUS);
    	bSTATE = FALSE; goto _EndOfFunc;
    }
     
    // Getting the size of the block used in the encryption. Since this is AES it must be 16 bytes.
    STATUS = BCryptGetProperty(hAlgorithm, BCRYPT_BLOCK_LENGTH, (PBYTE)&dwBlockSize, sizeof(DWORD), &cbResult, 0);
    if (!NT_SUCCESS(STATUS)) {
    printf("[!] BCryptGetProperty[2] Failed With Error: 0x%0.8X \n", STATUS);
    	bSTATE = FALSE; goto _EndOfFunc;
    }
  • BCryptSetProperty: Khởi tạo giá trị của thuộc tính BCRYPT_CHAINING_MODEBCRYPT_CHAIN_MODE_CBC.

    // Checking if block size is 16 bytes
    if (dwBlockSize != 16) {
    	bSTATE = FALSE; goto _EndOfFunc;
    }
     
    // Allocating memory for the key object 
    pbKeyObject = (PBYTE)HeapAlloc(GetProcessHeap(), 0, cbKeyObject);
    if (pbKeyObject == NULL) {
    	bSTATE = FALSE; goto _EndOfFunc;
    }
     
    // Setting Block Cipher Mode to CBC. This uses a 32 byte key and a 16 byte IV.
    STATUS = BCryptSetProperty(hAlgorithm, BCRYPT_CHAINING_MODE, (PBYTE)BCRYPT_CHAIN_MODE_CBC, sizeof(BCRYPT_CHAIN_MODE_CBC), 0);
    if (!NT_SUCCESS(STATUS)) {
    	printf("[!] BCryptSetProperty Failed With Error: 0x%0.8X \n", STATUS);
    	bSTATE = FALSE; goto _EndOfFunc;
    }
  • BCryptGenerateSymmetricKey: Tạo đối tượng khóa từ AES key đầu vào.

    // Generating the key object from the AES key "pAes->pKey". The output will be saved in pbKeyObject and will be of size cbKeyObject 
    STATUS = BCryptGenerateSymmetricKey(hAlgorithm, &hKeyHandle, pbKeyObject, cbKeyObject, (PBYTE)pAes->pKey, KEYSIZE, 0);
    if (!NT_SUCCESS(STATUS)) {
    	printf("[!] BCryptGenerateSymmetricKey Failed With Error: 0x%0.8X \n", STATUS);
    	bSTATE = FALSE; goto _EndOfFunc;
    }
  • BCryptEncrypt: Mã hóa dữ liệu. Gọi hai lần: lần đầu để lấy kích thước dữ liệu mã hóa và cấp phát bộ nhớ đệm, lần hai để mã hóa và lưu bãn mã vào bộ nhớ đệm.

    // Running BCryptEncrypt first time with NULL output parameters to retrieve the size of the output buffer which is saved in cbCipherText
    STATUS = BCryptEncrypt(hKeyHandle, (PUCHAR)pAes->pPlainText, (ULONG)pAes->dwPlainSize, NULL, pAes->pIv, IVSIZE, NULL, 0, &cbCipherText, BCRYPT_BLOCK_PADDING);
    if (!NT_SUCCESS(STATUS)) {
    	printf("[!] BCryptEncrypt[1] Failed With Error: 0x%0.8X \n", STATUS);
    	bSTATE = FALSE; goto _EndOfFunc;
    }
     
    // Allocating enough memory for the output buffer, cbCipherText
    pbCipherText = (PBYTE)HeapAlloc(GetProcessHeap(), 0, cbCipherText);
    if (pbCipherText == NULL) {
    	bSTATE = FALSE; goto _EndOfFunc;
    }
     
    // Running BCryptEncrypt again with pbCipherText as the output buffer
    STATUS = BCryptEncrypt(hKeyHandle, (PUCHAR)pAes->pPlainText, (ULONG)pAes->dwPlainSize, NULL, pAes->pIv, IVSIZE, pbCipherText, cbCipherText, &cbResult, BCRYPT_BLOCK_PADDING);
    if (!NT_SUCCESS(STATUS)) {
    	printf("[!] BCryptEncrypt[2] Failed With Error: 0x%0.8X \n", STATUS);
    	bSTATE = FALSE; goto _EndOfFunc;
    }
  • BCryptDestroyKey: Dùng để dọn dẹp bằng cách hủy đối tượng khóa đã tạo qua BCryptGenerateSymmetricKey.

  • BCryptCloseAlgorithmProvider: Dùng để dọn dẹp bằng cách đóng handle của provider đã tạo qua BCryptOpenAlgorithmProvider.

    Hai hàm trên sẽ được gọi ở label __EndOfFunc:

    // Clean up
    _EndOfFunc:
    if (hKeyHandle) 
    	BCryptDestroyKey(hKeyHandle);
    if (hAlgorithm) 
    	BCryptCloseAlgorithmProvider(hAlgorithm, 0);
    if (pbKeyObject) 
    	HeapFree(GetProcessHeap(), 0, pbKeyObject);
    if (pbCipherText != NULL && bSTATE) {
    	// If everything worked, save pbCipherText and cbCipherText 
    	pAes->pCipherText 	= pbCipherText;
    	pAes->dwCipherSize 	= cbCipherText;
    }

InstallAesDecryption Function

Hàm InstallAesDecryption cũng tương tự: nó sẽ nhận vào một con trỏ của cấu trúc AES và sẽ thực hiện giải mã. Kết quả trả về là TRUE hoặc FALSE, cho biết kết quả của việc giải mã.

Chỉ có duy nhất một chỗ khác biệt là thay vì dùng hàm BCryptEncrypt thì ta sẽ dùng hàm BCryptDecrypt để giải mã.

// Running BCryptDecrypt again with pbPlainText as the output buffer
STATUS = BCryptDecrypt(hKeyHandle, (PUCHAR)pAes->pCipherText, (ULONG)pAes->dwCipherSize, NULL, pAes->pIv, IVSIZE, pbPlainText, cbPlainText, &cbResult, BCRYPT_BLOCK_PADDING);
if (!NT_SUCCESS(STATUS)) {
	printf("[!] BCryptDecrypt[2] Failed With Error: 0x%0.8X \n", STATUS);
	bSTATE = FALSE; goto _EndOfFunc;
}

Additional Helper Functions

Có thêm 2 hàm hỗ trợ là PrintHexData để in ra dữ liệu dưới dạng hex và GenerateRandomBytes để tạo ra khóa hoặc IV ngẫu nhiên.

// Print the input buffer as a hex char array
VOID PrintHexData(LPCSTR Name, PBYTE Data, SIZE_T Size) {
 
  printf("unsigned char %s[] = {", Name);
 
  for (int i = 0; i < Size; i++) {
    	if (i % 16 == 0)
      	    printf("\n\t");
	    
    	if (i < Size - 1) {
            printf("0x%0.2X, ", Data[i]);
    	else
      	    printf("0x%0.2X ", Data[i]);
  }
 
  printf("};\n\n\n");
  
}
 
// Generate random bytes of size sSize
VOID GenerateRandomBytes(PBYTE pByte, SIZE_T sSize) {
 
  for (int i = 0; i < sSize; i++) {
    	pByte[i] = (BYTE)rand() % 0xFF;
  }
 
}

Padding

Về vấn đề padding cho các input không phải là bội của 16-byte: chúng ta sử dụng cờ BCRYPT_BLOCK_PADDING khi gọi hàm BCryptEncrypt và hàm BCryptDecrypt để tự động thêm padding.

Main Function

Ví dụ về việc sử dụng hàm SimpleEncryption để mã hóa:

// The plaintext, in hex format, that will be encrypted
// this is the following string in hex "This is a plain text string, we'll try to encrypt/decrypt !"
unsigned char Data[] = {
	0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x61, 0x20, 0x70, 0x6C,
	0x61, 0x69, 0x6E, 0x20, 0x74, 0x65, 0x78, 0x74, 0x20, 0x73, 0x74, 0x72,
	0x69, 0x6E, 0x67, 0x2C, 0x20, 0x77, 0x65, 0x27, 0x6C, 0x6C, 0x20, 0x74,
	0x72, 0x79, 0x20, 0x74, 0x6F, 0x20, 0x65, 0x6E, 0x63, 0x72, 0x79, 0x70,
	0x74, 0x2F, 0x64, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, 0x20, 0x21
};
 
int main() {
 
	BYTE pKey [KEYSIZE];                    // KEYSIZE is 32 bytes
	BYTE pIv [IVSIZE];                      // IVSIZE is 16 bytes
 
	srand(time(NULL));                      // The seed to generate the key. This is used to further randomize the key.
	GenerateRandomBytes(pKey, KEYSIZE);     // Generating a key with the helper function
	
	srand(time(NULL) ^ pKey[0]);            // The seed to generate the IV. Use the first byte of the key to add more randomness.
	GenerateRandomBytes(pIv, IVSIZE);       // Generating the IV with the helper function
 
	// Printing both key and IV onto the console 
	PrintHexData("pKey", pKey, KEYSIZE);
	PrintHexData("pIv", pIv, IVSIZE);
 
	// Defining two variables the output buffer and its respective size which will be used in SimpleEncryption
	PVOID pCipherText = NULL;
	DWORD dwCipherSize = NULL;
	
	// Encrypting
	if (!SimpleEncryption(Data, sizeof(Data), pKey, pIv, &pCipherText, &dwCipherSize)) {
		return -1;
	}
 
	// Print the encrypted buffer as a hex array
	PrintHexData("CipherText", pCipherText, dwCipherSize);
	
	// Clean up
	HeapFree(GetProcessHeap(), 0, pCipherText);
	system("PAUSE");
	return 0;
}

Ví dụ về việc sử dụng hàm SimpleDecryption để mã hóa:

// the key printed to the screen
unsigned char pKey[] = {
		0x3E, 0x31, 0xF4, 0x00, 0x50, 0xB6, 0x6E, 0xB8, 0xF6, 0x98, 0x95, 0x27, 0x43, 0x27, 0xC0, 0x55,
		0xEB, 0xDB, 0xE1, 0x7F, 0x05, 0xFE, 0x65, 0x6D, 0x0F, 0xA6, 0x5B, 0x00, 0x33, 0xE6, 0xD9, 0x0B };
 
// the iv printed to the screen
unsigned char pIv[] = {
		0xB4, 0xC8, 0x1D, 0x1D, 0x14, 0x7C, 0xCB, 0xFA, 0x07, 0x42, 0xD9, 0xED, 0x1A, 0x86, 0xD9, 0xCD };
 
 
// the encrypted buffer printed to the screen, which is:
unsigned char CipherText[] = {
		0x97, 0xFC, 0x24, 0xFE, 0x97, 0x64, 0xDF, 0x61, 0x81, 0xD8, 0xC1, 0x9E, 0x23, 0x30, 0x79, 0xA1,
		0xD3, 0x97, 0x5B, 0xAE, 0x29, 0x7F, 0x70, 0xB9, 0xC1, 0xEC, 0x5A, 0x09, 0xE3, 0xA4, 0x44, 0x67,
		0xD6, 0x12, 0xFC, 0xB5, 0x86, 0x64, 0x0F, 0xE5, 0x74, 0xF9, 0x49, 0xB3, 0x0B, 0xCA, 0x0C, 0x04,
		0x17, 0xDB, 0xEF, 0xB2, 0x74, 0xC2, 0x17, 0xF6, 0x34, 0x60, 0x33, 0xBA, 0x86, 0x84, 0x85, 0x5E };
 
int main() {
 
	// Defining two variables the output buffer and its respective size which will be used in SimpleDecryption
	PVOID	pPlaintext  = NULL;
	DWORD	dwPlainSize = NULL;
 
	// Decrypting
	if (!SimpleDecryption(CipherText, sizeof(CipherText), pKey, pIv, &pPlaintext, &dwPlainSize)) {
		return -1;
	}
	
	// Printing the decrypted data to the screen in hex format
	PrintHexData("PlainText", pPlaintext, dwPlainSize);
	
	// this will print: "This is a plain text string, we'll try to encrypt/decrypt !"
	printf("Data: %s \n", pPlaintext);
	
	// Clean up
	HeapFree(GetProcessHeap(), 0, pPlaintext);
	system("PAUSE");
	return 0;
}

Drawbacks

Điểm yếu của bCrypt là việc dùng các Windows API có thể bị các security solution phát hiện ra thông qua Import Address Table (IAT).

Ví dụ:

Using Tiny-AES Library

Thay vì sử dụng các hàm của Windows API mà dễ bị phát hiện, chúng ta có thể dùng thư viện tiny-AES-c.

Setting Up Tiny-AES

Để sử dụng, ta cần:

  1. Include file aes.hpp (C++) hoặc aes.h (C) vào project.
  2. Thêm file aes.c vào project.

Drawbacks

Một số hạn chế của thư viện:

  1. Không hỗ trợ padding nên tất cả các input phải là bội của 16-byte.
  2. Các mảng gán cứng ở trong mã nguồn có thể bị đánh dấu và phát hiện bởi các security solution do chúng là cố định và bắt buộc đối với thuật toán AES. Tức là, dù cho có thay đổi cách triển khai thì những mảng này vẫn còn. Để bypass việc này, chúng ta có thể che khuất chúng đi chẳng hạn như thực hiện XOR và chỉ giải mã khi cần.

Custom Padding Function

Do không có padding, chúng ta phải tự viết hàm padding:

BOOL PaddBuffer(IN PBYTE InputBuffer, IN SIZE_T InputBufferSize, OUT PBYTE* OutputPaddedBuffer, OUT SIZE_T* OutputPaddedSize) {
 
	PBYTE	PaddedBuffer        = NULL;
	SIZE_T	PaddedSize          = NULL;
 
	// calculate the nearest number that is multiple of 16 and saving it to PaddedSize
	PaddedSize = InputBufferSize + 16 - (InputBufferSize % 16);
	// allocating buffer of size "PaddedSize"
	PaddedBuffer = (PBYTE)HeapAlloc(GetProcessHeap(), 0, PaddedSize);
	if (!PaddedBuffer){
		return FALSE;
	}
	// cleaning the allocated buffer
	ZeroMemory(PaddedBuffer, PaddedSize);
	// copying old buffer to new padded buffer
	memcpy(PaddedBuffer, InputBuffer, InputBufferSize);
	//saving results :
	*OutputPaddedBuffer = PaddedBuffer;
	*OutputPaddedSize   = PaddedSize;
 
	return TRUE;
}

Có thể thấy, để tính số byte cần đệm, ta lấy 16 trừ đi phần dư của kích thước buffer khi chia cho 16.

Tiny-AES Encryption

Ta sẽ sử dụng hàm AES_init_ctx_iv và hàm AES_CBC_encrypt_buffer từ thư viện Tiny-AES để thực hiện mã hóa với chế độ CBC như sau:

#include <Windows.h>
#include <stdio.h>
#include "aes.h"
 
// "this is plaintext string, we'll try to encrypt... lets hope everything goes well :)" in hex
// since the upper string is 82 byte in size, and 82 is not mulitple of 16, we can't encrypt this directly using tiny-aes
unsigned char Data[] = {
	0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x70, 0x6C, 0x61, 0x6E,
	0x65, 0x20, 0x74, 0x65, 0x78, 0x74, 0x20, 0x73, 0x74, 0x69, 0x6E, 0x67,
	0x2C, 0x20, 0x77, 0x65, 0x27, 0x6C, 0x6C, 0x20, 0x74, 0x72, 0x79, 0x20,
	0x74, 0x6F, 0x20, 0x65, 0x6E, 0x63, 0x72, 0x79, 0x70, 0x74, 0x2E, 0x2E,
	0x2E, 0x20, 0x6C, 0x65, 0x74, 0x73, 0x20, 0x68, 0x6F, 0x70, 0x65, 0x20,
	0x65, 0x76, 0x65, 0x72, 0x79, 0x74, 0x68, 0x69, 0x67, 0x6E, 0x20, 0x67,
	0x6F, 0x20, 0x77, 0x65, 0x6C, 0x6C, 0x20, 0x3A, 0x29, 0x00
};
 
 
 
int main() {
	// struct needed for Tiny-AES library
	struct AES_ctx ctx;
 
 
	BYTE pKey[KEYSIZE];                             // KEYSIZE is 32 bytes
	BYTE pIv[IVSIZE];                               // IVSIZE is 16 bytes
		
 
	srand(time(NULL));                              // the seed to generate the key
	GenerateRandomBytes(pKey, KEYSIZE);             // generating the key bytes
	
	srand(time(NULL) ^ pKey[0]);                    // The seed to generate the IV. Use the first byte of the key to add more randomness.
	GenerateRandomBytes(pIv, IVSIZE);               // Generating the IV
 
	// Prints both key and IV to the console
	PrintHexData("pKey", pKey, KEYSIZE);
	PrintHexData("pIv", pIv, IVSIZE);
 
	// Initializing the Tiny-AES Library
	AES_init_ctx_iv(&ctx, pKey, pIv);
 
 
	// Initializing variables that will hold the new buffer base address in the case where padding is required and its size
	PBYTE	PaddedBuffer        = NULL;
	SIZE_T	PAddedSize          = NULL;
 
	// Padding the buffer, if required
	if (sizeof(Data) % 16 != 0){
		PaddBuffer(Data, sizeof(Data), &PaddedBuffer, &PAddedSize);
		// Encrypting the padded buffer instead
		`AES_CBC_encrypt_buffer`(&ctx, PaddedBuffer, PAddedSize);
		// Printing the encrypted buffer to the console
		PrintHexData("CipherText", PaddedBuffer, PAddedSize);
	}
	// No padding is required, encrypt 'Data' directly
	else {
		AES_CBC_encrypt_buffer(&ctx, Data, sizeof(Data));
		// Printing the encrypted buffer to the console
		PrintHexData("CipherText", Data, sizeof(Data));
	}
	// Freeing PaddedBuffer, if necessary
	if (PaddedBuffer != NULL){
		HeapFree(GetProcessHeap(), 0, PaddedBuffer);
	}
	system("PAUSE");
	return 0;
}

Tiny-AES Decryption

Quá trình giải mã sẽ sử dụng hàm AES_CBC_decrypt_buffer thay vì AES_CBC_encrypt_buffer:

#include <Windows.h>
#include <stdio.h>
#include "aes.h"
 
// Key
unsigned char pKey[] = {
		0xFA, 0x9C, 0x73, 0x6C, 0xF2, 0x3A, 0x47, 0x21, 0x7F, 0xD8, 0xE7, 0x1A, 0x4F, 0x76, 0x1D, 0x84,
		0x2C, 0xCB, 0x98, 0xE3, 0xDC, 0x94, 0xEF, 0x04, 0x46, 0x2D, 0xE3, 0x33, 0xD7, 0x5E, 0xE5, 0xAF };
 
// IV
unsigned char pIv[] = {
		0xCF, 0x00, 0x86, 0xE1, 0x6D, 0xA2, 0x6B, 0x06, 0xC4, 0x8B, 0x1F, 0xDA, 0xB6, 0xAB, 0x21, 0xF1 };
 
// Encrypted data, multiples of 16 bytes
unsigned char CipherText[] = {
		0xD8, 0x9C, 0xFE, 0x68, 0x97, 0x71, 0x5E, 0x5E, 0x79, 0x45, 0x3F, 0x05, 0x4B, 0x71, 0xB9, 0x9D,
		0xB2, 0xF3, 0x72, 0xEF, 0xC2, 0x64, 0xB2, 0xE8, 0xD8, 0x36, 0x29, 0x2A, 0x66, 0xEB, 0xAB, 0x80,
		0xE4, 0xDF, 0xF2, 0x3C, 0xEE, 0x53, 0xCF, 0x21, 0x3A, 0x88, 0x2C, 0x59, 0x8C, 0x85, 0x26, 0x79,
		0xF0, 0x04, 0xC2, 0x55, 0xA8, 0xDE, 0xB4, 0x50, 0xEE, 0x00, 0x65, 0xF8, 0xEE, 0x7C, 0x54, 0x98,
		0xEB, 0xA2, 0xD5, 0x21, 0xAA, 0x77, 0x35, 0x97, 0x67, 0x11, 0xCE, 0xB3, 0x53, 0x76, 0x17, 0xA5,
		0x0D, 0xF6, 0xC3, 0x55, 0xBA, 0xCD, 0xCF, 0xD1, 0x1E, 0x8F, 0x10, 0xA5, 0x32, 0x7E, 0xFC, 0xAC };
 
 
 
int main() {
 
	// Struct needed for Tiny-AES library
	struct AES_ctx ctx;
	// Initializing the Tiny-AES Library
	AES_init_ctx_iv(&ctx, pKey, pIv);
 
	// Decrypting
	AES_CBC_decrypt_buffer(&ctx, CipherText, sizeof(CipherText));
	 
	// Print the decrypted buffer to the console
	PrintHexData("PlainText", CipherText, sizeof(CipherText));
 
	// Print the string
	printf("Data: %s \n", CipherText);
 
	// exit
	system("PAUSE");
	return 0;
}
list
from outgoing([[MalDev - Payload Encryption]])
sort file.ctime asc

Resources