|
|
|
@ -38,10 +38,14 @@
|
|
|
|
|
|
|
|
|
|
const char LM_MAGIC[] = "KGS!@#$%";
|
|
|
|
|
|
|
|
|
|
static const char NTLM_CLIENT_SIGN_MAGIC[] = "session key to client-to-server signing key magic constant";
|
|
|
|
|
static const char NTLM_SERVER_SIGN_MAGIC[] = "session key to server-to-client signing key magic constant";
|
|
|
|
|
static const char NTLM_CLIENT_SEAL_MAGIC[] = "session key to client-to-server sealing key magic constant";
|
|
|
|
|
static const char NTLM_SERVER_SEAL_MAGIC[] = "session key to server-to-client sealing key magic constant";
|
|
|
|
|
static const char NTLM_CLIENT_SIGN_MAGIC[] =
|
|
|
|
|
"session key to client-to-server signing key magic constant";
|
|
|
|
|
static const char NTLM_SERVER_SIGN_MAGIC[] =
|
|
|
|
|
"session key to server-to-client signing key magic constant";
|
|
|
|
|
static const char NTLM_CLIENT_SEAL_MAGIC[] =
|
|
|
|
|
"session key to client-to-server sealing key magic constant";
|
|
|
|
|
static const char NTLM_SERVER_SEAL_MAGIC[] =
|
|
|
|
|
"session key to server-to-client sealing key magic constant";
|
|
|
|
|
|
|
|
|
|
static const BYTE NTLM_NULL_BUFFER[16] =
|
|
|
|
|
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
|
|
|
|
@ -111,7 +115,7 @@ void ntlm_print_version_info(NTLM_VERSION_INFO* versionInfo)
|
|
|
|
|
WLog_INFO(TAG, "\tProductMinorVersion: %"PRIu8"", versionInfo->ProductMinorVersion);
|
|
|
|
|
WLog_INFO(TAG, "\tProductBuild: %"PRIu16"", versionInfo->ProductBuild);
|
|
|
|
|
WLog_INFO(TAG, "\tReserved: 0x%02"PRIX8"%02"PRIX8"%02"PRIX8"", versionInfo->Reserved[0],
|
|
|
|
|
versionInfo->Reserved[1], versionInfo->Reserved[2]);
|
|
|
|
|
versionInfo->Reserved[1], versionInfo->Reserved[2]);
|
|
|
|
|
WLog_INFO(TAG, "\tNTLMRevisionCurrent: 0x%02"PRIX8"", versionInfo->NTLMRevisionCurrent);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -195,14 +199,14 @@ int ntlm_fetch_ntlm_v2_hash(NTLM_CONTEXT* context, BYTE* hash)
|
|
|
|
|
WINPR_SAM* sam;
|
|
|
|
|
WINPR_SAM_ENTRY* entry;
|
|
|
|
|
SSPI_CREDENTIALS* credentials = context->credentials;
|
|
|
|
|
|
|
|
|
|
sam = SamOpen(context->SamFile, TRUE);
|
|
|
|
|
|
|
|
|
|
if (!sam)
|
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
|
|
entry = SamLookupUserW(sam, (LPWSTR) credentials->identity.User, credentials->identity.UserLength * 2,
|
|
|
|
|
(LPWSTR) credentials->identity.Domain, credentials->identity.DomainLength * 2);
|
|
|
|
|
entry = SamLookupUserW(sam, (LPWSTR) credentials->identity.User,
|
|
|
|
|
credentials->identity.UserLength * 2,
|
|
|
|
|
(LPWSTR) credentials->identity.Domain, credentials->identity.DomainLength * 2);
|
|
|
|
|
|
|
|
|
|
if (entry)
|
|
|
|
|
{
|
|
|
|
@ -211,15 +215,16 @@ int ntlm_fetch_ntlm_v2_hash(NTLM_CONTEXT* context, BYTE* hash)
|
|
|
|
|
winpr_HexDump(TAG, WLOG_DEBUG, entry->NtHash, 16);
|
|
|
|
|
#endif
|
|
|
|
|
NTOWFv2FromHashW(entry->NtHash,
|
|
|
|
|
(LPWSTR) credentials->identity.User, credentials->identity.UserLength * 2,
|
|
|
|
|
(LPWSTR) credentials->identity.Domain, credentials->identity.DomainLength * 2,
|
|
|
|
|
(BYTE*) hash);
|
|
|
|
|
(LPWSTR) credentials->identity.User, credentials->identity.UserLength * 2,
|
|
|
|
|
(LPWSTR) credentials->identity.Domain, credentials->identity.DomainLength * 2,
|
|
|
|
|
(BYTE*) hash);
|
|
|
|
|
SamFreeEntry(sam, entry);
|
|
|
|
|
SamClose(sam);
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
entry = SamLookupUserW(sam, (LPWSTR) credentials->identity.User, credentials->identity.UserLength * 2, NULL, 0);
|
|
|
|
|
entry = SamLookupUserW(sam, (LPWSTR) credentials->identity.User,
|
|
|
|
|
credentials->identity.UserLength * 2, NULL, 0);
|
|
|
|
|
|
|
|
|
|
if (entry)
|
|
|
|
|
{
|
|
|
|
@ -228,9 +233,9 @@ int ntlm_fetch_ntlm_v2_hash(NTLM_CONTEXT* context, BYTE* hash)
|
|
|
|
|
winpr_HexDump(TAG, WLOG_DEBUG, entry->NtHash, 16);
|
|
|
|
|
#endif
|
|
|
|
|
NTOWFv2FromHashW(entry->NtHash,
|
|
|
|
|
(LPWSTR) credentials->identity.User, credentials->identity.UserLength * 2,
|
|
|
|
|
(LPWSTR) credentials->identity.Domain, credentials->identity.DomainLength * 2,
|
|
|
|
|
(BYTE*) hash);
|
|
|
|
|
(LPWSTR) credentials->identity.User, credentials->identity.UserLength * 2,
|
|
|
|
|
(LPWSTR) credentials->identity.Domain, credentials->identity.DomainLength * 2,
|
|
|
|
|
(BYTE*) hash);
|
|
|
|
|
SamFreeEntry(sam, entry);
|
|
|
|
|
SamClose(sam);
|
|
|
|
|
return 1;
|
|
|
|
@ -255,7 +260,7 @@ int ntlm_convert_password_hash(NTLM_CONTEXT* context, BYTE* hash)
|
|
|
|
|
/* Password contains a password hash of length (PasswordLength / SSPI_CREDENTIALS_HASH_LENGTH_FACTOR) */
|
|
|
|
|
PasswordHashLength = credentials->identity.PasswordLength / SSPI_CREDENTIALS_HASH_LENGTH_FACTOR;
|
|
|
|
|
status = ConvertFromUnicode(CP_UTF8, 0, (LPCWSTR) credentials->identity.Password,
|
|
|
|
|
PasswordHashLength, &PasswordHash, 0, NULL, NULL);
|
|
|
|
|
PasswordHashLength, &PasswordHash, 0, NULL, NULL);
|
|
|
|
|
|
|
|
|
|
if (status <= 0)
|
|
|
|
|
return -1;
|
|
|
|
@ -283,9 +288,9 @@ int ntlm_compute_ntlm_v2_hash(NTLM_CONTEXT* context, BYTE* hash)
|
|
|
|
|
if (memcmp(context->NtlmHash, NTLM_NULL_BUFFER, 16) != 0)
|
|
|
|
|
{
|
|
|
|
|
NTOWFv2FromHashW(context->NtlmHash,
|
|
|
|
|
(LPWSTR) credentials->identity.User, credentials->identity.UserLength * 2,
|
|
|
|
|
(LPWSTR) credentials->identity.Domain, credentials->identity.DomainLength * 2,
|
|
|
|
|
(BYTE*) hash);
|
|
|
|
|
(LPWSTR) credentials->identity.User, credentials->identity.UserLength * 2,
|
|
|
|
|
(LPWSTR) credentials->identity.Domain, credentials->identity.DomainLength * 2,
|
|
|
|
|
(BYTE*) hash);
|
|
|
|
|
}
|
|
|
|
|
else if (credentials->identity.PasswordLength > 256)
|
|
|
|
|
{
|
|
|
|
@ -294,15 +299,15 @@ int ntlm_compute_ntlm_v2_hash(NTLM_CONTEXT* context, BYTE* hash)
|
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
|
|
NTOWFv2FromHashW(context->NtlmHash,
|
|
|
|
|
(LPWSTR) credentials->identity.User, credentials->identity.UserLength * 2,
|
|
|
|
|
(LPWSTR) credentials->identity.Domain, credentials->identity.DomainLength * 2,
|
|
|
|
|
(BYTE*) hash);
|
|
|
|
|
(LPWSTR) credentials->identity.User, credentials->identity.UserLength * 2,
|
|
|
|
|
(LPWSTR) credentials->identity.Domain, credentials->identity.DomainLength * 2,
|
|
|
|
|
(BYTE*) hash);
|
|
|
|
|
}
|
|
|
|
|
else if (credentials->identity.Password)
|
|
|
|
|
{
|
|
|
|
|
NTOWFv2W((LPWSTR) credentials->identity.Password, credentials->identity.PasswordLength * 2,
|
|
|
|
|
(LPWSTR) credentials->identity.User, credentials->identity.UserLength * 2,
|
|
|
|
|
(LPWSTR) credentials->identity.Domain, credentials->identity.DomainLength * 2, (BYTE*) hash);
|
|
|
|
|
(LPWSTR) credentials->identity.User, credentials->identity.UserLength * 2,
|
|
|
|
|
(LPWSTR) credentials->identity.Domain, credentials->identity.DomainLength * 2, (BYTE*) hash);
|
|
|
|
|
}
|
|
|
|
|
else if (context->HashCallback)
|
|
|
|
|
{
|
|
|
|
@ -319,9 +324,8 @@ int ntlm_compute_ntlm_v2_hash(NTLM_CONTEXT* context, BYTE* hash)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ret = context->HashCallback(context->HashCallbackArg, &credentials->identity, &proofValue,
|
|
|
|
|
context->EncryptedRandomSessionKey, context->MessageIntegrityCheck, &micValue,
|
|
|
|
|
hash);
|
|
|
|
|
|
|
|
|
|
context->EncryptedRandomSessionKey, context->MessageIntegrityCheck, &micValue,
|
|
|
|
|
hash);
|
|
|
|
|
sspi_SecBufferFree(&proofValue);
|
|
|
|
|
sspi_SecBufferFree(&micValue);
|
|
|
|
|
return ret ? 1 : -1;
|
|
|
|
@ -363,8 +367,8 @@ int ntlm_compute_lm_v2_response(NTLM_CONTEXT* context)
|
|
|
|
|
response = (BYTE*) context->LmChallengeResponse.pvBuffer;
|
|
|
|
|
/* Compute the HMAC-MD5 hash of the resulting value using the NTLMv2 hash as the key */
|
|
|
|
|
winpr_HMAC(WINPR_MD_MD5, (void*) context->NtlmV2Hash, WINPR_MD5_DIGEST_LENGTH,
|
|
|
|
|
(BYTE*) value, WINPR_MD5_DIGEST_LENGTH,
|
|
|
|
|
(BYTE*) response, WINPR_MD5_DIGEST_LENGTH);
|
|
|
|
|
(BYTE*) value, WINPR_MD5_DIGEST_LENGTH,
|
|
|
|
|
(BYTE*) response, WINPR_MD5_DIGEST_LENGTH);
|
|
|
|
|
/* Concatenate the resulting HMAC-MD5 hash and the client challenge, giving us the LMv2 response (24 bytes) */
|
|
|
|
|
CopyMemory(&response[16], context->ClientChallenge, 8);
|
|
|
|
|
return 1;
|
|
|
|
@ -384,8 +388,6 @@ int ntlm_compute_ntlm_v2_response(NTLM_CONTEXT* context)
|
|
|
|
|
SecBuffer ntlm_v2_temp;
|
|
|
|
|
SecBuffer ntlm_v2_temp_chal;
|
|
|
|
|
PSecBuffer TargetInfo;
|
|
|
|
|
SSPI_CREDENTIALS* credentials;
|
|
|
|
|
credentials = context->credentials;
|
|
|
|
|
TargetInfo = &context->ChallengeTargetInfo;
|
|
|
|
|
|
|
|
|
|
if (!sspi_SecBufferAlloc(&ntlm_v2_temp, TargetInfo->cbBuffer + 28))
|
|
|
|
@ -400,11 +402,14 @@ int ntlm_compute_ntlm_v2_response(NTLM_CONTEXT* context)
|
|
|
|
|
|
|
|
|
|
#ifdef WITH_DEBUG_NTLM
|
|
|
|
|
WLog_DBG(TAG, "Password (length = %"PRIu32")", credentials->identity.PasswordLength * 2);
|
|
|
|
|
winpr_HexDump(TAG, WLOG_DEBUG, (BYTE*) credentials->identity.Password, credentials->identity.PasswordLength * 2);
|
|
|
|
|
winpr_HexDump(TAG, WLOG_DEBUG, (BYTE*) credentials->identity.Password,
|
|
|
|
|
credentials->identity.PasswordLength * 2);
|
|
|
|
|
WLog_DBG(TAG, "Username (length = %"PRIu32")", credentials->identity.UserLength * 2);
|
|
|
|
|
winpr_HexDump(TAG, WLOG_DEBUG, (BYTE*) credentials->identity.User, credentials->identity.UserLength * 2);
|
|
|
|
|
winpr_HexDump(TAG, WLOG_DEBUG, (BYTE*) credentials->identity.User,
|
|
|
|
|
credentials->identity.UserLength * 2);
|
|
|
|
|
WLog_DBG(TAG, "Domain (length = %"PRIu32")", credentials->identity.DomainLength * 2);
|
|
|
|
|
winpr_HexDump(TAG, WLOG_DEBUG, (BYTE*) credentials->identity.Domain, credentials->identity.DomainLength * 2);
|
|
|
|
|
winpr_HexDump(TAG, WLOG_DEBUG, (BYTE*) credentials->identity.Domain,
|
|
|
|
|
credentials->identity.DomainLength * 2);
|
|
|
|
|
WLog_DBG(TAG, "Workstation (length = %"PRIu16")", context->Workstation.Length);
|
|
|
|
|
winpr_HexDump(TAG, WLOG_DEBUG, (BYTE*) context->Workstation.Buffer, context->Workstation.Length);
|
|
|
|
|
WLog_DBG(TAG, "NTOWFv2, NTLMv2 Hash");
|
|
|
|
@ -433,8 +438,8 @@ int ntlm_compute_ntlm_v2_response(NTLM_CONTEXT* context)
|
|
|
|
|
CopyMemory(blob, context->ServerChallenge, 8);
|
|
|
|
|
CopyMemory(&blob[8], ntlm_v2_temp.pvBuffer, ntlm_v2_temp.cbBuffer);
|
|
|
|
|
winpr_HMAC(WINPR_MD_MD5, (BYTE*) context->NtlmV2Hash, WINPR_MD5_DIGEST_LENGTH,
|
|
|
|
|
(BYTE*) ntlm_v2_temp_chal.pvBuffer, ntlm_v2_temp_chal.cbBuffer,
|
|
|
|
|
(BYTE*) nt_proof_str, WINPR_MD5_DIGEST_LENGTH);
|
|
|
|
|
(BYTE*) ntlm_v2_temp_chal.pvBuffer, ntlm_v2_temp_chal.cbBuffer,
|
|
|
|
|
(BYTE*) nt_proof_str, WINPR_MD5_DIGEST_LENGTH);
|
|
|
|
|
|
|
|
|
|
/* NtChallengeResponse, Concatenate NTProofStr with temp */
|
|
|
|
|
|
|
|
|
@ -446,8 +451,8 @@ int ntlm_compute_ntlm_v2_response(NTLM_CONTEXT* context)
|
|
|
|
|
CopyMemory(&blob[16], ntlm_v2_temp.pvBuffer, ntlm_v2_temp.cbBuffer);
|
|
|
|
|
/* Compute SessionBaseKey, the HMAC-MD5 hash of NTProofStr using the NTLMv2 hash as the key */
|
|
|
|
|
winpr_HMAC(WINPR_MD_MD5, (BYTE*) context->NtlmV2Hash, WINPR_MD5_DIGEST_LENGTH,
|
|
|
|
|
(BYTE*) nt_proof_str, WINPR_MD5_DIGEST_LENGTH,
|
|
|
|
|
(BYTE*) context->SessionBaseKey, WINPR_MD5_DIGEST_LENGTH);
|
|
|
|
|
(BYTE*) nt_proof_str, WINPR_MD5_DIGEST_LENGTH,
|
|
|
|
|
(BYTE*) context->SessionBaseKey, WINPR_MD5_DIGEST_LENGTH);
|
|
|
|
|
sspi_SecBufferFree(&ntlm_v2_temp);
|
|
|
|
|
sspi_SecBufferFree(&ntlm_v2_temp_chal);
|
|
|
|
|
return 1;
|
|
|
|
@ -464,6 +469,7 @@ int ntlm_compute_ntlm_v2_response(NTLM_CONTEXT* context)
|
|
|
|
|
void ntlm_rc4k(BYTE* key, int length, BYTE* plaintext, BYTE* ciphertext)
|
|
|
|
|
{
|
|
|
|
|
WINPR_RC4_CTX* rc4 = winpr_RC4_New(key, 16);
|
|
|
|
|
|
|
|
|
|
if (rc4)
|
|
|
|
|
{
|
|
|
|
|
winpr_RC4_Update(rc4, length, plaintext, ciphertext);
|
|
|
|
@ -534,7 +540,8 @@ void ntlm_generate_exported_session_key(NTLM_CONTEXT* context)
|
|
|
|
|
void ntlm_encrypt_random_session_key(NTLM_CONTEXT* context)
|
|
|
|
|
{
|
|
|
|
|
/* In NTLMv2, EncryptedRandomSessionKey is the ExportedSessionKey RC4-encrypted with the KeyExchangeKey */
|
|
|
|
|
ntlm_rc4k(context->KeyExchangeKey, 16, context->RandomSessionKey, context->EncryptedRandomSessionKey);
|
|
|
|
|
ntlm_rc4k(context->KeyExchangeKey, 16, context->RandomSessionKey,
|
|
|
|
|
context->EncryptedRandomSessionKey);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
@ -553,7 +560,8 @@ void ntlm_decrypt_random_session_key(NTLM_CONTEXT* context)
|
|
|
|
|
* Set RandomSessionKey to KeyExchangeKey
|
|
|
|
|
*/
|
|
|
|
|
if (context->NegotiateKeyExchange)
|
|
|
|
|
ntlm_rc4k(context->KeyExchangeKey, 16, context->EncryptedRandomSessionKey, context->RandomSessionKey);
|
|
|
|
|
ntlm_rc4k(context->KeyExchangeKey, 16, context->EncryptedRandomSessionKey,
|
|
|
|
|
context->RandomSessionKey);
|
|
|
|
|
else
|
|
|
|
|
CopyMemory(context->RandomSessionKey, context->KeyExchangeKey, 16);
|
|
|
|
|
}
|
|
|
|
@ -570,7 +578,6 @@ int ntlm_generate_signing_key(BYTE* exported_session_key, PSecBuffer sign_magic,
|
|
|
|
|
{
|
|
|
|
|
int length;
|
|
|
|
|
BYTE* value;
|
|
|
|
|
|
|
|
|
|
length = WINPR_MD5_DIGEST_LENGTH + sign_magic->cbBuffer;
|
|
|
|
|
value = (BYTE*) malloc(length);
|
|
|
|
|
|
|
|
|
@ -586,6 +593,7 @@ int ntlm_generate_signing_key(BYTE* exported_session_key, PSecBuffer sign_magic,
|
|
|
|
|
free(value);
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
free(value);
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
@ -639,7 +647,8 @@ int ntlm_generate_sealing_key(BYTE* exported_session_key, PSecBuffer seal_magic,
|
|
|
|
|
CopyMemory(p, exported_session_key, WINPR_MD5_DIGEST_LENGTH);
|
|
|
|
|
CopyMemory(&p[WINPR_MD5_DIGEST_LENGTH], seal_magic->pvBuffer, seal_magic->cbBuffer);
|
|
|
|
|
|
|
|
|
|
if (!winpr_Digest(WINPR_MD_MD5, buffer.pvBuffer, buffer.cbBuffer, sealing_key, WINPR_MD5_DIGEST_LENGTH))
|
|
|
|
|
if (!winpr_Digest(WINPR_MD_MD5, buffer.pvBuffer, buffer.cbBuffer, sealing_key,
|
|
|
|
|
WINPR_MD5_DIGEST_LENGTH))
|
|
|
|
|
{
|
|
|
|
|
sspi_SecBufferFree(&buffer);
|
|
|
|
|
return -1;
|
|
|
|
@ -710,7 +719,6 @@ void ntlm_compute_message_integrity_check(NTLM_CONTEXT* context)
|
|
|
|
|
* Compute the HMAC-MD5 hash of ConcatenationOf(NEGOTIATE_MESSAGE,
|
|
|
|
|
* CHALLENGE_MESSAGE, AUTHENTICATE_MESSAGE) using the ExportedSessionKey
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
WINPR_HMAC_CTX* hmac = winpr_HMAC_New();
|
|
|
|
|
|
|
|
|
|
if (!hmac)
|
|
|
|
@ -718,10 +726,14 @@ void ntlm_compute_message_integrity_check(NTLM_CONTEXT* context)
|
|
|
|
|
|
|
|
|
|
if (winpr_HMAC_Init(hmac, WINPR_MD_MD5, context->ExportedSessionKey, WINPR_MD5_DIGEST_LENGTH))
|
|
|
|
|
{
|
|
|
|
|
winpr_HMAC_Update(hmac, (BYTE*) context->NegotiateMessage.pvBuffer, context->NegotiateMessage.cbBuffer);
|
|
|
|
|
winpr_HMAC_Update(hmac, (BYTE*) context->ChallengeMessage.pvBuffer, context->ChallengeMessage.cbBuffer);
|
|
|
|
|
winpr_HMAC_Update(hmac, (BYTE*) context->AuthenticateMessage.pvBuffer, context->AuthenticateMessage.cbBuffer);
|
|
|
|
|
winpr_HMAC_Update(hmac, (BYTE*) context->NegotiateMessage.pvBuffer,
|
|
|
|
|
context->NegotiateMessage.cbBuffer);
|
|
|
|
|
winpr_HMAC_Update(hmac, (BYTE*) context->ChallengeMessage.pvBuffer,
|
|
|
|
|
context->ChallengeMessage.cbBuffer);
|
|
|
|
|
winpr_HMAC_Update(hmac, (BYTE*) context->AuthenticateMessage.pvBuffer,
|
|
|
|
|
context->AuthenticateMessage.cbBuffer);
|
|
|
|
|
winpr_HMAC_Final(hmac, context->MessageIntegrityCheck, WINPR_MD5_DIGEST_LENGTH);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
winpr_HMAC_Free(hmac);
|
|
|
|
|
}
|
|
|
|
|