dot11decrypt: Support decrypting FT initial mobility domain
Add partial support for decrypting captures with connections established using FT BSS Transition (IEEE 802.11r). FT BSS Transition decryption comes with the following limitations: - Only FT-PSK is supported. - Keys can only be derived from the FT 4-way handshake messages. - Roaming is not supported.
This commit is contained in:
parent
75e90aa4e9
commit
2306cbddb9
|
@ -1472,6 +1472,19 @@ Dot11DecryptWepMng(
|
|||
return DOT11DECRYPT_RET_SUCCESS;
|
||||
}
|
||||
|
||||
/* From IEEE 802.11-2016 Table 9-133—AKM suite selectors */
|
||||
static gboolean Dot11DecryptIsFtAkm(int akm)
|
||||
{
|
||||
switch (akm) {
|
||||
case 3:
|
||||
case 4:
|
||||
case 9:
|
||||
case 13:
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* Refer to IEEE 802.11i-2004, 8.5.3, pag. 85 */
|
||||
static INT
|
||||
Dot11DecryptRsna4WHandshake(
|
||||
|
@ -1621,14 +1634,54 @@ Dot11DecryptRsna4WHandshake(
|
|||
return DOT11DECRYPT_RET_NO_VALID_HANDSHAKE;
|
||||
}
|
||||
|
||||
/* derive the PTK from the BSSID, STA MAC, PMK, SNonce, ANonce */
|
||||
Dot11DecryptDerivePtk(sa, /* authenticator nonce, bssid, station mac */
|
||||
tmp_pkt_key->KeyData.Wpa.Psk, /* PSK == PMK */
|
||||
tmp_pkt_key->KeyData.Wpa.PskLen,
|
||||
eapol_parsed->nonce, /* supplicant nonce */
|
||||
eapol_parsed->key_version,
|
||||
akm,
|
||||
cipher);
|
||||
if (Dot11DecryptIsFtAkm(akm)) {
|
||||
int hash_algo = Dot11DecryptGetHashAlgoFromAkm(akm);
|
||||
guint8 pmk_r0[DOT11DECRYPT_WPA_PMK_MAX_LEN];
|
||||
guint8 pmk_r1[DOT11DECRYPT_WPA_PMK_MAX_LEN];
|
||||
guint8 pmk_r0_name[16];
|
||||
guint8 pmk_r1_name[16];
|
||||
guint8 ptk_name[16];
|
||||
size_t pmk_r0_len;
|
||||
size_t pmk_r1_len;
|
||||
size_t ptk_len = Dot11DecryptGetPtkLen(akm, cipher) / 8;
|
||||
|
||||
if (!eapol_parsed->mdid || !eapol_parsed->r0kh_id || !eapol_parsed->r1kh_id) {
|
||||
DEBUG_PRINT_LINE("Fields missing for FT", DEBUG_LEVEL_3);
|
||||
return DOT11DECRYPT_RET_NO_VALID_HANDSHAKE;
|
||||
}
|
||||
/* TODO Handle other AKMS (xxkey == PSK is only valid for FT PSK) */
|
||||
dot11decrypt_derive_pmk_r0(tmp_pkt_key->KeyData.Wpa.Psk,
|
||||
tmp_pkt_key->KeyData.Wpa.PskLen,
|
||||
ctx->pkt_ssid, ctx->pkt_ssid_len,
|
||||
eapol_parsed->mdid,
|
||||
eapol_parsed->r0kh_id, eapol_parsed->r0kh_id_len,
|
||||
id->sta, hash_algo,
|
||||
pmk_r0, &pmk_r0_len, pmk_r0_name);
|
||||
DEBUG_DUMP("PMK-R0", pmk_r0, pmk_r0_len);
|
||||
DEBUG_DUMP("PMKR0Name", pmk_r0_name, 16);
|
||||
|
||||
dot11decrypt_derive_pmk_r1(pmk_r0, pmk_r0_len, pmk_r0_name,
|
||||
eapol_parsed->r1kh_id, id->sta, hash_algo,
|
||||
pmk_r1, &pmk_r1_len, pmk_r1_name);
|
||||
DEBUG_DUMP("PMK-R1", pmk_r1, pmk_r1_len);
|
||||
DEBUG_DUMP("PMKR1Name", pmk_r1_name, 16);
|
||||
|
||||
dot11decrypt_derive_ft_ptk(pmk_r1, pmk_r1_len, pmk_r1_name,
|
||||
eapol_parsed->nonce, sa->wpa.nonce,
|
||||
id->bssid, id->sta, hash_algo,
|
||||
sa->wpa.ptk, ptk_len, ptk_name);
|
||||
DEBUG_DUMP("PTK", sa->wpa.ptk, ptk_len);
|
||||
} else {
|
||||
/* derive the PTK from the BSSID, STA MAC, PMK, SNonce, ANonce */
|
||||
Dot11DecryptDerivePtk(sa, /* authenticator nonce, bssid, station mac */
|
||||
tmp_pkt_key->KeyData.Wpa.Psk, /* PSK == PMK */
|
||||
tmp_pkt_key->KeyData.Wpa.PskLen,
|
||||
eapol_parsed->nonce, /* supplicant nonce */
|
||||
eapol_parsed->key_version,
|
||||
akm,
|
||||
cipher);
|
||||
}
|
||||
|
||||
DEBUG_DUMP("TK", DOT11DECRYPT_GET_TK(sa->wpa.ptk, akm), Dot11DecryptGetTkLen(cipher) / 8);
|
||||
|
||||
ret = Dot11DecryptRsnaMicCheck(eapol_parsed,
|
||||
|
|
|
@ -161,6 +161,13 @@ typedef struct _DOT11DECRYPT_EAPOL_PARSED {
|
|||
guint16 mic_len;
|
||||
guint8 *gtk;
|
||||
guint16 gtk_len;
|
||||
|
||||
/* For fast bss transition akms */
|
||||
guint8 *mdid;
|
||||
guint8 *r0kh_id;
|
||||
guint8 r0kh_id_len;
|
||||
guint8 *r1kh_id;
|
||||
guint8 r1kh_id_len;
|
||||
} DOT11DECRYPT_EAPOL_PARSED, *PDOT11DECRYPT_EAPOL_PARSED;
|
||||
|
||||
/************************************************************************/
|
||||
|
|
|
@ -210,6 +210,188 @@ dot11decrypt_kdf(const guint8 *key, size_t key_len,
|
|||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean sha256(const guint8 *data, size_t len, guint8 output[32])
|
||||
{
|
||||
gcry_md_hd_t ctx;
|
||||
gcry_error_t result = gcry_md_open(&ctx, GCRY_MD_SHA256, 0);
|
||||
guint8 *digest;
|
||||
|
||||
if (result) {
|
||||
return FALSE;
|
||||
}
|
||||
gcry_md_write(ctx, data, len);
|
||||
digest = gcry_md_read(ctx, GCRY_MD_SHA256);
|
||||
if (!digest) {
|
||||
return FALSE;
|
||||
}
|
||||
memcpy(output, digest, 32);
|
||||
gcry_md_close(ctx);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Derive PMK-R0 and PMKR0Name. See IEEE 802.11-2016 12.7.1.7.3 PMK-R0
|
||||
*
|
||||
* @param xxkey PSK / MPMK or certain part of MSK.
|
||||
* @param xxkey_len Length of xxkey in bytes.
|
||||
* @param ssid SSID
|
||||
* @param ssid_len Length of SSID in bytes.
|
||||
* @param mdid MDID (Mobility Domain Identifier).
|
||||
* @param r0kh_id PMK-R0 key holder identifier in the Authenticator.
|
||||
* @param r0kh_id_len Lenth of r0kh_id in bytes.
|
||||
* @param s0kh_id PMK-R0 key holder in the Supplicant (STA mac address)
|
||||
* @param s0kd_id_len Length of s0kh_id in bytes.
|
||||
* @param hash_algo Hash algorithm to use for the KDF.
|
||||
* See gcrypt available hash algorithms:
|
||||
* https://gnupg.org/documentation/manuals/gcrypt/Available-hash-algorithms.html
|
||||
* @param[out] pmk_r0 Pairwise master key, first level
|
||||
* @param[out] pmk_r0_name Pairwise master key (PMK) R0 name.
|
||||
*/
|
||||
gboolean
|
||||
dot11decrypt_derive_pmk_r0(const guint8 *xxkey, size_t xxkey_len,
|
||||
const guint8 *ssid, size_t ssid_len,
|
||||
const guint8 mdid[2],
|
||||
const guint8 *r0kh_id, size_t r0kh_id_len,
|
||||
const guint8 s0kh_id[DOT11DECRYPT_MAC_LEN],
|
||||
int hash_algo,
|
||||
guint8 *pmk_r0,
|
||||
size_t *pmk_r0_len,
|
||||
guint8 pmk_r0_name[16])
|
||||
{
|
||||
const char *ft_r0n = "FT-R0N";
|
||||
const size_t ft_r0n_len = strlen(ft_r0n);
|
||||
guint8 context[MAX_CONTEXT_LEN];
|
||||
guint8 r0_key_data[DOT11DECRYPT_WPA_PMK_MAX_LEN + 16];
|
||||
guint8 sha256_res[32];
|
||||
size_t offset = 0;
|
||||
guint q = gcry_md_get_algo_dlen(hash_algo);
|
||||
guint16 mdid_le = GUINT16_TO_LE(*(guint16*)mdid);
|
||||
|
||||
if (!xxkey || !ssid || !mdid || !r0kh_id || !s0kh_id ||
|
||||
!pmk_r0 || !pmk_r0_len || !pmk_r0_name)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
if (1 + ssid_len + 2 + 1 + r0kh_id_len + DOT11DECRYPT_MAC_LEN > MAX_CONTEXT_LEN)
|
||||
{
|
||||
DEBUG_PRINT_LINE("Invalid input sizes", DEBUG_LEVEL_3);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// R0-Key-Data =
|
||||
// KDF-Hash-Length(XXKey, "FT-R0",
|
||||
// SSIDlength || SSID || MDID || R0KHlength || R0KH-ID || S0KH-ID)
|
||||
// PMK-R0 = L(R0-Key-Data, 0, Q) * PMK-R0Name-Salt = L(R0-Key-Data, Q, 128)
|
||||
context[offset++] = (guint8)ssid_len;
|
||||
memcpy(context + offset, ssid, ssid_len);
|
||||
offset += ssid_len;
|
||||
memcpy(context + offset, &mdid_le, 2);
|
||||
offset += 2;
|
||||
context[offset++] = (guint8)r0kh_id_len;
|
||||
memcpy(context + offset, r0kh_id, r0kh_id_len);
|
||||
offset += r0kh_id_len;
|
||||
memcpy(context + offset, s0kh_id, DOT11DECRYPT_MAC_LEN);
|
||||
offset += DOT11DECRYPT_MAC_LEN;
|
||||
dot11decrypt_kdf(xxkey, xxkey_len, "FT-R0", context, offset, hash_algo,
|
||||
r0_key_data, q + 16);
|
||||
memcpy(pmk_r0, r0_key_data, q);
|
||||
*pmk_r0_len = q;
|
||||
|
||||
// PMK-R0Name-Salt = L(R0-Key-Data, Q, 128)
|
||||
// PMKR0Name = Truncate-128(SHA-256("FT-R0N" || PMK-R0Name-Salt))
|
||||
offset = 0;
|
||||
memcpy(context + offset, ft_r0n, ft_r0n_len);
|
||||
offset += ft_r0n_len;
|
||||
memcpy(context + offset, r0_key_data + q, 16);
|
||||
offset += 16;
|
||||
sha256(context, offset, sha256_res);
|
||||
memcpy(pmk_r0_name, sha256_res, 16);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Derive PMK-R1 and PMKR1Name. See IEEE 802.11-2016 12.7.1.7.4 PMK-R1
|
||||
*
|
||||
*/
|
||||
gboolean
|
||||
dot11decrypt_derive_pmk_r1(const guint8 *pmk_r0, size_t pmk_r0_len,
|
||||
const guint8 *pmk_r0_name,
|
||||
const guint8 *r1kh_id, const guint8 *s1kh_id,
|
||||
int hash_algo,
|
||||
guint8 *pmk_r1, size_t *pmk_r1_len,
|
||||
guint8 *pmk_r1_name)
|
||||
{
|
||||
const char *ft_r1n = "FT-R1N";
|
||||
const size_t ft_r1n_len = strlen(ft_r1n);
|
||||
// context len = MAX(R1KH-ID || S1KH-ID, “FT-R1N” || PMKR0Name || R1KH-ID || S1KH-ID)
|
||||
guint8 context[6 + 16 + 6 + 6];
|
||||
guint8 sha256_res[32];
|
||||
size_t offset = 0;
|
||||
|
||||
if (!pmk_r0 || !pmk_r0_name || !r1kh_id || !s1kh_id ||
|
||||
!pmk_r1 || !pmk_r1_len || !pmk_r1_name)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
*pmk_r1_len = gcry_md_get_algo_dlen(hash_algo);
|
||||
|
||||
// PMK-R1 = KDF-Hash-Length(PMK-R0, "FT-R1", R1KH-ID || S1KH-ID)
|
||||
memcpy(context + offset, r1kh_id, DOT11DECRYPT_MAC_LEN);
|
||||
offset += DOT11DECRYPT_MAC_LEN;
|
||||
memcpy(context + offset, s1kh_id, DOT11DECRYPT_MAC_LEN);
|
||||
offset += DOT11DECRYPT_MAC_LEN;
|
||||
dot11decrypt_kdf(pmk_r0, pmk_r0_len, "FT-R1", context, offset, hash_algo,
|
||||
pmk_r1, *pmk_r1_len);
|
||||
|
||||
// PMKR1Name = Truncate-128(SHA-256(“FT-R1N” || PMKR0Name || R1KH-ID || S1KH-ID))
|
||||
offset = 0;
|
||||
memcpy(context + offset, ft_r1n, ft_r1n_len);
|
||||
offset += ft_r1n_len;
|
||||
memcpy(context + offset, pmk_r0_name, 16);
|
||||
offset += 16;
|
||||
memcpy(context + offset, r1kh_id, DOT11DECRYPT_MAC_LEN);
|
||||
offset += DOT11DECRYPT_MAC_LEN;
|
||||
memcpy(context + offset, s1kh_id, DOT11DECRYPT_MAC_LEN);
|
||||
offset += DOT11DECRYPT_MAC_LEN;
|
||||
sha256(context, offset, sha256_res);
|
||||
memcpy(pmk_r1_name, sha256_res, 16);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Derive PTK for FT AKMS. See IEE 802.11-2016 12.7.1.7.5 PTK
|
||||
*
|
||||
* PTK = KDF-Hash-Length(PMK-R1, "FT-PTK", SNonce || ANonce || BSSID || STA-ADDR)
|
||||
* PTKName = Truncate-128(
|
||||
* SHA-256(PMKR1Name || “FT-PTKN” || SNonce || ANonce || BSSID || STA-ADDR))
|
||||
*/
|
||||
gboolean
|
||||
dot11decrypt_derive_ft_ptk(const guint8 *pmk_r1, size_t pmk_r1_len,
|
||||
const guint8 *pmk_r1_name _U_,
|
||||
const guint8 *snonce, const guint8 *anonce,
|
||||
const guint8 *bssid, const guint8 *sta_addr,
|
||||
int hash_algo,
|
||||
guint8 *ptk, const size_t ptk_len, guint8 *ptk_name _U_)
|
||||
{
|
||||
guint8 context[32 + 32 + 6 + 6];
|
||||
guint offset = 0;
|
||||
|
||||
// PTK = KDF-Hash-Length(PMK-R1, "FT-PTK", SNonce || ANonce || BSSID || STA-ADDR)
|
||||
memcpy(context + offset, snonce, 32);
|
||||
offset += 32;
|
||||
memcpy(context + offset, anonce, 32);
|
||||
offset += 32;
|
||||
memcpy(context + offset, bssid, DOT11DECRYPT_MAC_LEN);
|
||||
offset += DOT11DECRYPT_MAC_LEN;
|
||||
memcpy(context + offset, sta_addr, DOT11DECRYPT_MAC_LEN);
|
||||
offset += DOT11DECRYPT_MAC_LEN;
|
||||
dot11decrypt_kdf(pmk_r1, pmk_r1_len, "FT-PTK", context, offset, hash_algo,
|
||||
ptk, ptk_len);
|
||||
|
||||
// TODO derive PTKName
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/*
|
||||
* Editor modelines
|
||||
*
|
||||
|
|
|
@ -32,6 +32,32 @@ dot11decrypt_kdf(const guint8 *key, size_t key_len,
|
|||
int hash_algo,
|
||||
guint8 *output, size_t output_len);
|
||||
|
||||
gboolean
|
||||
dot11decrypt_derive_pmk_r0(const guint8 *xxkey, size_t xxkey_len,
|
||||
const guint8 *ssid, size_t ssid_len,
|
||||
const guint8 mdid[2],
|
||||
const guint8 *r0kh_id, size_t r0kh_id_len,
|
||||
const guint8 s0kh_id[DOT11DECRYPT_MAC_LEN],
|
||||
int hash_algo,
|
||||
guint8 *pmk_r0,
|
||||
size_t *pmk_r0_len,
|
||||
guint8 pmk_r0_name[16]);
|
||||
|
||||
gboolean
|
||||
dot11decrypt_derive_pmk_r1(const guint8 *pmk_r0, size_t pmk_r0_len,
|
||||
const guint8 *pmk_r0_name,
|
||||
const guint8 *r1kh_id, const guint8 *s1kh_id,
|
||||
int hash_algo,
|
||||
guint8 *pmk_r1, size_t *pmk_r1_len,
|
||||
guint8 *pmk_r1_name);
|
||||
|
||||
gboolean
|
||||
dot11decrypt_derive_ft_ptk(const guint8 *pmk_r1, size_t pmk_r1_len,
|
||||
const guint8 *pmk_r1_name,
|
||||
const guint8 *snonce, const guint8 *anonce,
|
||||
const guint8 *bssid, const guint8 *sta_addr,
|
||||
int hash_algo,
|
||||
guint8 *ptk, size_t ptk_len, guint8 *ptk_name);
|
||||
#endif /* _DOT11DECRYPT_UTIL_H */
|
||||
|
||||
/*
|
||||
|
|
|
@ -284,6 +284,11 @@ typedef struct mimo_control
|
|||
#define KEY_DATA_LEN_KEY 18
|
||||
#define GTK_KEY 19
|
||||
#define GTK_LEN_KEY 20
|
||||
#define MDID_KEY 21
|
||||
#define FTE_R0KH_ID_KEY 22
|
||||
#define FTE_R0KH_ID_LEN_KEY 23
|
||||
#define FTE_R1KH_ID_KEY 24
|
||||
#define FTE_R1KH_ID_LEN_KEY 25
|
||||
|
||||
/* ************************************************************************* */
|
||||
/* Define some very useful macros that are used to analyze frame types etc. */
|
||||
|
@ -16424,6 +16429,7 @@ dissect_mobility_domain(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, voi
|
|||
return 1;
|
||||
}
|
||||
|
||||
save_proto_data(tvb, pinfo, offset, 2, MDID_KEY);
|
||||
proto_tree_add_item(tree, hf_ieee80211_tag_mobility_domain_mdid,
|
||||
tvb, offset, 2, ENC_LITTLE_ENDIAN);
|
||||
proto_tree_add_item(tree, hf_ieee80211_tag_mobility_domain_ft_capab,
|
||||
|
@ -16616,6 +16622,8 @@ dissect_fast_bss_transition(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree,
|
|||
s_end = offset + len;
|
||||
switch (id) {
|
||||
case 1:
|
||||
save_proto_data(tvb, pinfo, offset, len, FTE_R1KH_ID_KEY);
|
||||
save_proto_data_value(pinfo, len, FTE_R1KH_ID_LEN_KEY);
|
||||
proto_tree_add_item(tree, hf_ieee80211_tag_ft_subelem_r1kh_id,
|
||||
tvb, offset, len, ENC_NA);
|
||||
break;
|
||||
|
@ -16641,6 +16649,8 @@ dissect_fast_bss_transition(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree,
|
|||
tvb, offset, s_end - offset, ENC_NA);
|
||||
break;
|
||||
case 3:
|
||||
save_proto_data(tvb, pinfo, offset, len, FTE_R0KH_ID_KEY);
|
||||
save_proto_data_value(pinfo, len, FTE_R0KH_ID_LEN_KEY);
|
||||
proto_tree_add_item(tree, hf_ieee80211_tag_ft_subelem_r0kh_id,
|
||||
tvb, offset, len, ENC_NA);
|
||||
break;
|
||||
|
@ -27612,6 +27622,17 @@ get_eapol_parsed(packet_info *pinfo, PDOT11DECRYPT_EAPOL_PARSED eapol_parsed)
|
|||
eapol_parsed->gtk = (guint8 *)p_get_proto_data(pinfo->pool, pinfo, proto_wlan, GTK_KEY);
|
||||
eapol_parsed->gtk_len = (guint16)
|
||||
GPOINTER_TO_UINT(p_get_proto_data(pinfo->pool, pinfo, proto_wlan, GTK_LEN_KEY));
|
||||
|
||||
/* For fast bss transition akms */
|
||||
eapol_parsed->mdid = (guint8 *)p_get_proto_data(pinfo->pool, pinfo, proto_wlan, MDID_KEY);
|
||||
eapol_parsed->r0kh_id =
|
||||
(guint8 *)p_get_proto_data(pinfo->pool, pinfo, proto_wlan, FTE_R0KH_ID_KEY);
|
||||
eapol_parsed->r0kh_id_len = (guint8)
|
||||
GPOINTER_TO_UINT(p_get_proto_data(pinfo->pool, pinfo, proto_wlan, FTE_R0KH_ID_LEN_KEY));
|
||||
eapol_parsed->r1kh_id =
|
||||
(guint8 *)p_get_proto_data(pinfo->pool, pinfo, proto_wlan, FTE_R1KH_ID_KEY);
|
||||
eapol_parsed->r1kh_id_len = (guint8)
|
||||
GPOINTER_TO_UINT(p_get_proto_data(pinfo->pool, pinfo, proto_wlan, FTE_R1KH_ID_LEN_KEY));
|
||||
}
|
||||
|
||||
static void
|
||||
|
|
Binary file not shown.
|
@ -223,6 +223,17 @@ class case_decrypt_80211(subprocesstest.SubprocessTestCase):
|
|||
self.assertTrue(self.grepOutput('DHCP Request')) # Verifies TK is correct
|
||||
self.assertTrue(self.grepOutput(r'Echo \(ping\) request')) # Verifies TK is correct
|
||||
|
||||
def test_80211_wpa2_ft_psk(self, cmd_tshark, capture_file):
|
||||
'''IEEE 802.11 decode WPA2 FT PSK'''
|
||||
# Included in git sources test/captures/wpa2-ft-psk.pcapng.gz
|
||||
self.assertRun((cmd_tshark,
|
||||
'-o', 'wlan.enable_decryption: TRUE',
|
||||
'-r', capture_file('wpa2-ft-psk.pcapng.gz'),
|
||||
'-Y', 'wlan.analysis.tk == 58f564fd078c3cc8ceb8c8be8e51d30d || wlan.analysis.gtk == a2e4ae32e73603f12ecbce89992de9df',
|
||||
))
|
||||
self.assertTrue(self.grepOutput('DHCP Request')) # Verifies GTK decryption
|
||||
self.assertTrue(self.grepOutput(r'Echo \(ping\) request')) # Verifies TK decryption
|
||||
|
||||
@fixtures.mark_usefixtures('test_env_80211_user_tk')
|
||||
@fixtures.uses_fixtures
|
||||
class case_decrypt_80211_user_tk(subprocesstest.SubprocessTestCase):
|
||||
|
|
Loading…
Reference in New Issue