SMB2: allow users to give decryption keys directly

Previously users could only give a session key via

    uat:smb2_seskey_list:<id>,<seskey>

which was used to generate the decryption keys, as long as the trace
contained the session establishement.

Users have often asked about how to decrypt traffic captured in the
middle of an existing session but this wasn't possible.

This commit extends uat:smb2_seskey_list with 2 extra columns to store
decryption keys so that traffic can be decrypted at any point of the
session.

This has the side effect of changing the current syntax from:

    ... -o uat:smb2_seskey_list:<id>,<seskey>

To:

    ... -o 'uat:smb2_seskey_list:<id>,<seskey>,"",""'

(make sure the quoting is right)

Change-Id: I810d464b6f3e749de39b4428d73e0d6be29f3152
Reviewed-on: https://code.wireshark.org/review/36135
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:
Aurelien Aptel 2020-02-19 22:16:45 +01:00 committed by Alexis La Goutte
parent de665417ab
commit 95a37ff2fe
3 changed files with 102 additions and 42 deletions

View File

@ -1075,10 +1075,18 @@ smb2stat_packet(void *pss, packet_info *pinfo, epan_dissect_t *edt _U_, const vo
/* Structure for SessionID <=> SessionKey mapping for decryption. */
typedef struct _smb2_seskey_field_t {
/* session id */
guchar *id; /* *little-endian* - not necessarily host-endian! */
guint id_len;
guchar *key;
guint key_len;
/* session key */
guchar *seskey;
guint seskey_len;
/* server to client key */
guchar *s2ckey;
guint s2ckey_len;
/* client to server key */
guchar *c2skey;
guint c2skey_len;
} smb2_seskey_field_t;
static smb2_seskey_field_t *seskey_list = NULL;
@ -1088,13 +1096,18 @@ static const gint8 zeros[NTLMSSP_KEY_LEN] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
/* Callbacks for SessionID <=> SessionKey mapping. */
UAT_BUFFER_CB_DEF(seskey_list, id, smb2_seskey_field_t, id, id_len)
UAT_BUFFER_CB_DEF(seskey_list, key, smb2_seskey_field_t, key, key_len)
UAT_BUFFER_CB_DEF(seskey_list, seskey, smb2_seskey_field_t, seskey, seskey_len)
UAT_BUFFER_CB_DEF(seskey_list, s2ckey, smb2_seskey_field_t, s2ckey, s2ckey_len)
UAT_BUFFER_CB_DEF(seskey_list, c2skey, smb2_seskey_field_t, c2skey, c2skey_len)
#define SMB_SESSION_ID_SIZE 8
static gboolean seskey_list_update_cb(void *r, char **err)
{
smb2_seskey_field_t *rec = (smb2_seskey_field_t *)r;
gboolean has_seskey = rec->seskey_len != 0;
gboolean has_s2ckey = rec->s2ckey_len != 0;
gboolean has_c2skey = rec->c2skey_len != 0;
*err = NULL;
@ -1103,8 +1116,24 @@ static gboolean seskey_list_update_cb(void *r, char **err)
return FALSE;
}
if (rec->key_len == 0 || rec->key_len > NTLMSSP_KEY_LEN) {
*err = g_strdup("Session Key must be a non-empty hexadecimal string representing at most " G_STRINGIFY(NTLMSSP_KEY_LEN) " bytes");
if (!has_seskey && !(has_c2skey || has_s2ckey)) {
*err = g_strdup("Decryption requires either the Session Key or at least one of the client-server AES keys");
return FALSE;
}
if (rec->seskey_len > NTLMSSP_KEY_LEN) {
*err = g_strdup("Session Key must be a hexadecimal string representing at most " G_STRINGIFY(NTLMSSP_KEY_LEN) " bytes");
return FALSE;
}
if (has_s2ckey && rec->s2ckey_len != AES_KEY_SIZE) {
*err = g_strdup("Server-to-Client key must be a hexadecimal string representing " G_STRINGIFY(AES_KEY_SIZE));
return FALSE;
}
if (has_c2skey && rec->c2skey_len != AES_KEY_SIZE) {
*err = g_strdup("Client-to-Server key must be a hexadecimal string representing " G_STRINGIFY(AES_KEY_SIZE));
return FALSE;
}
@ -1118,8 +1147,12 @@ static void* seskey_list_copy_cb(void *n, const void *o, size_t siz _U_)
new_rec->id_len = old_rec->id_len;
new_rec->id = old_rec->id ? (guchar *)g_memdup(old_rec->id, old_rec->id_len) : NULL;
new_rec->key_len = old_rec->key_len;
new_rec->key = old_rec->key ? (guchar *)g_memdup(old_rec->key, old_rec->key_len) : NULL;
new_rec->seskey_len = old_rec->seskey_len;
new_rec->seskey = old_rec->seskey ? (guchar *)g_memdup(old_rec->seskey, old_rec->seskey_len) : NULL;
new_rec->s2ckey_len = old_rec->s2ckey_len;
new_rec->s2ckey = old_rec->s2ckey ? (guchar *)g_memdup(old_rec->s2ckey, old_rec->s2ckey_len) : NULL;
new_rec->c2skey_len = old_rec->c2skey_len;
new_rec->c2skey = old_rec->c2skey ? (guchar *)g_memdup(old_rec->c2skey, old_rec->c2skey_len) : NULL;
return new_rec;
}
@ -1129,10 +1162,12 @@ static void seskey_list_free_cb(void *r)
smb2_seskey_field_t *rec = (smb2_seskey_field_t *)r;
g_free(rec->id);
g_free(rec->key);
g_free(rec->seskey);
g_free(rec->s2ckey);
g_free(rec->c2skey);
}
static gboolean seskey_find_sid_key(guint64 sesid, guint8 *out_key)
static gboolean seskey_find_sid_key(guint64 sesid, guint8 *out_seskey, guint8 *out_s2ckey, guint8 *out_c2skey)
{
guint i;
guint64 sesid_le;
@ -1156,8 +1191,17 @@ static gboolean seskey_find_sid_key(guint64 sesid, guint8 *out_key)
for (i = 0; i < num_seskey_list; i++) {
const smb2_seskey_field_t *p = &seskey_list[i];
if (memcmp(&sesid_le, p->id, SMB_SESSION_ID_SIZE) == 0) {
memset(out_key, 0, NTLMSSP_KEY_LEN);
memcpy(out_key, p->key, p->key_len);
memset(out_seskey, 0, NTLMSSP_KEY_LEN);
memset(out_s2ckey, 0, AES_KEY_SIZE);
memset(out_c2skey, 0, AES_KEY_SIZE);
if (p->seskey_len != 0)
memcpy(out_seskey, p->seskey, p->seskey_len);
if (p->s2ckey_len != 0)
memcpy(out_s2ckey, p->s2ckey, p->s2ckey_len);
if (p->c2skey_len != 0)
memcpy(out_c2skey, p->c2skey, p->c2skey_len);
return TRUE;
}
}
@ -1336,7 +1380,7 @@ smb2_get_session(smb2_conv_info_t *conv, guint64 id, packet_info *pinfo, smb2_in
ses->sesid = id;
ses->auth_frame = (guint32)-1;
ses->tids = wmem_map_new(wmem_file_scope(), smb2_tid_info_hash, smb2_tid_info_equal);
seskey_find_sid_key(id, ses->session_key);
seskey_find_sid_key(id, ses->session_key, ses->client_decryption_key, ses->server_decryption_key);
if (pinfo && si) {
if (si->flags & SMB2_FLAGS_RESPONSE) {
ses->server_port = pinfo->srcport;
@ -3282,31 +3326,44 @@ dissect_smb2_secblob(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, smb2_i
*/
static void smb2_generate_decryption_keys(smb2_conv_info_t *conv, smb2_sesid_info_t *ses)
{
if (memcmp(ses->session_key, zeros, NTLMSSP_KEY_LEN) == 0)
gboolean has_seskey = memcmp(ses->session_key, zeros, NTLMSSP_KEY_LEN) != 0;
gboolean has_client_key = memcmp(ses->client_decryption_key, zeros, AES_KEY_SIZE) != 0;
gboolean has_server_key = memcmp(ses->server_decryption_key, zeros, AES_KEY_SIZE) != 0;
/* if all decryption keys are provided, nothing to do */
if (has_client_key && has_server_key)
return;
/* otherwise, generate them from session key, if it's there */
if (!has_seskey)
return;
if (conv->dialect == SMB2_DIALECT_300) {
smb2_key_derivation(ses->session_key,
NTLMSSP_KEY_LEN,
"SMB2AESCCM", 11,
"ServerIn ", 10,
ses->server_decryption_key);
smb2_key_derivation(ses->session_key,
NTLMSSP_KEY_LEN,
"SMB2AESCCM", 11,
"ServerOut", 10,
ses->client_decryption_key);
if (!has_server_key)
smb2_key_derivation(ses->session_key,
NTLMSSP_KEY_LEN,
"SMB2AESCCM", 11,
"ServerIn ", 10,
ses->server_decryption_key);
if (!has_client_key)
smb2_key_derivation(ses->session_key,
NTLMSSP_KEY_LEN,
"SMB2AESCCM", 11,
"ServerOut", 10,
ses->client_decryption_key);
} else if (conv->dialect >= SMB2_DIALECT_311) {
smb2_key_derivation(ses->session_key,
NTLMSSP_KEY_LEN,
"SMBC2SCipherKey", 16,
ses->preauth_hash, SMB2_PREAUTH_HASH_SIZE,
ses->server_decryption_key);
smb2_key_derivation(ses->session_key,
NTLMSSP_KEY_LEN,
"SMBS2CCipherKey", 16,
ses->preauth_hash, SMB2_PREAUTH_HASH_SIZE,
ses->client_decryption_key);
if (!has_server_key)
smb2_key_derivation(ses->session_key,
NTLMSSP_KEY_LEN,
"SMBC2SCipherKey", 16,
ses->preauth_hash, SMB2_PREAUTH_HASH_SIZE,
ses->server_decryption_key);
if (!has_client_key)
smb2_key_derivation(ses->session_key,
NTLMSSP_KEY_LEN,
"SMBS2CCipherKey", 16,
ses->preauth_hash, SMB2_PREAUTH_HASH_SIZE,
ses->client_decryption_key);
}
@ -12874,7 +12931,9 @@ proto_register_smb2(void)
static uat_field_t seskey_uat_fields[] = {
UAT_FLD_BUFFER(seskey_list, id, "Session ID", "The session ID buffer, coded as hex string, as it appears on the wire (LE)."),
UAT_FLD_BUFFER(seskey_list, key, "Session Key", "The secret session key buffer, coded as 16-byte hex string as it appears on the wire (LE)."),
UAT_FLD_BUFFER(seskey_list, seskey, "Session Key", "The secret session key buffer, coded as 16-byte hex string."),
UAT_FLD_BUFFER(seskey_list, s2ckey, "Server-to-Client", "The AES-128 key used by the client to decrypt server messages, coded as 16-byte hex string."),
UAT_FLD_BUFFER(seskey_list, c2skey, "Client-to-Server", "The AES-128 key used by the server to decrypt client messages, coded as 16-byte hex string."),
UAT_END_FIELDS
};
@ -12914,7 +12973,7 @@ proto_register_smb2(void)
prefs_register_uat_preference(smb2_module,
"seskey_list",
"Secret session keys for decryption",
"A table of Session ID to Session key mappings used to derive decryption keys.",
"A table of Session ID to Session keys mappings used to decrypt traffic.",
seskey_uat);
smb2_pipe_subdissector_list = register_heur_dissector_list("smb2_pipe_subdissectors", proto_smb2);

View File

@ -76,6 +76,7 @@ typedef struct _smb2_tid_info_t {
} smb2_tid_info_t;
#define SMB2_PREAUTH_HASH_SIZE 64
#define AES_KEY_SIZE 16
typedef struct _smb2_sesid_info_t {
guint64 sesid; /* *host* byte order - not necessarily little-endian! */
@ -85,8 +86,8 @@ typedef struct _smb2_sesid_info_t {
char *host_name;
guint16 server_port;
guint8 session_key[NTLMSSP_KEY_LEN];
guint8 client_decryption_key[16];
guint8 server_decryption_key[16];
guint8 client_decryption_key[AES_KEY_SIZE];
guint8 server_decryption_key[AES_KEY_SIZE];
wmem_map_t *tids;
guint8 preauth_hash[SMB2_PREAUTH_HASH_SIZE];
} smb2_sesid_info_t;

View File

@ -1112,7 +1112,7 @@ class case_decrypt_smb2(subprocesstest.SubprocessTestCase):
sesid = '1900009c003c0000'
proc = self.assertRun((cmd_tshark,
'-r', capture_file('smb300-aes-128-ccm.pcap.gz'),
'-o', 'uat:smb2_seskey_list:{},{}'.format(sesid, seskey),
'-o', 'uat:smb2_seskey_list:{},{},"",""'.format(sesid, seskey),
'-Y', 'frame.number == 7',
))
self.assertIn('Invalid header', proc.stdout_str)
@ -1122,7 +1122,7 @@ class case_decrypt_smb2(subprocesstest.SubprocessTestCase):
sesid = '2900009c003c0000'
proc = self.assertRun((cmd_tshark,
'-r', capture_file('smb311-aes-128-ccm.pcap.gz'),
'-o', 'uat:smb2_seskey_list:{},{}'.format(sesid, seskey),
'-o', 'uat:smb2_seskey_list:{},{},"",""'.format(sesid, seskey),
'-Y', 'frame.number == 7'
))
self.assertIn('Invalid header', proc.stdout_str)
@ -1134,7 +1134,7 @@ class case_decrypt_smb2(subprocesstest.SubprocessTestCase):
tree = r'\\dfsroot1.foo.test\IPC$'
proc = self.assertRun((cmd_tshark,
'-r', capture_file('smb300-aes-128-ccm.pcap.gz'),
'-o', 'uat:smb2_seskey_list:{},{}'.format(sesid, seskey),
'-o', 'uat:smb2_seskey_list:{},{},"",""'.format(sesid, seskey),
'-Tfields',
'-e', 'smb2.tree',
'-Y', 'smb2.tree == "{}"'.format(tree.replace('\\', '\\\\')),
@ -1148,7 +1148,7 @@ class case_decrypt_smb2(subprocesstest.SubprocessTestCase):
tree = r'\\dfsroot1.foo.test\IPC$'
proc = self.assertRun((cmd_tshark,
'-r', capture_file('smb311-aes-128-ccm.pcap.gz'),
'-o', 'uat:smb2_seskey_list:{},{}'.format(sesid, seskey),
'-o', 'uat:smb2_seskey_list:{},{},"",""'.format(sesid, seskey),
'-Tfields',
'-e', 'smb2.tree',
'-Y', 'smb2.tree == "{}"'.format(tree.replace('\\', '\\\\')),
@ -1162,7 +1162,7 @@ class case_decrypt_smb2(subprocesstest.SubprocessTestCase):
tree = r'\\dfsroot1.foo.test\IPC$'
proc = self.assertRun((cmd_tshark,
'-r', capture_file('smb311-aes-128-gcm.pcap.gz'),
'-o', 'uat:smb2_seskey_list:{},{}'.format(sesid, seskey),
'-o', 'uat:smb2_seskey_list:{},{},"",""'.format(sesid, seskey),
'-Tfields',
'-e', 'smb2.tree',
'-Y', 'smb2.tree == "{}"'.format(tree.replace('\\', '\\\\')),