Add a callback to provide NTLM hashes on server-side

Adds a callback that allows servers to compute NTLM hashes by themselves. The typical
use of this callback is to provide a function that gives precomputed hash values.

Sponsored by: Wheel Systems (http://www.wheelsystems.com)
This commit is contained in:
davewheel 2017-05-15 18:52:39 +02:00
parent 4cc1ba5b1d
commit 4bfb4dddbf
8 changed files with 122 additions and 50 deletions

View File

@ -28,6 +28,8 @@
#include <freerdp/autodetect.h>
#include <winpr/sspi.h>
#include <winpr/ntlm.h>
typedef BOOL (*psPeerContextNew)(freerdp_peer* peer, rdpContext* context);
typedef void (*psPeerContextFree)(freerdp_peer* peer, rdpContext* context);
@ -118,6 +120,7 @@ struct rdp_freerdp_peer
psPeerGetEventHandles GetEventHandles;
psPeerAdjustMonitorsLayout AdjustMonitorsLayout;
psPeerClientCapabilities ClientCapabilities;
psPeerComputeNtlmHash ComputeNtlmHash;
};
#ifdef __cplusplus

View File

@ -33,6 +33,7 @@
#include <freerdp/log.h>
#include <freerdp/crypto/tls.h>
#include <freerdp/build-config.h>
#include <freerdp/peer.h>
#include <winpr/crt.h>
#include <winpr/sam.h>
@ -715,10 +716,28 @@ int nla_server_authenticate(rdpNla* nla)
if ((nla->status == SEC_I_COMPLETE_AND_CONTINUE) || (nla->status == SEC_I_COMPLETE_NEEDED))
{
if (nla->SamFile)
freerdp_peer *peer = nla->instance->context->peer;
if (peer->ComputeNtlmHash)
{
nla->table->SetContextAttributes(&nla->context,
SECPKG_ATTR_AUTH_NTLM_SAM_FILE, nla->SamFile, strlen(nla->SamFile) + 1);
SECURITY_STATUS status;
status = nla->table->SetContextAttributes(&nla->context, SECPKG_ATTR_AUTH_NTLM_HASH_CB, peer->ComputeNtlmHash, 0);
if (status != SEC_E_OK)
{
WLog_ERR(TAG, "SetContextAttributesA(hash cb) status %s [0x%08"PRIX32"]", GetSecurityStatusString(status), status);
}
status = nla->table->SetContextAttributes(&nla->context, SECPKG_ATTR_AUTH_NTLM_HASH_CB_DATA, peer, 0);
if (status != SEC_E_OK)
{
WLog_ERR(TAG, "SetContextAttributesA(hash cb data) status %s [0x%08"PRIX32"]", GetSecurityStatusString(status), status);
}
}
else if (nla->SamFile)
{
nla->table->SetContextAttributes(&nla->context, SECPKG_ATTR_AUTH_NTLM_SAM_FILE, nla->SamFile,
strlen(nla->SamFile) + 1);
}
if (nla->table->CompleteAuthToken)
@ -758,7 +777,7 @@ int nla_server_authenticate(rdpNla* nla)
}
nla->havePubKeyAuth = TRUE;
nla->status = nla->table->QueryContextAttributes(&nla->context, SECPKG_ATTR_SIZES,
nla->status = nla->table->QueryContextAttributes(&nla->context, SECPKG_ATTR_SIZES,
&nla->ContextSizes);
if (nla->status != SEC_E_OK)
@ -1746,7 +1765,6 @@ rdpNla* nla_new(freerdp* instance, rdpTransport* transport, rdpSettings* setting
return NULL;
nla->identity = calloc(1, sizeof(SEC_WINNT_AUTH_IDENTITY));
if (!nla->identity)
{
free(nla);

View File

@ -25,11 +25,16 @@
#include <string.h>
#include <winpr/winpr.h>
#include <winpr/wtypes.h>
#include <winpr/sspi.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef SECURITY_STATUS (*psPeerComputeNtlmHash)(void *client, const SEC_WINNT_AUTH_IDENTITY *authIdentity,
const SecBuffer *ntproofvalue, const BYTE *randkey, const BYTE *mic, const SecBuffer *micvalue,
BYTE *ntlmhash);
WINPR_API BYTE* NTOWFv1W(LPWSTR Password, UINT32 PasswordLength, BYTE* NtHash);
WINPR_API BYTE* NTOWFv1A(LPSTR Password, UINT32 PasswordLength, BYTE* NtHash);

View File

@ -1014,6 +1014,8 @@ extern "C" {
#define SECPKG_ATTR_AUTH_NTLM_RANDKEY 1105
#define SECPKG_ATTR_AUTH_NTLM_MIC 1106
#define SECPKG_ATTR_AUTH_NTLM_MIC_VALUE 1107
#define SECPKG_ATTR_AUTH_NTLM_HASH_CB 1108
#define SECPKG_ATTR_AUTH_NTLM_HASH_CB_DATA 1109
struct _SecPkgContext_AuthIdentity

View File

@ -675,6 +675,53 @@ SECURITY_STATUS SEC_ENTRY ntlm_DeleteSecurityContext(PCtxtHandle phContext)
return SEC_E_OK;
}
SECURITY_STATUS ntlm_computeProofValue(NTLM_CONTEXT *ntlm, SecBuffer *ntproof)
{
BYTE* blob;
SecBuffer* target = &ntlm->ChallengeTargetInfo;
if (!sspi_SecBufferAlloc(ntproof, 36 + target->cbBuffer))
return SEC_E_INSUFFICIENT_MEMORY;
blob = (BYTE *)ntproof->pvBuffer;
CopyMemory(blob, ntlm->ServerChallenge, 8); /* Server challenge. */
blob[8] = 1; /* Response version. */
blob[9] = 1; /* Highest response version understood by the client. */
/* Reserved 6B. */
CopyMemory(&blob[16], ntlm->Timestamp, 8); /* Time. */
CopyMemory(&blob[24], ntlm->ClientChallenge, 8); /* Client challenge. */
/* Reserved 4B. */
/* Server name. */
CopyMemory(&blob[36], target->pvBuffer, target->cbBuffer);
return SEC_E_OK;
}
SECURITY_STATUS ntlm_computeMicValue(NTLM_CONTEXT *ntlm, SecBuffer *micvalue)
{
BYTE* blob;
ULONG msgSize = ntlm->NegotiateMessage.cbBuffer + ntlm->ChallengeMessage.cbBuffer +
ntlm->AuthenticateMessage.cbBuffer;
if (!sspi_SecBufferAlloc(micvalue, msgSize))
return SEC_E_INSUFFICIENT_MEMORY;
blob = (BYTE *) micvalue->pvBuffer;
CopyMemory(blob, ntlm->NegotiateMessage.pvBuffer, ntlm->NegotiateMessage.cbBuffer);
blob += ntlm->NegotiateMessage.cbBuffer;
CopyMemory(blob, ntlm->ChallengeMessage.pvBuffer, ntlm->ChallengeMessage.cbBuffer);
blob += ntlm->ChallengeMessage.cbBuffer;
CopyMemory(blob, ntlm->AuthenticateMessage.pvBuffer, ntlm->AuthenticateMessage.cbBuffer);
blob += ntlm->MessageIntegrityCheckOffset;
ZeroMemory(blob, 16);
return SEC_E_OK;
}
/* http://msdn.microsoft.com/en-us/library/windows/desktop/aa379337/ */
SECURITY_STATUS SEC_ENTRY ntlm_QueryContextAttributesW(PCtxtHandle phContext, ULONG ulAttribute,
@ -739,30 +786,7 @@ SECURITY_STATUS SEC_ENTRY ntlm_QueryContextAttributesW(PCtxtHandle phContext, UL
}
else if (ulAttribute == SECPKG_ATTR_AUTH_NTLM_NTPROOF_VALUE)
{
BYTE* blob;
SecBuffer* ntproof, *target;
ntproof = (SecBuffer*)pBuffer;
target = &context->ChallengeTargetInfo;
if (!sspi_SecBufferAlloc(ntproof, 36 + target->cbBuffer))
return (SEC_E_INSUFFICIENT_MEMORY);
blob = (BYTE*)ntproof->pvBuffer;
/* Server challenge. */
CopyMemory(blob, context->ServerChallenge, 8);
/* Response version. */
blob[8] = 1;
/* Highest response version understood by the client. */
blob[9] = 1;
/* Reserved 6B. */
/* Time. */
CopyMemory(&blob[16], context->Timestamp, 8);
/* Client challenge. */
CopyMemory(&blob[24], context->ClientChallenge, 8);
/* Reserved 4B. */
/* Server name. */
CopyMemory(&blob[36], target->pvBuffer, target->cbBuffer);
return (SEC_E_OK);
return ntlm_computeProofValue(context, (SecBuffer*)pBuffer);
}
else if (ulAttribute == SECPKG_ATTR_AUTH_NTLM_RANDKEY)
{
@ -790,24 +814,7 @@ SECURITY_STATUS SEC_ENTRY ntlm_QueryContextAttributesW(PCtxtHandle phContext, UL
}
else if (ulAttribute == SECPKG_ATTR_AUTH_NTLM_MIC_VALUE)
{
BYTE* blob;
SecBuffer* micvalue;
ULONG msgSize = context->NegotiateMessage.cbBuffer + context->ChallengeMessage.cbBuffer +
context->AuthenticateMessage.cbBuffer;
micvalue = (SecBuffer*) pBuffer;
if (!sspi_SecBufferAlloc(micvalue, msgSize))
return (SEC_E_INSUFFICIENT_MEMORY);
blob = (BYTE*) micvalue->pvBuffer;
CopyMemory(blob, context->NegotiateMessage.pvBuffer, context->NegotiateMessage.cbBuffer);
blob += context->NegotiateMessage.cbBuffer;
CopyMemory(blob, context->ChallengeMessage.pvBuffer, context->ChallengeMessage.cbBuffer);
blob += context->ChallengeMessage.cbBuffer;
CopyMemory(blob, context->AuthenticateMessage.pvBuffer, context->AuthenticateMessage.cbBuffer);
blob += context->MessageIntegrityCheckOffset;
ZeroMemory(blob, 16);
return (SEC_E_OK);
return ntlm_computeMicValue(context, (SecBuffer*) pBuffer);
}
return SEC_E_UNSUPPORTED_FUNCTION;
@ -935,6 +942,16 @@ SECURITY_STATUS SEC_ENTRY ntlm_SetContextAttributesW(PCtxtHandle phContext, ULON
CopyMemory(context->ServerChallenge, AuthNtlmServerChallenge->ServerChallenge, 8);
return SEC_E_OK;
}
else if (ulAttribute == SECPKG_ATTR_AUTH_NTLM_HASH_CB)
{
context->HashCallback = (psPeerComputeNtlmHash)pBuffer;
return SEC_E_OK;
}
else if (ulAttribute == SECPKG_ATTR_AUTH_NTLM_HASH_CB_DATA)
{
context->HashCallbackArg = pBuffer;
return SEC_E_OK;
}
return SEC_E_UNSUPPORTED_FUNCTION;
}

View File

@ -25,6 +25,7 @@
#include <winpr/nt.h>
#include <winpr/crypto.h>
#include <winpr/ntlm.h>
#include "../sspi.h"
@ -273,11 +274,15 @@ struct _NTLM_CONTEXT
BYTE ServerSealingKey[16];
BYTE MessageIntegrityCheck[16];
UINT32 MessageIntegrityCheckOffset;
psPeerComputeNtlmHash HashCallback;
void *HashCallbackArg;
};
typedef struct _NTLM_CONTEXT NTLM_CONTEXT;
NTLM_CONTEXT* ntlm_ContextNew(void);
void ntlm_ContextFree(NTLM_CONTEXT* context);
SECURITY_STATUS ntlm_computeProofValue(NTLM_CONTEXT *ntlm, SecBuffer *ntproof);
SECURITY_STATUS ntlm_computeMicValue(NTLM_CONTEXT *ntlm, SecBuffer *micvalue);
#ifdef WITH_DEBUG_NLA
#define WITH_DEBUG_NTLM

View File

@ -304,9 +304,31 @@ int ntlm_compute_ntlm_v2_hash(NTLM_CONTEXT* context, BYTE* hash)
(LPWSTR) credentials->identity.User, credentials->identity.UserLength * 2,
(LPWSTR) credentials->identity.Domain, credentials->identity.DomainLength * 2, (BYTE*) hash);
}
else if (context->HashCallback)
{
int ret;
SecBuffer proofValue, micValue;
if (ntlm_computeProofValue(context, &proofValue) != SEC_E_OK)
return -1;
if (ntlm_computeMicValue(context, &micValue) != SEC_E_OK)
{
sspi_SecBufferFree(&proofValue);
return -1;
}
ret = context->HashCallback(context->HashCallbackArg, &credentials->identity, &proofValue,
context->EncryptedRandomSessionKey, context->MessageIntegrityCheck, &micValue,
hash);
sspi_SecBufferFree(&proofValue);
sspi_SecBufferFree(&micValue);
return ret ? 1 : -1;
}
else if (context->UseSamFileDatabase)
{
ntlm_fetch_ntlm_v2_hash(context, hash);
return ntlm_fetch_ntlm_v2_hash(context, hash);
}
return 1;
@ -373,7 +395,6 @@ int ntlm_compute_ntlm_v2_response(NTLM_CONTEXT* context)
blob = (BYTE*) ntlm_v2_temp.pvBuffer;
/* Compute the NTLMv2 hash */
if (ntlm_compute_ntlm_v2_hash(context, (BYTE*) context->NtlmV2Hash) < 0)
return -1;

View File

@ -273,7 +273,8 @@ void sspi_SecBufferFree(PSecBuffer SecBuffer)
if (!SecBuffer)
return;
memset(SecBuffer->pvBuffer, 0, SecBuffer->cbBuffer);
if (SecBuffer->pvBuffer)
memset(SecBuffer->pvBuffer, 0, SecBuffer->cbBuffer);
free(SecBuffer->pvBuffer);
SecBuffer->pvBuffer = NULL;
SecBuffer->cbBuffer = 0;