Skip to content

Commit c03d8d4

Browse files
committed
Several improvements to 802.11
- add 802.11 ERP - add 802.11 DSSSet - provide the meaning of the address fields dynamically - add some nicer default build bindings
1 parent 11c379f commit c03d8d4

3 files changed

Lines changed: 187 additions & 45 deletions

File tree

scapy/layers/dot11.py

Lines changed: 132 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@
7070
from scapy.layers.inet import IP, TCP
7171
from scapy.error import warning, log_loading
7272
from scapy.sendrecv import sniff, sendp
73+
from scapy.utils import str2mac
7374

7475

7576
if conf.crypto_valid:
@@ -525,6 +526,7 @@ def post_build(self, p, pay):
525526

526527
# 802.11-2016 9.2
527528

529+
# 802.11-2016 9.2.4.1.3
528530
_dot11_subtypes = {
529531
0: { # Management
530532
0: "Association Request",
@@ -573,7 +575,7 @@ def post_build(self, p, pay):
573575
14: "QoS CF-Poll (no data)",
574576
15: "QoS CF-Ack+CF-Poll (no data)"
575577
},
576-
4: { # Extension
578+
3: { # Extension
577579
0: "DMG Beacon"
578580
}
579581
}
@@ -591,13 +593,52 @@ def post_build(self, p, pay):
591593
}
592594

593595

596+
_dot11_addr_meaning = [
597+
[ # Management: 802.11-2016 9.3.3.2
598+
"RA=DA", "TA=SA", "BSSID/STA", None,
599+
],
600+
[ # Control
601+
"RA", "TA", None, None
602+
],
603+
[ # Data: 802.11-2016 9.3.2.1: Table 9-26
604+
[["RA=DA", "RA=DA"], ["RA=BSSID", "RA"]],
605+
[["TA=SA", "TA=BSSID"], ["TA=SA", "TA"]],
606+
[["BSSID", "SA"], ["DA", "DA"]],
607+
[[None, None], ["SA", "BSSID"]],
608+
],
609+
[ # Extension
610+
"BSSID", None, None, None
611+
],
612+
]
613+
614+
615+
class _Dot11MacField(MACField):
616+
"""
617+
A MACField that displays the address type depending on the
618+
802.11 flags
619+
"""
620+
__slots__ = ["index"]
621+
622+
def __init__(self, name, default, index):
623+
self.index = index
624+
super(_Dot11MacField, self).__init__(name, default)
625+
626+
def i2repr(self, pkt, val):
627+
s = super(_Dot11MacField, self).i2repr(pkt, val)
628+
meaning = pkt.address_meaning(self.index)
629+
if meaning:
630+
return "%s (%s)" % (s, meaning)
631+
return s
632+
633+
634+
# 802.11-2016 9.2.4.1.1
594635
class Dot11(Packet):
595636
name = "802.11"
596637
fields_desc = [
597638
BitMultiEnumField("subtype", 0, 4, _dot11_subtypes,
598639
lambda pkt: pkt.type),
599640
BitEnumField("type", 0, 2, ["Management", "Control", "Data",
600-
"Reserved"]),
641+
"Extension"]),
601642
BitField("proto", 0, 2),
602643
ConditionalField(
603644
BitEnumField("cfe", 0, 4, _dot11_cfe),
@@ -616,19 +657,19 @@ class Dot11(Packet):
616657
"pw-mgt", "MD", "protected", "order"])
617658
),
618659
ShortField("ID", 0),
619-
MACField("addr1", ETHER_ANY),
660+
_Dot11MacField("addr1", ETHER_ANY, 1),
620661
ConditionalField(
621-
MACField("addr2", ETHER_ANY),
662+
_Dot11MacField("addr2", ETHER_ANY, 2),
622663
lambda pkt: (pkt.type != 1 or
623664
pkt.subtype in [0x8, 0x9, 0xa, 0xb, 0xe, 0xf]),
624665
),
625666
ConditionalField(
626-
MACField("addr3", ETHER_ANY),
667+
_Dot11MacField("addr3", ETHER_ANY, 3),
627668
lambda pkt: pkt.type in [0, 2],
628669
),
629670
ConditionalField(LEShortField("SC", 0), lambda pkt: pkt.type != 1),
630671
ConditionalField(
631-
MACField("addr4", ETHER_ANY),
672+
_Dot11MacField("addr4", ETHER_ANY, 4),
632673
lambda pkt: (pkt.type == 2 and
633674
pkt.FCfield & 3 == 3), # from-DS+to-DS
634675
)
@@ -639,7 +680,8 @@ def mysummary(self):
639680
return self.sprintf("802.11 %%%s.type%% %%%s.subtype%% %%%s.addr2%% > %%%s.addr1%%" % ((self.__class__.__name__,) * 4)) # noqa: E501
640681

641682
def guess_payload_class(self, payload):
642-
if self.type == 0x02 and (0x08 <= self.subtype <= 0xF and self.subtype != 0xD): # noqa: E501
683+
if self.type == 0x02 and (
684+
0x08 <= self.subtype <= 0xF and self.subtype != 0xD):
643685
return Dot11QoS
644686
elif self.FCfield.protected:
645687
# When a frame is handled by encryption, the Protected Frame bit
@@ -666,6 +708,31 @@ def answers(self, other):
666708
return 0
667709
return 0
668710

711+
def address_meaning(self, index):
712+
"""
713+
Return the meaning of the address[index] considering the context
714+
"""
715+
if index not in [1, 2, 3, 4]:
716+
raise ValueError("Wrong index: should be [1, 2, 3, 4]")
717+
index = index - 1
718+
if self.type == 0: # Management
719+
return _dot11_addr_meaning[0][index]
720+
elif self.type == 1: # Control
721+
return _dot11_addr_meaning[1][index]
722+
elif self.type == 2: # Data
723+
meaning = _dot11_addr_meaning[2][index][
724+
self.FCfield.to_DS
725+
][self.FCfield.from_DS]
726+
if meaning and index in [2, 3]: # Address 3-4
727+
if isinstance(self.payload, Dot11QoS):
728+
# MSDU and Short A-MSDU
729+
if self.payload.A_MSDU_Present:
730+
meaning = "BSSID"
731+
return meaning
732+
elif self.type == 3: # Extension
733+
return _dot11_addr_meaning[3][index]
734+
return None
735+
669736
def unwep(self, key=None, warn=1):
670737
if self.FCfield & 0x40 == 0:
671738
if warn:
@@ -699,11 +766,11 @@ def post_build(self, p, pay):
699766

700767
class Dot11QoS(Packet):
701768
name = "802.11 QoS"
702-
fields_desc = [BitField("Reserved", None, 1),
703-
BitField("Ack_Policy", None, 2),
704-
BitField("EOSP", None, 1),
705-
BitField("TID", None, 4),
706-
ByteField("TXOP", None)]
769+
fields_desc = [BitField("A_MSDU_Present", 0, 1),
770+
BitField("Ack_Policy", 0, 2),
771+
BitField("EOSP", 0, 1),
772+
BitField("TID", 0, 4),
773+
ByteField("TXOP", 0)]
707774

708775
def guess_payload_class(self, payload):
709776
if isinstance(self.underlayer, Dot11):
@@ -889,6 +956,15 @@ class Dot11Elt(Packet):
889956
max_length=255)]
890957
show_indent = 0
891958

959+
def __setattr__(self, attr, val):
960+
if attr == "info":
961+
# Will be caught by __slots__: we need an extra call
962+
try:
963+
self.setfieldval(attr, val)
964+
except AttributeError:
965+
pass
966+
super(Dot11Elt, self).__setattr__(attr, val)
967+
892968
def mysummary(self):
893969
if self.ID == 0:
894970
ssid = repr(self.info)
@@ -933,10 +1009,44 @@ def post_build(self, p, pay):
9331009
return p + pay
9341010

9351011

1012+
class _OUIField(X3BytesField):
1013+
def i2repr(self, pkt, val):
1014+
by_val = struct.pack("!I", val or 0)[1:]
1015+
oui = str2mac(by_val + b"\0" * 3)[:8]
1016+
if conf.manufdb:
1017+
fancy = conf.manufdb._get_manuf(oui)
1018+
if fancy != oui:
1019+
return "%s (%s)" % (fancy, oui)
1020+
return oui
1021+
1022+
1023+
class Dot11EltDSSSet(Dot11Elt):
1024+
name = "802.11 DSSS Parameter Set"
1025+
match_subclass = True
1026+
fields_desc = [
1027+
ByteEnumField("ID", 3, _dot11_id_enum),
1028+
ByteField("len", 1),
1029+
ByteField("channel", 0),
1030+
]
1031+
1032+
1033+
class Dot11EltERP(Dot11Elt):
1034+
name = "802.11 ERP"
1035+
match_subclass = True
1036+
fields_desc = [
1037+
ByteEnumField("ID", 42, _dot11_id_enum),
1038+
ByteField("len", 1),
1039+
BitField("NonERP_Present", 0, 1),
1040+
BitField("Use_Protection", 0, 1),
1041+
BitField("Barker_Preamble_Mode", 0, 1),
1042+
BitField("res", 0, 5),
1043+
]
1044+
1045+
9361046
class RSNCipherSuite(Packet):
9371047
name = "Cipher suite"
9381048
fields_desc = [
939-
X3BytesField("oui", 0x000fac),
1049+
_OUIField("oui", 0x000fac),
9401050
ByteEnumField("cipher", 0x04, {
9411051
0x00: "Use group cipher suite",
9421052
0x01: "WEP-40",
@@ -962,7 +1072,7 @@ def extract_padding(self, s):
9621072
class AKMSuite(Packet):
9631073
name = "AKM suite"
9641074
fields_desc = [
965-
X3BytesField("oui", 0x000fac),
1075+
_OUIField("oui", 0x000fac),
9661076
ByteEnumField("suite", 0x01, {
9671077
0x00: "Reserved",
9681078
0x01: "802.1X",
@@ -1049,8 +1159,8 @@ class Dot11EltRSN(Dot11Elt):
10491159
0 if pkt.len is None else
10501160
pkt.len - (
10511161
12 +
1052-
pkt.nb_pairwise_cipher_suites * 4 +
1053-
pkt.nb_akm_suites * 4
1162+
(pkt.nb_pairwise_cipher_suites or 0) * 4 +
1163+
(pkt.nb_akm_suites or 0) * 4
10541164
) >= 2
10551165
)
10561166
),
@@ -1125,7 +1235,7 @@ class Dot11EltRates(Dot11Elt):
11251235

11261236

11271237
class Dot11EltHTCapabilities(Dot11Elt):
1128-
name = "HT Capabilities"
1238+
name = "802.11 HT Capabilities"
11291239
match_subclass = True
11301240
fields_desc = [
11311241
ByteEnumField("ID", 45, _dot11_id_enum),
@@ -1211,7 +1321,7 @@ class Dot11EltVendorSpecific(Dot11Elt):
12111321
fields_desc = [
12121322
ByteEnumField("ID", 221, _dot11_id_enum),
12131323
ByteField("len", None),
1214-
X3BytesField("oui", 0x000000),
1324+
_OUIField("oui", 0x000000),
12151325
StrLenField("info", "", length_from=lambda x: x.len - 3)
12161326
]
12171327

@@ -1240,10 +1350,10 @@ class Dot11EltMicrosoftWPA(Dot11EltVendorSpecific):
12401350
name = "802.11 Microsoft WPA"
12411351
match_subclass = True
12421352
ID = 221
1353+
oui = 0x0050f2
12431354
# It appears many WPA implementations ignore the fact
12441355
# that this IE should only have a single cipher and auth suite
1245-
fields_desc = Dot11EltRSN.fields_desc[:2] + [
1246-
X3BytesField("oui", 0x0050f2),
1356+
fields_desc = Dot11EltVendorSpecific.fields_desc[:3] + [
12471357
XByteField("type", 0x01)
12481358
] + Dot11EltRSN.fields_desc[2:8]
12491359

@@ -1437,7 +1547,7 @@ class Dot11TKIP(Dot11Encrypted):
14371547

14381548

14391549
class Dot11CCMP(Dot11Encrypted):
1440-
name = "802.11 TKIP packet"
1550+
name = "802.11 CCMP packet"
14411551
fields_desc = [
14421552
# iv - 8 bytes
14431553
ByteField("PN0", 0),
@@ -1461,6 +1571,7 @@ class Dot11CCMP(Dot11Encrypted):
14611571

14621572

14631573
bind_top_down(RadioTap, Dot11FCS, present=2, Flags=16)
1574+
bind_top_down(Dot11, Dot11QoS, type=2, subtype=0xc)
14641575

14651576
bind_layers(PrismHeader, Dot11,)
14661577
bind_layers(Dot11, LLC, type=2)

scapy/modules/krack/automaton.py

Lines changed: 41 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,22 @@
1414
from scapy.compat import raw, chb
1515
from scapy.consts import LINUX
1616
from scapy.error import log_runtime
17-
from scapy.layers.dot11 import RadioTap, Dot11, Dot11AssoReq, Dot11AssoResp, \
18-
Dot11Auth, Dot11Beacon, Dot11Elt, Dot11EltRates, Dot11EltRSN, \
19-
Dot11ProbeReq, Dot11ProbeResp, RSNCipherSuite, AKMSuite
17+
from scapy.layers.dot11 import (
18+
AKMSuite,
19+
Dot11,
20+
Dot11AssoReq,
21+
Dot11AssoResp,
22+
Dot11Auth,
23+
Dot11Beacon,
24+
Dot11Elt,
25+
Dot11EltDSSSet,
26+
Dot11EltRSN,
27+
Dot11EltRates,
28+
Dot11ProbeReq,
29+
Dot11ProbeResp,
30+
RSNCipherSuite,
31+
RadioTap,
32+
)
2033
from scapy.layers.eap import EAPOL
2134
from scapy.layers.l2 import ARP, LLC, SNAP, Ether
2235
from scapy.layers.dhcp import DHCP_am
@@ -83,27 +96,33 @@ def parse_args(self, ap_mac, ssid, passphrase,
8396
**kwargs):
8497
"""
8598
Mandatory arguments:
86-
@iface: interface to use (must be in monitor mode)
87-
@ap_mac: AP's MAC
88-
@ssid: AP's SSID
89-
@passphrase: AP's Passphrase (min 8 char.)
99+
100+
:param iface: interface to use (must be in monitor mode)
101+
:param ap_mac: AP's MAC
102+
:param ssid: AP's SSID
103+
:param passphrase: AP's Passphrase (min 8 char.)
90104
91105
Optional arguments:
92-
@channel: used by the interface. Default 6, autodetected on windows
106+
107+
:param channel: used by the interface. Default 6
93108
94109
Krack attacks options:
95110
96111
- Msg 3/4 handshake replay:
97-
double_3handshake: double the 3/4 handshake message
98-
encrypt_3handshake: encrypt the second 3/4 handshake message
99-
wait_3handshake: time to wait (in sec.) before sending the second 3/4
100-
- double GTK rekeying:
101-
double_gtk_refresh: double the 1/2 GTK rekeying message
102-
wait_gtk: time to wait (in sec.) before sending the GTK rekeying
103-
arp_target_ip: Client IP to use in ARP req. (to detect attack success)
104-
If None, use a DHCP server
105-
arp_source_ip: Server IP to use in ARP req. (to detect attack success)
106-
If None, use the DHCP server gateway address
112+
113+
:param double_3handshake: double the 3/4 handshake message
114+
:param encrypt_3handshake: encrypt the second 3/4 handshake message
115+
:param wait_3handshake: time to wait (in sec.) before sending the
116+
second 3/4
117+
118+
- double GTK rekeying:
119+
120+
:param double_gtk_refresh: double the 1/2 GTK rekeying message
121+
:param wait_gtk: time to wait (in sec.) before sending the GTK rekeying
122+
:param arp_target_ip: Client IP to use in ARP req. (to detect attack
123+
success). If None, use a DHCP server
124+
:param arp_source_ip: Server IP to use in ARP req. (to detect attack
125+
success). If None, use the DHCP server gateway address
107126
"""
108127
super(KrackAP, self).parse_args(**kwargs)
109128

@@ -215,13 +234,14 @@ def build_ap_info_pkt(self, layer_cls, dest):
215234
"""Build a packet with info describing the current AP
216235
For beacon / proberesp use
217236
"""
237+
ts = int(time.time() * 1e6) & 0xffffffffffffffff
218238
return RadioTap() \
219239
/ Dot11(addr1=dest, addr2=self.mac, addr3=self.mac) \
220-
/ layer_cls(timestamp=0, beacon_interval=100,
240+
/ layer_cls(timestamp=ts, beacon_interval=100,
221241
cap='ESS+privacy') \
222242
/ Dot11Elt(ID="SSID", info=self.ssid) \
223243
/ Dot11EltRates(rates=[130, 132, 139, 150, 12, 18, 24, 36]) \
224-
/ Dot11Elt(ID="DSset", info=chb(self.channel)) \
244+
/ Dot11EltDSSSet(channel=self.channel) \
225245
/ Dot11EltRSN(group_cipher_suite=RSNCipherSuite(cipher=0x2),
226246
pairwise_cipher_suites=[RSNCipherSuite(cipher=0x2)],
227247
akm_suites=[AKMSuite(suite=0x2)])
@@ -481,7 +501,7 @@ def send_auth_response(self, pkt):
481501
log_runtime.warning("Client %s connected!", self.client)
482502

483503
# Launch DHCP Server
484-
self.dhcp_server.run()
504+
self.dhcp_server()
485505

486506
rep = RadioTap()
487507
rep /= Dot11(addr1=self.client, addr2=self.mac, addr3=self.mac)

0 commit comments

Comments
 (0)