forked from osmocom/wireshark
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>
This commit is contained in:
parent
95a37ff2fe
commit
b8f9448c78
|
@ -9601,21 +9601,109 @@ static smb2_function smb2_dissector[256] = {
|
||||||
#define SMB3_AES128GCM_NONCE 12
|
#define SMB3_AES128GCM_NONCE 12
|
||||||
|
|
||||||
#if GCRYPT_VERSION_NUMBER >= 0x010600 /* 1.6.0 */
|
#if GCRYPT_VERSION_NUMBER >= 0x010600 /* 1.6.0 */
|
||||||
|
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;
|
||||||
|
int mode;
|
||||||
|
int iv_size;
|
||||||
|
guint64 lengths[3];
|
||||||
|
|
||||||
|
switch (alg) {
|
||||||
|
case SMB2_CIPHER_AES_128_CCM:
|
||||||
|
mode = GCRY_CIPHER_MODE_CCM;
|
||||||
|
iv_size = SMB3_AES128CCM_NONCE;
|
||||||
|
break;
|
||||||
|
case SMB2_CIPHER_AES_128_GCM:
|
||||||
|
mode = GCRY_CIPHER_MODE_GCM;
|
||||||
|
iv_size = SMB3_AES128GCM_NONCE;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Open the cipher */
|
||||||
|
if ((err = gcry_cipher_open(&cipher_hd, GCRY_CIPHER_AES128, mode, 0))) {
|
||||||
|
DEBUG("GCRY: open %s/%s", gcry_strsource(err), gcry_strerror(err));
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set the key */
|
||||||
|
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 FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set the initial value */
|
||||||
|
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 FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
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)))) {
|
||||||
|
DEBUG("GCRY: ctl %s/%s", gcry_strsource(err), gcry_strerror(err));
|
||||||
|
gcry_cipher_close(cipher_hd);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((err = gcry_cipher_authenticate(cipher_hd, aad, aad_size))) {
|
||||||
|
DEBUG("GCRY: auth %s/%s", gcry_strsource(err), gcry_strerror(err));
|
||||||
|
gcry_cipher_close(cipher_hd);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Done with the cipher */
|
||||||
|
gcry_cipher_close(cipher_hd);
|
||||||
|
return is_decrypted_header_ok(data, data_size);
|
||||||
|
}
|
||||||
|
|
||||||
static guint8*
|
static guint8*
|
||||||
decrypt_smb_payload(packet_info *pinfo,
|
decrypt_smb_payload(packet_info *pinfo,
|
||||||
tvbuff_t *tvb, int offset,
|
tvbuff_t *tvb, int offset,
|
||||||
int offset_aad,
|
int offset_aad,
|
||||||
smb2_transform_info_t *sti)
|
smb2_transform_info_t *sti)
|
||||||
{
|
{
|
||||||
gcry_error_t err;
|
|
||||||
gcry_cipher_hd_t cipher_hd = NULL;
|
|
||||||
const guint8 *aad = NULL;
|
const guint8 *aad = NULL;
|
||||||
guint8 *data = NULL;
|
guint8 *data = NULL;
|
||||||
guint8 *key = NULL;
|
guint8 *keys[2], *key;
|
||||||
int mode;
|
gboolean ok;
|
||||||
int iv_size;
|
|
||||||
int aad_size;
|
int aad_size;
|
||||||
guint64 lengths[3];
|
int alg;
|
||||||
|
|
||||||
/* AAD is the rest of transform header after the ProtocolID and Signature */
|
/* AAD is the rest of transform header after the ProtocolID and Signature */
|
||||||
aad_size = 32;
|
aad_size = 32;
|
||||||
|
@ -9626,16 +9714,16 @@ decrypt_smb_payload(packet_info *pinfo,
|
||||||
if (tvb_captured_length_remaining(tvb, offset_aad) < aad_size)
|
if (tvb_captured_length_remaining(tvb, offset_aad) < aad_size)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
if (pinfo->destport == sti->session->server_port)
|
if (pinfo->destport == sti->session->server_port) {
|
||||||
key = sti->session->server_decryption_key;
|
keys[0] = sti->session->server_decryption_key;
|
||||||
else
|
keys[1] = sti->session->client_decryption_key;
|
||||||
key = sti->session->client_decryption_key;
|
} else {
|
||||||
|
keys[1] = sti->session->server_decryption_key;
|
||||||
|
keys[0] = sti->session->client_decryption_key;
|
||||||
|
}
|
||||||
|
|
||||||
if (memcmp(key, zeros, NTLMSSP_KEY_LEN) == 0)
|
aad = tvb_get_ptr(tvb, offset_aad, aad_size);
|
||||||
key = NULL;
|
data = (guint8 *)tvb_memdup(pinfo->pool, tvb, offset, sti->size);
|
||||||
|
|
||||||
if (!key)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* In SMB3.0 the transform header had a Algorithm field to
|
* In SMB3.0 the transform header had a Algorithm field to
|
||||||
|
@ -9648,78 +9736,70 @@ decrypt_smb_payload(packet_info *pinfo,
|
||||||
* within the Encryption Capability context which should only
|
* within the Encryption Capability context which should only
|
||||||
* have one element. That element is saved in the conversation
|
* have one element. That element is saved in the conversation
|
||||||
* struct (si->conv) and checked here.
|
* 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.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* g_warning("dialect 0x%x alg 0x%x conv alg 0x%x", sti->conv->dialect, sti->alg, sti->conv->enc_alg); */
|
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 (sti->conv->dialect == SMB2_DIALECT_300) {
|
||||||
/* If we are decrypting in SMB3.0, it must be CCM */
|
/* If we know we are decrypting SMB3.0, it must be CCM */
|
||||||
sti->conv->enc_alg = SMB2_CIPHER_AES_128_CCM;
|
sti->conv->enc_alg = SMB2_CIPHER_AES_128_CCM;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (sti->conv->enc_alg) {
|
for (guint i = 0; i < G_N_ELEMENTS(keys); i++) {
|
||||||
case SMB2_CIPHER_AES_128_CCM:
|
gboolean try_ccm, try_gcm;
|
||||||
mode = GCRY_CIPHER_MODE_CCM;
|
key = keys[i];
|
||||||
iv_size = SMB3_AES128CCM_NONCE;
|
ok = try_ccm = try_gcm = FALSE;
|
||||||
break;
|
|
||||||
case SMB2_CIPHER_AES_128_GCM:
|
|
||||||
mode = GCRY_CIPHER_MODE_GCM;
|
|
||||||
iv_size = SMB3_AES128GCM_NONCE;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Open the cipher */
|
switch (sti->conv->enc_alg) {
|
||||||
if ((err = gcry_cipher_open(&cipher_hd, GCRY_CIPHER_AES128, mode, 0))) {
|
case SMB2_CIPHER_AES_128_CCM:
|
||||||
/* g_warning("GCRY: open %s/%s\n", gcry_strsource(err), gcry_strerror(err)); */
|
try_ccm = TRUE;
|
||||||
return NULL;
|
break;
|
||||||
}
|
case SMB2_CIPHER_AES_128_GCM:
|
||||||
|
try_gcm = TRUE;
|
||||||
/* Set the key */
|
break;
|
||||||
if ((err = gcry_cipher_setkey(cipher_hd, key, NTLMSSP_KEY_LEN))) {
|
default:
|
||||||
/* g_warning("GCRY: setkey %s/%s\n", gcry_strsource(err), gcry_strerror(err)); */
|
/* we don't know, try both */
|
||||||
gcry_cipher_close(cipher_hd);
|
try_ccm = TRUE;
|
||||||
return NULL;
|
try_gcm = TRUE;
|
||||||
}
|
|
||||||
|
|
||||||
/* 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)); */
|
|
||||||
gcry_cipher_close(cipher_hd);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
aad = tvb_get_ptr(tvb, offset_aad, aad_size);
|
|
||||||
|
|
||||||
lengths[0] = sti->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)); */
|
|
||||||
gcry_cipher_close(cipher_hd);
|
|
||||||
return NULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 ((err = gcry_cipher_authenticate(cipher_hd, aad, aad_size))) {
|
if (!ok)
|
||||||
/* g_warning("GCRY: auth %s/%s\n", gcry_strsource(err), gcry_strerror(err)); */
|
|
||||||
gcry_cipher_close(cipher_hd);
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
|
||||||
|
|
||||||
data = (guint8 *)tvb_memdup(pinfo->pool, tvb, offset, sti->size);
|
/* Remember what worked */
|
||||||
|
sti->conv->enc_alg = alg;
|
||||||
if ((err = gcry_cipher_decrypt(cipher_hd, data, sti->size, NULL, 0))) {
|
if (key == sti->session->server_decryption_key)
|
||||||
/* g_warning("GCRY: decrypt %s/%s\n", gcry_strsource(err), gcry_strerror(err)); */
|
sti->session->server_port = pinfo->destport;
|
||||||
gcry_cipher_close(cipher_hd);
|
else
|
||||||
return NULL;
|
sti->session->server_port = pinfo->srcport;
|
||||||
}
|
|
||||||
|
|
||||||
/* Done with the cipher */
|
|
||||||
gcry_cipher_close(cipher_hd);
|
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1115,7 +1115,7 @@ class case_decrypt_smb2(subprocesstest.SubprocessTestCase):
|
||||||
'-o', 'uat:smb2_seskey_list:{},{},"",""'.format(sesid, seskey),
|
'-o', 'uat:smb2_seskey_list:{},{},"",""'.format(sesid, seskey),
|
||||||
'-Y', 'frame.number == 7',
|
'-Y', 'frame.number == 7',
|
||||||
))
|
))
|
||||||
self.assertIn('Invalid header', proc.stdout_str)
|
self.assertIn('Encrypted', proc.stdout_str)
|
||||||
|
|
||||||
def test_smb311_bad_key(self, cmd_tshark, capture_file):
|
def test_smb311_bad_key(self, cmd_tshark, capture_file):
|
||||||
seskey = 'ffffffffffffffffffffffffffffffff'
|
seskey = 'ffffffffffffffffffffffffffffffff'
|
||||||
|
@ -1125,7 +1125,7 @@ class case_decrypt_smb2(subprocesstest.SubprocessTestCase):
|
||||||
'-o', 'uat:smb2_seskey_list:{},{},"",""'.format(sesid, seskey),
|
'-o', 'uat:smb2_seskey_list:{},{},"",""'.format(sesid, seskey),
|
||||||
'-Y', 'frame.number == 7'
|
'-Y', 'frame.number == 7'
|
||||||
))
|
))
|
||||||
self.assertIn('Invalid header', proc.stdout_str)
|
self.assertIn('Encrypted', proc.stdout_str)
|
||||||
|
|
||||||
def test_smb300_aes128ccm(self, cmd_tshark, capture_file):
|
def test_smb300_aes128ccm(self, cmd_tshark, capture_file):
|
||||||
'''Check SMB 3.0 AES128CCM decryption.'''
|
'''Check SMB 3.0 AES128CCM decryption.'''
|
||||||
|
|
Loading…
Reference in New Issue