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à:
-
rc4Init
dùng để khởi tạoRc4Context
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ảngs
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.
-
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:
- Nạp
advapi32.dll
sử dụngLoadLibraryA
. - Lấy địa chỉ của
SystemFunction032
sử dụngGetProcAddress
. - Ép kiểu con trỏ trả về của
GetProcAddress
sang con trỏ hàmfnSystemFunction032
.
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à pCipherTextData
và sCipherTextSize
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à pPlainTextData
và sPlainTextSize
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ủaBCRYPT_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_MODE
làBCRYPT_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:
- Include file
aes.hpp
(C++) hoặcaes.h
(C) vào project. - Thêm file
aes.c
vào project.
Drawbacks
Một số hạn chế của thư viện:
- Không hỗ trợ padding nên tất cả các input phải là bội của 16-byte.
- 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;
}
Related
list
from outgoing([[MalDev - Payload Encryption]])
sort file.ctime asc