From 9e9561d9a50d64ad3dba386f4bdd554fe2600ae1 Mon Sep 17 00:00:00 2001 From: gpotter2 Date: Mon, 30 Apr 2018 03:32:21 +0200 Subject: [PATCH 1/2] RadioTap flags + Dot11 FCS&improvements + Merge PPI implementation --- scapy/contrib/ppi.py | 105 ---------- scapy/contrib/ppi_cace.py | 3 +- scapy/contrib/{ppi.uts => ppi_cace.uts} | 1 - scapy/contrib/ppi_geotag.py | 2 +- scapy/fields.py | 15 ++ scapy/layers/dot11.py | 261 ++++++++++++++++++++++-- scapy/packet.py | 7 + test/regression.uts | 25 +++ 8 files changed, 292 insertions(+), 127 deletions(-) delete mode 100644 scapy/contrib/ppi.py rename scapy/contrib/{ppi.uts => ppi_cace.uts} (99%) diff --git a/scapy/contrib/ppi.py b/scapy/contrib/ppi.py deleted file mode 100644 index b3b56b84d57..00000000000 --- a/scapy/contrib/ppi.py +++ /dev/null @@ -1,105 +0,0 @@ -# This file is part of Scapy -# Scapy is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 2 of the License, or -# any later version. -# -# Scapy is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Scapy. If not, see . - -# author: - -# scapy.contrib.description = PPI -# scapy.contrib.status = loads - - -""" -PPI (Per-Packet Information). -""" -import logging -import struct - - -from scapy.config import conf -from scapy.data import DLT_EN10MB, DLT_IEEE802_11, DLT_PPI -from scapy.packet import * -from scapy.fields import * -from scapy.layers.l2 import Ether -from scapy.layers.dot11 import Dot11 - -# Dictionary to map the TLV type to the class name of a sub-packet -_ppi_types = {} - - -def addPPIType(id, value): - _ppi_types[id] = value - - -def getPPIType(id, default="default"): - return _ppi_types.get(id, _ppi_types.get(default, None)) - - -# Default PPI Field Header -class PPIGenericFldHdr(Packet): - name = "PPI Field Header" - fields_desc = [LEShortField('pfh_type', 0), - FieldLenField('pfh_length', None, length_of="value", fmt='= 4: - t, pfh_len = struct.unpack(" pfh_len): - out.payload.payload = conf.padding_layer(p[pfh_len:]) - out.payload.payload.underlayer = out.payload - elif (len(p) > pfh_len): - out.payload = conf.padding_layer(p[pfh_len:]) - out.payload.underlayer = out - else: - out = conf.raw_layer(p, **kargs) - return out - - -class PPI(Packet): - name = "PPI Packet Header" - fields_desc = [ByteField('pph_version', 0), - ByteField('pph_flags', 0), - FieldLenField('pph_len', None, length_of="PPIFieldHeaders", fmt=". + # Copyright (C) Philippe Biondi -# This program is published under a GPLv2 license +# Original PPI author: """ Wireless LAN according to IEEE 802.11. """ from __future__ import print_function +import math import re import struct from zlib import crc32 @@ -90,27 +104,186 @@ def answers(self, other): return self.payload.answers(other) +class _RadiotapReversePadField(ReversePadField): + def __init__(self, fld): + self._fld = fld + self._padwith = b"\x00" + # Quote from https://www.radiotap.org/: + # ""Radiotap requires that all fields in the radiotap header are aligned to natural boundaries. + # For radiotap, that means all 8-, 16-, 32-, and 64-bit fields must begin on 8-, 16-, 32-, and 64-bit boundaries, respectively."" + if isinstance(self._fld, BitField): + self._align = int(math.ceil(self.i2len(None, None))) + else: + self._align = struct.calcsize(self._fld.fmt) + + +class _dbmField(ByteField): + def i2m(self, pkt, x): + return super(ByteField, self).i2m(pkt, x + 256) + + def m2i(self, pkt, x): + return super(ByteField, self).m2i(pkt, x) - 256 + + def i2repr(self, pkt, x): + return "%sdBm" % x + + +_vht_bandwidth = {0: "20MHz", 1: "40MHz", 2: "40MHz", 3: "40MHz", 4: "80MHz", 5: "80MHz", + 6: "80MHz", 7: "80MHz", 8: "80MHz", 9: "80MHz", 10: "80MHz", 11: "160MHz", + 12: "160MHz", 13: "160MHz", 14: "160MHz", 15: "160MHz", 16: "160MHz", 17: "160MHz", + 18: "160MHz", 19: "160MHz", 20: "160MHz", 21: "160MHz", 22: "160MHz", 23: "160MHz", + 24: "160MHz", 25: "160MHz"} + + class RadioTap(Packet): name = "RadioTap dummy" fields_desc = [ByteField('version', 0), ByteField('pad', 0), - FieldLenField('len', None, 'notdecoded', '= 4: + t, pfh_len = struct.unpack(" pfh_len): + out.payload.payload = conf.padding_layer(p[pfh_len:]) + out.payload.payload.underlayer = out.payload + elif (len(p) > pfh_len): + out.payload = conf.padding_layer(p[pfh_len:]) + out.payload.underlayer = out + else: + out = conf.raw_layer(p, **kargs) + return out class PPI(Packet): - name = "Per-Packet Information header (partial)" - fields_desc = [ByteField("version", 0), - ByteField("flags", 0), - FieldLenField("len", None, fmt=" %Dot11.addr1%") + # Supports both Dot11 and Dot11FCS + return self.sprintf("802.11 %%%s.type%% %%%s.subtype%% %%%s.addr2%% > %%%s.addr1%%" % ((self.__class__.__name__,) * 4)) def guess_payload_class(self, payload): if self.type == 0x02 and (0x08 <= self.subtype <= 0xF and self.subtype != 0xD): @@ -185,6 +359,30 @@ def unwep(self, key=None, warn=1): self.payload = self.payload.payload +class Dot11FCS(Dot11): + name = "802.11-FCS" + fields_desc = Dot11.fields_desc + [XLEIntField("fcs", None)] # Automatically moved to the end of the packet + + def compute_fcs(self, s): + return struct.pack("!I", crc32(s) & 0xffffffff)[::-1] + + def post_build(self, p, pay): + # Switch payload and frame check sequence + return p[:-4] + pay + (p[-4:] if self.fcs is not None else self.compute_fcs(p[:-4] + pay)) + + def post_dissect(self, s): + self.raw_packet_cache = None # Reset packet to allow post_build + return s + + def pre_dissect(self, s): + # Get the frame check sequence + sty = orb(s[0]) + ty = orb(s[1]) >> 2 + fc = struct.unpack("!H", s[2:4])[0] + length = 12 + 6 * ((ty != 1 or sty in [0x8, 0x9, 0xa, 0xb, 0xe, 0xf]) + (ty in [0, 2]) + (ty == 2 and fc & 3 == 3)) + return s[:length] + s[-4:] + s[length:-4] + + class Dot11QoS(Packet): name = "802.11 QoS" fields_desc = [BitField("Reserved", None, 1), @@ -224,10 +422,37 @@ class Dot11Beacon(Packet): FlagsField("cap", 0, 16, capability_list)] +_dot11_info_elts_ids = { + 0: "SSID", + 1: "Rates", + 2: "FHset", + 3: "DSset", + 4: "CFset", + 5: "TIM", + 6: "IBSSset", + 7: "Country", + 10: "Request", + 16: "challenge", + 33: "PowerCapability", + 36: "Channels", + 42: "ERPinfo", + 45: "HTCapabilities", + 46: "QoSCapability", + 47: "ERPinfo", + 48: "RSNinfo", + 50: "ESRates", + 52: "PowerConstraint", + 107: "Interworking", + 127: "ExtendendCapatibilities", + 191: "VHTCapabilities", + 221: "vendor", + 68: "reserved" +} + + class Dot11Elt(Packet): name = "802.11 Information Element" - fields_desc = [ByteEnumField("ID", 0, {0: "SSID", 1: "Rates", 2: "FHset", 3: "DSset", 4: "CFset", 5: "TIM", 6: "IBSSset", 16: "challenge", - 42: "ERPinfo", 46: "QoS Capability", 47: "ERPinfo", 48: "RSNinfo", 50: "ESRates", 221: "vendor", 68: "reserved"}), + fields_desc = [ByteEnumField("ID", 0, _dot11_info_elts_ids), FieldLenField("len", None, "info", "B"), StrLenField("info", "", length_from=lambda x: x.len, max_length=255)] @@ -539,8 +764,8 @@ class Dot11Ack(Packet): bind_layers(PrismHeader, Dot11,) -bind_layers(RadioTap, Dot11,) -bind_layers(PPI, Dot11, dlt=105) +bind_layers(PPI, Dot11, dlt=DLT_IEEE802_11) +bind_layers(PPI, Ether, dlt=DLT_EN10MB) bind_layers(Dot11, LLC, type=2) bind_layers(Dot11QoS, LLC,) bind_layers(Dot11, Dot11AssoReq, subtype=0, type=0) diff --git a/scapy/packet.py b/scapy/packet.py index 54a0554adbc..a87a866a703 100644 --- a/scapy/packet.py +++ b/scapy/packet.py @@ -64,6 +64,8 @@ class Packet(six.with_metaclass(Packet_metaclass, BasePacket)): "direction", "sniffed_on", # handle snaplen Vs real length "wirelen", + # used while performing advanced dissection to handle padding + "_tmp_dissect_pos", ] name = None fields_desc = [] @@ -712,16 +714,21 @@ def do_dissect(self, s): s = raw(s) _raw = s self.raw_packet_cache_fields = {} + # Temporary value, used by getfield() in some advanced cases (eg: dot11) + _lr = len(_raw) + self._tmp_dissect_pos = 0 # How many bytes have already been dissected for f in self.fields_desc: if not s: break s, fval = f.getfield(self, s) + self._tmp_dissect_pos = _lr - len(s) # We need to track fields with mutable values to discard # .raw_packet_cache when needed. if f.islist or f.holds_packets or f.ismutable: self.raw_packet_cache_fields[f.name] = f.do_copy(fval) self.fields[f.name] = fval assert(_raw.endswith(raw(s))) + del self._tmp_dissect_pos self.raw_packet_cache = _raw[:-len(s)] if s else _raw self.explicit = 1 return s diff --git a/test/regression.uts b/test/regression.uts index 3ad57748a15..d31f27bf936 100644 --- a/test/regression.uts +++ b/test/regression.uts @@ -688,6 +688,15 @@ class TestPad(Packet): TestPad() == TestPad(raw(TestPad())) += ReversePadField +~ PadField padding + +class TestReversePad(Packet): + fields_desc = [ ByteField("a", 0), + ReversePadField(IntField("b", 0), 4)] + +assert raw(TestReversePad(a=1, b=0xffffffff)) == b'\x01\x00\x00\x00\xff\xff\xff\xff' +assert TestReversePad(raw(TestReversePad(a=1, b=0xffffffff))).b == 0xffffffff ############ ############ @@ -768,6 +777,18 @@ p = Dot11WEP(r) p assert(TCP in p and p[TCP].seq == 12345678) += RadioTap - dissection & build +data = b'\x00\x008\x00k\x084\x00oo\x0f\x98\x00\x00\x00\x00\x10\x00\x99\x16@\x01\xc5\xa1\x01\x00\x00\x00@\x01\x02\x00\x99\x16\x9d"\x05\x0b\x00\x00\x00\x00\x00\x00\xff\x01\x16\x01\x82\x00\x00\x00\x01\x00\x00\x00\x88\x020\x00\xb8\xe8VB_\xb2\x82*\xa8Uq\x15\xf0\x9f\xc2\x11\x16dP\xb0\x00\x00\xaa\xaa\x03\x00\x00\x00\x08\x00E\x00\x00GC\xad@\x007\x11\x97;\xd0C\xde{\xac\x10\r\xee\x005\xed\xec\x003\xd5/\xfc\\\x81\x80\x00\x01\x00\x01\x00\x00\x00\x00\tlocalhost\x00\x00\x01\x00\x01\xc0\x0c\x00\x01\x00\x01\x00\t:\x80\x00\x04\x7f\x00\x00\x01\xcdj\x88]' +r = RadioTap(data) +r = RadioTap(raw(r)) +assert r.dBm_AntSignal == -59 +assert r.ChannelFrequency == 5785 +assert r.present == 3410027 +assert r.A_MPDU_ref == 2821 +assert r.KnownVHT == 511 +assert r.PresentVHT == 22 +assert r.notdecoded == b'' + = RadioTap Big-Small endian dissection data = b'\x00\x00\x1a\x00/H\x00\x00\xe1\xd3\xcb\x05\x00\x00\x00\x00@0x\x14@\x01\xac\x00\x00\x00' r = RadioTap(data) @@ -9820,6 +9841,10 @@ assert len(f[Dot11EltRSN].pmkids.pmkid_list) == 1 assert f[Dot11EltRSN].pmkids.pmkid_list[0] == b'LD\xfe\xf2l\xdcV\xce\x0b7\xab\xc62\x02O\x11' +###################################### +# More PPI tests in contrib/ppi_cace # +###################################### + ############ ############ + 802.3 From 1b8ad56e2d28da4b0f0986c987ef191337ad27df Mon Sep 17 00:00:00 2001 From: gpotter2 Date: Wed, 9 May 2018 16:26:16 +0200 Subject: [PATCH 2/2] Move PPI to new layer file --- scapy/contrib/ppi_cace.py | 2 +- scapy/contrib/ppi_geotag.py | 2 +- scapy/layers/bluetooth4LE.py | 6 +-- scapy/layers/dot11.py | 72 ------------------------- scapy/layers/ppi.py | 101 +++++++++++++++++++++++++++++++++++ 5 files changed, 106 insertions(+), 77 deletions(-) create mode 100644 scapy/layers/ppi.py diff --git a/scapy/contrib/ppi_cace.py b/scapy/contrib/ppi_cace.py index 7b54e27f01e..f6110172576 100644 --- a/scapy/contrib/ppi_cace.py +++ b/scapy/contrib/ppi_cace.py @@ -26,7 +26,7 @@ from scapy.packet import * from scapy.fields import * from scapy.layers.l2 import Ether -from scapy.layers.dot11 import * +from scapy.layers.ppi import addPPIType PPI_DOT11COMMON = 2 PPI_DOT11NMAC = 3 diff --git a/scapy/contrib/ppi_geotag.py b/scapy/contrib/ppi_geotag.py index b21478e28cd..f43ddc5b2ce 100644 --- a/scapy/contrib/ppi_geotag.py +++ b/scapy/contrib/ppi_geotag.py @@ -26,7 +26,7 @@ import time from scapy.packet import * from scapy.fields import * -from scapy.layers.dot11 import * +from scapy.layers.ppi import addPPIType from scapy.error import warning import scapy.modules.six as six from scapy.modules.six.moves import range diff --git a/scapy/layers/bluetooth4LE.py b/scapy/layers/bluetooth4LE.py index 67ac10a7967..a77a4b603f2 100644 --- a/scapy/layers/bluetooth4LE.py +++ b/scapy/layers/bluetooth4LE.py @@ -14,7 +14,7 @@ from scapy.data import MTU, DLT_BLUETOOTH_LE_LL from scapy.packet import * from scapy.fields import * -from scapy.layers import dot11 +from scapy.layers.ppi import PPI from scapy.modules.six.moves import range @@ -123,7 +123,7 @@ def pre_dissect(self, s): return s[:4] + s[-3:] + s[4:-3] def post_dissection(self, pkt): - if isinstance(pkt, dot11.PPI): + if isinstance(pkt, PPI): pkt.notdecoded = PPI_FieldHeader(pkt.notdecoded) def hashret(self): @@ -261,5 +261,5 @@ class BTLE_CONNECT_REQ(Packet): conf.l2types.register(DLT_BLUETOOTH_LE_LL, BTLE) -bind_layers(dot11.PPI, BTLE, dlt=147) +bind_layers(PPI, BTLE, dlt=147) bind_layers(PPI_FieldHeader, BTLE_PPI, pfh_type=30006) diff --git a/scapy/layers/dot11.py b/scapy/layers/dot11.py index cc4c31e50fb..5edfa839cce 100644 --- a/scapy/layers/dot11.py +++ b/scapy/layers/dot11.py @@ -14,7 +14,6 @@ # along with Scapy. If not, see . # Copyright (C) Philippe Biondi -# Original PPI author: """ Wireless LAN according to IEEE 802.11. @@ -218,74 +217,6 @@ def post_build(self, p, pay): return p + pay -# Dictionary to map the TLV type to the class name of a sub-packet -_ppi_types = {} - - -def addPPIType(id, value): - _ppi_types[id] = value - - -def getPPIType(id, default="default"): - return _ppi_types.get(id, _ppi_types.get(default, None)) - - -# Default PPI Field Header -class PPIGenericFldHdr(Packet): - name = "PPI Field Header" - fields_desc = [LEShortField('pfh_type', 0), - FieldLenField('pfh_length', None, length_of="value", fmt='= 4: - t, pfh_len = struct.unpack(" pfh_len): - out.payload.payload = conf.padding_layer(p[pfh_len:]) - out.payload.payload.underlayer = out.payload - elif (len(p) > pfh_len): - out.payload = conf.padding_layer(p[pfh_len:]) - out.payload.underlayer = out - else: - out = conf.raw_layer(p, **kargs) - return out - - -class PPI(Packet): - name = "Per-Packet Information header (PPI)" - fields_desc = [ByteField('version', 0), - ByteField('flags', 0), - FieldLenField('len', None, length_of="PPIFieldHeaders", fmt=". + +# Original PPI author: + +""" +Per-Packet Information (PPI) Protocol +""" + +import struct + +from scapy.config import conf +from scapy.data import DLT_EN10MB, DLT_IEEE802_11, DLT_PPI +from scapy.packet import bind_layers, Packet +from scapy.fields import * +from scapy.layers.l2 import Ether +from scapy.layers.dot11 import Dot11 + +# Dictionary to map the TLV type to the class name of a sub-packet +_ppi_types = {} + + +def addPPIType(id, value): + _ppi_types[id] = value + + +def getPPIType(id, default="default"): + return _ppi_types.get(id, _ppi_types.get(default, None)) + + +# Default PPI Field Header +class PPIGenericFldHdr(Packet): + name = "PPI Field Header" + fields_desc = [LEShortField('pfh_type', 0), + FieldLenField('pfh_length', None, length_of="value", fmt='= 4: + t, pfh_len = struct.unpack(" pfh_len): + out.payload.payload = conf.padding_layer(p[pfh_len:]) + out.payload.payload.underlayer = out.payload + elif (len(p) > pfh_len): + out.payload = conf.padding_layer(p[pfh_len:]) + out.payload.underlayer = out + else: + out = conf.raw_layer(p, **kargs) + return out + + +class PPI(Packet): + name = "Per-Packet Information header (PPI)" + fields_desc = [ByteField('version', 0), + ByteField('flags', 0), + FieldLenField('len', None, length_of="PPIFieldHeaders", fmt="