220 lines
9 KiB
Python
220 lines
9 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>
|
||
|
# Copyright (C) Gabriel Potter <gabriel@potter.fr>
|
||
|
# This program is published under a GPLv2 license
|
||
|
|
||
|
"""
|
||
|
Native Microsoft Windows sockets (L3 only)
|
||
|
|
||
|
## Notice: ICMP packets
|
||
|
|
||
|
DISCLAIMER: Please use Npcap/Winpcap to send/receive ICMP. It is going to work.
|
||
|
Below is some additional information, mainly implemented in a testing purpose.
|
||
|
|
||
|
When in native mode, everything goes through the Windows kernel.
|
||
|
This firstly requires that the Firewall is open. Be sure it allows ICMPv4/6
|
||
|
packets in and out.
|
||
|
Windows may drop packets that it finds wrong. for instance, answers to
|
||
|
ICMP packets with id=0 or seq=0 may be dropped. It means that sent packets
|
||
|
should (most of the time) be perfectly built.
|
||
|
|
||
|
A perfectly built ICMP req packet on Windows means that its id is 1, its
|
||
|
checksum (IP and ICMP) are correctly built, but also that its seq number is
|
||
|
in the "allowed range".
|
||
|
In fact, every time an ICMP packet is sent on Windows, a global sequence
|
||
|
number is increased, which is only reset at boot time. The seq number of the
|
||
|
received ICMP packet must be in the range [current, current + 3] to be valid,
|
||
|
and received by the socket. The current number is quite hard to get, thus we
|
||
|
provide in this module the get_actual_icmp_seq() function.
|
||
|
|
||
|
Example:
|
||
|
>>> conf.use_pcap = False
|
||
|
>>> a = conf.L3socket()
|
||
|
# This will (most likely) work:
|
||
|
>>> current = get_current_icmp_seq()
|
||
|
>>> a.sr(IP(dst="www.google.com", ttl=128)/ICMP(id=1, seq=current))
|
||
|
# This won't:
|
||
|
>>> a.sr(IP(dst="www.google.com", ttl=128)/ICMP())
|
||
|
|
||
|
PS: on computers where the firewall isn't open, Windows temporarily opens it
|
||
|
when using the `ping` util from cmd.exe. One can first call a ping on cmd,
|
||
|
then do custom calls through the socket using get_current_icmp_seq(). See
|
||
|
the tests (windows.uts) for an example.
|
||
|
"""
|
||
|
|
||
|
import io
|
||
|
import os
|
||
|
import socket
|
||
|
import subprocess
|
||
|
import time
|
||
|
|
||
|
from scapy.automaton import SelectableObject
|
||
|
from scapy.arch.common import _select_nonblock
|
||
|
from scapy.arch.windows.structures import GetIcmpStatistics
|
||
|
from scapy.compat import raw
|
||
|
from scapy.config import conf
|
||
|
from scapy.data import MTU
|
||
|
from scapy.error import Scapy_Exception, warning
|
||
|
from scapy.supersocket import SuperSocket
|
||
|
|
||
|
# Watch out for import loops (inet...)
|
||
|
|
||
|
|
||
|
class L3WinSocket(SuperSocket, SelectableObject):
|
||
|
desc = "a native Layer 3 (IPv4) raw socket under Windows"
|
||
|
nonblocking_socket = True
|
||
|
__slots__ = ["promisc", "cls", "ipv6", "proto"]
|
||
|
|
||
|
def __init__(self, iface=None, proto=socket.IPPROTO_IP,
|
||
|
ttl=128, ipv6=False, promisc=True, **kwargs):
|
||
|
from scapy.layers.inet import IP
|
||
|
from scapy.layers.inet6 import IPv6
|
||
|
for kwarg in kwargs:
|
||
|
warning("Dropping unsupported option: %s" % kwarg)
|
||
|
af = socket.AF_INET6 if ipv6 else socket.AF_INET
|
||
|
self.proto = proto
|
||
|
if ipv6:
|
||
|
from scapy.arch import get_if_addr6
|
||
|
self.host_ip6 = get_if_addr6(conf.iface) or "::1"
|
||
|
if proto == socket.IPPROTO_IP:
|
||
|
# We'll restrict ourselves to UDP, as TCP isn't bindable
|
||
|
# on AF_INET6
|
||
|
self.proto = socket.IPPROTO_UDP
|
||
|
# On Windows, with promisc=False, you won't get much
|
||
|
self.ipv6 = ipv6
|
||
|
self.cls = IPv6 if ipv6 else IP
|
||
|
self.promisc = promisc
|
||
|
# Notes:
|
||
|
# - IPPROTO_RAW only works to send packets.
|
||
|
# - IPPROTO_IPV6 exists in MSDN docs, but using it will result in
|
||
|
# no packets being received. Same for its options (IPV6_HDRINCL...)
|
||
|
# However, using IPPROTO_IP with AF_INET6 will still receive
|
||
|
# the IPv6 packets
|
||
|
try:
|
||
|
self.ins = socket.socket(af,
|
||
|
socket.SOCK_RAW,
|
||
|
self.proto)
|
||
|
self.outs = socket.socket(af,
|
||
|
socket.SOCK_RAW,
|
||
|
socket.IPPROTO_RAW)
|
||
|
except OSError as e:
|
||
|
if e.errno == 10013:
|
||
|
raise OSError("Windows native L3 Raw sockets are only "
|
||
|
"usable as administrator ! "
|
||
|
"Install Winpcap/Npcap to workaround !")
|
||
|
raise
|
||
|
self.ins.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||
|
self.outs.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||
|
self.ins.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 2**30)
|
||
|
self.outs.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 2**30)
|
||
|
# IOCTL Include IP headers
|
||
|
self.ins.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1)
|
||
|
self.outs.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1)
|
||
|
# set TTL
|
||
|
self.ins.setsockopt(socket.IPPROTO_IP, socket.IP_TTL, ttl)
|
||
|
self.outs.setsockopt(socket.IPPROTO_IP, socket.IP_TTL, ttl)
|
||
|
# Bind on all ports
|
||
|
iface = iface or conf.iface
|
||
|
host = iface.ip if iface.ip else socket.gethostname()
|
||
|
self.ins.bind((host, 0))
|
||
|
self.ins.setblocking(False)
|
||
|
# Get as much data as possible: reduce what is cropped
|
||
|
if ipv6:
|
||
|
try: # Not all Windows versions
|
||
|
self.ins.setsockopt(socket.IPPROTO_IPV6,
|
||
|
socket.IPV6_RECVTCLASS, 1)
|
||
|
self.ins.setsockopt(socket.IPPROTO_IPV6,
|
||
|
socket.IPV6_HOPLIMIT, 1)
|
||
|
except (OSError, socket.error):
|
||
|
pass
|
||
|
else:
|
||
|
try: # Not Windows XP
|
||
|
self.ins.setsockopt(socket.IPPROTO_IP,
|
||
|
socket.IP_RECVDSTADDR, 1)
|
||
|
except (OSError, socket.error):
|
||
|
pass
|
||
|
try: # Windows 10+ recent builds only
|
||
|
self.ins.setsockopt(socket.IPPROTO_IP, socket.IP_RECVTTL, 1)
|
||
|
except (OSError, socket.error):
|
||
|
pass
|
||
|
if promisc:
|
||
|
# IOCTL Receive all packets
|
||
|
self.ins.ioctl(socket.SIO_RCVALL, socket.RCVALL_ON)
|
||
|
|
||
|
def send(self, x):
|
||
|
data = raw(x)
|
||
|
if self.cls not in x:
|
||
|
raise Scapy_Exception("L3WinSocket can only send IP/IPv6 packets !"
|
||
|
" Install Npcap/Winpcap to send more")
|
||
|
dst_ip = str(x[self.cls].dst)
|
||
|
self.outs.sendto(data, (dst_ip, 0))
|
||
|
|
||
|
def nonblock_recv(self, x=MTU):
|
||
|
return self.recv()
|
||
|
|
||
|
# https://docs.microsoft.com/en-us/windows/desktop/winsock/tcp-ip-raw-sockets-2 # noqa: E501
|
||
|
# - For IPv4 (address family of AF_INET), an application receives the IP
|
||
|
# header at the front of each received datagram regardless of the
|
||
|
# IP_HDRINCL socket option.
|
||
|
# - For IPv6 (address family of AF_INET6), an application receives
|
||
|
# everything after the last IPv6 header in each received datagram
|
||
|
# regardless of the IPV6_HDRINCL socket option. The application does
|
||
|
# not receive any IPv6 headers using a raw socket.
|
||
|
|
||
|
def recv_raw(self, x=MTU):
|
||
|
try:
|
||
|
data, address = self.ins.recvfrom(x)
|
||
|
except io.BlockingIOError:
|
||
|
return None, None, None
|
||
|
from scapy.layers.inet import IP
|
||
|
from scapy.layers.inet6 import IPv6
|
||
|
if self.ipv6:
|
||
|
# AF_INET6 does not return the IPv6 header. Let's build it
|
||
|
# (host, port, flowinfo, scopeid)
|
||
|
host, _, flowinfo, _ = address
|
||
|
header = raw(IPv6(src=host,
|
||
|
dst=self.host_ip6,
|
||
|
fl=flowinfo,
|
||
|
nh=self.proto, # fixed for AF_INET6
|
||
|
plen=len(data)))
|
||
|
return IPv6, header + data, time.time()
|
||
|
else:
|
||
|
return IP, data, time.time()
|
||
|
|
||
|
def check_recv(self):
|
||
|
return True
|
||
|
|
||
|
def close(self):
|
||
|
if not self.closed and self.promisc:
|
||
|
self.ins.ioctl(socket.SIO_RCVALL, socket.RCVALL_OFF)
|
||
|
super(L3WinSocket, self).close()
|
||
|
|
||
|
@staticmethod
|
||
|
def select(sockets, remain=None):
|
||
|
return _select_nonblock(sockets, remain=remain)
|
||
|
|
||
|
|
||
|
class L3WinSocket6(L3WinSocket):
|
||
|
desc = "a native Layer 3 (IPv6) raw socket under Windows"
|
||
|
|
||
|
def __init__(self, **kwargs):
|
||
|
super(L3WinSocket6, self).__init__(ipv6=True, **kwargs)
|
||
|
|
||
|
|
||
|
def open_icmp_firewall(host):
|
||
|
"""Temporarily open the ICMP firewall. Tricks Windows into allowing
|
||
|
ICMP packets for a short period of time (~ 1 minute)"""
|
||
|
# We call ping with a timeout of 1ms: will return instantly
|
||
|
with open(os.devnull, 'wb') as DEVNULL:
|
||
|
return subprocess.Popen("ping -4 -w 1 -n 1 %s" % host,
|
||
|
shell=True,
|
||
|
stdout=DEVNULL,
|
||
|
stderr=DEVNULL).wait()
|
||
|
|
||
|
|
||
|
def get_current_icmp_seq():
|
||
|
"""See help(scapy.arch.windows.native) for more information.
|
||
|
Returns the current ICMP seq number."""
|
||
|
return GetIcmpStatistics()['stats']['icmpOutStats']['dwEchos']
|