CryptoAPI in Malware
1. Trojans using CryptoAPI
In this section we analyze some bankers and Remote Access Trojans, or RATs, that use CryptoAPI. These particular families have been selected since they use the library in distinct, interesting ways.
1.1 PandaBanker
PandaBanker is a Zeus-like banking malware. Like all other Zeus-based bankers, the malware carries an encrypted configuration in its own binary, and in this case decrypts the static configuration with AES algorithm (AES key and IV are stored into the binary, close to the config). However, it doesn’t call CryptoAPI to perform the AES decryption, but rather carries the AES algorithm within its own code. In the decrypted static configuration it carries an RSA public key in DER format:
It uses this RSA key to encrypt communications with the C2. To perform RSA encryption, PandaBanker imports and calls CryptoAPI’s frequently used functions. (CryptEncrypt, CryptAcquireContext, CryptImportPublicKeyInfo, CryptDecodeObjectEx,…). It then imports the DER formatted key with the following code:
And the RSA encryption is performed here:
As we can see, after obtaining the ciphertext from CryptoAPI, PandaBanker then reverses it. Unlike other cryptographic libraries, CryptoAPI returns the resulting ciphertext in reverse order. This is a consequence of the fact that CryptoAPI BLOBs, where keys’ parameters are stored, keep all information in little-endian. PandaBanker then reverses it in order to achieve a more standard format that the C2 is able to decrypt. If we explore code of one of the leaked versions of the PandaBanker panel, we find a directory /gate/cert/ with a private PEM formatted RSA key (and its associated public key) used to decrypt queries received from the bots, which carry the same public key:
Additionally, we find a PHP script named cryptography.php at /gate/libs/ that contains the code that imports the private key in /gate/cert/private.pm and decrypts the queries it receives:
This PHP code is based on openssl library, and cannot directly decrypt ciphertexts coming from CryptoAPI. As a result, PandaBanker’s bot reverses the ciphertext after encryption.
1.2 AgentTesla
The following code is a strings decryptor from AgentTesla malware:
It decrypts the strings by using AES algorithm in CBC mode, and uses the .Net class RijndaelManaged. To create an AES key, it derives it from a password with the class PasswordDeriveBytes. .Net documentation describes the process thus, “this class uses an extension of the PBKDF1 algorithm defined in the PKCS#5 v2.0 standard to derive bytes suitable for use as key material from a password. The standard is documented in IETF RRC 2898.” PBKDF1 and PBKDF2 (Password-Based Key Derivation Function 2) are key derivation functions with a sliding computational cost, which aim to reduce the vulnerability of encrypted keys to brute force attacks. Brute force attacks are outlined in our in-depth report into the Credential Theft Ecosystem. The implementation of Microsoft’s PasswordDeriveBytes is a variation of PBKDF1. For example, if you try to use PBKDF1 or PBKDF2 Python classes to decrypt AgentTesla strings, it won’t work, in spite of the fact that you are using the same keys and parameters. This is because Microsoft’s implementation is different. We have written a Python implementation of Microsoft’s PasswordDeriveBytes. Below is an example where we decrypt AgentTesla’s string:
1.3 UrlZone
Using RSA, UrlZone banking malware encrypts communications with the C2. It is fairly frequent to find malware that stores a public RSA key into its binary in formats like PEM or DER. However, UrlZone stores it directly as a Microsoft BLOB. This is an RSA public key BLOB recovered from an UrlZone sample:
All the information contained in CryptoAPI’s BLOBs fields is stored in little-endian, so if we want, for example, to rewrite the communications protocol for UrlZone in Python, we would need to import these BLOBs. We could call openssl to transform this BLOB into another format that we could import with Python libraries… openssl rsa -pubin -inform MS\ PUBLICKEYBLOB -in <path blob> -outform PEM -out <path pem> …or we can extract the exponent and the modulus from the BLOB and use it directly in Python:
1.4 OrcaKiller RAT
This RAT uses CryptoAPI to generate a random key, encrypting the data it sends to the C2 with RC4. First, the malware calls CryptGenRandom to fill a 6-byte buffer with random data.
After this, it concatenates the strings ‘OrcaKiller’ to this random buffer and calculates MD5 using CryptoAPI too. It derives the RC4 from this MD5 value:
It then encrypts/decrypts data with RC4 by calling CryptoAPI’s CryptEncrypt/CryptDecrypt:
2. 2. Ransomware using CryptoAPI
It is usual to find trojans that use CryptoAPI, but in the case of ransomware we find even more families relying on this library, mainly to encrypt files (in addition to encrypting communications, decrypt configs, etc.). Cryptography’s usage has evolved in ransomware, growing in complexity over the years. For example, Rector ransomware carried a unique RSA public key, and encrypted files in the infected computer it. With each evolution, new binaries were created by the author with new embedded RSA public keys. But for each new binary, the same key was used, and if that sample infected multiple machines, all of them could be decrypted with the same RSA private-key. In this case, once a victim paid the rescue, all the victims infected with the same public key could decrypt its files (in fact, Kaspersky developed a decryption tool for this family that simply carried all the author’s known private-keys up to now). Newer ransomware has evolved new cryptographic schemas. A typical behavior has been that the malware carries an RSA public-key embedded into its code, and it generates a new AES symmetric-key for each new victim (or for each file to be encrypted). It then encrypts files with the newly-generated AES key, before encrypting this AES key with the RSA public-key (only the author has the associated private-key) and destroying the AES key. In this way, with the RSA private-key the author can recover the AES key and decrypt the files of a victim who has paid the ransom, but that same AES key is totally useless to other victims.
2.1 Wannacry
WannaCry provides an interesting use case for CryptoAPI, in addition to exhibiting rather complex cryptographic behavior. For each victim, a 2048-bit RSA key-pair is generated. Both public and private keys, victim-specific and RSA-generated, are kept on disk (00000000.pky and 00000000.eky). For each file the ransomware encrypts, it generates a new AES CBC key (128-bit key). Once it has encrypted the file with that AES key, it encrypts the file-specific AES key with the victim-specific RSA-generated public key. It then destroys the AES key and continues with another file. Additionally, the malware carries a public RSA key in BLOB format embedded into its data. Only the author knows the associated private RSA key. The malware encrypts the victim-specific RSA-generated private key with this public RSA embedded into the malware’s data (so only the author can decrypt and get the victim-specific private key, necessary to decrypt each AES key used to encrypt each file). The following code was extracted from a WannaCry sample and it is responsible of generating the victim-specific key-pair:
It generates the victim-specific key-pair by using CryptGenKey from CryptoAPI:
And with the following code it exports and encrypts the victim-specific RSA private key:
3. Rewriting CryptoAPI code in Python
In this section we share some proof-of-concept C / C# using CryptoAPI and their equivalent Python scripts.
3.1 POC RSA Encryption
3.1.1. C Code: RSA encryption using CryptoAPI
#include <stdio.h> #include <tchar.h> #include <windows.h> #pragma comment(lib, “Crypt32″) int main(int argc, _TCHAR* argv[]) { BYTE* plain = (BYTE*)”lalalalalalalalalalalalalalalala”; DWORD dwplainLen = (DWORD)strlen((char*)plain); char * pem = “—–BEGIN RSA PRIVATE KEY—–” “MIICXQIBAAKBgQCnPswx7NWNY5AGaD7D9LVA7+aNJ/z17Pt/6s7CvK519iw7oHoG” “1YcifTaHIfwZz1D3XtcsyrGFb9UG4QRstyO+Q2d9mwjsX/LSE71V395KiVFGtQVe” “/b0CjlCpRm1yfs1WbahLXeYM/kD5RDd9CZp2E0pokka14byKE1snxZNT4QIDAQAB” “AoGBAJCwWYQPuykZK67/XN22xWCqq7EPGV/BaEvgXoRHLD/Ne7MSQL/M155U6Wm7” “UxkZLJj2Kf4MVcx1Vb0fyu4q+vXn39DNfTa8xv/Cy7Fb1cM9WFndqaabARC677x6” “ugqfE/boSsVC/Kjo1VuVmsXM+CQYkt1RyvRYIIjwxAjxUIfhAkEAzyClC06ASDR/” “nhejvTZbfie0ymHS9Mck/3zdx77SnV5dX5GQgNIr4bvkpTceFoZQQtOHYaEIfY8Z” “TJklkA05bQJBAM61GlUO6vEBMGXv3rqocZJUbB2bqY+mGh13xQ3dPeyC0lBaxnf3” “OgaqUdL6boA4+j1pHjIeLVGW1A8F32Zzz8UCQC5Gitk11q9LG2AExA5YAKT01g2J” “QYpym6+BBEPGPGPwW0goy3IcgrVSN0k6QTyjEXd8rvh+89ipiet1I9FFQxkCQBNx” “DSz63jYUuoyb5wL/XM86iYCvZ19PbB1hanNHX8+i7k0IfKpD4n1F/7QsQcBlm4Oz” “I1frZq/J0+Al2UE1m1ECQQCO7csOphDIjkuVQErkErhEmNOnZf/7wl5pEapVhrge” “A4IdLRgbJ6od+Dq6zC2tTHbRI/62xGv0Tey2mIDwD1d0” “—–END RSA PRIVATE KEY—–“; HCRYPTPROV hProv = NULL; HCRYPTPROV hProvAES = NULL; HCRYPTKEY hKey = NULL; HCRYPTKEY hKeyAES = NULL; BOOL bStatus = FALSE; LPBYTE pEncryptedData = NULL; DWORD i, dwKeyLen = 0, dwValLen = 0; DWORD dwEncryptedDataLen = 0; DWORD err = 0; bStatus = CryptAcquireContext(&hProv, NULL, MS_ENHANCED_PROV, PROV_RSA_FULL, 0); if (bStatus == 0) { bStatus = CryptAcquireContext(&hProv, NULL, MS_ENHANCED_PROV, PROV_RSA_FULL, CRYPT_NEWKEYSET); } bStatus = CryptAcquireContext(&hProvAES, NULL, MS_ENH_RSA_AES_PROV, PROV_RSA_AES, 0); if (bStatus == 0) { bStatus = CryptAcquireContext(&hProvAES, NULL, MS_ENH_RSA_AES_PROV, PROV_RSA_AES, 0); } printf(“Plain:\n”); for (i = 0; i< dwplainLen; i++) printf(“\\x%.2X”, plain[i]); printf(“\n\n”); //Gen random AES key BYTE * plainAES = (BYTE*)malloc(dwplainLen*3); memcpy(plainAES, plain, dwplainLen); bStatus = CryptGenKey(hProvAES, CALG_AES_128, CRYPT_EXPORTABLE, &hKeyAES); //use CBC cipher mode DWORD mode = CRYPT_MODE_CBC; bStatus = CryptSetKeyParam(hKeyAES, KP_MODE, (BYTE*)&mode, 0); //PKCS 5 padding method DWORD padData = PKCS5_PADDING; bStatus = CryptSetKeyParam(hKeyAES, KP_PADDING, (BYTE*)&padData, 0); //Set IV BYTE *iv = (BYTE*) “\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x01”; bStatus = CryptSetKeyParam(hKeyAES, KP_IV, iv, 0); // Export and print AES key BYTE exportKey[1024]; DWORD exportKeyLen; bStatus = CryptExportKey(hKeyAES, NULL, PLAINTEXTKEYBLOB, 0, exportKey, &exportKeyLen); printf(“Generated AES key:\n”); for (i = 0; i< exportKeyLen; i++) printf(“\\x%.2X”, exportKey[i]); printf(“\n\n”); //Encrypt with random AES key bStatus = CryptEncrypt(hKeyAES, NULL, TRUE, 0, (BYTE*)plainAES, &dwplainLen, dwplainLen*3); plain = plainAES; //in spite of the fact that our plaintext length is aligned to 128 bits, //CryptoAPI add 16 unuseful padding bytes Lets remove that unuseful padding dwplainLen -= 16; printf(“Plain+AES:\n”); for (i = 0; i< dwplainLen; i++) printf(“\\x%.2X”, plain[i]); printf(“\n\n”); //key PEM -> key BLOB DWORD dwBufferLen; LPBYTE pbBuffer; bStatus = CryptStringToBinaryA(pem, 0, CRYPT_STRING_BASE64HEADER, NULL, &dwBufferLen, NULL, NULL); pbBuffer = (LPBYTE)malloc(dwBufferLen); bStatus = CryptStringToBinaryA(pem, 0, CRYPT_STRING_BASE64HEADER, pbBuffer, &dwBufferLen, NULL, NULL); DWORD cbKeyBlob=0; bStatus = CryptDecodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, PKCS_RSA_PRIVATE_KEY, (BYTE*)pbBuffer, dwBufferLen, 0, NULL, NULL, &cbKeyBlob); LPBYTE pbKeyBlob = (LPBYTE)malloc(cbKeyBlob); bStatus = CryptDecodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, PKCS_RSA_PRIVATE_KEY, (BYTE*)pbBuffer, dwBufferLen, 0, NULL, pbKeyBlob, &cbKeyBlob); bStatus = CryptImportKey(hProv, pbKeyBlob, cbKeyBlob, 0, 0, &hKey); dwValLen = sizeof(DWORD); bStatus = CryptGetKeyParam(hKey, KP_KEYLEN, (LPBYTE) &dwKeyLen, &dwValLen, 0); //CryptoAPI docu describes ZERO_PADDING or //RANDOM_PADDING padding types, but they dont work, it only works PKCS5_PADDING //Previously we have hooked ProcessPrng and in this way we can control the padding //DWORD paddingtype = ZERO_PADDING; -> DISABLED; ONLY PKCS5_PADDING WORKS //DWORD paddingtype = RANDOM_PADDING; -> DISABLED; ONLY PKCS5_PADDING WORKS //DWORD paddingtype = PKCS5_PADDING; -> NOT NECESARY TO SET THIS, DEFAULT //CryptSetKeyParam(hKey, KP_PADDING, (BYTE*)&paddingtype, 0); //GetLastError(); dwKeyLen = (dwKeyLen + 7) / 8; // tranform to bytes length pEncryptedData = (LPBYTE) LocalAlloc(0, dwKeyLen); CopyMemory(pEncryptedData, plain, dwplainLen); dwEncryptedDataLen = dwplainLen; bStatus = CryptEncrypt(hKey, NULL, TRUE, 0, pEncryptedData, &dwEncryptedDataLen, dwKeyLen); printf(“Plain+AES+RSA:\n”); for (i=0; i< dwEncryptedDataLen; i++) printf(“\\x%.2X”, pEncryptedData[i]); printf(“\n\n”); bStatus = CryptDecrypt(hKey, NULL, TRUE, 0, pEncryptedData, &dwEncryptedDataLen); printf(“Plain+AES+RSA-RSA:\n”); for (i = 0; i< dwEncryptedDataLen; i++) printf(“\\x%.2X”, pEncryptedData[i]); printf(“\n\n”); bStatus = CryptDecrypt(hKeyAES, NULL, TRUE, 0, pEncryptedData, &dwEncryptedDataLen); printf(“Plain+AES+RSA-RSA-AES:\n”); for (i = 0; i< dwEncryptedDataLen; i++) printf(“\\x%.2X”, pEncryptedData[i]); printf(“\n\n”); LocalFree(pEncryptedData); CryptDestroyKey(hKey); CryptReleaseContext(hProv, 0); CryptDestroyKey(hKeyAES); CryptReleaseContext(hProvAES, 0); free(pbKeyBlob); free(pbBuffer); return 0; }
3.1.2 RSA-encryption equivalent Python code
from Crypto.Hash import SHA from Crypto import Random from base64 import b64decode from base64 import b64encode from Crypto.PublicKey.RSA import construct from Crypto.Cipher import PKCS1_v1_5 from Crypto.Cipher import AES import binascii ######################## modulus = “00:a7:3e:cc:31:ec:d5:8d:63:90:06:68:3e:c3:f4:” + \ “b5:40:ef:e6:8d:27:fc:f5:ec:fb:7f:ea:ce:c2:bc:” + \ “ae:75:f6:2c:3b:a0:7a:06:d5:87:22:7d:36:87:21:” + \ “fc:19:cf:50:f7:5e:d7:2c:ca:b1:85:6f:d5:06:e1:” + \ “04:6c:b7:23:be:43:67:7d:9b:08:ec:5f:f2:d2:13:” + \ “bd:55:df:de:4a:89:51:46:b5:05:5e:fd:bd:02:8e:” + \ “50:a9:46:6d:72:7e:cd:56:6d:a8:4b:5d:e6:0c:fe:” + \ “40:f9:44:37:7d:09:9a:76:13:4a:68:92:46:b5:e1:” + \ “bc:8a:13:5b:27:c5:93:53:e1” priv_exp = “00:90:b0:59:84:0f:bb:29:19:2b:ae:ff:5c:dd:b6:” + \ “c5:60:aa:ab:b1:0f:19:5f:c1:68:4b:e0:5e:84:47:” + \ “2c:3f:cd:7b:b3:12:40:bf:cc:d7:9e:54:e9:69:bb:” + \ “53:19:19:2c:98:f6:29:fe:0c:55:cc:75:55:bd:1f:” + \ “ca:ee:2a:fa:f5:e7:df:d0:cd:7d:36:bc:c6:ff:c2:” + \ “cb:b1:5b:d5:c3:3d:58:59:dd:a9:a6:9b:01:10:ba:” + \ “ef:bc:7a:ba:0a:9f:13:f6:e8:4a:c5:42:fc:a8:e8:” + \ “d5:5b:95:9a:c5:cc:f8:24:18:92:dd:51:ca:f4:58:” + \ “20:88:f0:c4:08:f1:50:87:e1″ _modulus = b”” for e in modulus.split(“:”) : _modulus += bytes([int(e, 16)]) _priv_exp = b”” for e in priv_exp.split(“:”) : _priv_exp += bytes([int(e, 16)]) ######################## plain = b”lalalalalalalalalalalalalalalala” print(“plain:”) print(list(map(hex, list(plain)))) ######################## aeskey = b’\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01′ iv = b’\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x01′ aescipher = AES.new(aeskey, AES.MODE_CBC, iv) plain = aescipher.encrypt(plain) print(“plain+aes:”) print(list(map(hex, list(plain)))) ######################## _exponent = b64decode(“AQAB”) n = int.from_bytes(_modulus, byteorder = ‘big’) e = int.from_bytes(_exponent, byteorder = ‘big’) pubkey = construct((n, e)) pubkey = PKCS1_v1_5.new(pubkey) enc = pubkey.encrypt(plain) print(“plain+aes+rsa:”) print(list(map(hex, list(enc)))) ######################## _exponent = b64decode(“AQAB”) n = int.from_bytes(_modulus, byteorder = ‘big’) e = int.from_bytes(_exponent, byteorder = ‘big’) d = int.from_bytes(_priv_exp, byteorder = ‘big’) privkey = construct((n, e, d)) privkey = PKCS1_v1_5.new(privkey) dsize = SHA.digest_size sentinel = Random.new().read(15 + dsize) dec = privkey.decrypt(enc, sentinel) print(“plain+aes+rsa-rsa:”) print(list(map(hex, list(dec)))) ######################## aesdecipher = AES.new(aeskey, AES.MODE_CBC, iv) dec = aesdecipher.decrypt(dec) print(“plain+aes+rsa-rsa-aes:”) print(list(map(hex, list(dec))))
3.2 PoC Password Derivation
3.2.1 C# Code (AgentTesla-based): PasswordDeriveBytes
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.IO; using System.Security.Cryptography; namespace ConsoleApplication1 { class Program { public static string Q(string S_0) { string strPassword = “amp4Z0wpKzJ5Cg0GDT5sJD0sMw0IDAsaGQ1Afik6NwXr6rrSEQE=”; string s = “aGQ1Afik6NampDT5sJEQE4Z0wpsMw0IDAD06rrSswXrKzJ5Cg0G=”; string strHashName = “SHA1”; int iterations = 2; int num = 256; string s2 = “@1B2c3D4e5F6g7H8”; byte[] bytes = Encoding.ASCII.GetBytes(s2); byte[] bytes2 = Encoding.ASCII.GetBytes(s); byte[] array = Convert.FromBase64String(S_0); PasswordDeriveBytes passwordDeriveBytes = new PasswordDeriveBytes(strPassword, bytes2, strHashName, iterations); byte[] bytes3 = passwordDeriveBytes.GetBytes(num / 8); ICryptoTransform transform = new RijndaelManaged{Mode = CipherMode.CBC}.CreateDecryptor(bytes3, bytes); MemoryStream memoryStream = new MemoryStream(array); CryptoStream cryptoStream = new CryptoStream(memoryStream, transform, CryptoStreamMode.Read); byte[] array2 = new byte[array.Length]; int count = cryptoStream.Read(array2, 0, array2.Length); memoryStream.Close(); cryptoStream.Close(); return Encoding.UTF8.GetString(array2, 0, count); } static void Main(string[] args) { string a = Q(“yS9slCo/crMuFQwxBg4raUY80Bq7UD7Ec9T0QcTHge/aOvrRkTW2gsH4B+w0mSo5”); } } }
3.2.2 PasswordDeriveBytes equivalent Python Code
from Crypto.Cipher import AES from base64 import b64decode import binascii import hashlib def dohash(s) : m = hashlib.sha1() m.update(s) s = m.digest() return s def my_MS_PBKDF1(password, salt, length, count) : ret = “” hash = password + salt for i in range(0, count – 1) : hash = dohash(hash) r1 = dohash(hash) ret = r1 i = 1 while len(ret)<length : ret += dohash(str(i) + hash) i += 1 return ret[0:length] data = b64decode(“yS9slCo/crMuFQwxBg4raUY80Bq7UD7Ec9T0QcTHge/aOvrRkTW2gsH4B+w0mSo5”) key1 = “amp4Z0wpKzJ5Cg0GDT5sJD0sMw0IDAsaGQ1Afik6NwXr6rrSEQE=” key2 = “aGQ1Afik6NampDT5sJEQE4Z0wpsMw0IDAD06rrSswXrKzJ5Cg0G=” iv = “@1B2c3D4e5F6g7H8” derivedkey = my_MS_PBKDF1(key1, key2, 32, 2) cipher = AES.new(derivedkey, AES.MODE_CBC, iv) print cipher.decrypt(data)
3.3 CryptoAPI BLOBs
The following code imports a PEM key, then exporting both public and private keys to BLOBs: #include <stdio.h> #include <tchar.h> #include <windows.h> #pragma comment(lib, “Crypt32”) int main(int argc, _TCHAR* argv[]) { char * pem = “—–BEGIN RSA PRIVATE KEY—–” “MIICXQIBAAKBgQCnPswx7NWNY5AGaD7D9LVA7+aNJ/z17Pt/6s7CvK519iw7oHoG” “1YcifTaHIfwZz1D3XtcsyrGFb9UG4QRstyO+Q2d9mwjsX/LSE71V395KiVFGtQVe” “/b0CjlCpRm1yfs1WbahLXeYM/kD5RDd9CZp2E0pokka14byKE1snxZNT4QIDAQAB” “AoGBAJCwWYQPuykZK67/XN22xWCqq7EPGV/BaEvgXoRHLD/Ne7MSQL/M155U6Wm7” “UxkZLJj2Kf4MVcx1Vb0fyu4q+vXn39DNfTa8xv/Cy7Fb1cM9WFndqaabARC677x6” “ugqfE/boSsVC/Kjo1VuVmsXM+CQYkt1RyvRYIIjwxAjxUIfhAkEAzyClC06ASDR/” “nhejvTZbfie0ymHS9Mck/3zdx77SnV5dX5GQgNIr4bvkpTceFoZQQtOHYaEIfY8Z” “TJklkA05bQJBAM61GlUO6vEBMGXv3rqocZJUbB2bqY+mGh13xQ3dPeyC0lBaxnf3” “OgaqUdL6boA4+j1pHjIeLVGW1A8F32Zzz8UCQC5Gitk11q9LG2AExA5YAKT01g2J” “QYpym6+BBEPGPGPwW0goy3IcgrVSN0k6QTyjEXd8rvh+89ipiet1I9FFQxkCQBNx” “DSz63jYUuoyb5wL/XM86iYCvZ19PbB1hanNHX8+i7k0IfKpD4n1F/7QsQcBlm4Oz” “I1frZq/J0+Al2UE1m1ECQQCO7csOphDIjkuVQErkErhEmNOnZf/7wl5pEapVhrge” “A4IdLRgbJ6od+Dq6zC2tTHbRI/62xGv0Tey2mIDwD1d0” “—–END RSA PRIVATE KEY—–“; HCRYPTPROV hProv = NULL; HCRYPTKEY hKey = NULL; BOOL bStatus = FALSE; LPBYTE pEncryptedData = NULL; DWORD i = 0, dwKeyLen = 0, dwValLen = 0; DWORD dwEncryptedDataLen = 0; DWORD err = 0; bStatus = CryptAcquireContext(&hProv, NULL, MS_ENHANCED_PROV, PROV_RSA_FULL, 0); if (bStatus == 0) { bStatus = CryptAcquireContext(&hProv, NULL, MS_ENHANCED_PROV, PROV_RSA_FULL, CRYPT_NEWKEYSET); } //key PEM -> key BLOB DWORD dwBufferLen; LPBYTE pbBuffer; bStatus = CryptStringToBinaryA(pem, 0, CRYPT_STRING_BASE64HEADER, NULL, &dwBufferLen, NULL, NULL); pbBuffer = (LPBYTE)malloc(dwBufferLen); bStatus = CryptStringToBinaryA(pem, 0, CRYPT_STRING_BASE64HEADER, pbBuffer, &dwBufferLen, NULL, NULL); DWORD cbKeyBlob = 0; bStatus = CryptDecodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, PKCS_RSA_PRIVATE_KEY, (BYTE*)pbBuffer, dwBufferLen, 0, NULL, NULL, &cbKeyBlob); LPBYTE pbKeyBlob = (LPBYTE)malloc(cbKeyBlob); bStatus = CryptDecodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, PKCS_RSA_PRIVATE_KEY, (BYTE*)pbBuffer, dwBufferLen, 0, NULL, pbKeyBlob, &cbKeyBlob); bStatus = CryptImportKey(hProv, pbKeyBlob, cbKeyBlob, 0, CRYPT_EXPORTABLE, &hKey); dwValLen = sizeof(DWORD); bStatus = CryptGetKeyParam(hKey, KP_KEYLEN, (LPBYTE)&dwKeyLen, &dwValLen, 0); DWORD cbPubKeyBlob = 0; CryptExportKey(hKey, NULL, PUBLICKEYBLOB, 0, NULL, &cbPubKeyBlob); LPBYTE pbPubKeyBlob = (LPBYTE)malloc(cbPubKeyBlob); CryptExportKey(hKey, NULL, PUBLICKEYBLOB, 0, pbPubKeyBlob, &cbPubKeyBlob); printf(“Private:\n”); for (i = 0; i< cbKeyBlob; i++) printf(“\\x%.2X”, pbKeyBlob[i]); printf(“\n\n”); printf(“Public:\n”); for (i = 0; i< cbPubKeyBlob; i++) printf(“\\x%.2X”, pbPubKeyBlob[i]); printf(“\n\n”); CryptDestroyKey(hKey); CryptReleaseContext(hProv, 0); free(pbPubKeyBlob); free(pbKeyBlob); free(pbBuffer); return 0; } Here is the format of the Microsoft BLOBs for the same PEM key:
There are the parameters of the PEM key:
And these are the equivalent private and public BLOBs, where we can locate these parameters:
As we can see, the private BLOB stores all the RSA key parameters and the public BLOB stores the exponent and the modulus. They are stored in reverse order because CryptoAPI manages all these parameters in little-endian (in spite of the fact most of the cryptographic libraries store them in big-endian).
4. References
Here are some references and further reading that readers may find of interest:
Microsoft Cryptography Reference: https://docs.microsoft.com/en-us/windows/desktop/seccrypto/cryptography-reference
Microsoft .Net documentation: https://docs.microsoft.com/es-es/dotnet/api/system.security.cryptography.passwordderivebytes?redirectedfrom=MSDN&view=netframework-4.7.2
Microsoft BLOBs documentation: https://docs.microsoft.com/en-us/windows/desktop/seccrypto/rsa-schannel-key-blobs#public-key-blobs
Post at stackexchange about PasswordDeriveBytes internals: https://crypto.stackexchange.com/questions/22271/what-is-the-algorithm-behind-passwordderivebytes
OrcaRAT – A whale of a tale: http://pwc.blogs.com/cyber_security_updates/2014/10/orcarat-a-whale-of-a-tale.html
For additional detail, feel free to get in contact with us via the Blueliv Threat Exchange Network. It’s a global community of thousands of cybersecurity experts, IT professionals and academics, and each month our members publish the latest news, threat data, IOCs and more in order to improve resilience and accelerate incident response.