Hello, Please find attached a proposed patch for the SMB protocol support.
The attached patch file contains an enhancement for the SMB support in the Curl libraries. Currently, uploading and downloading using the SMB protocol fails when used against windows domain controllers, as these machines require signing of the messages by default. I've added a function to the smb.c file to create the Message Authentication Codes (MAC), and sign the messages with them. I've also added logic to the connection_state function to recognise when the server requires the messages to be signed. In order to create the MAC for signing, the full message needs to be hashed; therefore there are some changes to the logic of smb_send_write to allow for the parameters of the message, and the data from the uploading file to be joined at this point. Also added is a function to check the signatures of messages received from the server. Hopefully you can consider this patch for inclusion into the Curl libraries. If you have any questions about this functionality, let me know and I can go into more detail on the changes I've made. Kind Regards, Joseph Tetzlaff-Deas Barracuda Networks =========================================================== Considering Office 365? Barracuda security and storage solutions can help. Learn more about Barracuda solutions for Office 365 at http://barracuda.com/office365. DISCLAIMER: This e-mail and any attachments to it contain confidential and proprietary material of Barracuda, its affiliates or agents, and is solely for the use of the intended recipient. Any review, use, disclosure, distribution or copying of this transmittal is prohibited except by or on behalf of the intended recipient. If you have received this transmittal in error, please notify the sender and destroy this e-mail and any attachments and all copies, whether electronic or printed.
From 042e3af5d9d676bff2318697156a06a4b902930c Mon Sep 17 00:00:00 2001 From: Joseph Tetzlaff-Deas <[email protected]> Date: Wed, 16 Dec 2015 12:19:45 +0000 Subject: [PATCH] SMB: Implemented MAC signing of SMB messages Added logic in smb.c to sign SMB messages when negotiated with the server. This is required to talk to windows domain controllers and other servers that require message signing by default. --- lib/smb.c | 222 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++------ lib/smb.h | 7 ++ 2 files changed, 210 insertions(+), 19 deletions(-) diff --git a/lib/smb.c b/lib/smb.c index 8e2c164..2ea80a8 100644 --- a/lib/smb.c +++ b/lib/smb.c @@ -46,6 +46,8 @@ #include "curl_ntlm_core.h" #include "escape.h" #include "curl_endian.h" +#include "openssl/md4.h" +#include "openssl/md5.h" /* The last #include files should be: */ #include "curl_memory.h" @@ -285,6 +287,96 @@ static CURLcode smb_connect(struct connectdata *conn, bool *done) return CURLE_OK; } +static void smb_sign(struct connectdata *conn, struct smb_conn *smbc, + size_t msg_len) +{ + char *full_msg; + unsigned char *security_features; + unsigned char MAC_key[40]; + unsigned char MAC[16]; + char *MAC_data; + MD5_CTX MD5pw; + + full_msg = conn->data->state.uploadbuffer + 4; + security_features = conn->data->state.uploadbuffer + 18; + + /* put sequence in first 4 bytes of signature field */ + memset(security_features, 0, 8); + security_features[0] = (unsigned char)(smbc->sequence & 0xFF); + security_features[1] = (unsigned char)((smbc->sequence >> 8) & 0xFF); + security_features[2] = (unsigned char)((smbc->sequence >> 16) & 0xFF); + security_features[3] = (unsigned char)((smbc->sequence >> 24) & 0xFF); + memcpy(MAC_key, smbc->MAC_key, sizeof(MAC_key)); + + /* calculate MAC signature */ + /* concat the MAC key and entire SMB message */ + MAC_data = malloc(sizeof(MAC_key) + msg_len + + sizeof(struct smb_header) - 4); + memcpy(MAC_data, MAC_key, sizeof(MAC_key)); + memcpy(MAC_data + sizeof(MAC_key), full_msg, msg_len + + sizeof(struct smb_header) - 4); + + /* get MD5 hash of all that */ + MD5_Init(&MD5pw); + MD5_Update(&MD5pw, MAC_data, sizeof(MAC_key) + msg_len + + sizeof(struct smb_header) - 4); + MD5_Final(MAC, &MD5pw); + + /* put first 8 bytes of MD5 hash in security features */ + memcpy(security_features, MAC, 8); + + /* increment sequence */ + smbc->sequence++; + free(MAC_data); +} + +static CURLcode smb_check_sign(struct smb_conn *smbc, char *msg, + size_t msg_size) +{ + char *fullmsg; + char *security_features; + unsigned char recv_signature[8]; + unsigned char MAC[16]; + char *MAC_data; + MD5_CTX MD5pw; + int i; + + /* check signature here */ + fullmsg = msg + 4; + security_features = msg + 18; + memcpy(recv_signature, security_features, 8); + MAC_data = malloc(sizeof(smbc->MAC_key) + msg_size); + + /* get mac key */ + memcpy(MAC_data, smbc->MAC_key, sizeof(smbc->MAC_key)); + + /* put sequence number in sig field */ + memset(security_features, 0, 8); + security_features[0] = (unsigned char)(smbc->sequence & 0xFF); + security_features[1] = (unsigned char)((smbc->sequence >> 8) & 0xFF); + security_features[2] = (unsigned char)((smbc->sequence >> 16) & 0xFF); + security_features[3] = (unsigned char)((smbc->sequence >> 24) & 0xFF); + + /* concat mac key with received SMB message */ + memcpy(MAC_data + sizeof(smbc->MAC_key), fullmsg, msg_size); + + /* MD5 the whole lot */ + MD5_Init(&MD5pw); + MD5_Update(&MD5pw, MAC_data, sizeof(smbc->MAC_key) + msg_size); + MD5_Final(MAC, &MD5pw); + + /* check first 8 bytes against 8 bytes sig from SMB */ + free(MAC_data); + for(i = 0; i < 8; i++) { + if(recv_signature[i] != MAC[i]) + return CURLE_RECV_ERROR; + } + + /* increment sequence */ + smbc->sequence++; + return CURLE_OK; +} + static CURLcode smb_recv_message(struct connectdata *conn, void **msg) { struct smb_conn *smbc = &conn->proto.smbc; @@ -328,6 +420,9 @@ static CURLcode smb_recv_message(struct connectdata *conn, void **msg) *msg = buf; + /* Check message signing */ + if(smbc->sec_sig_req) + return smb_check_sign(smbc, *msg, msg_size - 4); return CURLE_OK; } @@ -408,11 +503,18 @@ static CURLcode smb_flush(struct connectdata *conn) static CURLcode smb_send_message(struct connectdata *conn, unsigned char cmd, const void *msg, size_t msg_len) { + struct smb_conn *smbc = &conn->proto.smbc; + smb_format_message(conn, (struct smb_header *)conn->data->state.uploadbuffer, cmd, msg_len); memcpy(conn->data->state.uploadbuffer + sizeof(struct smb_header), msg, msg_len); + /* Message signing */ + if(smbc->sec_sig_req) { + smb_sign(conn, smbc, msg_len); + } + return smb_send(conn, sizeof(struct smb_header) + msg_len, 0); } @@ -428,9 +530,7 @@ static CURLcode smb_send_setup(struct connectdata *conn) struct smb_conn *smbc = &conn->proto.smbc; struct smb_setup msg; char *p = msg.bytes; - unsigned char lm_hash[21]; unsigned char lm[24]; - unsigned char nt_hash[21]; unsigned char nt[24]; size_t byte_count = sizeof(lm) + sizeof(nt); @@ -439,14 +539,9 @@ static CURLcode smb_send_setup(struct connectdata *conn) if(byte_count > sizeof(msg.bytes)) return CURLE_FILESIZE_EXCEEDED; - Curl_ntlm_core_mk_lm_hash(conn->data, conn->passwd, lm_hash); - Curl_ntlm_core_lm_resp(lm_hash, smbc->challenge, lm); -#if USE_NTRESPONSES - Curl_ntlm_core_mk_nt_hash(conn->data, conn->passwd, nt_hash); - Curl_ntlm_core_lm_resp(nt_hash, smbc->challenge, nt); -#else - memset(nt, 0, sizeof(nt)); -#endif + /* get the responses */ + memcpy(lm, smbc->lm_resp, 24); + memcpy(nt, smbc->nt_resp, 24); memset(&msg, 0, sizeof(msg)); msg.word_count = SMB_WC_SETUP_ANDX; @@ -454,7 +549,7 @@ static CURLcode smb_send_setup(struct connectdata *conn) msg.max_buffer_size = smb_swap16(MAX_MESSAGE_SIZE); msg.max_mpx_count = smb_swap16(1); msg.vc_number = smb_swap16(1); - msg.session_key = smb_swap32(smbc->session_key); + msg.session_key = smb_swap32(SMB_SESSION_KEY); msg.capabilities = smb_swap32(SMB_CAP_LARGE_FILES); msg.lengths[0] = smb_swap16(sizeof(lm)); msg.lengths[1] = smb_swap16(sizeof(nt)); @@ -573,26 +668,52 @@ static CURLcode smb_send_write(struct connectdata *conn) { struct smb_write *msg = (struct smb_write *)conn->data->state.uploadbuffer; struct smb_request *req = conn->data->req.protop; + CURLcode result; curl_off_t offset = conn->data->req.offset; - curl_off_t upload_size = conn->data->req.size - conn->data->req.bytecount; + int nread; + size_t msgsize, fullmsgsize; + if(upload_size >= MAX_PAYLOAD_SIZE - 1) /* There is one byte of padding */ upload_size = MAX_PAYLOAD_SIZE - 1; - memset(msg, 0, sizeof(*msg)); + msgsize = sizeof(*msg); + + /* Ensure we will read no more than BUFSIZE, taking into account the message + * that we will be appending to */ + nread = upload_size > (BUFSIZE - msgsize) ? (BUFSIZE - msgsize) : + (int) upload_size; + + memset(msg, 0, msgsize); msg->word_count = SMB_WC_WRITE_ANDX; msg->andx.command = SMB_COM_NO_ANDX_COMMAND; msg->fid = smb_swap16(req->fid); msg->offset = smb_swap32((unsigned int) offset); msg->offset_high = smb_swap32((unsigned int) (offset >> 32)); - msg->data_length = smb_swap16((unsigned short) upload_size); - msg->data_offset = smb_swap16(sizeof(*msg) - sizeof(unsigned int)); - msg->byte_count = smb_swap16((unsigned short) (upload_size + 1)); + msg->data_offset = smb_swap16((unsigned short) + (msgsize - sizeof(unsigned int))); smb_format_message(conn, &msg->h, SMB_COM_WRITE_ANDX, - sizeof(*msg) - sizeof(msg->h) + (size_t) upload_size); + msgsize - sizeof(msg->h) + (size_t)nread); + + /* Read the file and append it here - this is necessary in order to have + * the full message for signing correctly */ + fullmsgsize = msgsize + nread; + conn->data->req.upload_fromhere = conn->data->state.uploadbuffer + msgsize; + result = Curl_fillreadbuffer(conn, nread, &nread); + if(result && result != CURLE_AGAIN) + return result; - return smb_send(conn, sizeof(*msg), (size_t) upload_size); + if(!nread) + return CURLE_OK; + + msg->data_length = smb_swap16((unsigned short) nread); + msg->byte_count = smb_swap16((unsigned short) nread); + + /* sign the full message */ + smb_sign(conn, &conn->proto.smbc, fullmsgsize - sizeof(struct smb_header)); + + return smb_send(conn, fullmsgsize, 0); } static CURLcode smb_send_and_recv(struct connectdata *conn, void **msg) @@ -630,6 +751,61 @@ static CURLcode smb_send_and_recv(struct connectdata *conn, void **msg) return smb_recv_message(conn, msg); } +static void smb_passwd_hash(struct connectdata *conn, struct smb_conn *smbc) { + unsigned char lm_hash[21]; + unsigned char lm[24]; + unsigned char nt_hash[21]; + unsigned char nt[24]; + unsigned char ntlm_hash[16]; + unsigned char session_key[16]; + unsigned char MAC_key[40]; + MD4_CTX MD4sk; + + /* create the hashes & responses */ + Curl_ntlm_core_mk_lm_hash(conn->data, conn->passwd, lm_hash); + Curl_ntlm_core_lm_resp(lm_hash, smbc->challenge, lm); + memcpy(smbc->lm_resp, lm, sizeof(lm)); +#if USE_NTRESPONSES + Curl_ntlm_core_mk_nt_hash(conn->data, conn->passwd, nt_hash); + Curl_ntlm_core_lm_resp(nt_hash, smbc->challenge, nt); + memcpy(smbc->nt_resp, nt, sizeof(nt)); + if(smbc->sec_sig_req) { + + /* generate the NTLM session key for signing */ + memset(session_key, 0, 16); + memcpy(ntlm_hash, nt_hash, sizeof(ntlm_hash)); + MD4_Init(&MD4sk); + MD4_Update(&MD4sk, ntlm_hash, sizeof(ntlm_hash)); + MD4_Final(session_key, &MD4sk); + + /* create MAC key */ + memcpy(MAC_key, session_key, 16); + memcpy(MAC_key + 16, smbc->nt_resp, 24); + memcpy(smbc->MAC_key, MAC_key, sizeof(smbc->MAC_key)); + + /* init sequence to 0 */ + smbc->sequence = 0; + } +#else + memset(smbc->nt_resp, 0, sizeof(smbc->nt_resp)); + if(smbc->sec_sig_req) { + + /* generate the LM session key for signing */ + memset(session_key, 0, 16); + memcpy(session_key, lm_hash, 8); + memcpy(session_key + 8, "\0\0\0\0\0\0\0\0", 8); + + /* create MAC key */ + memcpy(MAC_key, session_key, 16); + memcpy(MAC_key + 16, smbc->lm_resp, 24); + memcpy(smbc->MAC_key, MAC_key, sizeof(smbc->MAC_key)); + + /* init sequence to 0 */ + smbc->sequence = 0; + } +#endif +} + static CURLcode smb_connection_state(struct connectdata *conn, bool *done) { struct smb_conn *smbc = &conn->proto.smbc; @@ -679,7 +855,15 @@ static CURLcode smb_connection_state(struct connectdata *conn, bool *done) } nrsp = msg; memcpy(smbc->challenge, nrsp->bytes, sizeof(smbc->challenge)); - smbc->session_key = smb_swap32(nrsp->session_key); + + /* Check security mode for message signing required */ + if(nrsp->security_mode & 0x08) + smbc->sec_sig_req = true; + else + smbc->sec_sig_req = false; + + smb_passwd_hash(conn, smbc); + result = smb_send_setup(conn); if(result) { connclose(conn, "SMB: failed to send setup message"); diff --git a/lib/smb.h b/lib/smb.h index 7852fa1..c0e1efe 100644 --- a/lib/smb.h +++ b/lib/smb.h @@ -35,6 +35,9 @@ struct smb_conn { char *user; char *domain; unsigned char challenge[8]; + unsigned char lm_resp[24]; + unsigned char nt_resp[24]; + unsigned char MAC_key[40]; unsigned int session_key; unsigned short uid; char *recv_buf; @@ -42,6 +45,8 @@ struct smb_conn { size_t send_size; size_t sent; size_t got; + unsigned int sequence; + bool sec_sig_req; }; /* @@ -59,6 +64,8 @@ struct smb_conn { # define PACK #endif +#define SMB_SESSION_KEY 0x00000000 + #define SMB_COM_CLOSE 0x04 #define SMB_COM_READ_ANDX 0x2e #define SMB_COM_WRITE_ANDX 0x2f -- 1.9.1
------------------------------------------------------------------- List admin: http://cool.haxx.se/list/listinfo/curl-library Etiquette: http://curl.haxx.se/mail/etiquette.html
