Friday, April 19, 2013

NTLM Authentication and Signing.


SMB connection is established over 3 steps.
1) Negotiation: The client and the server exchange a list of their own capabilities.
We have 1 Negotiation call per connection to the server.
2) Session Setup: Here the user authentication takes place.
There is one Session Setup per user using the connection negotiated in step 1.
3) Tree Connect: We connect to the share available to the user.
We have one tcon per smb share.

NTLM authentication is based on a challenge response mechanism. On responding to the Negotiate call, the server sends over a 'Challenge' which is used in the authentication process. The client then calculates a response using
1) NTLM Hash: This is created using the users password. This will be CIFS_AUTH_RESP_SIZE ie. 24 bytes long.
http://ubiqx.org/cifs/SMB.html#SMB.8.4
2) Server Challenge: This was sent by the server in response to the Negotiate call.
http://ubiqx.org/cifs/SMB.html#SMB.8.4
http://ubiqx.org/cifs/SMB.html#SMB.8.3.4

When calculating the Response to the server, we also calculate the Session Key. This will be used to Sign packets if the feature is chosen. To calculate the Session Key, we simply obtain a md4 hash of the NTLM hash. This will be CIFS_SESS_KEY_SIZE ie. 16 bytes long.
http://ubiqx.org/cifs/SMB.html#SMB.8.9.1

We first describe how the NTLM hash is calculated and the "client Response" calculated.

IC -> Implementing CIFS by Chris Hertel. http://ubiqx.org/cifs/SMB.html
MAC ->  Message Authentication Code

On Negotiate, we receive a Challenge from the server. We store this Server Challenge into ses->cryptKey.
int
CIFSSMBNegotiate(unsigned int xid, struct cifsSesInfo *ses)
{
..
        if (pSMBr->EncryptionKeyLength == CIFS_CRYPTO_KEY_SIZE) {
                memcpy(ses->cryptKey, pSMBr->u.EncryptionKey,
                       CIFS_CRYPTO_KEY_SIZE);
..
}


We then call CIFS_SessSetup to initiate a session.

int
CIFS_SessSetup(unsigned int xid, struct cifsSesInfo *ses,
               const struct nls_table *nls_cp)
{
..
        type = ses->server->secType;
..
        } else if (type == NTLM) {
..
                /* calculate ntlm response and session key */
                rc = setup_ntlm_response(ses);
..
}

We call setup_ntlm_response() to build the NTLM hash and the Response to the 'Server Challenge'


/* first calculate 24 bytes ntlm response and then 16 byte session key */
int setup_ntlm_response(struct cifsSesInfo *ses)
{
//The key-response combo length Total: 16 + 24 = 40 bytes.
        unsigned int temp_len = CIFS_SESS_KEY_SIZE + CIFS_AUTH_RESP_SIZE;
//A temporary buffer to hold it.
        char temp_key[CIFS_SESS_KEY_SIZE];

        if (!ses)
                return -EINVAL;

//We allocate the memory which will be used to store this response.
        ses->auth_key.response = kmalloc(temp_len, GFP_KERNEL);
        if (!ses->auth_key.response) {
                cERROR(1, "NTLM can't allocate (%u bytes) memory", temp_len);
                return -ENOMEM;
        }
//Store the key len.
        ses->auth_key.len = temp_len;

//First we get the NTLM Response
//Look at notes for SMBNTencrypt() below.
//The response created is stored at the buffer 
//starting at ses->auth_key.response + CIFS_SESS_KEY_SIZE(16 bytes) 
        SMBNTencrypt(ses->password, ses->cryptKey,
                        ses->auth_key.response + CIFS_SESS_KEY_SIZE);

//We re-create the NTLM hash.
//See notes for E_md4hash in SMBNTencrypt() below
//The NTLM key is stored in temp_key
        E_md4hash(ses->password, temp_key);

//The md4 NTLM hash of the NTLM hash is then stored at buffer starting
//at ses->auth_key.response. This session key is 16 bytes long
        mdfour(ses->auth_key.response, temp_key, CIFS_SESS_KEY_SIZE);

        return 0;
}
void
SMBNTencrypt(unsigned char *passwd, unsigned char *c8, unsigned char *p24)
{
//Temporary buffer.
        unsigned char p21[21];

//Zero temporary buffer
        memset(p21, '\0', 21);

//We first find the NTLM hash
        E_md4hash(passwd, p21);
-->
We just need the password to generate the NTLM hash
void
E_md4hash(const unsigned char *passwd, unsigned char *p16)
{
int len;
__u16 wpwd[129];


/* Password cannot be longer than 128 characters */
if (passwd) {
//We first trim to password to 128 bytes if longer.
len = strlen((char *) passwd);
if (len > 128)
       len = 128;

//We then convert it to unicode.
/* Password must be converted to NT unicode */
_my_mbstowcs(wpwd, passwd, len);
} else
len = 0;

wpwd[len] = 0;  /* Ensure string is null terminated */
/* Calculate length in bytes */
//We get the new length. This is 2 times the length of the password.
len = _my_wcslen(wpwd) * sizeof(__u16);

//We then get a md4 hash. This is 16 bytes long.
mdfour(p16, (unsigned char *) wpwd, len);
//We then zero the temporary buffer to avoid leaking the passwd hash.
memset(wpwd, 0, 129 * 2);
}
<-- blockquote="">
//We then create the response.
//Look at the notes for SMBOWFencrypt below
        SMBOWFencrypt(p21, c8, p24);

//At this stage, we have the response in the p24 array.
}


http://ubiqx.org/cifs/SMB.html#SMB.8.3.4 
For this we need the NTLM hash and the Server provided Challenge.


/* Does the des encryption from the NT or LM MD4 hash. */
static void
SMBOWFencrypt(unsigned char passwd[16], const unsigned char *c8,
              unsigned char p24[24])
{
//Temporary buffer
        unsigned char p21[21];

//Zero temporary buffer.
        memset(p21, '\0', 21);

//Copy 16 byte NTLM hash to temporary buffer.
        memcpy(p21, passwd, 16);
//Calculate response
        E_P24(p21, c8, p24);
-->
The response is simply calculated by padding the 16 byte NTLM hash to 21 bytes.
We then divide the 21 byte key into 3 keys of 7 bytes each.
We use each 7 byte key to encrypt the Server Challenge separately into 3 
seperate 8 byte values. 
These values are stored in a concatenated form in the p24 buffer.
void
E_P24(unsigned char *p21, const unsigned char *c8, unsigned char *p24)
{
smbhash(p24, c8, p21, 1);
smbhash(p24 + 8, c8, p21 + 7, 1);
smbhash(p24 + 16, c8, p21 + 14, 1);
}
<-- div="">
}

At this point,
ses->auth_key points to the Session Key.
ses->auth_key+CIFS_SESS_KEY_SIZE points to the response we send to the server.

The response generated is used in the SESSION_SETUP_ANDX call to the server. There are 2 copies of the response sent in
SMB->SESSION_SETUP_ANDX->Ansi Password and SMB->SESSION_SETUP_ANDX->Unicode Password


int
CIFS_SessSetup(unsigned int xid, struct cifsSesInfo *ses,
               const struct nls_table *nls_cp)
{
..
        } else if (type == NTLM) {
..
                /* copy ntlm response */
                memcpy(bcc_ptr, ses->auth_key.response + CIFS_SESS_KEY_SIZE,
                                CIFS_AUTH_RESP_SIZE);
                bcc_ptr += CIFS_AUTH_RESP_SIZE;
                memcpy(bcc_ptr, ses->auth_key.response + CIFS_SESS_KEY_SIZE,
                                CIFS_AUTH_RESP_SIZE);
                memcpy(bcc_ptr, ses->auth_key.response + CIFS_SESS_KEY_SIZE,
                                CIFS_AUTH_RESP_SIZE);
                bcc_ptr += CIFS_AUTH_RESP_SIZE;
..
}


At this stage the user is authenticated..

The first time this session-key / response key is created for a server, it is copied over to
server->auth_key. This is then used for calculation of signatures for all sessions on the server.


int cifs_setup_session(unsigned int xid, struct cifsSesInfo *ses,
                        struct nls_table *nls_info)
{
..
//Call Session setup.
        rc = CIFS_SessSetup(xid, ses, nls_info);
..
                mutex_lock(&ses->server->srv_mutex);
//If this is the first time we create a session on the server, then save 
//The session key and response for use in creating signatures.
                if (!server->session_estab) {
//Copy over auth_key to the server.
                        server->session_key.response = ses->auth_key.response;
                        server->session_key.len = ses->auth_key.len;
//Change sequence number to 2 (1 & 2 are used by the SESSION_SETUP_ANDX call)
                        server->sequence_number = 0x2;
//Set established to true.
                        server->session_estab = true;
//Set this to NULL so that the response string isn't freed by kfree() below.
                        ses->auth_key.response = NULL;
                }
                mutex_unlock(&server->srv_mutex);

                cFYI(1, "CIFS Session Established successfully");
                spin_lock(&GlobalMid_Lock);
                ses->status = CifsGood;
                ses->need_reconnect = false;
                spin_unlock(&GlobalMid_Lock);
        }

//Free Session key and response.
        kfree(ses->auth_key.response);
        ses->auth_key.response = NULL;
        ses->auth_key.len = 0;

        return rc;
}

We now look at the Signature generation for each SMB message.

To calculate the Message Authentication Code ( MAC - Signature) for a SMB message, we need the following 3
1) The session key
2) The response
both of which are calculated during the initial session setup and are available in server->auth_key
3) The SMB Message with a sequence number(http://ubiqx.org/cifs/SMB.html#SMB.8.9.2) set at smb_hdr->Extra->Signature.
The 3 values above are concatenated and a md5 hash generated.

http://ubiqx.org/cifs/SMB.html#SMB.8.9.3
The function setup_ntlm_response() is used to calculate the first part of the MAC
This is the Session-key & Response part of the MAC(shown as MAC_Key in IC).
At the end of this function, we have
1) Session key at ses->auth_key.response. 16 bytes
2) md4 hash of NTLM response in ses->auth_key.response+16. 24 bytes

We sign the SMB in SendReceive()


SendReceive(const unsigned int xid, struct cifsSesInfo *ses,
            struct smb_hdr *in_buf, struct smb_hdr *out_buf,
            int *pbytes_returned, const int long_op)
{
..
        rc = cifs_sign_smb(in_buf, ses->server, &midQ->sequence_number);
..
}

int cifs_sign_smb(struct smb_hdr *cifs_pdu, struct TCP_Server_Info *server,
                  __u32 *pexpected_response_sequence_number)
{
        int rc = 0;
        char smb_signature[20];

        if ((cifs_pdu == NULL) || (server == NULL))
                return -EINVAL;

        if ((cifs_pdu->Flags2 & SMBFLG2_SECURITY_SIGNATURE) == 0)
                return rc;

//Obtain global lock on the MIDs
        spin_lock(&GlobalMid_Lock);
//Set the sequence number in the smb_hdr->Extra->Signature field.
// http://ubiqx.org/cifs/SMB.html#SMB.8.9.2
        cifs_pdu->Signature.Sequence.SequenceNumber =
                        cpu_to_le32(server->sequence_number);
        cifs_pdu->Signature.Sequence.Reserved = 0;

//Store the seq number for the response. 
//This is used to check the signature of the server sent response
// and is stores in the MID->sequence_number for the request.
        *pexpected_response_sequence_number = server->sequence_number++;
//Increment it one more time so that it is an even number for use by the next request.
        server->sequence_number++;
        spin_unlock(&GlobalMid_Lock);

//Now calculate signature.
        rc = cifs_calculate_signature(cifs_pdu, server, smb_signature);
-->
http://ubiqx.org/cifs/SMB.html#SMB.8.9.3
static int cifs_calculate_signature(const struct smb_hdr *cifs_pdu,
               struct TCP_Server_Info *server, char *signature)
{
struct  MD5Context context;

if (cifs_pdu == NULL || signature == NULL || server == NULL)
return -EINVAL;

//Initialise MD5
cifs_MD5_init(&context);
//Add the Session key and response which are concatanated at server->session_key.response
cifs_MD5_update(&context, server->session_key.response,
       server->session_key.len);
//Now update the md5 hash with the entire smb packet.
cifs_MD5_update(&context, cifs_pdu->Protocol, cifs_pdu->smb_buf_length);

cifs_MD5_final(signature, &context);
return 0;
}
<-- i="">

//We now overwrite the sequence number stored in smb_hdr->Extra->Signature field
//With the first 8 bytes of the signature we generated above.
        if (rc)
                memset(cifs_pdu->Signature.SecuritySignature, 0, 8);
        else
                memcpy(cifs_pdu->Signature.SecuritySignature, smb_signature, 8);

        return rc;
}

The packet so generated is sent to the server.

When receiving a response from the server, we verify the signature on the packet.


int
SendReceive(const unsigned int xid, struct cifsSesInfo *ses,
            struct smb_hdr *in_buf, struct smb_hdr *out_buf,
            int *pbytes_returned, const int long_op)
{
..
        rc = smb_send(ses->server, in_buf, in_buf->smb_buf_length);
..
//The stored sequence number is the sequence number of the request.
//We need to increment it for the response.
                        rc = cifs_verify_signature(out_buf,
                                                ses->server,
                                                midQ->sequence_number+1);
..
}

int cifs_verify_signature(struct smb_hdr *cifs_pdu,
                          struct TCP_Server_Info *server,
                          __u32 expected_sequence_number)
{
..
        /* save off the origiginal signature so we can modify the smb and check
                its signature against what the server sent */
        memcpy(server_response_sig, cifs_pdu->Signature.SecuritySignature, 8);
//We copy over the expected sequence number to the packet in place of the Signature.
        cifs_pdu->Signature.Sequence.SequenceNumber =
                                        cpu_to_le32(expected_sequence_number);
        cifs_pdu->Signature.Sequence.Reserved = 0;

//We then calculate the signature.
//Notes for cifs_calculate_signature() are given above.
        rc = cifs_calculate_signature(cifs_pdu, server,
                what_we_think_sig_should_be);

        if (rc)
                return rc;

//We then compare the first 8 bytes of the expected signature we generated with
//the signature sent by the server.
        if (memcmp(server_response_sig, what_we_think_sig_should_be, 8))
//If they do not match, it could be a man in the middle attack.
                return -EACCES;
        else
//It matches. So is fine.
                return 0;

}

The additional steps required for verifying signatures increases the load on the client/server. This is therefore switched off by default and only enabled when the user requests it.

NTLMv2 Authentication:
NTLMv2 authentication differs in the following ways from NTLM authentication.
1) The NTLMv2 hash is created using the NTLM hash and additional data.
3) The NTLMv2 Session key is created using additional data.
2) The client generates a random string of bytes to use as a client challenge(referred to as blob/blip) which is then used to create the response. It seperately sends the client challenge along with the response calculated in the NTLMv2 response to the server in the SESSION_SETUP_ANDX request. The server then creates its own response using the client challenge and compares it with the response to confirm authentication.

Setting a frost alarm on Home assistant

One of the issues with winter is the possibility of ice covering your windscreen which needs to be cleared before you can drive. Clearing ou...