aboutsummaryrefslogtreecommitdiffstats
path: root/epan
diff options
context:
space:
mode:
authorAurelien Aptel <aaptel@suse.com>2020-02-19 22:30:37 +0100
committerAlexis La Goutte <alexis.lagoutte@gmail.com>2020-02-23 06:13:30 +0000
commitb8f9448c7887729ce82efeb097da01b9f8d246de (patch)
tree9c6bebb4d0ce360e5373a5b689baa24ed0dbfcaa /epan
parent95a37ff2fe1017bd2a97e9f0d96d9fd0faffdbf4 (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.c222
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