Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 17 additions & 8 deletions scapy/arch/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,9 @@ def compile_filter(filter_exp, # type: str
from scapy.libs.winpcapy import (
PCAP_ERRBUF_SIZE,
pcap_open_live,
pcap_open_dead,
pcap_compile,
pcap_compile_nopcap,
pcap_geterr,
pcap_close
)
except OSError:
Expand Down Expand Up @@ -109,9 +110,9 @@ def compile_filter(filter_exp, # type: str
# Some conversion aliases (e.g. linktype_to_dlt in libpcap)
if linktype == DLT_RAW_ALT:
linktype = DLT_RAW
ret = pcap_compile_nopcap(
MTU, linktype, ctypes.byref(bpf), bpf_filter, 1, -1
)
# Use a "dead" capture handle (no interface / no root required) so that,
# on failure, the libpcap error message can be retrieved with pcap_geterr
pcap = pcap_open_dead(linktype, MTU)
elif iface:
err = create_string_buffer(PCAP_ERRBUF_SIZE)
iface_b = create_string_buffer(network_name(iface).encode("utf8"))
Expand All @@ -121,14 +122,22 @@ def compile_filter(filter_exp, # type: str
error = decode_locale_str(bytearray(err).strip(b"\x00"))
if error:
raise OSError(error)
ret = pcap_compile(
pcap, ctypes.byref(bpf), bpf_filter, 1, -1
else:
raise Scapy_Exception("Please provide an interface or linktype!")
ret = pcap_compile(
pcap, ctypes.byref(bpf), bpf_filter, 1, -1
)
if ret == -1:
# Retrieve the underlying libpcap error message: it explains why the
# filter is invalid or incompatible with the link-layer type
errstr = decode_locale_str(
bytearray(pcap_geterr(pcap)).strip(b"\x00")
)
pcap_close(pcap)
if ret == -1:
raise Scapy_Exception(
"Failed to compile filter expression %s (%s)" % (filter_exp, ret)
"Failed to compile filter expression %r (%s)" % (filter_exp, errstr)
)
pcap_close(pcap)
return bpf


Expand Down
2 changes: 1 addition & 1 deletion scapy/contrib/http2.py
Original file line number Diff line number Diff line change
Expand Up @@ -2454,7 +2454,7 @@ def gen_txt_repr(self, hdrs, register=True):

:param H2Frame|list of HPackHeaders hdrs: the list of headers to
convert to textual representation.
:param bool: whether incremental headers should be added to the dynamic
:param bool register: whether incremental headers should be added to the dynamic
table as we generate the text representation
:return: str: the textual representation of the provided headers
:raises: AssertionError
Expand Down
20 changes: 15 additions & 5 deletions scapy/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,8 @@ class Field(Generic[I, M], metaclass=Field_metaclass):
islist = 0
ismutable = False
holds_packets = 0
isconditional = False
ismayend = False

def __init__(self, name, default, fmt="H"):
# type: (str, Any, str) -> None
Expand Down Expand Up @@ -198,7 +200,7 @@ def i2count(self, pkt, x):
def h2i(self, pkt, x):
# type: (Optional[Packet], Any) -> I
"""Convert human value to internal value"""
return cast(I, x)
return x # type: ignore

def i2h(self, pkt, x):
# type: (Optional[Packet], I) -> Any
Expand All @@ -208,16 +210,16 @@ def i2h(self, pkt, x):
def m2i(self, pkt, x):
# type: (Optional[Packet], M) -> I
"""Convert machine value to internal value"""
return cast(I, x)
return x # type: ignore

def i2m(self, pkt, x):
# type: (Optional[Packet], Optional[I]) -> M
"""Convert internal value to machine value"""
if x is None:
return cast(M, 0)
return 0 # type: ignore
elif isinstance(x, str):
return cast(M, bytes_encode(x))
return cast(M, x)
return bytes_encode(x) # type: ignore
return x # type: ignore

def any2i(self, pkt, x):
# type: (Optional[Packet], Any) -> Optional[I]
Expand Down Expand Up @@ -257,6 +259,10 @@ def getfield(self, pkt, s):
first the raw packet string after having removed the extracted field,
second the extracted field itself in internal representation.
"""
# Use unpack_from for plain bytes (avoids temporary slice allocation).
# Fall back to unpack+slice for subclasses that override __getitem__.
if type(s) is bytes:
return s[self.sz:], self.m2i(pkt, self.struct.unpack_from(s)[0])
return s[self.sz:], self.m2i(pkt, self.struct.unpack(s[:self.sz])[0])

def do_copy(self, x):
Expand Down Expand Up @@ -311,6 +317,8 @@ class _FieldContainer(object):
A field that acts as a container for another field
"""
__slots__ = ["fld"]
isconditional = False
ismayend = False

def __getattr__(self, attr):
# type: (str) -> Any
Expand Down Expand Up @@ -349,6 +357,7 @@ class MayEnd(_FieldContainer):
to an empty value, else the behavior will be unexpected.
"""
__slots__ = ["fld"]
ismayend = True

def __init__(self, fld):
# type: (Any) -> None
Expand Down Expand Up @@ -380,6 +389,7 @@ def any2i(self, pkt, val):

class ConditionalField(_FieldContainer):
__slots__ = ["fld", "cond"]
isconditional = True

def __init__(self,
fld, # type: AnyField
Expand Down
9 changes: 6 additions & 3 deletions scapy/fwdmachine.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ class ForwardMachine:
:param proto: the proto to use (default SOCK_STREAM)
:param remote_address: the IP to use in SERVER mode, or by default in TPROXY when
the destination is the local IP.
:param remote_port: the port to use in SERVER mode. (else use 'port')
:param remote_af: (optional) if provided, use a different address family to connect
to the remote host.
:param bind_address: the IP to bind locally. "0.0.0.0" by default in SERVER mode,
Expand Down Expand Up @@ -108,6 +109,7 @@ def __init__(
af: socket.AddressFamily = socket.AF_INET,
proto: socket.SocketKind = socket.SOCK_STREAM,
remote_address: str = None,
remote_port: int = None,
remote_af: Optional[socket.AddressFamily] = None,
bind_address: str = None,
tls: bool = False,
Expand All @@ -129,6 +131,7 @@ def __init__(
self.timeout = timeout
self.MTU = MTU
self.remote_address = remote_address
self.remote_port = remote_port
if self.tls or self.af == 40: # TLS or VSOCK
self.sockcls = StreamSocketPeekless
else:
Expand Down Expand Up @@ -164,9 +167,9 @@ def run(self):
conn, addr = self.ssock.accept()
# Calc dest
dest = conn.getsockname()
if self.mode == ForwardMachine.MODE.SERVER or (
dest[0] in self.local_ips and self.remote_address
):
if self.mode == ForwardMachine.MODE.SERVER:
dest = (self.remote_address, self.remote_port or self.port)
elif dest[0] in self.local_ips and self.remote_address:
dest = (self.remote_address,) + dest[1:]
print(self.ct.green("%s -> %s connected !" % (repr(addr), repr(dest))))
try:
Expand Down
12 changes: 9 additions & 3 deletions scapy/layers/igmp.py
Original file line number Diff line number Diff line change
Expand Up @@ -412,7 +412,7 @@ class IGMPv3_MRT(IGMPv3):


bind_layers(IP, IGMP, proto=2)
bind_top_down(IP, IGMP, proto=2, ttl=1, tox=0xC0)
bind_top_down(IP, IGMP, proto=2, ttl=1, tos=0xC0)


def _igmp_mq_addr(pkt):
Expand Down Expand Up @@ -455,14 +455,20 @@ def igmp_join(gaddr: str, version=2, psrc=None, iface=None):


@conf.commands.register
def igmp_leave(gaddr: str, psrc=None, iface=None):
def igmp_leave(gaddr: str, version=2, psrc=None, iface=None):
"""
Send a IGMP Leave Group to leave a multicast group

:param gaddr: the IPv4 of the group to leave
:param psrc: (optional) the source IP
"""
send(IP(src=psrc) / IGMPv2_LG(gaddr=gaddr), iface=iface)
if version == 1:
raise ValueError("IGMPv1 does not include a mechanism to leave !")
elif version == 2:
pkt = IP(src=psrc) / IGMPv2_LG(gaddr=gaddr)
elif version == 3:
pkt = IP(src=psrc) / IGMPv3_MR(records=[IGMPv3_MR_Group(rtype=3, maddr=gaddr)])
send(pkt, iface=iface)


class IGMPMQResult(PacketList):
Expand Down
51 changes: 49 additions & 2 deletions scapy/layers/kerberos.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,17 +98,19 @@
FieldLenField,
FlagsField,
IntEnumField,
IntField,
LEIntEnumField,
LenField,
LEShortEnumField,
LEShortField,
LenField,
LongField,
MayEnd,
MultipleTypeField,
PacketField,
PacketLenField,
PacketListField,
PadField,
ScalingField,
ShortEnumField,
ShortField,
StrField,
Expand Down Expand Up @@ -2848,10 +2850,55 @@ def answers(self, other):
}


class DOMAIN_PASSWORD_INFORMATION(Packet):
# [MS-SAMR] sect 2.2.3.5
fields_desc = [
IntField("MinPasswordLength", 0),
IntField("PasswordHistoryLength", 0),
FlagsField(
"PasswordProperties",
0,
32,
{
0x00000001: "DOMAIN_PASSWORD_COMPLEX",
0x00000002: "DOMAIN_PASSWORD_NO_ANON_CHANGE",
0x00000004: "DOMAIN_PASSWORD_NO_CLEAR_CHANGE",
0x00000008: "DOMAIN_LOCKOUT_ADMINS",
0x00000010: "DOMAIN_PASSWORD_STORE_CLEARTEXT",
0x00000020: "DOMAIN_REFUSE_PASSWORD_CHANGE",
0x00000040: "DOMAIN_NO_LM_OWF_CHANGE",
},
),
ScalingField("MaxPasswordAge", 30 * 24 * 3600, scaling=1 / 1e7, fmt="!Q"),
ScalingField("MinPasswordAge", 0, scaling=1 / 1e7, fmt="!Q"),
]


class KPasswdResult(Packet):
# This is guessed from looking at MIT's implementation + ntsecapi.h
fields_desc = [
ShortField("PasswordInfoValid", 0),
PacketField(
"DomainPasswordInfo",
DOMAIN_PASSWORD_INFORMATION(),
DOMAIN_PASSWORD_INFORMATION,
),
]


class _KPasswdRepDataResult_Field(StrField):
def m2i(self, pkt, s):
val = super(_KPasswdRepDataResult_Field, self).m2i(pkt, s)
if len(val or b"") == 30:
# A 30 octets blob is most likely the AD policy block
return KPasswdResult(val)
return val


class KPasswdRepData(Packet):
fields_desc = [
ShortEnumField("resultCode", 0, KPASSWD_RESULTS),
StrField("resultString", ""),
_KPasswdRepDataResult_Field("resultString", ""),
]


Expand Down
45 changes: 29 additions & 16 deletions scapy/packet.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
Field,
FlagsField,
FlagValue,
MayEnd,
MultiEnumField,
MultipleTypeField,
PadField,
Expand Down Expand Up @@ -156,7 +155,7 @@ def __init__(self,
**fields # type: Any
):
# type: (...) -> None
self.time = time.time() # type: Union[EDecimal, float]
self.time = 0.0 if _internal else time.time() # type: Union[EDecimal, float]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wtf

self.sent_time = None # type: Union[EDecimal, float, None]
self.name = (self.__class__.__name__
if self._name is None else
Expand Down Expand Up @@ -353,11 +352,12 @@ def do_init_cached_fields(self, for_dissect_only=False):
cls_name = self.__class__

# Build the fields information
if Packet.class_default_fields.get(cls_name, None) is None:
default_fields = Packet.class_default_fields.get(cls_name)
if default_fields is None:
self.prepare_cached_fields(self.fields_desc)
default_fields = Packet.class_default_fields.get(cls_name)
Comment thread
polybassa marked this conversation as resolved.

# 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]
Expand Down Expand Up @@ -517,12 +517,18 @@ def getfieldval(self, attr):
# type: (str) -> Any
if self.deprecated_fields and attr in self.deprecated_fields:
attr = self._resolve_alias(attr)
if attr in self.fields:
try:
return self.fields[attr]
if attr in self.overloaded_fields:
except KeyError:
pass
try:
return self.overloaded_fields[attr]
if attr in self.default_fields:
except KeyError:
pass
try:
return self.default_fields[attr]
except KeyError:
pass
return self.payload.getfieldval(attr)

def getfield_and_val(self, attr):
Expand Down Expand Up @@ -726,17 +732,24 @@ def copy_fields_dict(self, fields):
def _raw_packet_cache_field_value(self, fld, val, copy=False):
# type: (AnyField, Any, bool) -> Optional[Any]
"""Get a value representative of a mutable field to detect changes"""
_cpy = lambda x: fld.do_copy(x) if copy else x # type: Callable[[Any], Any]
if fld.holds_packets:
# avoid copying whole packets (perf: #GH3894)
if fld.islist:
if copy:
return [
(fld.do_copy(x.fields), x.payload.raw_packet_cache)
for x in val
]
return [
(_cpy(x.fields), x.payload.raw_packet_cache) for x in val
(x.fields, x.payload.raw_packet_cache) for x in val
]
else:
return (_cpy(val.fields), val.payload.raw_packet_cache)
if copy:
return (fld.do_copy(val.fields),
val.payload.raw_packet_cache)
return (val.fields, val.payload.raw_packet_cache)
elif fld.islist or fld.ismutable:
return _cpy(val)
return fld.do_copy(val) if copy else val
return None

def clear_cache(self):
Expand Down Expand Up @@ -1082,7 +1095,7 @@ def do_dissect(self, s):
for f in self.fields_desc:
s, fval = f.getfield(self, s)
# Skip unused ConditionalField
if isinstance(f, ConditionalField) and fval is None:
if f.isconditional and fval is None:
continue
# We need to track fields with mutable values to discard
# .raw_packet_cache when needed.
Expand All @@ -1091,9 +1104,9 @@ def do_dissect(self, s):
self._raw_packet_cache_field_value(f, fval, copy=True)
self.fields[f.name] = fval
# Nothing left to dissect
if not s and (isinstance(f, MayEnd) or
(fval is not None and isinstance(f, ConditionalField) and
isinstance(f.fld, MayEnd))):
if not s and (f.ismayend or
(fval is not None and f.isconditional and
f.fld.ismayend)): # type: ignore
break
self.raw_packet_cache = _raw[:-len(s)] if s else _raw
self.explicit = 1
Expand Down Expand Up @@ -1859,7 +1872,7 @@ def __new__(cls, *args, **kargs):
if singl is None:
cls.__singl__ = singl = Packet.__new__(cls)
Packet.__init__(singl)
return cast(NoPayload, singl)
return singl # type: ignore

def __init__(self, *args, **kargs):
# type: (*Any, **Any) -> None
Expand Down
Loading
Loading