Skip to content

Commit 5335768

Browse files
authored
Merge pull request #1381 from gpotter2/dot11improvements
RadioTap flags support + Dot11 FCS&improvements
2 parents aba966e + 1b8ad56 commit 5335768

File tree

9 files changed

+238
-44
lines changed

9 files changed

+238
-44
lines changed

scapy/contrib/ppi_cace.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,7 @@
2626
from scapy.packet import *
2727
from scapy.fields import *
2828
from scapy.layers.l2 import Ether
29-
from scapy.layers.dot11 import Dot11
30-
from scapy.contrib.ppi import *
29+
from scapy.layers.ppi import addPPIType
3130

3231
PPI_DOT11COMMON = 2
3332
PPI_DOT11NMAC = 3

scapy/contrib/ppi.uts renamed to scapy/contrib/ppi_cace.uts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55

66
= Define test suite
77

8-
from scapy.contrib.ppi import *
98
from scapy.contrib.ppi_cace import *
109
from scapy.contrib.ppi_geotag import *
1110

scapy/contrib/ppi_geotag.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
import time
2727
from scapy.packet import *
2828
from scapy.fields import *
29-
from scapy.contrib.ppi import PPIGenericFldHdr, addPPIType
29+
from scapy.layers.ppi import addPPIType
3030
from scapy.error import warning
3131
import scapy.modules.six as six
3232
from scapy.modules.six.moves import range

scapy/fields.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,21 @@ def __getattr__(self, attr):
360360
return getattr(self._fld, attr)
361361

362362

363+
class ReversePadField(PadField):
364+
"""Add bytes BEFORE the proxified field so that it starts at the specified
365+
alignment from its beginning"""
366+
367+
def getfield(self, pkt, s):
368+
# We need to get the length that has already been dissected
369+
padlen = self.padlen(pkt._tmp_dissect_pos)
370+
remain, val = self._fld.getfield(pkt, s[padlen:])
371+
return remain, val
372+
373+
def addfield(self, pkt, s, val):
374+
sval = self._fld.addfield(pkt, b"", val)
375+
return s + struct.pack("%is" % (self.padlen(len(s))), self._padwith) + sval
376+
377+
363378
class DestField(Field):
364379
__slots__ = ["defaultdst"]
365380
# Each subclass must have its own bindings attribute

scapy/layers/bluetooth4LE.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from scapy.data import MTU, DLT_BLUETOOTH_LE_LL
1515
from scapy.packet import *
1616
from scapy.fields import *
17-
from scapy.layers import dot11
17+
from scapy.layers.ppi import PPI
1818

1919
from scapy.modules.six.moves import range
2020

@@ -123,7 +123,7 @@ def pre_dissect(self, s):
123123
return s[:4] + s[-3:] + s[4:-3]
124124

125125
def post_dissection(self, pkt):
126-
if isinstance(pkt, dot11.PPI):
126+
if isinstance(pkt, PPI):
127127
pkt.notdecoded = PPI_FieldHeader(pkt.notdecoded)
128128

129129
def hashret(self):
@@ -261,5 +261,5 @@ class BTLE_CONNECT_REQ(Packet):
261261

262262
conf.l2types.register(DLT_BLUETOOTH_LE_LL, BTLE)
263263

264-
bind_layers(dot11.PPI, BTLE, dlt=147)
264+
bind_layers(PPI, BTLE, dlt=147)
265265
bind_layers(PPI_FieldHeader, BTLE_PPI, pfh_type=30006)

scapy/layers/dot11.py

Lines changed: 175 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,26 @@
11
# This file is part of Scapy
22
# See http://www.secdev.org/projects/scapy for more informations
3+
# Scapy is free software: you can redistribute it and/or modify
4+
# it under the terms of the GNU General Public License as published by
5+
# the Free Software Foundation, either version 2 of the License, or
6+
# any later version.
7+
#
8+
# Scapy is distributed in the hope that it will be useful,
9+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+
# GNU General Public License for more details.
12+
#
13+
# You should have received a copy of the GNU General Public License
14+
# along with Scapy. If not, see <http://www.gnu.org/licenses/>.
15+
316
# Copyright (C) Philippe Biondi <[email protected]>
4-
# This program is published under a GPLv2 license
517

618
"""
719
Wireless LAN according to IEEE 802.11.
820
"""
921

1022
from __future__ import print_function
23+
import math
1124
import re
1225
import struct
1326
from zlib import crc32
@@ -90,27 +103,118 @@ def answers(self, other):
90103
return self.payload.answers(other)
91104

92105

106+
class _RadiotapReversePadField(ReversePadField):
107+
def __init__(self, fld):
108+
self._fld = fld
109+
self._padwith = b"\x00"
110+
# Quote from https://www.radiotap.org/:
111+
# ""Radiotap requires that all fields in the radiotap header are aligned to natural boundaries.
112+
# For radiotap, that means all 8-, 16-, 32-, and 64-bit fields must begin on 8-, 16-, 32-, and 64-bit boundaries, respectively.""
113+
if isinstance(self._fld, BitField):
114+
self._align = int(math.ceil(self.i2len(None, None)))
115+
else:
116+
self._align = struct.calcsize(self._fld.fmt)
117+
118+
119+
class _dbmField(ByteField):
120+
def i2m(self, pkt, x):
121+
return super(ByteField, self).i2m(pkt, x + 256)
122+
123+
def m2i(self, pkt, x):
124+
return super(ByteField, self).m2i(pkt, x) - 256
125+
126+
def i2repr(self, pkt, x):
127+
return "%sdBm" % x
128+
129+
130+
_vht_bandwidth = {0: "20MHz", 1: "40MHz", 2: "40MHz", 3: "40MHz", 4: "80MHz", 5: "80MHz",
131+
6: "80MHz", 7: "80MHz", 8: "80MHz", 9: "80MHz", 10: "80MHz", 11: "160MHz",
132+
12: "160MHz", 13: "160MHz", 14: "160MHz", 15: "160MHz", 16: "160MHz", 17: "160MHz",
133+
18: "160MHz", 19: "160MHz", 20: "160MHz", 21: "160MHz", 22: "160MHz", 23: "160MHz",
134+
24: "160MHz", 25: "160MHz"}
135+
136+
93137
class RadioTap(Packet):
94138
name = "RadioTap dummy"
95139
fields_desc = [ByteField('version', 0),
96140
ByteField('pad', 0),
97-
FieldLenField('len', None, 'notdecoded', '<H', adjust=lambda pkt, x:x + 8),
141+
LEShortField('len', None),
98142
FlagsField('present', None, -32, ['TSFT', 'Flags', 'Rate', 'Channel', 'FHSS', 'dBm_AntSignal',
99143
'dBm_AntNoise', 'Lock_Quality', 'TX_Attenuation', 'dB_TX_Attenuation',
100144
'dBm_TX_Power', 'Antenna', 'dB_AntSignal', 'dB_AntNoise',
101-
'b14', 'b15', 'b16', 'b17', 'b18', 'b19', 'b20', 'b21', 'b22', 'b23',
102-
'b24', 'b25', 'b26', 'b27', 'b28', 'b29', 'b30', 'Ext']),
103-
StrLenField('notdecoded', "", length_from=lambda pkt:pkt.len - 8)]
104-
105-
106-
class PPI(Packet):
107-
name = "Per-Packet Information header (partial)"
108-
fields_desc = [ByteField("version", 0),
109-
ByteField("flags", 0),
110-
FieldLenField("len", None, fmt="<H", length_of="notdecoded", adjust=lambda pkt, x:x + 8),
111-
LEIntField("dlt", 0),
112-
StrLenField("notdecoded", "", length_from=lambda pkt:pkt.len - 8)
113-
]
145+
'RXFlags', 'b16', 'b17', 'b18', 'ChannelPlus', 'MCS', 'A_MPDU',
146+
'VHT', 'timestamp', 'b24', 'b25', 'b26', 'b27', 'b28', 'b29',
147+
'RadiotapNS', 'VendorNS', 'Ext']),
148+
ConditionalField(_RadiotapReversePadField(BitField("mac_timestamp", 0, -64)), lambda pkt: pkt.present and pkt.present.TSFT),
149+
ConditionalField(
150+
_RadiotapReversePadField(
151+
FlagsField("Flags", None, -8, ['CFP', 'ShortPreamble', 'wep', 'fragment',
152+
'FCS', 'pad', 'badFCS', 'ShortGI'])
153+
),
154+
lambda pkt: pkt.present and pkt.present.Flags),
155+
ConditionalField(_RadiotapReversePadField(ByteField("Rate", 0)), lambda pkt: pkt.present and pkt.present.Rate),
156+
ConditionalField(_RadiotapReversePadField(LEShortField("Channel", 0)), lambda pkt: pkt.present and pkt.present.Channel),
157+
ConditionalField(
158+
_RadiotapReversePadField(
159+
FlagsField("ChannelFlags", None, -16, ['res1', 'res2', 'res3', 'res4', 'Turbo', 'CCK',
160+
'OFDM', '2GHz', '5GHz', 'Passive', 'Dynamic_CCK_OFDM',
161+
'GFSK', 'GSM', 'StaticTurbo', '10MHz', '5MHz'])
162+
),
163+
lambda pkt: pkt.present and pkt.present.Channel),
164+
ConditionalField(_RadiotapReversePadField(_dbmField("dBm_AntSignal", 0)), lambda pkt: pkt.present and pkt.present.dBm_AntSignal),
165+
ConditionalField(_RadiotapReversePadField(_dbmField("dBm_AntNoise", 0)), lambda pkt: pkt.present and pkt.present.dBm_AntNoise),
166+
ConditionalField(_RadiotapReversePadField(ByteField("Antenna", 0)), lambda pkt: pkt.present and pkt.present.Antenna),
167+
# ChannelPlus
168+
ConditionalField(
169+
_RadiotapReversePadField(
170+
FlagsField("ChannelFlags2", None, -32, ['res1', 'res2', 'res3', 'res4', 'Turbo', 'CCK',
171+
'OFDM', '2GHz', '5GHz', 'Passive', 'Dynamic_CCK_OFDM',
172+
'GFSK', 'GSM', 'StaticTurbo', '10MHz', '5MHz',
173+
'20MHz', '40MHz_ext_channel_above', '40MHz_ext_channel_below',
174+
'res5', 'res6', 'res7', 'res8', 'res9'])
175+
),
176+
lambda pkt: pkt.present and pkt.present.ChannelPlus),
177+
ConditionalField(_RadiotapReversePadField(LEShortField("ChannelFrequency", 0)), lambda pkt: pkt.present and pkt.present.ChannelPlus),
178+
ConditionalField(_RadiotapReversePadField(ByteField("ChannelNumber", 0)), lambda pkt: pkt.present and pkt.present.ChannelPlus),
179+
# A_MPDU
180+
ConditionalField(_RadiotapReversePadField(LEIntField("A_MPDU_ref", 0)), lambda pkt: pkt.present and pkt.present.A_MPDU),
181+
ConditionalField(
182+
_RadiotapReversePadField(
183+
FlagsField("A_MPDU_flags", None, -32, ['Report0Subframe', 'Is0Subframe', 'KnownLastSubframe',
184+
'LastSubframe', 'CRCerror', 'EOFsubframe', 'KnownEOF',
185+
'res1', 'res2', 'res3', 'res4', 'res5', 'res6', 'res7', 'res8'])
186+
),
187+
lambda pkt: pkt.present and pkt.present.A_MPDU),
188+
# VHT
189+
ConditionalField(
190+
_RadiotapReversePadField(
191+
FlagsField("KnownVHT", None, -16, ['STBC', 'TXOP_PS_NOT_ALLOWED', 'GuardInterval', 'SGINsysmDis',
192+
'LDPCextraOFDM', 'Beamformed', 'Bandwidth', 'GroupID', 'PartialAID',
193+
'res1', 'res2', 'res3', 'res4', 'res5', 'res6', 'res7'])
194+
),
195+
lambda pkt: pkt.present and pkt.present.VHT),
196+
ConditionalField(
197+
_RadiotapReversePadField(
198+
FlagsField("PresentVHT", None, -8, ['STBC', 'TXOP_PS_NOT_ALLOWED', 'GuardInterval', 'SGINsysmDis',
199+
'LDPCextraOFDM', 'Beamformed', 'res1', 'res2'])
200+
),
201+
lambda pkt: pkt.present and pkt.present.VHT),
202+
ConditionalField(_RadiotapReversePadField(ByteEnumField("bandwidth", 0, _vht_bandwidth)), lambda pkt: pkt.present and pkt.present.VHT),
203+
ConditionalField(_RadiotapReversePadField(StrFixedLenField("mcs_nss", 0, length=5)), lambda pkt: pkt.present and pkt.present.VHT),
204+
ConditionalField(_RadiotapReversePadField(ByteField("GroupID", 0)), lambda pkt: pkt.present and pkt.present.VHT),
205+
ConditionalField(_RadiotapReversePadField(ShortField("PartialAID", 0)), lambda pkt: pkt.present and pkt.present.VHT),
206+
StrLenField('notdecoded', "", length_from=lambda pkt: pkt.len - pkt._tmp_dissect_pos)]
207+
208+
def guess_payload_class(self, payload):
209+
if self.Flags.FCS:
210+
return Dot11FCS
211+
else:
212+
return Dot11
213+
214+
def post_build(self, p, pay):
215+
if self.len is None:
216+
p = p[:2] + struct.pack("!H", len(p))[::-1] + p[3:]
217+
return p + pay
114218

115219

116220
class Dot11(Packet):
@@ -138,11 +242,12 @@ class Dot11(Packet):
138242
MACField("addr4", ETHER_ANY),
139243
lambda pkt: (pkt.type == 2 and
140244
pkt.FCfield & 3 == 3), # from-DS+to-DS
141-
),
245+
)
142246
]
143247

144248
def mysummary(self):
145-
return self.sprintf("802.11 %Dot11.type% %Dot11.subtype% %Dot11.addr2% > %Dot11.addr1%")
249+
# Supports both Dot11 and Dot11FCS
250+
return self.sprintf("802.11 %%%s.type%% %%%s.subtype%% %%%s.addr2%% > %%%s.addr1%%" % ((self.__class__.__name__,) * 4))
146251

147252
def guess_payload_class(self, payload):
148253
if self.type == 0x02 and (0x08 <= self.subtype <= 0xF and self.subtype != 0xD):
@@ -185,6 +290,30 @@ def unwep(self, key=None, warn=1):
185290
self.payload = self.payload.payload
186291

187292

293+
class Dot11FCS(Dot11):
294+
name = "802.11-FCS"
295+
fields_desc = Dot11.fields_desc + [XLEIntField("fcs", None)] # Automatically moved to the end of the packet
296+
297+
def compute_fcs(self, s):
298+
return struct.pack("!I", crc32(s) & 0xffffffff)[::-1]
299+
300+
def post_build(self, p, pay):
301+
# Switch payload and frame check sequence
302+
return p[:-4] + pay + (p[-4:] if self.fcs is not None else self.compute_fcs(p[:-4] + pay))
303+
304+
def post_dissect(self, s):
305+
self.raw_packet_cache = None # Reset packet to allow post_build
306+
return s
307+
308+
def pre_dissect(self, s):
309+
# Get the frame check sequence
310+
sty = orb(s[0])
311+
ty = orb(s[1]) >> 2
312+
fc = struct.unpack("!H", s[2:4])[0]
313+
length = 12 + 6 * ((ty != 1 or sty in [0x8, 0x9, 0xa, 0xb, 0xe, 0xf]) + (ty in [0, 2]) + (ty == 2 and fc & 3 == 3))
314+
return s[:length] + s[-4:] + s[length:-4]
315+
316+
188317
class Dot11QoS(Packet):
189318
name = "802.11 QoS"
190319
fields_desc = [BitField("Reserved", None, 1),
@@ -224,10 +353,37 @@ class Dot11Beacon(Packet):
224353
FlagsField("cap", 0, 16, capability_list)]
225354

226355

356+
_dot11_info_elts_ids = {
357+
0: "SSID",
358+
1: "Rates",
359+
2: "FHset",
360+
3: "DSset",
361+
4: "CFset",
362+
5: "TIM",
363+
6: "IBSSset",
364+
7: "Country",
365+
10: "Request",
366+
16: "challenge",
367+
33: "PowerCapability",
368+
36: "Channels",
369+
42: "ERPinfo",
370+
45: "HTCapabilities",
371+
46: "QoSCapability",
372+
47: "ERPinfo",
373+
48: "RSNinfo",
374+
50: "ESRates",
375+
52: "PowerConstraint",
376+
107: "Interworking",
377+
127: "ExtendendCapatibilities",
378+
191: "VHTCapabilities",
379+
221: "vendor",
380+
68: "reserved"
381+
}
382+
383+
227384
class Dot11Elt(Packet):
228385
name = "802.11 Information Element"
229-
fields_desc = [ByteEnumField("ID", 0, {0: "SSID", 1: "Rates", 2: "FHset", 3: "DSset", 4: "CFset", 5: "TIM", 6: "IBSSset", 16: "challenge",
230-
42: "ERPinfo", 46: "QoS Capability", 47: "ERPinfo", 48: "RSNinfo", 50: "ESRates", 221: "vendor", 68: "reserved"}),
386+
fields_desc = [ByteEnumField("ID", 0, _dot11_info_elts_ids),
231387
FieldLenField("len", None, "info", "B"),
232388
StrLenField("info", "", length_from=lambda x: x.len,
233389
max_length=255)]
@@ -539,8 +695,6 @@ class Dot11Ack(Packet):
539695

540696

541697
bind_layers(PrismHeader, Dot11,)
542-
bind_layers(RadioTap, Dot11,)
543-
bind_layers(PPI, Dot11, dlt=105)
544698
bind_layers(Dot11, LLC, type=2)
545699
bind_layers(Dot11QoS, LLC,)
546700
bind_layers(Dot11, Dot11AssoReq, subtype=0, type=0)
@@ -572,7 +726,6 @@ class Dot11Ack(Packet):
572726
conf.l2types.register_num2layer(802, PrismHeader)
573727
conf.l2types.register(DLT_IEEE802_11_RADIO, RadioTap)
574728
conf.l2types.register_num2layer(803, RadioTap)
575-
conf.l2types.register(DLT_PPI, PPI)
576729

577730

578731
class WiFi_am(AnsweringMachine):

scapy/contrib/ppi.py renamed to scapy/layers/ppi.py

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# This file is part of Scapy
2+
# See http://www.secdev.org/projects/scapy for more informations
23
# Scapy is free software: you can redistribute it and/or modify
34
# it under the terms of the GNU General Public License as published by
45
# the Free Software Foundation, either version 2 of the License, or
@@ -12,22 +13,17 @@
1213
# You should have received a copy of the GNU General Public License
1314
# along with Scapy. If not, see <http://www.gnu.org/licenses/>.
1415

15-
# author: <[email protected]>
16-
17-
# scapy.contrib.description = PPI
18-
# scapy.contrib.status = loads
19-
16+
# Original PPI author: <[email protected]>
2017

2118
"""
22-
PPI (Per-Packet Information).
19+
Per-Packet Information (PPI) Protocol
2320
"""
24-
import logging
25-
import struct
2621

22+
import struct
2723

2824
from scapy.config import conf
2925
from scapy.data import DLT_EN10MB, DLT_IEEE802_11, DLT_PPI
30-
from scapy.packet import *
26+
from scapy.packet import bind_layers, Packet
3127
from scapy.fields import *
3228
from scapy.layers.l2 import Ether
3329
from scapy.layers.dot11 import Dot11
@@ -85,12 +81,12 @@ def _PPIGuessPayloadClass(p, **kargs):
8581

8682

8783
class PPI(Packet):
88-
name = "PPI Packet Header"
89-
fields_desc = [ByteField('pph_version', 0),
90-
ByteField('pph_flags', 0),
91-
FieldLenField('pph_len', None, length_of="PPIFieldHeaders", fmt="<H", adjust=lambda p, x:x + 8),
92-
LEIntField('dlt', None),
93-
PacketListField("PPIFieldHeaders", [], _PPIGuessPayloadClass, length_from=lambda p:p.pph_len - 8,)]
84+
name = "Per-Packet Information header (PPI)"
85+
fields_desc = [ByteField('version', 0),
86+
ByteField('flags', 0),
87+
FieldLenField('len', None, length_of="PPIFieldHeaders", fmt="<H", adjust=lambda p, x: x + 8),
88+
LEIntField('dlt', 1),
89+
PacketListField("PPIFieldHeaders", [], _PPIGuessPayloadClass, length_from=lambda p: p.len - 8,)]
9490

9591
def guess_payload_class(self, payload):
9692
return conf.l2types.get(self.dlt, Packet.guess_payload_class(self, payload))

0 commit comments

Comments
 (0)