2025-12-25 upload

This commit is contained in:
“shengyudong”
2025-12-25 11:16:59 +08:00
commit 322ac74336
2241 changed files with 639966 additions and 0 deletions

View File

@@ -0,0 +1,350 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Fabio Falcinelli, Maximilian Hils
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import ctypes
import pprint
import socket
from pydivert import windivert_dll
from pydivert.consts import Direction, IPV6_EXT_HEADERS, Protocol, Layer
from pydivert.packet.header import Header
from pydivert.packet.icmp import ICMPv4Header, ICMPv6Header
from pydivert.packet.ip import IPv4Header, IPv6Header
from pydivert.packet.tcp import TCPHeader
from pydivert.packet.udp import UDPHeader
from pydivert.util import cached_property, indexbyte as i, PY2
class Packet(object):
"""
A single packet, possibly including an IP header, a TCP/UDP header and a payload.
Creation of packets is cheap, parsing is done on first attribute access.
"""
def __init__(self, raw, interface, direction):
if isinstance(raw, bytes):
raw = memoryview(bytearray(raw))
self.raw = raw # type: memoryview
self.interface = interface
self.direction = direction
def __repr__(self):
def dump(x):
if isinstance(x, Header) or isinstance(x, Packet):
d = {}
for k in dir(x):
v = getattr(x, k)
if k.startswith("_") or callable(v):
continue
if k in {"address_family", "protocol", "ip", "icmp"}:
continue
if k == "payload" and v and len(v) > 20:
v = v[:20] + b"..."
d[k] = dump(v)
if isinstance(x, Packet):
return pprint.pformat(d)
return d
return x
return "Packet({})".format(dump(self))
@property
def is_outbound(self):
"""
Indicates if the packet is outbound.
Convenience method for ``.direction``.
"""
return self.direction == Direction.OUTBOUND
@property
def is_inbound(self):
"""
Indicates if the packet is inbound.
Convenience method for ``.direction``.
"""
return self.direction == Direction.INBOUND
@property
def is_loopback(self):
"""
- True, if the packet is on the loopback interface.
- False, otherwise.
"""
return self.interface[0] == 1
@cached_property
def address_family(self):
"""
The packet address family:
- socket.AF_INET, if IPv4
- socket.AF_INET6, if IPv6
- None, otherwise.
"""
if len(self.raw) >= 20:
v = i(self.raw[0]) >> 4
if v == 4:
return socket.AF_INET
if v == 6:
return socket.AF_INET6
@cached_property
def protocol(self):
"""
- | A (ipproto, proto_start) tuple.
| ``ipproto`` is the IP protocol in use, e.g. Protocol.TCP or Protocol.UDP.
| ``proto_start`` denotes the beginning of the protocol data.
| If the packet does not match our expectations, both ipproto and proto_start are None.
"""
if self.address_family == socket.AF_INET:
proto = i(self.raw[9])
start = (i(self.raw[0]) & 0b1111) * 4
elif self.address_family == socket.AF_INET6:
proto = i(self.raw[6])
# skip over well-known ipv6 headers
start = 40
while proto in IPV6_EXT_HEADERS:
if start >= len(self.raw):
# less than two bytes left
start = None
proto = None
break
if proto == Protocol.FRAGMENT:
hdrlen = 8
elif proto == Protocol.AH:
hdrlen = (i(self.raw[start + 1]) + 2) * 4
else:
# Protocol.HOPOPT, Protocol.DSTOPTS, Protocol.ROUTING
hdrlen = (i(self.raw[start + 1]) + 1) * 8
proto = i(self.raw[start])
start += hdrlen
else:
start = None
proto = None
out_of_bounds = (
(proto == Protocol.TCP and start + 20 > len(self.raw)) or
(proto == Protocol.UDP and start + 8 > len(self.raw)) or
(proto in {Protocol.ICMP, Protocol.ICMPV6} and start + 4 > len(self.raw))
)
if out_of_bounds:
# special-case tcp/udp so that we can rely on .protocol for the port properties.
start = None
proto = None
return proto, start
@cached_property
def ipv4(self):
"""
- An IPv4Header instance, if the packet is valid IPv4.
- None, otherwise.
"""
if self.address_family == socket.AF_INET:
return IPv4Header(self)
@cached_property
def ipv6(self):
"""
- An IPv6Header instance, if the packet is valid IPv6.
- None, otherwise.
"""
if self.address_family == socket.AF_INET6:
return IPv6Header(self)
@cached_property
def ip(self):
"""
- An IPHeader instance, if the packet is valid IPv4 or IPv6.
- None, otherwise.
"""
return self.ipv4 or self.ipv6
@cached_property
def icmpv4(self):
"""
- An ICMPv4Header instance, if the packet is valid ICMPv4.
- None, otherwise.
"""
ipproto, proto_start = self.protocol
if ipproto == Protocol.ICMP:
return ICMPv4Header(self, proto_start)
@cached_property
def icmpv6(self):
"""
- An ICMPv6Header instance, if the packet is valid ICMPv6.
- None, otherwise.
"""
ipproto, proto_start = self.protocol
if ipproto == Protocol.ICMPV6:
return ICMPv6Header(self, proto_start)
@cached_property
def icmp(self):
"""
- An ICMPHeader instance, if the packet is valid ICMPv4 or ICMPv6.
- None, otherwise.
"""
return self.icmpv4 or self.icmpv6
@cached_property
def tcp(self):
"""
- An TCPHeader instance, if the packet is valid TCP.
- None, otherwise.
"""
ipproto, proto_start = self.protocol
if ipproto == Protocol.TCP:
return TCPHeader(self, proto_start)
@cached_property
def udp(self):
"""
- An TCPHeader instance, if the packet is valid UDP.
- None, otherwise.
"""
ipproto, proto_start = self.protocol
if ipproto == Protocol.UDP:
return UDPHeader(self, proto_start)
@cached_property
def _port(self):
"""header that implements PortMixin"""
return self.tcp or self.udp
@cached_property
def _payload(self):
"""header that implements PayloadMixin"""
return self.tcp or self.udp or self.icmpv4 or self.icmpv6
@property
def src_addr(self):
"""
- The source address, if the packet is valid IPv4 or IPv6.
- None, otherwise.
"""
if self.ip:
return self.ip.src_addr
@src_addr.setter
def src_addr(self, val):
self.ip.src_addr = val
@property
def dst_addr(self):
"""
- The destination address, if the packet is valid IPv4 or IPv6.
- None, otherwise.
"""
if self.ip:
return self.ip.dst_addr
@dst_addr.setter
def dst_addr(self, val):
self.ip.dst_addr = val
@property
def src_port(self):
"""
- The source port, if the packet is valid TCP or UDP.
- None, otherwise.
"""
if self._port:
return self._port.src_port
@src_port.setter
def src_port(self, val):
self._port.src_port = val
@property
def dst_port(self):
"""
- The destination port, if the packet is valid TCP or UDP.
- None, otherwise.
"""
if self._port:
return self._port.dst_port
@dst_port.setter
def dst_port(self, val):
self._port.dst_port = val
@property
def payload(self):
"""
- The payload, if the packet is valid TCP, UDP, ICMP or ICMPv6.
- None, otherwise.
"""
if self._payload:
return self._payload.payload
@payload.setter
def payload(self, val):
self._payload.payload = val
def recalculate_checksums(self, flags=0):
"""
(Re)calculates the checksum for any IPv4/ICMP/ICMPv6/TCP/UDP checksum present in the given packet.
Individual checksum calculations may be disabled via the appropriate flag.
Typically this function should be invoked on a modified packet before it is injected with WinDivert.send().
Returns the number of checksums calculated.
See: https://reqrypt.org/windivert-doc.html#divert_helper_calc_checksums
"""
buff, buff_ = self.__to_buffers()
num = windivert_dll.WinDivertHelperCalcChecksums(ctypes.byref(buff_), len(self.raw), flags)
if PY2:
self.raw = memoryview(buff)[:len(self.raw)]
return num
def __to_buffers(self):
buff = bytearray(self.raw.tobytes()) if PY2 else self.raw.obj
return buff, (ctypes.c_char * len(self.raw)).from_buffer(buff)
@property
def wd_addr(self):
"""
Gets the interface and direction as a `WINDIVERT_ADDRESS` structure.
:return: The `WINDIVERT_ADDRESS` structure.
"""
address = windivert_dll.WinDivertAddress()
address.IfIdx, address.SubIfIdx = self.interface
address.Direction = self.direction
return address
def matches(self, filter, layer=Layer.NETWORK):
"""
Evaluates the packet against the given packet filter string.
The remapped function is::
BOOL WinDivertHelperEvalFilter(
__in const char *filter,
__in WINDIVERT_LAYER layer,
__in PVOID pPacket,
__in UINT packetLen,
__in PWINDIVERT_ADDRESS pAddr
);
See: https://reqrypt.org/windivert-doc.html#divert_helper_eval_filter
:param filter: The filter string.
:param layer: The network layer.
:return: True if the packet matches, and False otherwise.
"""
buff, buff_ = self.__to_buffers()
return windivert_dll.WinDivertHelperEvalFilter(filter.encode(), layer, ctypes.byref(buff_), len(self.raw),
ctypes.byref(self.wd_addr))

View File

@@ -0,0 +1,91 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Fabio Falcinelli, Maximilian Hils
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import struct
class Header(object):
def __init__(self, packet, start=0):
self._packet = packet # type: "pydivert.Packet"
self._start = start
@property
def raw(self):
"""
The raw header, possibly including payload.
"""
return self._packet.raw[self._start:]
@raw.setter
def raw(self, val):
if len(val) == len(self.raw):
self.raw[:] = val
else:
self._packet.raw = memoryview(bytearray(
self._packet.raw[:self._start].tobytes() + val
))
self._packet.ip.packet_len = len(self._packet.raw)
def __setattr__(self, key, value):
if key in dir(self) or key in {"_packet", "_start"}:
return super(Header, self).__setattr__(key, value)
raise AttributeError("AttributeError: '{}' object has no attribute '{}'".format(
type(self).__name__,
key
))
class PayloadMixin(object):
@property
def header_len(self):
raise NotImplementedError() # pragma: no cover
@property
def payload(self):
"""
The packet payload data.
"""
return self.raw[self.header_len:].tobytes()
@payload.setter
def payload(self, val):
if len(val) == len(self.raw) - self.header_len:
self.raw[self.header_len:] = val
else:
self.raw = self.raw[:self.header_len].tobytes() + val
class PortMixin(object):
@property
def src_port(self):
"""
The source port.
"""
return struct.unpack_from("!H", self.raw, 0)[0]
@property
def dst_port(self):
"""
The destination port.
"""
return struct.unpack_from("!H", self.raw, 2)[0]
@src_port.setter
def src_port(self, val):
self.raw[0:2] = struct.pack("!H", val)
@dst_port.setter
def dst_port(self, val):
self.raw[2:4] = struct.pack("!H", val)

View File

@@ -0,0 +1,53 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Fabio Falcinelli, Maximilian Hils
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from pydivert.packet.header import Header, PayloadMixin
from pydivert.util import indexbyte as i, raw_property
class ICMPHeader(Header, PayloadMixin):
header_len = 4
@property
def type(self):
"""
The ICMP message type.
"""
return i(self.raw[0])
@type.setter
def type(self, val):
self.raw[0] = i(val)
@property
def code(self):
"""
The ICMP message code.
"""
return i(self.raw[1])
@code.setter
def code(self, val):
self.raw[1] = i(val)
cksum = raw_property('!H', 2, docs='The ICMP header checksum field.')
class ICMPv4Header(ICMPHeader):
pass
class ICMPv6Header(ICMPHeader):
pass

View File

@@ -0,0 +1,216 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Fabio Falcinelli, Maximilian Hils
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import socket
import struct
from pydivert.packet.header import Header
from pydivert.util import PY2, PY34, flag_property, indexbyte as i, raw_property
class IPHeader(Header):
_src_addr = slice(0, 0)
_dst_addr = slice(0, 0)
_af = None
@property
def src_addr(self):
"""
The packet source address.
"""
try:
return socket.inet_ntop(self._af, self.raw[self._src_addr].tobytes())
except (ValueError, socket.error):
pass
@src_addr.setter
def src_addr(self, val):
self.raw[self._src_addr] = socket.inet_pton(self._af, val)
@property
def dst_addr(self):
"""
The packet destination address.
"""
try:
return socket.inet_ntop(self._af, self.raw[self._dst_addr].tobytes())
except (ValueError, socket.error):
pass
@dst_addr.setter
def dst_addr(self, val):
self.raw[self._dst_addr] = socket.inet_pton(self._af, val)
@property
def packet_len(self):
"""
The total packet length, including *all* headers, as reported by the IP header.
"""
raise NotImplementedError() # pragma: no cover
@packet_len.setter
def packet_len(self, val):
raise NotImplementedError() # pragma: no cover
class IPv4Header(IPHeader):
_src_addr = slice(12, 16)
_dst_addr = slice(16, 20)
_af = socket.AF_INET
@property
def header_len(self):
"""
The IP header length in bytes.
"""
return self.hdr_len * 4
@property
def hdr_len(self):
"""
The header length in words of 32bit.
"""
return i(self.raw[0]) & 0x0F
@hdr_len.setter
def hdr_len(self, val):
if val < 5:
raise ValueError("IP header length must be greater or equal than 5.")
struct.pack_into('!B', self.raw, 0, 0x40 | val)
packet_len = raw_property('!H', 2, docs=IPHeader.packet_len.__doc__)
tos = raw_property('!B', 1, docs='The Type Of Service field (six-bit DiffServ field and a two-bit ECN field).')
ident = raw_property('!H', 4, docs='The Identification field.')
reserved = flag_property('reserved', 6, 0b10000000)
evil = flag_property('evil', 6, 0b10000000, docs='Just an april\'s fool joke for the RESERVED flag.')
df = flag_property('df', 6, 0b01000000)
mf = flag_property('mf', 6, 0b00100000)
ttl = raw_property('!B', 8, docs='The Time To Live field.')
protocol = raw_property('!B', 9, docs='The Protocol field.')
cksum = raw_property('!H', 10, docs='The IP header Checksum field.')
@property
def flags(self):
"""
The flags field: RESERVED (the evil bit), DF (don't fragment), MF (more fragments).
"""
return i(self.raw[6]) >> 5
@flags.setter
def flags(self, val):
struct.pack_into('!B', self.raw, 6, (val << 5) | (self.frag_offset & 0xFF00))
@property
def frag_offset(self):
"""
The Fragment Offset field in blocks of 8 bytes.
"""
return struct.unpack_from("!H", self.raw, 6)[0] & 0x1FFF
@frag_offset.setter
def frag_offset(self, val):
self.raw[6:8] = struct.pack("!H", (self.flags << 13) | (val & 0x1FFF))
@property
def dscp(self):
"""
The Differentiated Services Code Point field (originally defined as Type of Service) also known as DiffServ.
"""
return (i(self.raw[1]) >> 2) & 0x3F
@dscp.setter
def dscp(self, val):
struct.pack_into('!B', self.raw, 1, (val << 2) | self.ecn)
diff_serv = dscp
@property
def ecn(self):
"""
The Explicit Congestion Notification field.
"""
return i(self.raw[1]) & 0x03
@ecn.setter
def ecn(self, val):
struct.pack_into('!B', self.raw, 1, (self.dscp << 2) | (val & 0x03))
class IPv6Header(IPHeader):
_src_addr = slice(8, 24)
_dst_addr = slice(24, 40)
_af = socket.AF_INET6
header_len = 40
payload_len = raw_property('!H', 4, docs='The Payload Length field.')
next_hdr = raw_property('!B', 6, docs='The Next Header field. Replaces the Protocol field in IPv4.')
hop_limit = raw_property('!B', 7, docs='The Hop Limit field. Replaces the TTL field in IPv4.')
@property
def packet_len(self):
return self.payload_len + self.header_len
@packet_len.setter
def packet_len(self, val):
self.payload_len = val - self.header_len
@property
def traffic_class(self):
"""
The Traffic Class field (six-bit DiffServ field and a two-bit ECN field).
"""
return (struct.unpack_from('!H', self.raw, 0)[0] >> 4) & 0x00FF
@traffic_class.setter
def traffic_class(self, val):
struct.pack_into('!H', self.raw, 0, 0x6000 | (val << 4) | (self.flow_label & 0x000F0000))
@property
def flow_label(self):
"""
The Flow Label field.
"""
return struct.unpack_from('!I', self.raw, 0)[0] & 0x000FFFFF
@flow_label.setter
def flow_label(self, val):
struct.pack_into('!I', self.raw, 0, 0x60000000 | (self.traffic_class << 20) | (val & 0x000FFFFF))
@property
def diff_serv(self):
"""
The DiffServ field.
"""
return (self.traffic_class & 0xFC) >> 2
@diff_serv.setter
def diff_serv(self, val):
self.traffic_class = self.ecn | (val << 2)
@property
def ecn(self):
"""
The Explicit Congestion Notification field.
"""
return self.traffic_class & 0x03
@ecn.setter
def ecn(self, val):
self.traffic_class = (self.diff_serv << 2) | val
if not PY2 and not PY34:
packet_len.__doc__ = IPHeader.packet_len.__doc__

View File

@@ -0,0 +1,82 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Fabio Falcinelli, Maximilian Hils
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import struct
from pydivert.packet.header import Header, PayloadMixin, PortMixin
from pydivert.util import indexbyte as i, flag_property, raw_property
class TCPHeader(Header, PayloadMixin, PortMixin):
ns = flag_property("ns", 12, 0b00000001)
cwr = flag_property("cwr", 13, 0b10000000)
ece = flag_property("ece", 13, 0b01000000)
urg = flag_property("syn", 13, 0b00100000)
ack = flag_property("ack", 13, 0b00010000)
psh = flag_property("psh", 13, 0b00001000)
rst = flag_property("rst", 13, 0b00000100)
syn = flag_property("syn", 13, 0b00000010)
fin = flag_property("fin", 13, 0b00000001)
@property
def header_len(self):
"""
The TCP header length.
"""
return self.data_offset * 4
seq_num = raw_property('!I', 4, docs='The sequence number field.')
ack_num = raw_property('!I', 8, docs='The acknowledgement number field.')
window_size = raw_property('!H', 14, docs='The size of the receive window in bytes.')
cksum = raw_property('!H', 16, docs='The TCP header checksum field.')
urg_ptr = raw_property('!H', 18, docs='The Urgent Pointer field.')
@property
def data_offset(self):
"""
The size of TCP header in 32bit words.
"""
return i(self.raw[12]) >> 4
@data_offset.setter
def data_offset(self, val):
if val < 5 or val > 15:
raise ValueError("TCP data offset must be greater or equal than 5 and less than 15.")
struct.pack_into('!B', self.raw, 12, (val << 4) | (self.reserved << 1) | self.ns)
@property
def reserved(self):
"""
The reserved field.
"""
return (i(self.raw[12]) >> 1) & 0x07
@reserved.setter
def reserved(self, val):
struct.pack_into('!B', self.raw, 12, (self.data_offset << 4) | (val << 1) | self.ns)
@property
def control_bits(self):
"""
The Control Bits field.
"""
return struct.unpack_from('!H', self.raw, 12)[0] & 0x01FF
@control_bits.setter
def control_bits(self, val):
struct.pack_into('!H', self.raw, 12, (self.data_offset << 12) | (self.reserved << 9) | (val & 0x01FF))

View File

@@ -0,0 +1,45 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Fabio Falcinelli, Maximilian Hils
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import struct
from pydivert.packet.header import Header, PayloadMixin, PortMixin
from pydivert.util import PY2, PY34, raw_property
class UDPHeader(Header, PayloadMixin, PortMixin):
header_len = 8
@property
def payload(self):
return PayloadMixin.payload.fget(self)
@payload.setter
def payload(self, val):
PayloadMixin.payload.fset(self, val)
self.payload_len = len(val)
if not PY2 and not PY34:
payload.__doc__ = PayloadMixin.payload.__doc__
@property
def payload_len(self):
return struct.unpack_from("!H", self.raw, 4)[0] - 8
@payload_len.setter
def payload_len(self, val):
self.raw[4:6] = struct.pack("!H", val + 8)
cksum = raw_property('!H', 6, docs='The UDP header checksum field.')