2276 lines
82 KiB
Python
2276 lines
82 KiB
Python
|
# This file is part of Scapy
|
||
|
# See http://www.secdev.org/projects/scapy for more information
|
||
|
# Copyright (C) Philippe Biondi <phil@secdev.org>
|
||
|
# This program is published under a GPLv2 license
|
||
|
|
||
|
"""
|
||
|
Packet class
|
||
|
|
||
|
Provides:
|
||
|
- the default Packet classes
|
||
|
- binding mechanisms
|
||
|
- fuzz() method
|
||
|
- exploration methods: explore() / ls()
|
||
|
"""
|
||
|
|
||
|
from __future__ import absolute_import
|
||
|
from __future__ import print_function
|
||
|
from collections import defaultdict
|
||
|
import re
|
||
|
import time
|
||
|
import itertools
|
||
|
import copy
|
||
|
import types
|
||
|
import warnings
|
||
|
|
||
|
from scapy.fields import StrField, ConditionalField, Emph, PacketListField, \
|
||
|
BitField, MultiEnumField, EnumField, FlagsField, MultipleTypeField
|
||
|
from scapy.config import conf, _version_checker
|
||
|
from scapy.compat import raw, orb, bytes_encode
|
||
|
from scapy.base_classes import BasePacket, Gen, SetGen, Packet_metaclass, \
|
||
|
_CanvasDumpExtended
|
||
|
from scapy.volatile import RandField, VolatileValue
|
||
|
from scapy.utils import import_hexcap, tex_escape, colgen, issubtype, \
|
||
|
pretty_list
|
||
|
from scapy.error import Scapy_Exception, log_runtime, warning
|
||
|
from scapy.extlib import PYX
|
||
|
import scapy.modules.six as six
|
||
|
|
||
|
try:
|
||
|
import pyx
|
||
|
except ImportError:
|
||
|
pass
|
||
|
|
||
|
|
||
|
class RawVal:
|
||
|
def __init__(self, val=""):
|
||
|
self.val = val
|
||
|
|
||
|
def __str__(self):
|
||
|
return str(self.val)
|
||
|
|
||
|
def __bytes__(self):
|
||
|
return raw(self.val)
|
||
|
|
||
|
def __repr__(self):
|
||
|
return "<RawVal [%r]>" % self.val
|
||
|
|
||
|
|
||
|
class Packet(six.with_metaclass(Packet_metaclass, BasePacket,
|
||
|
_CanvasDumpExtended)):
|
||
|
__slots__ = [
|
||
|
"time", "sent_time", "name",
|
||
|
"default_fields", "fields", "fieldtype",
|
||
|
"overload_fields", "overloaded_fields",
|
||
|
"packetfields",
|
||
|
"original", "explicit", "raw_packet_cache",
|
||
|
"raw_packet_cache_fields", "_pkt", "post_transforms",
|
||
|
# then payload and underlayer
|
||
|
"payload", "underlayer",
|
||
|
"name",
|
||
|
# used for sr()
|
||
|
"_answered",
|
||
|
# used when sniffing
|
||
|
"direction", "sniffed_on",
|
||
|
# handle snaplen Vs real length
|
||
|
"wirelen",
|
||
|
]
|
||
|
name = None
|
||
|
fields_desc = []
|
||
|
deprecated_fields = {}
|
||
|
overload_fields = {}
|
||
|
payload_guess = []
|
||
|
show_indent = 1
|
||
|
show_summary = True
|
||
|
match_subclass = False
|
||
|
class_dont_cache = dict()
|
||
|
class_packetfields = dict()
|
||
|
class_default_fields = dict()
|
||
|
class_default_fields_ref = dict()
|
||
|
class_fieldtype = dict()
|
||
|
|
||
|
@classmethod
|
||
|
def from_hexcap(cls):
|
||
|
return cls(import_hexcap())
|
||
|
|
||
|
@classmethod
|
||
|
def upper_bonds(self):
|
||
|
for fval, upper in self.payload_guess:
|
||
|
print("%-20s %s" % (upper.__name__, ", ".join("%-12s" % ("%s=%r" % i) for i in six.iteritems(fval)))) # noqa: E501
|
||
|
|
||
|
@classmethod
|
||
|
def lower_bonds(self):
|
||
|
for lower, fval in six.iteritems(self._overload_fields):
|
||
|
print("%-20s %s" % (lower.__name__, ", ".join("%-12s" % ("%s=%r" % i) for i in six.iteritems(fval)))) # noqa: E501
|
||
|
|
||
|
def _unpickle(self, dlist):
|
||
|
"""Used to unpack pickling"""
|
||
|
self.__init__(b"".join(dlist))
|
||
|
return self
|
||
|
|
||
|
def __reduce__(self):
|
||
|
"""Used by pickling methods"""
|
||
|
return (self.__class__, (), (self.build(),))
|
||
|
|
||
|
def __reduce_ex__(self, proto):
|
||
|
"""Used by pickling methods"""
|
||
|
return self.__reduce__()
|
||
|
|
||
|
def __getstate__(self):
|
||
|
"""Mark object as pickable"""
|
||
|
return self.__reduce__()[2]
|
||
|
|
||
|
def __setstate__(self, state):
|
||
|
"""Rebuild state using pickable methods"""
|
||
|
return self._unpickle(state)
|
||
|
|
||
|
def __deepcopy__(self, memo):
|
||
|
"""Used by copy.deepcopy"""
|
||
|
return self.copy()
|
||
|
|
||
|
def __init__(self, _pkt=b"", post_transform=None, _internal=0, _underlayer=None, **fields): # noqa: E501
|
||
|
self.time = time.time()
|
||
|
self.sent_time = None
|
||
|
self.name = (self.__class__.__name__
|
||
|
if self._name is None else
|
||
|
self._name)
|
||
|
self.default_fields = {}
|
||
|
self.overload_fields = self._overload_fields
|
||
|
self.overloaded_fields = {}
|
||
|
self.fields = {}
|
||
|
self.fieldtype = {}
|
||
|
self.packetfields = []
|
||
|
self.payload = NoPayload()
|
||
|
self.init_fields()
|
||
|
self.underlayer = _underlayer
|
||
|
self.original = _pkt
|
||
|
self.explicit = 0
|
||
|
self.raw_packet_cache = None
|
||
|
self.raw_packet_cache_fields = None
|
||
|
self.wirelen = None
|
||
|
if _pkt:
|
||
|
self.dissect(_pkt)
|
||
|
if not _internal:
|
||
|
self.dissection_done(self)
|
||
|
# We use this strange initialization so that the fields
|
||
|
# are initialized in their declaration order.
|
||
|
# It is required to always support MultipleTypeField
|
||
|
for field in self.fields_desc:
|
||
|
fname = field.name
|
||
|
try:
|
||
|
value = fields.pop(fname)
|
||
|
except KeyError:
|
||
|
continue
|
||
|
self.fields[fname] = self.get_field(fname).any2i(self, value)
|
||
|
# The remaining fields are unknown
|
||
|
for fname in fields:
|
||
|
if fname in self.deprecated_fields:
|
||
|
# Resolve deprecated fields
|
||
|
value = fields[fname]
|
||
|
fname = self._resolve_alias(fname)
|
||
|
self.fields[fname] = self.get_field(fname).any2i(self, value)
|
||
|
continue
|
||
|
raise AttributeError(fname)
|
||
|
if isinstance(post_transform, list):
|
||
|
self.post_transforms = post_transform
|
||
|
elif post_transform is None:
|
||
|
self.post_transforms = []
|
||
|
else:
|
||
|
self.post_transforms = [post_transform]
|
||
|
|
||
|
def init_fields(self):
|
||
|
"""
|
||
|
Initialize each fields of the fields_desc dict
|
||
|
"""
|
||
|
|
||
|
if self.class_dont_cache.get(self.__class__, False):
|
||
|
self.do_init_fields(self.fields_desc)
|
||
|
else:
|
||
|
self.do_init_cached_fields()
|
||
|
|
||
|
def do_init_fields(self, flist):
|
||
|
"""
|
||
|
Initialize each fields of the fields_desc dict
|
||
|
"""
|
||
|
default_fields = {}
|
||
|
for f in flist:
|
||
|
default_fields[f.name] = copy.deepcopy(f.default)
|
||
|
self.fieldtype[f.name] = f
|
||
|
if f.holds_packets:
|
||
|
self.packetfields.append(f)
|
||
|
# We set default_fields last to avoid race issues
|
||
|
self.default_fields = default_fields
|
||
|
|
||
|
def do_init_cached_fields(self):
|
||
|
"""
|
||
|
Initialize each fields of the fields_desc dict, or use the cached
|
||
|
fields information
|
||
|
"""
|
||
|
|
||
|
cls_name = self.__class__
|
||
|
|
||
|
# Build the fields information
|
||
|
if Packet.class_default_fields.get(cls_name, None) is None:
|
||
|
self.prepare_cached_fields(self.fields_desc)
|
||
|
|
||
|
# Use fields information from cache
|
||
|
default_fields = Packet.class_default_fields.get(cls_name, None)
|
||
|
if default_fields:
|
||
|
self.default_fields = default_fields
|
||
|
self.fieldtype = Packet.class_fieldtype[cls_name]
|
||
|
self.packetfields = Packet.class_packetfields[cls_name]
|
||
|
|
||
|
# Deepcopy default references
|
||
|
for fname in Packet.class_default_fields_ref[cls_name]:
|
||
|
value = self.default_fields[fname]
|
||
|
try:
|
||
|
self.fields[fname] = value.copy()
|
||
|
except AttributeError:
|
||
|
# Python 2.7 - list only
|
||
|
self.fields[fname] = value[:]
|
||
|
|
||
|
def prepare_cached_fields(self, flist):
|
||
|
"""
|
||
|
Prepare the cached fields of the fields_desc dict
|
||
|
"""
|
||
|
|
||
|
cls_name = self.__class__
|
||
|
|
||
|
# Fields cache initialization
|
||
|
if not flist:
|
||
|
return
|
||
|
|
||
|
class_default_fields = dict()
|
||
|
class_default_fields_ref = list()
|
||
|
class_fieldtype = dict()
|
||
|
class_packetfields = list()
|
||
|
|
||
|
# Fields initialization
|
||
|
for f in flist:
|
||
|
if isinstance(f, MultipleTypeField):
|
||
|
# Abort
|
||
|
self.class_dont_cache[cls_name] = True
|
||
|
self.do_init_fields(self.fields_desc)
|
||
|
return
|
||
|
|
||
|
tmp_copy = copy.deepcopy(f.default)
|
||
|
class_default_fields[f.name] = tmp_copy
|
||
|
class_fieldtype[f.name] = f
|
||
|
if f.holds_packets:
|
||
|
class_packetfields.append(f)
|
||
|
|
||
|
# Remember references
|
||
|
if isinstance(f.default, (list, dict, set, RandField, Packet)):
|
||
|
class_default_fields_ref.append(f.name)
|
||
|
|
||
|
# Apply
|
||
|
Packet.class_default_fields_ref[cls_name] = class_default_fields_ref
|
||
|
Packet.class_fieldtype[cls_name] = class_fieldtype
|
||
|
Packet.class_packetfields[cls_name] = class_packetfields
|
||
|
# Last to avoid racing issues
|
||
|
Packet.class_default_fields[cls_name] = class_default_fields
|
||
|
|
||
|
def dissection_done(self, pkt):
|
||
|
"""DEV: will be called after a dissection is completed"""
|
||
|
self.post_dissection(pkt)
|
||
|
self.payload.dissection_done(pkt)
|
||
|
|
||
|
def post_dissection(self, pkt):
|
||
|
"""DEV: is called after the dissection of the whole packet"""
|
||
|
pass
|
||
|
|
||
|
def get_field(self, fld):
|
||
|
"""DEV: returns the field instance from the name of the field"""
|
||
|
return self.fieldtype[fld]
|
||
|
|
||
|
def add_payload(self, payload):
|
||
|
if payload is None:
|
||
|
return
|
||
|
elif not isinstance(self.payload, NoPayload):
|
||
|
self.payload.add_payload(payload)
|
||
|
else:
|
||
|
if isinstance(payload, Packet):
|
||
|
self.payload = payload
|
||
|
payload.add_underlayer(self)
|
||
|
for t in self.aliastypes:
|
||
|
if t in payload.overload_fields:
|
||
|
self.overloaded_fields = payload.overload_fields[t]
|
||
|
break
|
||
|
elif isinstance(payload, bytes):
|
||
|
self.payload = conf.raw_layer(load=payload)
|
||
|
else:
|
||
|
raise TypeError("payload must be either 'Packet' or 'bytes', not [%s]" % repr(payload)) # noqa: E501
|
||
|
|
||
|
def remove_payload(self):
|
||
|
self.payload.remove_underlayer(self)
|
||
|
self.payload = NoPayload()
|
||
|
self.overloaded_fields = {}
|
||
|
|
||
|
def add_underlayer(self, underlayer):
|
||
|
self.underlayer = underlayer
|
||
|
|
||
|
def remove_underlayer(self, other):
|
||
|
self.underlayer = None
|
||
|
|
||
|
def copy(self):
|
||
|
"""Returns a deep copy of the instance."""
|
||
|
clone = self.__class__()
|
||
|
clone.fields = self.copy_fields_dict(self.fields)
|
||
|
clone.default_fields = self.copy_fields_dict(self.default_fields)
|
||
|
clone.overloaded_fields = self.overloaded_fields.copy()
|
||
|
clone.underlayer = self.underlayer
|
||
|
clone.explicit = self.explicit
|
||
|
clone.raw_packet_cache = self.raw_packet_cache
|
||
|
clone.raw_packet_cache_fields = self.copy_fields_dict(
|
||
|
self.raw_packet_cache_fields
|
||
|
)
|
||
|
clone.wirelen = self.wirelen
|
||
|
clone.post_transforms = self.post_transforms[:]
|
||
|
clone.payload = self.payload.copy()
|
||
|
clone.payload.add_underlayer(clone)
|
||
|
clone.time = self.time
|
||
|
return clone
|
||
|
|
||
|
def _resolve_alias(self, attr):
|
||
|
new_attr, version = self.deprecated_fields[attr]
|
||
|
warnings.warn(
|
||
|
"%s has been deprecated in favor of %s since %s !" % (
|
||
|
attr, new_attr, version
|
||
|
), DeprecationWarning
|
||
|
)
|
||
|
return new_attr
|
||
|
|
||
|
def getfieldval(self, attr):
|
||
|
if self.deprecated_fields and attr in self.deprecated_fields:
|
||
|
attr = self._resolve_alias(attr)
|
||
|
if attr in self.fields:
|
||
|
return self.fields[attr]
|
||
|
if attr in self.overloaded_fields:
|
||
|
return self.overloaded_fields[attr]
|
||
|
if attr in self.default_fields:
|
||
|
return self.default_fields[attr]
|
||
|
return self.payload.getfieldval(attr)
|
||
|
|
||
|
def getfield_and_val(self, attr):
|
||
|
if self.deprecated_fields and attr in self.deprecated_fields:
|
||
|
attr = self._resolve_alias(attr)
|
||
|
if attr in self.fields:
|
||
|
return self.get_field(attr), self.fields[attr]
|
||
|
if attr in self.overloaded_fields:
|
||
|
return self.get_field(attr), self.overloaded_fields[attr]
|
||
|
if attr in self.default_fields:
|
||
|
return self.get_field(attr), self.default_fields[attr]
|
||
|
|
||
|
def __getattr__(self, attr):
|
||
|
try:
|
||
|
fld, v = self.getfield_and_val(attr)
|
||
|
except TypeError:
|
||
|
return self.payload.__getattr__(attr)
|
||
|
if fld is not None:
|
||
|
return fld.i2h(self, v)
|
||
|
return v
|
||
|
|
||
|
def setfieldval(self, attr, val):
|
||
|
if self.deprecated_fields and attr in self.deprecated_fields:
|
||
|
attr = self._resolve_alias(attr)
|
||
|
if attr in self.default_fields:
|
||
|
fld = self.get_field(attr)
|
||
|
if fld is None:
|
||
|
any2i = lambda x, y: y
|
||
|
else:
|
||
|
any2i = fld.any2i
|
||
|
self.fields[attr] = any2i(self, val)
|
||
|
self.explicit = 0
|
||
|
self.raw_packet_cache = None
|
||
|
self.raw_packet_cache_fields = None
|
||
|
self.wirelen = None
|
||
|
elif attr == "payload":
|
||
|
self.remove_payload()
|
||
|
self.add_payload(val)
|
||
|
else:
|
||
|
self.payload.setfieldval(attr, val)
|
||
|
|
||
|
def __setattr__(self, attr, val):
|
||
|
if attr in self.__all_slots__:
|
||
|
if attr == "sent_time":
|
||
|
self.update_sent_time(val)
|
||
|
return object.__setattr__(self, attr, val)
|
||
|
try:
|
||
|
return self.setfieldval(attr, val)
|
||
|
except AttributeError:
|
||
|
pass
|
||
|
return object.__setattr__(self, attr, val)
|
||
|
|
||
|
def delfieldval(self, attr):
|
||
|
if attr in self.fields:
|
||
|
del(self.fields[attr])
|
||
|
self.explicit = 0 # in case a default value must be explicit
|
||
|
self.raw_packet_cache = None
|
||
|
self.raw_packet_cache_fields = None
|
||
|
self.wirelen = None
|
||
|
elif attr in self.default_fields:
|
||
|
pass
|
||
|
elif attr == "payload":
|
||
|
self.remove_payload()
|
||
|
else:
|
||
|
self.payload.delfieldval(attr)
|
||
|
|
||
|
def __delattr__(self, attr):
|
||
|
if attr == "payload":
|
||
|
return self.remove_payload()
|
||
|
if attr in self.__all_slots__:
|
||
|
return object.__delattr__(self, attr)
|
||
|
try:
|
||
|
return self.delfieldval(attr)
|
||
|
except AttributeError:
|
||
|
pass
|
||
|
return object.__delattr__(self, attr)
|
||
|
|
||
|
def _superdir(self):
|
||
|
"""
|
||
|
Return a list of slots and methods, including those from subclasses.
|
||
|
"""
|
||
|
attrs = set()
|
||
|
cls = self.__class__
|
||
|
if hasattr(cls, '__all_slots__'):
|
||
|
attrs.update(cls.__all_slots__)
|
||
|
for bcls in cls.__mro__:
|
||
|
if hasattr(bcls, '__dict__'):
|
||
|
attrs.update(bcls.__dict__)
|
||
|
return attrs
|
||
|
|
||
|
def __dir__(self):
|
||
|
"""
|
||
|
Add fields to tab completion list.
|
||
|
"""
|
||
|
return sorted(itertools.chain(self._superdir(), self.default_fields))
|
||
|
|
||
|
def __repr__(self):
|
||
|
s = ""
|
||
|
ct = conf.color_theme
|
||
|
for f in self.fields_desc:
|
||
|
if isinstance(f, ConditionalField) and not f._evalcond(self):
|
||
|
continue
|
||
|
if f.name in self.fields:
|
||
|
fval = self.fields[f.name]
|
||
|
if isinstance(fval, (list, dict, set)) and len(fval) == 0:
|
||
|
continue
|
||
|
val = f.i2repr(self, fval)
|
||
|
elif f.name in self.overloaded_fields:
|
||
|
fover = self.overloaded_fields[f.name]
|
||
|
if isinstance(fover, (list, dict, set)) and len(fover) == 0:
|
||
|
continue
|
||
|
val = f.i2repr(self, fover)
|
||
|
else:
|
||
|
continue
|
||
|
if isinstance(f, Emph) or f in conf.emph:
|
||
|
ncol = ct.emph_field_name
|
||
|
vcol = ct.emph_field_value
|
||
|
else:
|
||
|
ncol = ct.field_name
|
||
|
vcol = ct.field_value
|
||
|
|
||
|
s += " %s%s%s" % (ncol(f.name),
|
||
|
ct.punct("="),
|
||
|
vcol(val))
|
||
|
return "%s%s %s %s%s%s" % (ct.punct("<"),
|
||
|
ct.layer_name(self.__class__.__name__),
|
||
|
s,
|
||
|
ct.punct("|"),
|
||
|
repr(self.payload),
|
||
|
ct.punct(">"))
|
||
|
|
||
|
if six.PY2:
|
||
|
def __str__(self):
|
||
|
return self.build()
|
||
|
else:
|
||
|
def __str__(self):
|
||
|
warning("Calling str(pkt) on Python 3 makes no sense!")
|
||
|
return str(self.build())
|
||
|
|
||
|
def __bytes__(self):
|
||
|
return self.build()
|
||
|
|
||
|
def __div__(self, other):
|
||
|
if isinstance(other, Packet):
|
||
|
cloneA = self.copy()
|
||
|
cloneB = other.copy()
|
||
|
cloneA.add_payload(cloneB)
|
||
|
return cloneA
|
||
|
elif isinstance(other, (bytes, str)):
|
||
|
return self / conf.raw_layer(load=other)
|
||
|
else:
|
||
|
return other.__rdiv__(self)
|
||
|
__truediv__ = __div__
|
||
|
|
||
|
def __rdiv__(self, other):
|
||
|
if isinstance(other, (bytes, str)):
|
||
|
return conf.raw_layer(load=other) / self
|
||
|
else:
|
||
|
raise TypeError
|
||
|
__rtruediv__ = __rdiv__
|
||
|
|
||
|
def __mul__(self, other):
|
||
|
if isinstance(other, int):
|
||
|
return [self] * other
|
||
|
else:
|
||
|
raise TypeError
|
||
|
|
||
|
def __rmul__(self, other):
|
||
|
return self.__mul__(other)
|
||
|
|
||
|
def __nonzero__(self):
|
||
|
return True
|
||
|
__bool__ = __nonzero__
|
||
|
|
||
|
def __len__(self):
|
||
|
return len(self.__bytes__())
|
||
|
|
||
|
def copy_field_value(self, fieldname, value):
|
||
|
return self.get_field(fieldname).do_copy(value)
|
||
|
|
||
|
def copy_fields_dict(self, fields):
|
||
|
if fields is None:
|
||
|
return None
|
||
|
return {fname: self.copy_field_value(fname, fval)
|
||
|
for fname, fval in six.iteritems(fields)}
|
||
|
|
||
|
def clear_cache(self):
|
||
|
"""Clear the raw packet cache for the field and all its subfields"""
|
||
|
self.raw_packet_cache = None
|
||
|
for fld, fval in six.iteritems(self.fields):
|
||
|
fld = self.get_field(fld)
|
||
|
if fld.holds_packets:
|
||
|
if isinstance(fval, Packet):
|
||
|
fval.clear_cache()
|
||
|
elif isinstance(fval, list):
|
||
|
for fsubval in fval:
|
||
|
fsubval.clear_cache()
|
||
|
self.payload.clear_cache()
|
||
|
|
||
|
def self_build(self, field_pos_list=None):
|
||
|
"""
|
||
|
Create the default layer regarding fields_desc dict
|
||
|
|
||
|
:param field_pos_list:
|
||
|
"""
|
||
|
if self.raw_packet_cache is not None:
|
||
|
for fname, fval in six.iteritems(self.raw_packet_cache_fields):
|
||
|
if self.getfieldval(fname) != fval:
|
||
|
self.raw_packet_cache = None
|
||
|
self.raw_packet_cache_fields = None
|
||
|
self.wirelen = None
|
||
|
break
|
||
|
if self.raw_packet_cache is not None:
|
||
|
return self.raw_packet_cache
|
||
|
p = b""
|
||
|
for f in self.fields_desc:
|
||
|
val = self.getfieldval(f.name)
|
||
|
if isinstance(val, RawVal):
|
||
|
sval = raw(val)
|
||
|
p += sval
|
||
|
if field_pos_list is not None:
|
||
|
field_pos_list.append((f.name, sval.encode("string_escape"), len(p), len(sval))) # noqa: E501
|
||
|
else:
|
||
|
p = f.addfield(self, p, val)
|
||
|
return p
|
||
|
|
||
|
def do_build_payload(self):
|
||
|
"""
|
||
|
Create the default version of the payload layer
|
||
|
|
||
|
:return: a string of payload layer
|
||
|
"""
|
||
|
return self.payload.do_build()
|
||
|
|
||
|
def do_build(self):
|
||
|
"""
|
||
|
Create the default version of the layer
|
||
|
|
||
|
:return: a string of the packet with the payload
|
||
|
"""
|
||
|
if not self.explicit:
|
||
|
self = next(iter(self))
|
||
|
pkt = self.self_build()
|
||
|
for t in self.post_transforms:
|
||
|
pkt = t(pkt)
|
||
|
pay = self.do_build_payload()
|
||
|
if self.raw_packet_cache is None:
|
||
|
return self.post_build(pkt, pay)
|
||
|
else:
|
||
|
return pkt + pay
|
||
|
|
||
|
def build_padding(self):
|
||
|
return self.payload.build_padding()
|
||
|
|
||
|
def build(self):
|
||
|
"""
|
||
|
Create the current layer
|
||
|
|
||
|
:return: string of the packet with the payload
|
||
|
"""
|
||
|
p = self.do_build()
|
||
|
p += self.build_padding()
|
||
|
p = self.build_done(p)
|
||
|
return p
|
||
|
|
||
|
def post_build(self, pkt, pay):
|
||
|
"""
|
||
|
DEV: called right after the current layer is build.
|
||
|
|
||
|
:param str pkt: the current packet (build by self_buil function)
|
||
|
:param str pay: the packet payload (build by do_build_payload function)
|
||
|
:return: a string of the packet with the payload
|
||
|
"""
|
||
|
return pkt + pay
|
||
|
|
||
|
def build_done(self, p):
|
||
|
return self.payload.build_done(p)
|
||
|
|
||
|
def do_build_ps(self):
|
||
|
p = b""
|
||
|
pl = []
|
||
|
q = b""
|
||
|
for f in self.fields_desc:
|
||
|
if isinstance(f, ConditionalField) and not f._evalcond(self):
|
||
|
continue
|
||
|
p = f.addfield(self, p, self.getfieldval(f.name))
|
||
|
if isinstance(p, bytes):
|
||
|
r = p[len(q):]
|
||
|
q = p
|
||
|
else:
|
||
|
r = b""
|
||
|
pl.append((f, f.i2repr(self, self.getfieldval(f.name)), r))
|
||
|
|
||
|
pkt, lst = self.payload.build_ps(internal=1)
|
||
|
p += pkt
|
||
|
lst.append((self, pl))
|
||
|
|
||
|
return p, lst
|
||
|
|
||
|
def build_ps(self, internal=0):
|
||
|
p, lst = self.do_build_ps()
|
||
|
# if not internal:
|
||
|
# pkt = self
|
||
|
# while pkt.haslayer(conf.padding_layer):
|
||
|
# pkt = pkt.getlayer(conf.padding_layer)
|
||
|
# lst.append( (pkt, [ ("loakjkjd", pkt.load, pkt.load) ] ) )
|
||
|
# p += pkt.load
|
||
|
# pkt = pkt.payload
|
||
|
return p, lst
|
||
|
|
||
|
def canvas_dump(self, layer_shift=0, rebuild=1):
|
||
|
if PYX == 0:
|
||
|
raise ImportError("PyX and its dependencies must be installed")
|
||
|
canvas = pyx.canvas.canvas()
|
||
|
if rebuild:
|
||
|
_, t = self.__class__(raw(self)).build_ps()
|
||
|
else:
|
||
|
_, t = self.build_ps()
|
||
|
YTXT = len(t)
|
||
|
for _, l in t:
|
||
|
YTXT += len(l)
|
||
|
YTXT = float(YTXT)
|
||
|
YDUMP = YTXT
|
||
|
|
||
|
XSTART = 1
|
||
|
XDSTART = 10
|
||
|
y = 0.0
|
||
|
yd = 0.0
|
||
|
XMUL = 0.55
|
||
|
YMUL = 0.4
|
||
|
|
||
|
backcolor = colgen(0.6, 0.8, 1.0, trans=pyx.color.rgb)
|
||
|
forecolor = colgen(0.2, 0.5, 0.8, trans=pyx.color.rgb)
|
||
|
# backcolor=makecol(0.376, 0.729, 0.525, 1.0)
|
||
|
|
||
|
def hexstr(x):
|
||
|
return " ".join("%02x" % orb(c) for c in x)
|
||
|
|
||
|
def make_dump_txt(x, y, txt):
|
||
|
return pyx.text.text(XDSTART + x * XMUL, (YDUMP - y) * YMUL, r"\tt{%s}" % hexstr(txt), [pyx.text.size.Large]) # noqa: E501
|
||
|
|
||
|
def make_box(o):
|
||
|
return pyx.box.rect(o.left(), o.bottom(), o.width(), o.height(), relcenter=(0.5, 0.5)) # noqa: E501
|
||
|
|
||
|
def make_frame(lst):
|
||
|
if len(lst) == 1:
|
||
|
b = lst[0].bbox()
|
||
|
b.enlarge(pyx.unit.u_pt)
|
||
|
return b.path()
|
||
|
else:
|
||
|
fb = lst[0].bbox()
|
||
|
fb.enlarge(pyx.unit.u_pt)
|
||
|
lb = lst[-1].bbox()
|
||
|
lb.enlarge(pyx.unit.u_pt)
|
||
|
if len(lst) == 2 and fb.left() > lb.right():
|
||
|
return pyx.path.path(pyx.path.moveto(fb.right(), fb.top()),
|
||
|
pyx.path.lineto(fb.left(), fb.top()),
|
||
|
pyx.path.lineto(fb.left(), fb.bottom()), # noqa: E501
|
||
|
pyx.path.lineto(fb.right(), fb.bottom()), # noqa: E501
|
||
|
pyx.path.moveto(lb.left(), lb.top()),
|
||
|
pyx.path.lineto(lb.right(), lb.top()),
|
||
|
pyx.path.lineto(lb.right(), lb.bottom()), # noqa: E501
|
||
|
pyx.path.lineto(lb.left(), lb.bottom())) # noqa: E501
|
||
|
else:
|
||
|
# XXX
|
||
|
gb = lst[1].bbox()
|
||
|
if gb != lb:
|
||
|
gb.enlarge(pyx.unit.u_pt)
|
||
|
kb = lst[-2].bbox()
|
||
|
if kb != gb and kb != lb:
|
||
|
kb.enlarge(pyx.unit.u_pt)
|
||
|
return pyx.path.path(pyx.path.moveto(fb.left(), fb.top()),
|
||
|
pyx.path.lineto(fb.right(), fb.top()),
|
||
|
pyx.path.lineto(fb.right(), kb.bottom()), # noqa: E501
|
||
|
pyx.path.lineto(lb.right(), kb.bottom()), # noqa: E501
|
||
|
pyx.path.lineto(lb.right(), lb.bottom()), # noqa: E501
|
||
|
pyx.path.lineto(lb.left(), lb.bottom()), # noqa: E501
|
||
|
pyx.path.lineto(lb.left(), gb.top()),
|
||
|
pyx.path.lineto(fb.left(), gb.top()),
|
||
|
pyx.path.closepath(),)
|
||
|
|
||
|
def make_dump(s, shift=0, y=0, col=None, bkcol=None, large=16):
|
||
|
c = pyx.canvas.canvas()
|
||
|
tlist = []
|
||
|
while s:
|
||
|
dmp, s = s[:large - shift], s[large - shift:]
|
||
|
txt = make_dump_txt(shift, y, dmp)
|
||
|
tlist.append(txt)
|
||
|
shift += len(dmp)
|
||
|
if shift >= 16:
|
||
|
shift = 0
|
||
|
y += 1
|
||
|
if col is None:
|
||
|
col = pyx.color.rgb.red
|
||
|
if bkcol is None:
|
||
|
bkcol = pyx.color.rgb.white
|
||
|
c.stroke(make_frame(tlist), [col, pyx.deco.filled([bkcol]), pyx.style.linewidth.Thick]) # noqa: E501
|
||
|
for txt in tlist:
|
||
|
c.insert(txt)
|
||
|
return c, tlist[-1].bbox(), shift, y
|
||
|
|
||
|
last_shift, last_y = 0, 0.0
|
||
|
while t:
|
||
|
bkcol = next(backcolor)
|
||
|
proto, fields = t.pop()
|
||
|
y += 0.5
|
||
|
pt = pyx.text.text(XSTART, (YTXT - y) * YMUL, r"\font\cmssfont=cmss10\cmssfont{%s}" % tex_escape(proto.name), [pyx.text.size.Large]) # noqa: E501
|
||
|
y += 1
|
||
|
ptbb = pt.bbox()
|
||
|
ptbb.enlarge(pyx.unit.u_pt * 2)
|
||
|
canvas.stroke(ptbb.path(), [pyx.color.rgb.black, pyx.deco.filled([bkcol])]) # noqa: E501
|
||
|
canvas.insert(pt)
|
||
|
for field, fval, fdump in fields:
|
||
|
col = next(forecolor)
|
||
|
ft = pyx.text.text(XSTART, (YTXT - y) * YMUL, r"\font\cmssfont=cmss10\cmssfont{%s}" % tex_escape(field.name)) # noqa: E501
|
||
|
if isinstance(field, BitField):
|
||
|
fsize = '%sb' % field.size
|
||
|
else:
|
||
|
fsize = '%sB' % len(fdump)
|
||
|
if (hasattr(field, 'field') and
|
||
|
'LE' in field.field.__class__.__name__[:3] or
|
||
|
'LE' in field.__class__.__name__[:3]):
|
||
|
fsize = r'$\scriptstyle\langle$' + fsize
|
||
|
st = pyx.text.text(XSTART + 3.4, (YTXT - y) * YMUL, r"\font\cmbxfont=cmssbx10 scaled 600\cmbxfont{%s}" % fsize, [pyx.text.halign.boxright]) # noqa: E501
|
||
|
if isinstance(fval, str):
|
||
|
if len(fval) > 18:
|
||
|
fval = fval[:18] + "[...]"
|
||
|
else:
|
||
|
fval = ""
|
||
|
vt = pyx.text.text(XSTART + 3.5, (YTXT - y) * YMUL, r"\font\cmssfont=cmss10\cmssfont{%s}" % tex_escape(fval)) # noqa: E501
|
||
|
y += 1.0
|
||
|
if fdump:
|
||
|
dt, target, last_shift, last_y = make_dump(fdump, last_shift, last_y, col, bkcol) # noqa: E501
|
||
|
|
||
|
dtb = target
|
||
|
vtb = vt.bbox()
|
||
|
bxvt = make_box(vtb)
|
||
|
bxdt = make_box(dtb)
|
||
|
dtb.enlarge(pyx.unit.u_pt)
|
||
|
try:
|
||
|
if yd < 0:
|
||
|
cnx = pyx.connector.curve(bxvt, bxdt, absangle1=0, absangle2=-90) # noqa: E501
|
||
|
else:
|
||
|
cnx = pyx.connector.curve(bxvt, bxdt, absangle1=0, absangle2=90) # noqa: E501
|
||
|
except Exception:
|
||
|
pass
|
||
|
else:
|
||
|
canvas.stroke(cnx, [pyx.style.linewidth.thin, pyx.deco.earrow.small, col]) # noqa: E501
|
||
|
|
||
|
canvas.insert(dt)
|
||
|
|
||
|
canvas.insert(ft)
|
||
|
canvas.insert(st)
|
||
|
canvas.insert(vt)
|
||
|
last_y += layer_shift
|
||
|
|
||
|
return canvas
|
||
|
|
||
|
def extract_padding(self, s):
|
||
|
"""
|
||
|
DEV: to be overloaded to extract current layer's padding.
|
||
|
|
||
|
:param str s: the current layer
|
||
|
:return: a couple of strings (actual layer, padding)
|
||
|
"""
|
||
|
return s, None
|
||
|
|
||
|
def post_dissect(self, s):
|
||
|
"""DEV: is called right after the current layer has been dissected"""
|
||
|
return s
|
||
|
|
||
|
def pre_dissect(self, s):
|
||
|
"""DEV: is called right before the current layer is dissected"""
|
||
|
return s
|
||
|
|
||
|
def do_dissect(self, s):
|
||
|
_raw = s
|
||
|
self.raw_packet_cache_fields = {}
|
||
|
for f in self.fields_desc:
|
||
|
if not s:
|
||
|
break
|
||
|
s, fval = f.getfield(self, 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
|
||
|
self.raw_packet_cache = _raw[:-len(s)] if s else _raw
|
||
|
self.explicit = 1
|
||
|
return s
|
||
|
|
||
|
def do_dissect_payload(self, s):
|
||
|
"""
|
||
|
Perform the dissection of the layer's payload
|
||
|
|
||
|
:param str s: the raw layer
|
||
|
"""
|
||
|
if s:
|
||
|
cls = self.guess_payload_class(s)
|
||
|
try:
|
||
|
p = cls(s, _internal=1, _underlayer=self)
|
||
|
except KeyboardInterrupt:
|
||
|
raise
|
||
|
except Exception:
|
||
|
if conf.debug_dissector:
|
||
|
if issubtype(cls, Packet):
|
||
|
log_runtime.error("%s dissector failed" % cls.__name__)
|
||
|
else:
|
||
|
log_runtime.error("%s.guess_payload_class() returned [%s]" % (self.__class__.__name__, repr(cls))) # noqa: E501
|
||
|
if cls is not None:
|
||
|
raise
|
||
|
p = conf.raw_layer(s, _internal=1, _underlayer=self)
|
||
|
self.add_payload(p)
|
||
|
|
||
|
def dissect(self, s):
|
||
|
s = self.pre_dissect(s)
|
||
|
|
||
|
s = self.do_dissect(s)
|
||
|
|
||
|
s = self.post_dissect(s)
|
||
|
|
||
|
payl, pad = self.extract_padding(s)
|
||
|
self.do_dissect_payload(payl)
|
||
|
if pad and conf.padding:
|
||
|
self.add_payload(conf.padding_layer(pad))
|
||
|
|
||
|
def guess_payload_class(self, payload):
|
||
|
"""
|
||
|
DEV: Guesses the next payload class from layer bonds.
|
||
|
Can be overloaded to use a different mechanism.
|
||
|
|
||
|
:param str payload: the layer's payload
|
||
|
:return: the payload class
|
||
|
"""
|
||
|
for t in self.aliastypes:
|
||
|
for fval, cls in t.payload_guess:
|
||
|
try:
|
||
|
for k, v in six.iteritems(fval):
|
||
|
# case where v is a function
|
||
|
if callable(v):
|
||
|
if not v(self.getfieldval(k)):
|
||
|
break
|
||
|
elif v != self.getfieldval(k):
|
||
|
break
|
||
|
else:
|
||
|
return cls
|
||
|
except AttributeError:
|
||
|
pass
|
||
|
return self.default_payload_class(payload)
|
||
|
|
||
|
def default_payload_class(self, payload):
|
||
|
"""
|
||
|
DEV: Returns the default payload class if nothing has been found by the
|
||
|
guess_payload_class() method.
|
||
|
|
||
|
:param str payload: the layer's payload
|
||
|
:return: the default payload class define inside the configuration file
|
||
|
"""
|
||
|
return conf.raw_layer
|
||
|
|
||
|
def hide_defaults(self):
|
||
|
"""Removes fields' values that are the same as default values."""
|
||
|
# use list(): self.fields is modified in the loop
|
||
|
for k, v in list(six.iteritems(self.fields)):
|
||
|
v = self.fields[k]
|
||
|
if k in self.default_fields:
|
||
|
if self.default_fields[k] == v:
|
||
|
del self.fields[k]
|
||
|
self.payload.hide_defaults()
|
||
|
|
||
|
def update_sent_time(self, time):
|
||
|
"""Use by clone_with to share the sent_time value"""
|
||
|
pass
|
||
|
|
||
|
def clone_with(self, payload=None, share_time=False, **kargs):
|
||
|
pkt = self.__class__()
|
||
|
pkt.explicit = 1
|
||
|
pkt.fields = kargs
|
||
|
pkt.default_fields = self.copy_fields_dict(self.default_fields)
|
||
|
pkt.overloaded_fields = self.overloaded_fields.copy()
|
||
|
pkt.time = self.time
|
||
|
pkt.underlayer = self.underlayer
|
||
|
pkt.post_transforms = self.post_transforms
|
||
|
pkt.raw_packet_cache = self.raw_packet_cache
|
||
|
pkt.raw_packet_cache_fields = self.copy_fields_dict(
|
||
|
self.raw_packet_cache_fields
|
||
|
)
|
||
|
pkt.wirelen = self.wirelen
|
||
|
if payload is not None:
|
||
|
pkt.add_payload(payload)
|
||
|
if share_time:
|
||
|
# This binds the subpacket .sent_time to this layer
|
||
|
def _up_time(x, parent=self):
|
||
|
parent.sent_time = x
|
||
|
pkt.update_sent_time = _up_time
|
||
|
return pkt
|
||
|
|
||
|
def __iter__(self):
|
||
|
"""Iterates through all sub-packets generated by this Packet."""
|
||
|
# We use __iterlen__ as low as possible, to lower processing time
|
||
|
def loop(todo, done, self=self):
|
||
|
if todo:
|
||
|
eltname = todo.pop()
|
||
|
elt = self.getfieldval(eltname)
|
||
|
if not isinstance(elt, Gen):
|
||
|
if self.get_field(eltname).islist:
|
||
|
elt = SetGen([elt])
|
||
|
else:
|
||
|
elt = SetGen(elt)
|
||
|
for e in elt:
|
||
|
done[eltname] = e
|
||
|
for x in loop(todo[:], done):
|
||
|
yield x
|
||
|
else:
|
||
|
if isinstance(self.payload, NoPayload):
|
||
|
payloads = SetGen([None])
|
||
|
else:
|
||
|
payloads = self.payload
|
||
|
share_time = False
|
||
|
if self.fields == done and payloads.__iterlen__() == 1:
|
||
|
# In this case, the packets are identical. Let's bind
|
||
|
# their sent_time attribute for sending purpose
|
||
|
share_time = True
|
||
|
for payl in payloads:
|
||
|
# Let's make sure subpackets are consistent
|
||
|
done2 = done.copy()
|
||
|
for k in done2:
|
||
|
if isinstance(done2[k], VolatileValue):
|
||
|
done2[k] = done2[k]._fix()
|
||
|
pkt = self.clone_with(payload=payl, share_time=share_time,
|
||
|
**done2)
|
||
|
yield pkt
|
||
|
|
||
|
if self.explicit or self.raw_packet_cache is not None:
|
||
|
todo = []
|
||
|
done = self.fields
|
||
|
else:
|
||
|
todo = [k for (k, v) in itertools.chain(six.iteritems(self.default_fields), # noqa: E501
|
||
|
six.iteritems(self.overloaded_fields)) # noqa: E501
|
||
|
if isinstance(v, VolatileValue)] + list(self.fields)
|
||
|
done = {}
|
||
|
return loop(todo, done)
|
||
|
|
||
|
def __iterlen__(self):
|
||
|
"""Predict the total length of the iterator"""
|
||
|
fields = [key for (key, val) in itertools.chain(six.iteritems(self.default_fields), # noqa: E501
|
||
|
six.iteritems(self.overloaded_fields))
|
||
|
if isinstance(val, VolatileValue)] + list(self.fields)
|
||
|
length = 1
|
||
|
|
||
|
def is_valid_gen_tuple(x):
|
||
|
if not isinstance(x, tuple):
|
||
|
return False
|
||
|
return len(x) == 2 and all(isinstance(z, int) for z in x)
|
||
|
|
||
|
for field in fields:
|
||
|
fld, val = self.getfield_and_val(field)
|
||
|
if hasattr(val, "__iterlen__"):
|
||
|
length *= val.__iterlen__()
|
||
|
elif is_valid_gen_tuple(val):
|
||
|
length *= (val[1] - val[0] + 1)
|
||
|
elif isinstance(val, list) and not fld.islist:
|
||
|
len2 = 0
|
||
|
for x in val:
|
||
|
if hasattr(x, "__iterlen__"):
|
||
|
len2 += x.__iterlen__()
|
||
|
elif is_valid_gen_tuple(x):
|
||
|
len2 += (x[1] - x[0] + 1)
|
||
|
elif isinstance(x, list):
|
||
|
len2 += len(x)
|
||
|
else:
|
||
|
len2 += 1
|
||
|
length *= len2 or 1
|
||
|
if not isinstance(self.payload, NoPayload):
|
||
|
return length * self.payload.__iterlen__()
|
||
|
return length
|
||
|
|
||
|
def iterpayloads(self):
|
||
|
"""Used to iter through the paylods of a Packet.
|
||
|
Useful for DNS or 802.11 for instance.
|
||
|
"""
|
||
|
yield self
|
||
|
current = self
|
||
|
while current.payload:
|
||
|
current = current.payload
|
||
|
yield current
|
||
|
|
||
|
def __gt__(self, other):
|
||
|
"""True if other is an answer from self (self ==> other)."""
|
||
|
if isinstance(other, Packet):
|
||
|
return other < self
|
||
|
elif isinstance(other, bytes):
|
||
|
return 1
|
||
|
else:
|
||
|
raise TypeError((self, other))
|
||
|
|
||
|
def __lt__(self, other):
|
||
|
"""True if self is an answer from other (other ==> self)."""
|
||
|
if isinstance(other, Packet):
|
||
|
return self.answers(other)
|
||
|
elif isinstance(other, bytes):
|
||
|
return 1
|
||
|
else:
|
||
|
raise TypeError((self, other))
|
||
|
|
||
|
def __eq__(self, other):
|
||
|
if not isinstance(other, self.__class__):
|
||
|
return False
|
||
|
for f in self.fields_desc:
|
||
|
if f not in other.fields_desc:
|
||
|
return False
|
||
|
if self.getfieldval(f.name) != other.getfieldval(f.name):
|
||
|
return False
|
||
|
return self.payload == other.payload
|
||
|
|
||
|
def __ne__(self, other):
|
||
|
return not self.__eq__(other)
|
||
|
|
||
|
__hash__ = None
|
||
|
|
||
|
def hashret(self):
|
||
|
"""DEV: returns a string that has the same value for a request
|
||
|
and its answer."""
|
||
|
return self.payload.hashret()
|
||
|
|
||
|
def answers(self, other):
|
||
|
"""DEV: true if self is an answer from other"""
|
||
|
if other.__class__ == self.__class__:
|
||
|
return self.payload.answers(other.payload)
|
||
|
return 0
|
||
|
|
||
|
def layers(self):
|
||
|
"""returns a list of layer classes (including subclasses) in this packet""" # noqa: E501
|
||
|
layers = []
|
||
|
lyr = self
|
||
|
while lyr:
|
||
|
layers.append(lyr.__class__)
|
||
|
lyr = lyr.payload.getlayer(0, _subclass=True)
|
||
|
return layers
|
||
|
|
||
|
def haslayer(self, cls, _subclass=None):
|
||
|
"""
|
||
|
true if self has a layer that is an instance of cls.
|
||
|
Superseded by "cls in self" syntax.
|
||
|
"""
|
||
|
if _subclass is None:
|
||
|
_subclass = self.match_subclass or None
|
||
|
if _subclass:
|
||
|
match = issubtype
|
||
|
else:
|
||
|
match = lambda cls1, cls2: cls1 == cls2
|
||
|
if cls is None or match(self.__class__, cls) \
|
||
|
or cls in [self.__class__.__name__, self._name]:
|
||
|
return True
|
||
|
for f in self.packetfields:
|
||
|
fvalue_gen = self.getfieldval(f.name)
|
||
|
if fvalue_gen is None:
|
||
|
continue
|
||
|
if not f.islist:
|
||
|
fvalue_gen = SetGen(fvalue_gen, _iterpacket=0)
|
||
|
for fvalue in fvalue_gen:
|
||
|
if isinstance(fvalue, Packet):
|
||
|
ret = fvalue.haslayer(cls, _subclass=_subclass)
|
||
|
if ret:
|
||
|
return ret
|
||
|
return self.payload.haslayer(cls, _subclass=_subclass)
|
||
|
|
||
|
def getlayer(self, cls, nb=1, _track=None, _subclass=None, **flt):
|
||
|
"""Return the nb^th layer that is an instance of cls, matching flt
|
||
|
values.
|
||
|
"""
|
||
|
if _subclass is None:
|
||
|
_subclass = self.match_subclass or None
|
||
|
if _subclass:
|
||
|
match = issubtype
|
||
|
else:
|
||
|
match = lambda cls1, cls2: cls1 == cls2
|
||
|
if isinstance(cls, int):
|
||
|
nb = cls + 1
|
||
|
cls = None
|
||
|
if isinstance(cls, str) and "." in cls:
|
||
|
ccls, fld = cls.split(".", 1)
|
||
|
else:
|
||
|
ccls, fld = cls, None
|
||
|
if cls is None or match(self.__class__, cls) \
|
||
|
or ccls in [self.__class__.__name__, self._name]:
|
||
|
if all(self.getfieldval(fldname) == fldvalue
|
||
|
for fldname, fldvalue in six.iteritems(flt)):
|
||
|
if nb == 1:
|
||
|
if fld is None:
|
||
|
return self
|
||
|
else:
|
||
|
return self.getfieldval(fld)
|
||
|
else:
|
||
|
nb -= 1
|
||
|
for f in self.packetfields:
|
||
|
fvalue_gen = self.getfieldval(f.name)
|
||
|
if fvalue_gen is None:
|
||
|
continue
|
||
|
if not f.islist:
|
||
|
fvalue_gen = SetGen(fvalue_gen, _iterpacket=0)
|
||
|
for fvalue in fvalue_gen:
|
||
|
if isinstance(fvalue, Packet):
|
||
|
track = []
|
||
|
ret = fvalue.getlayer(cls, nb=nb, _track=track,
|
||
|
_subclass=_subclass, **flt)
|
||
|
if ret is not None:
|
||
|
return ret
|
||
|
nb = track[0]
|
||
|
return self.payload.getlayer(cls, nb=nb, _track=_track,
|
||
|
_subclass=_subclass, **flt)
|
||
|
|
||
|
def firstlayer(self):
|
||
|
q = self
|
||
|
while q.underlayer is not None:
|
||
|
q = q.underlayer
|
||
|
return q
|
||
|
|
||
|
def __getitem__(self, cls):
|
||
|
if isinstance(cls, slice):
|
||
|
lname = cls.start
|
||
|
if cls.stop:
|
||
|
ret = self.getlayer(cls.start, nb=cls.stop, **(cls.step or {}))
|
||
|
else:
|
||
|
ret = self.getlayer(cls.start, **(cls.step or {}))
|
||
|
else:
|
||
|
lname = cls
|
||
|
ret = self.getlayer(cls)
|
||
|
if ret is None:
|
||
|
if isinstance(lname, Packet_metaclass):
|
||
|
lname = lname.__name__
|
||
|
elif not isinstance(lname, bytes):
|
||
|
lname = repr(lname)
|
||
|
raise IndexError("Layer [%s] not found" % lname)
|
||
|
return ret
|
||
|
|
||
|
def __delitem__(self, cls):
|
||
|
del(self[cls].underlayer.payload)
|
||
|
|
||
|
def __setitem__(self, cls, val):
|
||
|
self[cls].underlayer.payload = val
|
||
|
|
||
|
def __contains__(self, cls):
|
||
|
""""cls in self" returns true if self has a layer which is an instance of cls.""" # noqa: E501
|
||
|
return self.haslayer(cls)
|
||
|
|
||
|
def route(self):
|
||
|
return self.payload.route()
|
||
|
|
||
|
def fragment(self, *args, **kargs):
|
||
|
return self.payload.fragment(*args, **kargs)
|
||
|
|
||
|
def display(self, *args, **kargs): # Deprecated. Use show()
|
||
|
"""Deprecated. Use show() method."""
|
||
|
self.show(*args, **kargs)
|
||
|
|
||
|
def _show_or_dump(self, dump=False, indent=3, lvl="", label_lvl="", first_call=True): # noqa: E501
|
||
|
"""
|
||
|
Internal method that shows or dumps a hierarchical view of a packet.
|
||
|
Called by show.
|
||
|
|
||
|
:param dump: determine if it prints or returns the string value
|
||
|
:param int indent: the size of indentation for each layer
|
||
|
:param str lvl: additional information about the layer lvl
|
||
|
:param str label_lvl: additional information about the layer fields
|
||
|
:param first_call: determine if the current function is the first
|
||
|
:return: return a hierarchical view if dump, else print it
|
||
|
"""
|
||
|
|
||
|
if dump:
|
||
|
from scapy.themes import AnsiColorTheme
|
||
|
ct = AnsiColorTheme() # No color for dump output
|
||
|
else:
|
||
|
ct = conf.color_theme
|
||
|
s = "%s%s %s %s \n" % (label_lvl,
|
||
|
ct.punct("###["),
|
||
|
ct.layer_name(self.name),
|
||
|
ct.punct("]###"))
|
||
|
for f in self.fields_desc:
|
||
|
if isinstance(f, ConditionalField) and not f._evalcond(self):
|
||
|
continue
|
||
|
if isinstance(f, Emph) or f in conf.emph:
|
||
|
ncol = ct.emph_field_name
|
||
|
vcol = ct.emph_field_value
|
||
|
else:
|
||
|
ncol = ct.field_name
|
||
|
vcol = ct.field_value
|
||
|
fvalue = self.getfieldval(f.name)
|
||
|
if isinstance(fvalue, Packet) or (f.islist and f.holds_packets and isinstance(fvalue, list)): # noqa: E501
|
||
|
s += "%s \\%-10s\\\n" % (label_lvl + lvl, ncol(f.name))
|
||
|
fvalue_gen = SetGen(fvalue, _iterpacket=0)
|
||
|
for fvalue in fvalue_gen:
|
||
|
s += fvalue._show_or_dump(dump=dump, indent=indent, label_lvl=label_lvl + lvl + " |", first_call=False) # noqa: E501
|
||
|
else:
|
||
|
begn = "%s %-10s%s " % (label_lvl + lvl,
|
||
|
ncol(f.name),
|
||
|
ct.punct("="),)
|
||
|
reprval = f.i2repr(self, fvalue)
|
||
|
if isinstance(reprval, str):
|
||
|
reprval = reprval.replace("\n", "\n" + " " * (len(label_lvl) + # noqa: E501
|
||
|
len(lvl) +
|
||
|
len(f.name) +
|
||
|
4))
|
||
|
s += "%s%s\n" % (begn, vcol(reprval))
|
||
|
if self.payload:
|
||
|
s += self.payload._show_or_dump(dump=dump, indent=indent, lvl=lvl + (" " * indent * self.show_indent), label_lvl=label_lvl, first_call=False) # noqa: E501
|
||
|
|
||
|
if first_call and not dump:
|
||
|
print(s)
|
||
|
else:
|
||
|
return s
|
||
|
|
||
|
def show(self, dump=False, indent=3, lvl="", label_lvl=""):
|
||
|
"""
|
||
|
Prints or returns (when "dump" is true) a hierarchical view of the
|
||
|
packet.
|
||
|
|
||
|
:param dump: determine if it prints or returns the string value
|
||
|
:param int indent: the size of indentation for each layer
|
||
|
:param str lvl: additional information about the layer lvl
|
||
|
:param str label_lvl: additional information about the layer fields
|
||
|
:return: return a hierarchical view if dump, else print it
|
||
|
"""
|
||
|
return self._show_or_dump(dump, indent, lvl, label_lvl)
|
||
|
|
||
|
def show2(self, dump=False, indent=3, lvl="", label_lvl=""):
|
||
|
"""
|
||
|
Prints or returns (when "dump" is true) a hierarchical view of an
|
||
|
assembled version of the packet, so that automatic fields are
|
||
|
calculated (checksums, etc.)
|
||
|
|
||
|
:param dump: determine if it prints or returns the string value
|
||
|
:param int indent: the size of indentation for each layer
|
||
|
:param str lvl: additional information about the layer lvl
|
||
|
:param str label_lvl: additional information about the layer fields
|
||
|
:return: return a hierarchical view if dump, else print it
|
||
|
"""
|
||
|
return self.__class__(raw(self)).show(dump, indent, lvl, label_lvl)
|
||
|
|
||
|
def sprintf(self, fmt, relax=1):
|
||
|
"""
|
||
|
sprintf(format, [relax=1]) -> str
|
||
|
|
||
|
Where format is a string that can include directives. A directive
|
||
|
begins and ends by % and has the following format:
|
||
|
``%[fmt[r],][cls[:nb].]field%``
|
||
|
|
||
|
:param fmt: is a classic printf directive, "r" can be appended for raw
|
||
|
substitution:
|
||
|
(ex: IP.flags=0x18 instead of SA), nb is the number of the layer
|
||
|
(ex: for IP/IP packets, IP:2.src is the src of the upper IP layer).
|
||
|
Special case : "%.time%" is the creation time.
|
||
|
Ex::
|
||
|
|
||
|
p.sprintf(
|
||
|
"%.time% %-15s,IP.src% -> %-15s,IP.dst% %IP.chksum% "
|
||
|
"%03xr,IP.proto% %r,TCP.flags%"
|
||
|
)
|
||
|
|
||
|
Moreover, the format string can include conditional statements. A
|
||
|
conditional statement looks like : {layer:string} where layer is a
|
||
|
layer name, and string is the string to insert in place of the
|
||
|
condition if it is true, i.e. if layer is present. If layer is
|
||
|
preceded by a "!", the result is inverted. Conditions can be
|
||
|
imbricated. A valid statement can be::
|
||
|
|
||
|
p.sprintf("This is a{TCP: TCP}{UDP: UDP}{ICMP:n ICMP} packet")
|
||
|
p.sprintf("{IP:%IP.dst% {ICMP:%ICMP.type%}{TCP:%TCP.dport%}}")
|
||
|
|
||
|
A side effect is that, to obtain "{" and "}" characters, you must use
|
||
|
"%(" and "%)".
|
||
|
"""
|
||
|
|
||
|
escape = {"%": "%",
|
||
|
"(": "{",
|
||
|
")": "}"}
|
||
|
|
||
|
# Evaluate conditions
|
||
|
while "{" in fmt:
|
||
|
i = fmt.rindex("{")
|
||
|
j = fmt[i + 1:].index("}")
|
||
|
cond = fmt[i + 1:i + j + 1]
|
||
|
k = cond.find(":")
|
||
|
if k < 0:
|
||
|
raise Scapy_Exception("Bad condition in format string: [%s] (read sprintf doc!)" % cond) # noqa: E501
|
||
|
cond, format_ = cond[:k], cond[k + 1:]
|
||
|
res = False
|
||
|
if cond[0] == "!":
|
||
|
res = True
|
||
|
cond = cond[1:]
|
||
|
if self.haslayer(cond):
|
||
|
res = not res
|
||
|
if not res:
|
||
|
format_ = ""
|
||
|
fmt = fmt[:i] + format_ + fmt[i + j + 2:]
|
||
|
|
||
|
# Evaluate directives
|
||
|
s = ""
|
||
|
while "%" in fmt:
|
||
|
i = fmt.index("%")
|
||
|
s += fmt[:i]
|
||
|
fmt = fmt[i + 1:]
|
||
|
if fmt and fmt[0] in escape:
|
||
|
s += escape[fmt[0]]
|
||
|
fmt = fmt[1:]
|
||
|
continue
|
||
|
try:
|
||
|
i = fmt.index("%")
|
||
|
sfclsfld = fmt[:i]
|
||
|
fclsfld = sfclsfld.split(",")
|
||
|
if len(fclsfld) == 1:
|
||
|
f = "s"
|
||
|
clsfld = fclsfld[0]
|
||
|
elif len(fclsfld) == 2:
|
||
|
f, clsfld = fclsfld
|
||
|
else:
|
||
|
raise Scapy_Exception
|
||
|
if "." in clsfld:
|
||
|
cls, fld = clsfld.split(".")
|
||
|
else:
|
||
|
cls = self.__class__.__name__
|
||
|
fld = clsfld
|
||
|
num = 1
|
||
|
if ":" in cls:
|
||
|
cls, num = cls.split(":")
|
||
|
num = int(num)
|
||
|
fmt = fmt[i + 1:]
|
||
|
except Exception:
|
||
|
raise Scapy_Exception("Bad format string [%%%s%s]" % (fmt[:25], fmt[25:] and "...")) # noqa: E501
|
||
|
else:
|
||
|
if fld == "time":
|
||
|
val = time.strftime("%H:%M:%S.%%06i", time.localtime(self.time)) % int((self.time - int(self.time)) * 1000000) # noqa: E501
|
||
|
elif cls == self.__class__.__name__ and hasattr(self, fld):
|
||
|
if num > 1:
|
||
|
val = self.payload.sprintf("%%%s,%s:%s.%s%%" % (f, cls, num - 1, fld), relax) # noqa: E501
|
||
|
f = "s"
|
||
|
elif f[-1] == "r": # Raw field value
|
||
|
val = getattr(self, fld)
|
||
|
f = f[:-1]
|
||
|
if not f:
|
||
|
f = "s"
|
||
|
else:
|
||
|
val = getattr(self, fld)
|
||
|
if fld in self.fieldtype:
|
||
|
val = self.fieldtype[fld].i2repr(self, val)
|
||
|
else:
|
||
|
val = self.payload.sprintf("%%%s%%" % sfclsfld, relax)
|
||
|
f = "s"
|
||
|
s += ("%" + f) % val
|
||
|
|
||
|
s += fmt
|
||
|
return s
|
||
|
|
||
|
def mysummary(self):
|
||
|
"""DEV: can be overloaded to return a string that summarizes the layer.
|
||
|
Only one mysummary() is used in a whole packet summary: the one of the upper layer, # noqa: E501
|
||
|
except if a mysummary() also returns (as a couple) a list of layers whose # noqa: E501
|
||
|
mysummary() must be called if they are present."""
|
||
|
return ""
|
||
|
|
||
|
def _do_summary(self):
|
||
|
found, s, needed = self.payload._do_summary()
|
||
|
ret = ""
|
||
|
if not found or self.__class__ in needed:
|
||
|
ret = self.mysummary()
|
||
|
if isinstance(ret, tuple):
|
||
|
ret, n = ret
|
||
|
needed += n
|
||
|
if ret or needed:
|
||
|
found = 1
|
||
|
if not ret:
|
||
|
ret = self.__class__.__name__ if self.show_summary else ""
|
||
|
if self.__class__ in conf.emph:
|
||
|
impf = []
|
||
|
for f in self.fields_desc:
|
||
|
if f in conf.emph:
|
||
|
impf.append("%s=%s" % (f.name, f.i2repr(self, self.getfieldval(f.name)))) # noqa: E501
|
||
|
ret = "%s [%s]" % (ret, " ".join(impf))
|
||
|
if ret and s:
|
||
|
ret = "%s / %s" % (ret, s)
|
||
|
else:
|
||
|
ret = "%s%s" % (ret, s)
|
||
|
return found, ret, needed
|
||
|
|
||
|
def summary(self, intern=0):
|
||
|
"""Prints a one line summary of a packet."""
|
||
|
return self._do_summary()[1]
|
||
|
|
||
|
def lastlayer(self, layer=None):
|
||
|
"""Returns the uppest layer of the packet"""
|
||
|
return self.payload.lastlayer(self)
|
||
|
|
||
|
def decode_payload_as(self, cls):
|
||
|
"""Reassembles the payload and decode it using another packet class"""
|
||
|
s = raw(self.payload)
|
||
|
self.payload = cls(s, _internal=1, _underlayer=self)
|
||
|
pp = self
|
||
|
while pp.underlayer is not None:
|
||
|
pp = pp.underlayer
|
||
|
self.payload.dissection_done(pp)
|
||
|
|
||
|
def command(self):
|
||
|
"""
|
||
|
Returns a string representing the command you have to type to
|
||
|
obtain the same packet
|
||
|
"""
|
||
|
f = []
|
||
|
for fn, fv in six.iteritems(self.fields):
|
||
|
fld = self.get_field(fn)
|
||
|
if isinstance(fv, (list, dict, set)) and len(fv) == 0:
|
||
|
continue
|
||
|
if isinstance(fv, Packet):
|
||
|
fv = fv.command()
|
||
|
elif fld.islist and fld.holds_packets and isinstance(fv, list):
|
||
|
fv = "[%s]" % ",".join(map(Packet.command, fv))
|
||
|
elif isinstance(fld, FlagsField):
|
||
|
fv = int(fv)
|
||
|
else:
|
||
|
fv = repr(fv)
|
||
|
f.append("%s=%s" % (fn, fv))
|
||
|
c = "%s(%s)" % (self.__class__.__name__, ", ".join(f))
|
||
|
pc = self.payload.command()
|
||
|
if pc:
|
||
|
c += "/" + pc
|
||
|
return c
|
||
|
|
||
|
def convert_to(self, other_cls, **kwargs):
|
||
|
"""Converts this Packet to another type.
|
||
|
|
||
|
This is not guaranteed to be a lossless process.
|
||
|
|
||
|
By default, this only implements conversion to ``Raw``.
|
||
|
|
||
|
:param other_cls: Reference to a Packet class to convert to.
|
||
|
:type other_cls: Type[scapy.packet.Packet]
|
||
|
:return: Converted form of the packet.
|
||
|
:rtype: other_cls
|
||
|
:raises TypeError: When conversion is not possible
|
||
|
"""
|
||
|
if not issubtype(other_cls, Packet):
|
||
|
raise TypeError("{} must implement Packet".format(other_cls))
|
||
|
|
||
|
if other_cls is Raw:
|
||
|
return Raw(raw(self))
|
||
|
|
||
|
if "_internal" not in kwargs:
|
||
|
return other_cls.convert_packet(self, _internal=True, **kwargs)
|
||
|
|
||
|
raise TypeError("Cannot convert {} to {}".format(
|
||
|
type(self).__name__, other_cls.__name__))
|
||
|
|
||
|
@classmethod
|
||
|
def convert_packet(cls, pkt, **kwargs):
|
||
|
"""Converts another packet to be this type.
|
||
|
|
||
|
This is not guaranteed to be a lossless process.
|
||
|
|
||
|
:param pkt: The packet to convert.
|
||
|
:type pkt: scapy.packet.Packet
|
||
|
:return: Converted form of the packet.
|
||
|
:rtype: cls
|
||
|
:raises TypeError: When conversion is not possible
|
||
|
"""
|
||
|
if not isinstance(pkt, Packet):
|
||
|
raise TypeError("Can only convert Packets")
|
||
|
|
||
|
if "_internal" not in kwargs:
|
||
|
return pkt.convert_to(cls, _internal=True, **kwargs)
|
||
|
|
||
|
raise TypeError("Cannot convert {} to {}".format(
|
||
|
type(pkt).__name__, cls.__name__))
|
||
|
|
||
|
@classmethod
|
||
|
def convert_packets(cls, pkts, **kwargs):
|
||
|
"""Converts many packets to this type.
|
||
|
|
||
|
This is implemented as a generator.
|
||
|
|
||
|
See ``Packet.convert_packet``.
|
||
|
"""
|
||
|
for pkt in pkts:
|
||
|
yield cls.convert_packet(pkt, **kwargs)
|
||
|
|
||
|
|
||
|
class NoPayload(Packet):
|
||
|
def __new__(cls, *args, **kargs):
|
||
|
singl = cls.__dict__.get("__singl__")
|
||
|
if singl is None:
|
||
|
cls.__singl__ = singl = Packet.__new__(cls)
|
||
|
Packet.__init__(singl)
|
||
|
return singl
|
||
|
|
||
|
def __init__(self, *args, **kargs):
|
||
|
pass
|
||
|
|
||
|
def dissection_done(self, pkt):
|
||
|
return
|
||
|
|
||
|
def add_payload(self, payload):
|
||
|
raise Scapy_Exception("Can't add payload to NoPayload instance")
|
||
|
|
||
|
def remove_payload(self):
|
||
|
pass
|
||
|
|
||
|
def add_underlayer(self, underlayer):
|
||
|
pass
|
||
|
|
||
|
def remove_underlayer(self, other):
|
||
|
pass
|
||
|
|
||
|
def copy(self):
|
||
|
return self
|
||
|
|
||
|
def clear_cache(self):
|
||
|
pass
|
||
|
|
||
|
def __repr__(self):
|
||
|
return ""
|
||
|
|
||
|
def __str__(self):
|
||
|
return ""
|
||
|
|
||
|
def __bytes__(self):
|
||
|
return b""
|
||
|
|
||
|
def __nonzero__(self):
|
||
|
return False
|
||
|
__bool__ = __nonzero__
|
||
|
|
||
|
def do_build(self):
|
||
|
return b""
|
||
|
|
||
|
def build(self):
|
||
|
return b""
|
||
|
|
||
|
def build_padding(self):
|
||
|
return b""
|
||
|
|
||
|
def build_done(self, p):
|
||
|
return p
|
||
|
|
||
|
def build_ps(self, internal=0):
|
||
|
return b"", []
|
||
|
|
||
|
def getfieldval(self, attr):
|
||
|
raise AttributeError(attr)
|
||
|
|
||
|
def getfield_and_val(self, attr):
|
||
|
raise AttributeError(attr)
|
||
|
|
||
|
def setfieldval(self, attr, val):
|
||
|
raise AttributeError(attr)
|
||
|
|
||
|
def delfieldval(self, attr):
|
||
|
raise AttributeError(attr)
|
||
|
|
||
|
def hide_defaults(self):
|
||
|
pass
|
||
|
|
||
|
def __iter__(self):
|
||
|
return iter([])
|
||
|
|
||
|
def __eq__(self, other):
|
||
|
if isinstance(other, NoPayload):
|
||
|
return True
|
||
|
return False
|
||
|
|
||
|
def hashret(self):
|
||
|
return b""
|
||
|
|
||
|
def answers(self, other):
|
||
|
return isinstance(other, NoPayload) or isinstance(other, conf.padding_layer) # noqa: E501
|
||
|
|
||
|
def haslayer(self, cls, _subclass=None):
|
||
|
return 0
|
||
|
|
||
|
def getlayer(self, cls, nb=1, _track=None, **flt):
|
||
|
if _track is not None:
|
||
|
_track.append(nb)
|
||
|
return None
|
||
|
|
||
|
def fragment(self, *args, **kargs):
|
||
|
raise Scapy_Exception("cannot fragment this packet")
|
||
|
|
||
|
def show(self, indent=3, lvl="", label_lvl=""):
|
||
|
pass
|
||
|
|
||
|
def sprintf(self, fmt, relax):
|
||
|
if relax:
|
||
|
return "??"
|
||
|
else:
|
||
|
raise Scapy_Exception("Format not found [%s]" % fmt)
|
||
|
|
||
|
def _do_summary(self):
|
||
|
return 0, "", []
|
||
|
|
||
|
def layers(self):
|
||
|
return []
|
||
|
|
||
|
def lastlayer(self, layer):
|
||
|
return layer
|
||
|
|
||
|
def command(self):
|
||
|
return ""
|
||
|
|
||
|
def route(self):
|
||
|
return (None, None, None)
|
||
|
|
||
|
|
||
|
####################
|
||
|
# packet classes #
|
||
|
####################
|
||
|
|
||
|
|
||
|
class Raw(Packet):
|
||
|
name = "Raw"
|
||
|
fields_desc = [StrField("load", "")]
|
||
|
|
||
|
def __init__(self, _pkt=None, *args, **kwargs):
|
||
|
if _pkt and not isinstance(_pkt, bytes):
|
||
|
_pkt = bytes_encode(_pkt)
|
||
|
super(Raw, self).__init__(_pkt, *args, **kwargs)
|
||
|
|
||
|
def answers(self, other):
|
||
|
return 1
|
||
|
|
||
|
def mysummary(self):
|
||
|
cs = conf.raw_summary
|
||
|
if cs:
|
||
|
if callable(cs):
|
||
|
return "Raw %s" % cs(self.load)
|
||
|
else:
|
||
|
return "Raw %r" % self.load
|
||
|
return Packet.mysummary(self)
|
||
|
|
||
|
@classmethod
|
||
|
def convert_packet(cls, pkt, **kwargs):
|
||
|
return Raw(raw(pkt))
|
||
|
|
||
|
|
||
|
class Padding(Raw):
|
||
|
name = "Padding"
|
||
|
|
||
|
def self_build(self):
|
||
|
return b""
|
||
|
|
||
|
def build_padding(self):
|
||
|
return (raw(self.load) if self.raw_packet_cache is None
|
||
|
else self.raw_packet_cache) + self.payload.build_padding()
|
||
|
|
||
|
|
||
|
conf.raw_layer = Raw
|
||
|
conf.padding_layer = Padding
|
||
|
if conf.default_l2 is None:
|
||
|
conf.default_l2 = Raw
|
||
|
|
||
|
#################
|
||
|
# Bind layers #
|
||
|
#################
|
||
|
|
||
|
|
||
|
def bind_bottom_up(lower, upper, __fval=None, **fval):
|
||
|
r"""Bind 2 layers for dissection.
|
||
|
The upper layer will be chosen for dissection on top of the lower layer, if
|
||
|
ALL the passed arguments are validated. If multiple calls are made with
|
||
|
the same layers, the last one will be used as default.
|
||
|
|
||
|
ex:
|
||
|
>>> bind_bottom_up(Ether, SNAP, type=0x1234)
|
||
|
>>> Ether(b'\xff\xff\xff\xff\xff\xff\xd0P\x99V\xdd\xf9\x124\x00\x00\x00\x00\x00') # noqa: E501
|
||
|
<Ether dst=ff:ff:ff:ff:ff:ff src=d0:50:99:56:dd:f9 type=0x1234 |<SNAP OUI=0x0 code=0x0 |>> # noqa: E501
|
||
|
"""
|
||
|
if __fval is not None:
|
||
|
fval.update(__fval)
|
||
|
lower.payload_guess = lower.payload_guess[:]
|
||
|
lower.payload_guess.append((fval, upper))
|
||
|
|
||
|
|
||
|
def bind_top_down(lower, upper, __fval=None, **fval):
|
||
|
"""Bind 2 layers for building.
|
||
|
When the upper layer is added as a payload of the lower layer, all the
|
||
|
arguments will be applied to them.
|
||
|
|
||
|
ex:
|
||
|
>>> bind_top_down(Ether, SNAP, type=0x1234)
|
||
|
>>> Ether()/SNAP()
|
||
|
<Ether type=0x1234 |<SNAP |>>
|
||
|
"""
|
||
|
if __fval is not None:
|
||
|
fval.update(__fval)
|
||
|
upper._overload_fields = upper._overload_fields.copy()
|
||
|
upper._overload_fields[lower] = fval
|
||
|
|
||
|
|
||
|
@conf.commands.register
|
||
|
def bind_layers(lower, upper, __fval=None, **fval):
|
||
|
"""Bind 2 layers on some specific fields' values.
|
||
|
|
||
|
It makes the packet being built and dissected when the arguments
|
||
|
are present.
|
||
|
|
||
|
This function calls both bind_bottom_up and bind_top_down, with
|
||
|
all passed arguments.
|
||
|
|
||
|
Please have a look at their docs:
|
||
|
- help(bind_bottom_up)
|
||
|
- help(bind_top_down)
|
||
|
"""
|
||
|
if __fval is not None:
|
||
|
fval.update(__fval)
|
||
|
bind_top_down(lower, upper, **fval)
|
||
|
bind_bottom_up(lower, upper, **fval)
|
||
|
|
||
|
|
||
|
def split_bottom_up(lower, upper, __fval=None, **fval):
|
||
|
"""This call un-links an association that was made using bind_bottom_up.
|
||
|
Have a look at help(bind_bottom_up)
|
||
|
"""
|
||
|
if __fval is not None:
|
||
|
fval.update(__fval)
|
||
|
|
||
|
def do_filter(params, cls):
|
||
|
params_is_invalid = any(
|
||
|
k not in params or params[k] != v for k, v in six.iteritems(fval)
|
||
|
)
|
||
|
return cls != upper or params_is_invalid
|
||
|
lower.payload_guess = [x for x in lower.payload_guess if do_filter(*x)]
|
||
|
|
||
|
|
||
|
def split_top_down(lower, upper, __fval=None, **fval):
|
||
|
"""This call un-links an association that was made using bind_top_down.
|
||
|
Have a look at help(bind_top_down)
|
||
|
"""
|
||
|
if __fval is not None:
|
||
|
fval.update(__fval)
|
||
|
if lower in upper._overload_fields:
|
||
|
ofval = upper._overload_fields[lower]
|
||
|
if any(k not in ofval or ofval[k] != v for k, v in six.iteritems(fval)): # noqa: E501
|
||
|
return
|
||
|
upper._overload_fields = upper._overload_fields.copy()
|
||
|
del(upper._overload_fields[lower])
|
||
|
|
||
|
|
||
|
@conf.commands.register
|
||
|
def split_layers(lower, upper, __fval=None, **fval):
|
||
|
"""Split 2 layers previously bound.
|
||
|
This call un-links calls bind_top_down and bind_bottom_up. It is the opposite of # noqa: E501
|
||
|
bind_layers.
|
||
|
|
||
|
Please have a look at their docs:
|
||
|
- help(split_bottom_up)
|
||
|
- help(split_top_down)
|
||
|
"""
|
||
|
if __fval is not None:
|
||
|
fval.update(__fval)
|
||
|
split_bottom_up(lower, upper, **fval)
|
||
|
split_top_down(lower, upper, **fval)
|
||
|
|
||
|
|
||
|
@conf.commands.register
|
||
|
def explore(layer=None):
|
||
|
"""Function used to discover the Scapy layers and protocols.
|
||
|
It helps to see which packets exists in contrib or layer files.
|
||
|
|
||
|
params:
|
||
|
- layer: If specified, the function will explore the layer. If not,
|
||
|
the GUI mode will be activated, to browse the available layers
|
||
|
|
||
|
examples:
|
||
|
>>> explore() # Launches the GUI
|
||
|
>>> explore("dns") # Explore scapy.layers.dns
|
||
|
>>> explore("http2") # Explore scapy.contrib.http2
|
||
|
>>> explore(scapy.layers.bluetooth4LE)
|
||
|
|
||
|
Note: to search a packet by name, use ls("name") rather than explore.
|
||
|
"""
|
||
|
if layer is None: # GUI MODE
|
||
|
if not conf.interactive:
|
||
|
raise Scapy_Exception("explore() GUI-mode cannot be run in "
|
||
|
"interactive mode. Please provide a "
|
||
|
"'layer' parameter !")
|
||
|
# 0 - Imports
|
||
|
try:
|
||
|
import prompt_toolkit
|
||
|
except ImportError:
|
||
|
raise ImportError("prompt_toolkit is not installed ! "
|
||
|
"You may install IPython, which contains it, via"
|
||
|
" `pip install ipython`")
|
||
|
if not _version_checker(prompt_toolkit, (2, 0)):
|
||
|
raise ImportError("prompt_toolkit >= 2.0.0 is required !")
|
||
|
# Only available with prompt_toolkit > 2.0, not released on PyPi yet
|
||
|
from prompt_toolkit.shortcuts.dialogs import radiolist_dialog, \
|
||
|
button_dialog
|
||
|
from prompt_toolkit.formatted_text import HTML
|
||
|
# Check for prompt_toolkit >= 3.0.0
|
||
|
if _version_checker(prompt_toolkit, (3, 0)):
|
||
|
call_ptk = lambda x: x.run()
|
||
|
else:
|
||
|
call_ptk = lambda x: x
|
||
|
# 1 - Ask for layer or contrib
|
||
|
btn_diag = button_dialog(
|
||
|
title=six.text_type("Scapy v%s" % conf.version),
|
||
|
text=HTML(
|
||
|
six.text_type(
|
||
|
'<style bg="white" fg="red">Chose the type of packets'
|
||
|
' you want to explore:</style>'
|
||
|
)
|
||
|
),
|
||
|
buttons=[
|
||
|
(six.text_type("Layers"), "layers"),
|
||
|
(six.text_type("Contribs"), "contribs"),
|
||
|
(six.text_type("Cancel"), "cancel")
|
||
|
])
|
||
|
action = call_ptk(btn_diag)
|
||
|
# 2 - Retrieve list of Packets
|
||
|
if action == "layers":
|
||
|
# Get all loaded layers
|
||
|
values = conf.layers.layers()
|
||
|
# Restrict to layers-only (not contribs) + packet.py and asn1*.py
|
||
|
values = [x for x in values if ("layers" in x[0] or
|
||
|
"packet" in x[0] or
|
||
|
"asn1" in x[0])]
|
||
|
elif action == "contribs":
|
||
|
# Get all existing contribs
|
||
|
from scapy.main import list_contrib
|
||
|
values = list_contrib(ret=True)
|
||
|
values = [(x['name'], x['description'])
|
||
|
for x in values]
|
||
|
# Remove very specific modules
|
||
|
values = [x for x in values if "can" not in x[0]]
|
||
|
else:
|
||
|
# Escape/Cancel was pressed
|
||
|
return
|
||
|
# Python 2 compat
|
||
|
if six.PY2:
|
||
|
values = [(six.text_type(x), six.text_type(y))
|
||
|
for x, y in values]
|
||
|
# Build tree
|
||
|
if action == "contribs":
|
||
|
# A tree is a dictionary. Each layer contains a keyword
|
||
|
# _l which contains the files in the layer, and a _name
|
||
|
# argument which is its name. The other keys are the subfolders,
|
||
|
# which are similar dictionaries
|
||
|
tree = defaultdict(list)
|
||
|
for name, desc in values:
|
||
|
if "." in name: # Folder detected
|
||
|
parts = name.split(".")
|
||
|
subtree = tree
|
||
|
for pa in parts[:-1]:
|
||
|
if pa not in subtree:
|
||
|
subtree[pa] = {}
|
||
|
subtree = subtree[pa] # one layer deeper
|
||
|
subtree["_name"] = pa
|
||
|
if "_l" not in subtree:
|
||
|
subtree["_l"] = []
|
||
|
subtree["_l"].append((parts[-1], desc))
|
||
|
else:
|
||
|
tree["_l"].append((name, desc))
|
||
|
elif action == "layers":
|
||
|
tree = {"_l": values}
|
||
|
# 3 - Ask for the layer/contrib module to explore
|
||
|
current = tree
|
||
|
previous = []
|
||
|
while True:
|
||
|
# Generate tests & form
|
||
|
folders = list(current.keys())
|
||
|
_radio_values = [
|
||
|
("$" + name, six.text_type('[+] ' + name.capitalize()))
|
||
|
for name in folders if not name.startswith("_")
|
||
|
] + current.get("_l", [])
|
||
|
cur_path = ""
|
||
|
if previous:
|
||
|
cur_path = ".".join(
|
||
|
itertools.chain(
|
||
|
(x["_name"] for x in previous[1:]),
|
||
|
(current["_name"],)
|
||
|
)
|
||
|
)
|
||
|
extra_text = (
|
||
|
'\n<style bg="white" fg="green">> scapy.%s</style>'
|
||
|
) % (action + ("." + cur_path if cur_path else ""))
|
||
|
# Show popup
|
||
|
rd_diag = radiolist_dialog(
|
||
|
values=_radio_values,
|
||
|
title=six.text_type(
|
||
|
"Scapy v%s" % conf.version
|
||
|
),
|
||
|
text=HTML(
|
||
|
six.text_type((
|
||
|
'<style bg="white" fg="red">Please select a file'
|
||
|
'among the following, to see all layers contained in'
|
||
|
' it:</style>'
|
||
|
) + extra_text)
|
||
|
),
|
||
|
cancel_text="Back" if previous else "Cancel"
|
||
|
)
|
||
|
result = call_ptk(rd_diag)
|
||
|
if result is None:
|
||
|
# User pressed "Cancel/Back"
|
||
|
if previous: # Back
|
||
|
current = previous.pop()
|
||
|
continue
|
||
|
else: # Cancel
|
||
|
return
|
||
|
if result.startswith("$"):
|
||
|
previous.append(current)
|
||
|
current = current[result[1:]]
|
||
|
else:
|
||
|
# Enter on layer
|
||
|
if previous: # In subfolder
|
||
|
result = cur_path + "." + result
|
||
|
break
|
||
|
# 4 - (Contrib only): load contrib
|
||
|
if action == "contribs":
|
||
|
from scapy.main import load_contrib
|
||
|
load_contrib(result)
|
||
|
result = "scapy.contrib." + result
|
||
|
else: # NON-GUI MODE
|
||
|
# We handle layer as a short layer name, full layer name
|
||
|
# or the module itself
|
||
|
if isinstance(layer, types.ModuleType):
|
||
|
layer = layer.__name__
|
||
|
if isinstance(layer, str):
|
||
|
if layer.startswith("scapy.layers."):
|
||
|
result = layer
|
||
|
else:
|
||
|
if layer.startswith("scapy.contrib."):
|
||
|
layer = layer.replace("scapy.contrib.", "")
|
||
|
from scapy.main import load_contrib
|
||
|
load_contrib(layer)
|
||
|
result_layer, result_contrib = (("scapy.layers.%s" % layer),
|
||
|
("scapy.contrib.%s" % layer))
|
||
|
if result_layer in conf.layers.ldict:
|
||
|
result = result_layer
|
||
|
elif result_contrib in conf.layers.ldict:
|
||
|
result = result_contrib
|
||
|
else:
|
||
|
raise Scapy_Exception("Unknown scapy module '%s'" % layer)
|
||
|
else:
|
||
|
warning("Wrong usage ! Check out help(explore)")
|
||
|
return
|
||
|
|
||
|
# COMMON PART
|
||
|
# Get the list of all Packets contained in that module
|
||
|
try:
|
||
|
all_layers = conf.layers.ldict[result]
|
||
|
except KeyError:
|
||
|
raise Scapy_Exception("Unknown scapy module '%s'" % layer)
|
||
|
# Print
|
||
|
print(conf.color_theme.layer_name("Packets contained in %s:" % result))
|
||
|
rtlst = [(lay.__name__ or "", lay._name or "") for lay in all_layers]
|
||
|
print(pretty_list(rtlst, [("Class", "Name")], borders=True))
|
||
|
|
||
|
|
||
|
def _pkt_ls(obj, verbose=False):
|
||
|
"""Internal function used to resolve `fields_desc` to display it.
|
||
|
|
||
|
:param obj: a packet object or class
|
||
|
:returns: a list containing tuples [(name, clsname, clsname_extras,
|
||
|
default, long_attrs)]
|
||
|
"""
|
||
|
is_pkt = isinstance(obj, Packet)
|
||
|
if not issubtype(obj, Packet) and not is_pkt:
|
||
|
raise ValueError
|
||
|
fields = []
|
||
|
for f in obj.fields_desc:
|
||
|
cur_fld = f
|
||
|
attrs = []
|
||
|
long_attrs = []
|
||
|
while isinstance(cur_fld, (Emph, ConditionalField)):
|
||
|
if isinstance(cur_fld, ConditionalField):
|
||
|
attrs.append(cur_fld.__class__.__name__[:4])
|
||
|
cur_fld = cur_fld.fld
|
||
|
if verbose and isinstance(cur_fld, EnumField) \
|
||
|
and hasattr(cur_fld, "i2s"):
|
||
|
if len(cur_fld.i2s) < 50:
|
||
|
long_attrs.extend(
|
||
|
"%s: %d" % (strval, numval)
|
||
|
for numval, strval in
|
||
|
sorted(six.iteritems(cur_fld.i2s))
|
||
|
)
|
||
|
elif isinstance(cur_fld, MultiEnumField):
|
||
|
fld_depend = cur_fld.depends_on(obj.__class__
|
||
|
if is_pkt else obj)
|
||
|
attrs.append("Depends on %s" % fld_depend.name)
|
||
|
if verbose:
|
||
|
cur_i2s = cur_fld.i2s_multi.get(
|
||
|
cur_fld.depends_on(obj if is_pkt else obj()), {}
|
||
|
)
|
||
|
if len(cur_i2s) < 50:
|
||
|
long_attrs.extend(
|
||
|
"%s: %d" % (strval, numval)
|
||
|
for numval, strval in
|
||
|
sorted(six.iteritems(cur_i2s))
|
||
|
)
|
||
|
elif verbose and isinstance(cur_fld, FlagsField):
|
||
|
names = cur_fld.names
|
||
|
long_attrs.append(", ".join(names))
|
||
|
cls = cur_fld.__class__
|
||
|
class_name_extras = "(%s)" % (
|
||
|
", ".join(attrs)
|
||
|
) if attrs else ""
|
||
|
if isinstance(cur_fld, BitField):
|
||
|
class_name_extras += " (%d bit%s)" % (
|
||
|
cur_fld.size,
|
||
|
"s" if cur_fld.size > 1 else ""
|
||
|
)
|
||
|
fields.append(
|
||
|
(f.name,
|
||
|
cls,
|
||
|
class_name_extras,
|
||
|
f.default,
|
||
|
long_attrs)
|
||
|
)
|
||
|
return fields
|
||
|
|
||
|
|
||
|
@conf.commands.register
|
||
|
def ls(obj=None, case_sensitive=False, verbose=False):
|
||
|
"""List available layers, or infos on a given layer class or name.
|
||
|
|
||
|
:param obj: Packet / packet name to use
|
||
|
:param case_sensitive: if obj is a string, is it case sensitive?
|
||
|
:param verbose:
|
||
|
"""
|
||
|
is_string = isinstance(obj, six.string_types)
|
||
|
|
||
|
if obj is None or is_string:
|
||
|
tip = False
|
||
|
if obj is None:
|
||
|
tip = True
|
||
|
all_layers = sorted(conf.layers, key=lambda x: x.__name__)
|
||
|
else:
|
||
|
pattern = re.compile(obj, 0 if case_sensitive else re.I)
|
||
|
# We first order by accuracy, then length
|
||
|
if case_sensitive:
|
||
|
sorter = lambda x: (x.__name__.index(obj), len(x.__name__))
|
||
|
else:
|
||
|
obj = obj.lower()
|
||
|
sorter = lambda x: (x.__name__.lower().index(obj),
|
||
|
len(x.__name__))
|
||
|
all_layers = sorted((layer for layer in conf.layers
|
||
|
if (isinstance(layer.__name__, str) and
|
||
|
pattern.search(layer.__name__)) or
|
||
|
(isinstance(layer.name, str) and
|
||
|
pattern.search(layer.name))),
|
||
|
key=sorter)
|
||
|
for layer in all_layers:
|
||
|
print("%-10s : %s" % (layer.__name__, layer._name))
|
||
|
if tip and conf.interactive:
|
||
|
print("\nTIP: You may use explore() to navigate through all "
|
||
|
"layers using a clear GUI")
|
||
|
else:
|
||
|
try:
|
||
|
fields = _pkt_ls(obj, verbose=verbose)
|
||
|
is_pkt = isinstance(obj, Packet)
|
||
|
# Print
|
||
|
for fname, cls, clsne, dflt, long_attrs in fields:
|
||
|
cls = cls.__name__ + " " + clsne
|
||
|
print("%-10s : %-35s =" % (fname, cls), end=' ')
|
||
|
if is_pkt:
|
||
|
print("%-15r" % (getattr(obj, fname),), end=' ')
|
||
|
print("(%r)" % (dflt,))
|
||
|
for attr in long_attrs:
|
||
|
print("%-15s%s" % ("", attr))
|
||
|
# Restart for payload if any
|
||
|
if is_pkt and not isinstance(obj.payload, NoPayload):
|
||
|
print("--")
|
||
|
ls(obj.payload)
|
||
|
except ValueError:
|
||
|
print("Not a packet class or name. Type 'ls()' to list packet classes.") # noqa: E501
|
||
|
|
||
|
|
||
|
@conf.commands.register
|
||
|
def rfc(cls, ret=False, legend=True):
|
||
|
"""
|
||
|
Generate an RFC-like representation of a packet def.
|
||
|
|
||
|
:param cls: the Packet class
|
||
|
:param ret: return the result instead of printing (def. False)
|
||
|
:param legend: show text under the diagram (default True)
|
||
|
|
||
|
Ex::
|
||
|
|
||
|
>>> rfc(Ether)
|
||
|
|
||
|
"""
|
||
|
if not issubclass(cls, Packet):
|
||
|
raise TypeError("Packet class expected")
|
||
|
cur_len = 0
|
||
|
cur_line = []
|
||
|
lines = []
|
||
|
# Get the size (width) that a field will take
|
||
|
# when formatted, from its length in bits
|
||
|
clsize = lambda x: 2 * x - 1
|
||
|
ident = 0 # Fields UUID
|
||
|
# Generate packet groups
|
||
|
for f in cls.fields_desc:
|
||
|
flen = int(f.sz * 8)
|
||
|
cur_len += flen
|
||
|
ident += 1
|
||
|
# Fancy field name
|
||
|
fname = f.name.upper().replace("_", " ")
|
||
|
# The field might exceed the current line or
|
||
|
# take more than one line. Copy it as required
|
||
|
while True:
|
||
|
over = max(0, cur_len - 32) # Exceed
|
||
|
len1 = clsize(flen - over) # What fits
|
||
|
cur_line.append((fname[:len1], len1, ident))
|
||
|
if cur_len >= 32:
|
||
|
# Current line is full. start a new line
|
||
|
lines.append(cur_line)
|
||
|
cur_len = flen = over
|
||
|
fname = "" # do not repeat the field
|
||
|
cur_line = []
|
||
|
if not over:
|
||
|
# there is no data left
|
||
|
break
|
||
|
else:
|
||
|
# End of the field
|
||
|
break
|
||
|
# Add the last line if un-finished
|
||
|
if cur_line:
|
||
|
lines.append(cur_line)
|
||
|
# Calculate separations between lines
|
||
|
seps = []
|
||
|
seps.append("+-" * 32 + "+\n")
|
||
|
for i in range(len(lines) - 1):
|
||
|
# Start with a full line
|
||
|
sep = "+-" * 32 + "+\n"
|
||
|
# Get the line above and below the current
|
||
|
# separation
|
||
|
above, below = lines[i], lines[i + 1]
|
||
|
# The last field of above is shared with below
|
||
|
if above[-1][2] == below[0][2]:
|
||
|
# where the field in "above" starts
|
||
|
pos_above = sum(x[1] for x in above[:-1])
|
||
|
# where the field in "below" ends
|
||
|
pos_below = below[0][1]
|
||
|
if pos_above < pos_below:
|
||
|
# they are overlapping.
|
||
|
# Now crop the space between those pos
|
||
|
# and fill it with " "
|
||
|
pos_above = pos_above + pos_above % 2
|
||
|
sep = (
|
||
|
sep[:1 + pos_above] +
|
||
|
" " * (pos_below - pos_above) +
|
||
|
sep[1 + pos_below:]
|
||
|
)
|
||
|
# line is complete
|
||
|
seps.append(sep)
|
||
|
# Graph
|
||
|
result = ""
|
||
|
# Bytes markers
|
||
|
result += " " + (" " * 19).join(
|
||
|
str(x) for x in range(4)
|
||
|
) + "\n"
|
||
|
# Bits markers
|
||
|
result += " " + " ".join(
|
||
|
str(x % 10) for x in range(32)
|
||
|
) + "\n"
|
||
|
# Add fields and their separations
|
||
|
for line, sep in zip(lines, seps):
|
||
|
result += sep
|
||
|
for elt, flen, _ in line:
|
||
|
result += "|" + elt.center(flen, " ")
|
||
|
result += "|\n"
|
||
|
result += "+-" * (cur_len or 32) + "+\n"
|
||
|
# Annotate with the figure name
|
||
|
if legend:
|
||
|
result += "\n" + ("Fig. " + cls.__name__).center(66, " ")
|
||
|
# return if asked for, else print
|
||
|
if ret:
|
||
|
return result
|
||
|
print(result)
|
||
|
|
||
|
|
||
|
#############
|
||
|
# Fuzzing #
|
||
|
#############
|
||
|
|
||
|
@conf.commands.register
|
||
|
def fuzz(p, _inplace=0):
|
||
|
"""
|
||
|
Transform a layer into a fuzzy layer by replacing some default values
|
||
|
by random objects.
|
||
|
|
||
|
:param p: the Packet instance to fuzz
|
||
|
:return: the fuzzed packet.
|
||
|
"""
|
||
|
if not _inplace:
|
||
|
p = p.copy()
|
||
|
q = p
|
||
|
while not isinstance(q, NoPayload):
|
||
|
new_default_fields = {}
|
||
|
multiple_type_fields = []
|
||
|
for f in q.fields_desc:
|
||
|
if isinstance(f, PacketListField):
|
||
|
for r in getattr(q, f.name):
|
||
|
fuzz(r, _inplace=1)
|
||
|
elif isinstance(f, MultipleTypeField):
|
||
|
# the type of the field will depend on others
|
||
|
multiple_type_fields.append(f.name)
|
||
|
elif f.default is not None:
|
||
|
if not isinstance(f, ConditionalField) or f._evalcond(q):
|
||
|
rnd = f.randval()
|
||
|
if rnd is not None:
|
||
|
new_default_fields[f.name] = rnd
|
||
|
# Process packets with MultipleTypeFields
|
||
|
if multiple_type_fields:
|
||
|
# freeze the other random values
|
||
|
new_default_fields = {
|
||
|
key: (val._fix() if isinstance(val, VolatileValue) else val)
|
||
|
for key, val in six.iteritems(new_default_fields)
|
||
|
}
|
||
|
q.default_fields.update(new_default_fields)
|
||
|
# add the random values of the MultipleTypeFields
|
||
|
for name in multiple_type_fields:
|
||
|
rnd = q.get_field(name)._find_fld_pkt(q).randval()
|
||
|
if rnd is not None:
|
||
|
new_default_fields[name] = rnd
|
||
|
q.default_fields.update(new_default_fields)
|
||
|
q = q.payload
|
||
|
return p
|