diff options
author | Aurelien Aptel <aaptel@suse.com> | 2020-02-19 22:30:37 +0100 |
---|---|---|
committer | Alexis La Goutte <alexis.lagoutte@gmail.com> | 2020-02-23 06:13:30 +0000 |
commit | b8f9448c7887729ce82efeb097da01b9f8d246de (patch) | |
tree | 9c6bebb4d0ce360e5373a5b689baa24ed0dbfcaa /epan | |
parent | 95a37ff2fe1017bd2a97e9f0d96d9fd0faffdbf4 (diff) |
SMB2: try to guess encryption settings when not available
When dissecting a capture made in the middle of an existing encrypted
session we cannot decrypt the traffic because we don't know:
* what SMB dialect and encryption algorithm was picked during the
session establishment
* which host is the server and which host is the client
Since we know the decrypted payload always starts with a valid header
we use this as an heuristic and try all possible decryption settings.
Change-Id: I1daa297ced98e62cf361b9022871c668e56f8f4b
Reviewed-on: https://code.wireshark.org/review/36136
Reviewed-by: Peter Wu <peter@lekensteyn.nl>
Petri-Dish: Peter Wu <peter@lekensteyn.nl>
Tested-by: Petri Dish Buildbot
Reviewed-by: Alexis La Goutte <alexis.lagoutte@gmail.com>
Diffstat (limited to 'epan')
-rw-r--r-- | epan/dissectors/packet-smb2.c | 222 |
1 files changed, 151 insertions, 71 deletions
diff --git a/epan/dissectors/packet-smb2.c b/epan/dissectors/packet-smb2.c index 84d99b8441..0503c4df4f 100644 --- a/epan/dissectors/packet-smb2.c +++ b/epan/dissectors/packet-smb2.c @@ -9601,63 +9601,36 @@ static smb2_function smb2_dissector[256] = { #define SMB3_AES128GCM_NONCE 12 #if GCRYPT_VERSION_NUMBER >= 0x010600 /* 1.6.0 */ -static guint8* -decrypt_smb_payload(packet_info *pinfo, - tvbuff_t *tvb, int offset, - int offset_aad, - smb2_transform_info_t *sti) +static gboolean is_decrypted_header_ok(guint8 *p, size_t size) +{ + if (size < 4) + return FALSE; + + if ((p[0] == SMB2_COMP_HEADER || p[0] == SMB2_NORM_HEADER) + && (p[1] == 'S' || p[2] == 'M' || p[3] == 'B')) { + return TRUE; + } + + DEBUG("decrypt: bad SMB header"); + return FALSE; +} + +static gboolean +do_decrypt(guint8 *data, + size_t data_size, + const guint8 *key, + const guint8 *aad, + int aad_size, + const guint8 *nonce, + int alg) { gcry_error_t err; gcry_cipher_hd_t cipher_hd = NULL; - const guint8 *aad = NULL; - guint8 *data = NULL; - guint8 *key = NULL; int mode; int iv_size; - int aad_size; guint64 lengths[3]; - /* AAD is the rest of transform header after the ProtocolID and Signature */ - aad_size = 32; - - if ((unsigned)tvb_captured_length_remaining(tvb, offset) < sti->size) - return NULL; - - if (tvb_captured_length_remaining(tvb, offset_aad) < aad_size) - return NULL; - - if (pinfo->destport == sti->session->server_port) - key = sti->session->server_decryption_key; - else - key = sti->session->client_decryption_key; - - if (memcmp(key, zeros, NTLMSSP_KEY_LEN) == 0) - key = NULL; - - if (!key) - return NULL; - - /* - * In SMB3.0 the transform header had a Algorithm field to - * know which type of encryption was used but only CCM was - * supported. - * - * SMB3.1.1 turned that field into a generic "Encrypted" flag - * which cannot be used to determine the encryption - * type. Instead the type is decided in the NegProt response, - * within the Encryption Capability context which should only - * have one element. That element is saved in the conversation - * struct (si->conv) and checked here. - */ - - /* g_warning("dialect 0x%x alg 0x%x conv alg 0x%x", sti->conv->dialect, sti->alg, sti->conv->enc_alg); */ - - if (sti->conv->dialect == SMB2_DIALECT_300) { - /* If we are decrypting in SMB3.0, it must be CCM */ - sti->conv->enc_alg = SMB2_CIPHER_AES_128_CCM; - } - - switch (sti->conv->enc_alg) { + switch (alg) { case SMB2_CIPHER_AES_128_CCM: mode = GCRY_CIPHER_MODE_CCM; iv_size = SMB3_AES128CCM_NONCE; @@ -9667,59 +9640,166 @@ decrypt_smb_payload(packet_info *pinfo, iv_size = SMB3_AES128GCM_NONCE; break; default: - return NULL; + return FALSE; } /* Open the cipher */ if ((err = gcry_cipher_open(&cipher_hd, GCRY_CIPHER_AES128, mode, 0))) { - /* g_warning("GCRY: open %s/%s\n", gcry_strsource(err), gcry_strerror(err)); */ - return NULL; + DEBUG("GCRY: open %s/%s", gcry_strsource(err), gcry_strerror(err)); + return FALSE; } /* Set the key */ - if ((err = gcry_cipher_setkey(cipher_hd, key, NTLMSSP_KEY_LEN))) { - /* g_warning("GCRY: setkey %s/%s\n", gcry_strsource(err), gcry_strerror(err)); */ + if ((err = gcry_cipher_setkey(cipher_hd, key, AES_KEY_SIZE))) { + DEBUG("GCRY: setkey %s/%s", gcry_strsource(err), gcry_strerror(err)); gcry_cipher_close(cipher_hd); - return NULL; + return FALSE; } /* Set the initial value */ - if ((err = gcry_cipher_setiv(cipher_hd, sti->nonce, iv_size))) { - /* g_warning("GCRY: setiv %s/%s\n", gcry_strsource(err), gcry_strerror(err)); */ + if ((err = gcry_cipher_setiv(cipher_hd, nonce, iv_size))) { + DEBUG("GCRY: setiv %s/%s", gcry_strsource(err), gcry_strerror(err)); gcry_cipher_close(cipher_hd); - return NULL; + return FALSE; } - aad = tvb_get_ptr(tvb, offset_aad, aad_size); - - lengths[0] = sti->size; /* encrypted length */ + lengths[0] = data_size; /* encrypted length */ lengths[1] = aad_size; /* AAD length */ lengths[2] = 16; /* tag length (signature size) */ if (mode == GCRY_CIPHER_MODE_CCM) { if ((err = gcry_cipher_ctl(cipher_hd, GCRYCTL_SET_CCM_LENGTHS, lengths, sizeof(lengths)))) { - /* g_warning("GCRY: ctl %s/%s\n", gcry_strsource(err), gcry_strerror(err)); */ + DEBUG("GCRY: ctl %s/%s", gcry_strsource(err), gcry_strerror(err)); gcry_cipher_close(cipher_hd); - return NULL; + return FALSE; } } if ((err = gcry_cipher_authenticate(cipher_hd, aad, aad_size))) { - /* g_warning("GCRY: auth %s/%s\n", gcry_strsource(err), gcry_strerror(err)); */ + DEBUG("GCRY: auth %s/%s", gcry_strsource(err), gcry_strerror(err)); gcry_cipher_close(cipher_hd); - return NULL; + return FALSE; } - data = (guint8 *)tvb_memdup(pinfo->pool, tvb, offset, sti->size); - - if ((err = gcry_cipher_decrypt(cipher_hd, data, sti->size, NULL, 0))) { - /* g_warning("GCRY: decrypt %s/%s\n", gcry_strsource(err), gcry_strerror(err)); */ + if ((err = gcry_cipher_decrypt(cipher_hd, data, data_size, NULL, 0))) { + DEBUG("GCRY: decrypt %s/%s", gcry_strsource(err), gcry_strerror(err)); gcry_cipher_close(cipher_hd); - return NULL; + return FALSE; } /* Done with the cipher */ gcry_cipher_close(cipher_hd); + return is_decrypted_header_ok(data, data_size); +} + +static guint8* +decrypt_smb_payload(packet_info *pinfo, + tvbuff_t *tvb, int offset, + int offset_aad, + smb2_transform_info_t *sti) +{ + const guint8 *aad = NULL; + guint8 *data = NULL; + guint8 *keys[2], *key; + gboolean ok; + int aad_size; + int alg; + + /* AAD is the rest of transform header after the ProtocolID and Signature */ + aad_size = 32; + + if ((unsigned)tvb_captured_length_remaining(tvb, offset) < sti->size) + return NULL; + + if (tvb_captured_length_remaining(tvb, offset_aad) < aad_size) + return NULL; + + if (pinfo->destport == sti->session->server_port) { + keys[0] = sti->session->server_decryption_key; + keys[1] = sti->session->client_decryption_key; + } else { + keys[1] = sti->session->server_decryption_key; + keys[0] = sti->session->client_decryption_key; + } + + aad = tvb_get_ptr(tvb, offset_aad, aad_size); + data = (guint8 *)tvb_memdup(pinfo->pool, tvb, offset, sti->size); + + /* + * In SMB3.0 the transform header had a Algorithm field to + * know which type of encryption was used but only CCM was + * supported. + * + * SMB3.1.1 turned that field into a generic "Encrypted" flag + * which cannot be used to determine the encryption + * type. Instead the type is decided in the NegProt response, + * within the Encryption Capability context which should only + * have one element. That element is saved in the conversation + * struct (si->conv) and checked here. + * + * If the trace didn't contain NegProt packets, we have to + * guess the encryption type by trying them all. + * + * Similarly, if we don't have unencrypted packets telling us + * which host is the server and which host is the client, we + * have to guess by trying both keys. + */ + + DEBUG("dialect 0x%x alg 0x%x conv alg 0x%x", sti->conv->dialect, sti->alg, sti->conv->enc_alg); + + if (sti->conv->dialect == SMB2_DIALECT_300) { + /* If we know we are decrypting SMB3.0, it must be CCM */ + sti->conv->enc_alg = SMB2_CIPHER_AES_128_CCM; + } + + for (guint i = 0; i < G_N_ELEMENTS(keys); i++) { + gboolean try_ccm, try_gcm; + key = keys[i]; + ok = try_ccm = try_gcm = FALSE; + + switch (sti->conv->enc_alg) { + case SMB2_CIPHER_AES_128_CCM: + try_ccm = TRUE; + break; + case SMB2_CIPHER_AES_128_GCM: + try_gcm = TRUE; + break; + default: + /* we don't know, try both */ + try_ccm = TRUE; + try_gcm = TRUE; + } + + if (try_ccm) { + DEBUG("trying CCM decryption"); + alg = SMB2_CIPHER_AES_128_CCM; + ok = do_decrypt(data, sti->size, key, aad, aad_size, sti->nonce, alg); + if (ok) + break; + DEBUG("bad decrypted buffer with CCM"); + } + if (try_gcm) { + DEBUG("trying GCM decryption"); + alg = SMB2_CIPHER_AES_128_GCM; + tvb_memcpy(tvb, data, offset, sti->size); + ok = do_decrypt(data, sti->size, key, aad, aad_size, sti->nonce, alg); + if (ok) + break; + DEBUG("bad decrypted buffer with GCM"); + } + DEBUG("trying to decrypt with swapped client/server keys"); + tvb_memcpy(tvb, data, offset, sti->size); + } + + if (!ok) + return NULL; + + /* Remember what worked */ + sti->conv->enc_alg = alg; + if (key == sti->session->server_decryption_key) + sti->session->server_port = pinfo->destport; + else + sti->session->server_port = pinfo->srcport; return data; } #endif |