diff --git a/scapy/automaton.py b/scapy/automaton.py index 15179e306bb..a12fb6953c5 100644 --- a/scapy/automaton.py +++ b/scapy/automaton.py @@ -16,6 +16,7 @@ import logging import os import random +import socket import sys import threading import time @@ -77,7 +78,7 @@ def select_objects(inputs, remain): [b] :param inputs: objects to process - :param remain: timeout. If 0, return []. + :param remain: timeout. If 0, poll. """ if not WINDOWS: return select.select(inputs, [], [], remain)[0] @@ -901,35 +902,33 @@ def __init__(self, wr # type: Union[int, ObjectPipe[bytes], None] ): # type: (...) -> None - if rd is not None and not isinstance(rd, (int, ObjectPipe)): - rd = rd.fileno() - if wr is not None and not isinstance(wr, (int, ObjectPipe)): - wr = wr.fileno() self.rd = rd self.wr = wr + if isinstance(self.rd, socket.socket): + self.__selectable_force_select__ = True def fileno(self): # type: () -> int - if isinstance(self.rd, ObjectPipe): - return self.rd.fileno() - elif isinstance(self.rd, int): + if isinstance(self.rd, int): return self.rd + elif self.rd: + return self.rd.fileno() return 0 def read(self, n=65535): # type: (int) -> Optional[bytes] - if isinstance(self.rd, ObjectPipe): - return self.rd.recv(n) - elif isinstance(self.rd, int): + if isinstance(self.rd, int): return os.read(self.rd, n) + elif self.rd: + return self.rd.recv(n) return None def write(self, msg): # type: (bytes) -> int - if isinstance(self.wr, ObjectPipe): - return self.wr.send(msg) - elif isinstance(self.wr, int): + if isinstance(self.wr, int): return os.write(self.wr, msg) + elif self.wr: + return self.wr.send(msg) return 0 def recv(self, n=65535): diff --git a/scapy/layers/inet.py b/scapy/layers/inet.py index 95c07073800..ad12ce82515 100644 --- a/scapy/layers/inet.py +++ b/scapy/layers/inet.py @@ -21,8 +21,16 @@ from scapy.base_classes import Gen, Net from scapy.data import ETH_P_IP, ETH_P_ALL, DLT_RAW, DLT_RAW_ALT, DLT_IPV4, \ IP_PROTOS, TCP_SERVICES, UDP_SERVICES -from scapy.layers.l2 import Ether, Dot3, getmacbyip, CookedLinux, GRE, SNAP, \ - Loopback +from scapy.layers.l2 import ( + CookedLinux, + Dot3, + Ether, + GRE, + Loopback, + SNAP, + arpcachepoison, + getmacbyip, +) from scapy.compat import raw, chb, orb, bytes_encode, Optional from scapy.config import conf from scapy.fields import ( @@ -1888,15 +1896,18 @@ class TCP_client(Automaton): :param ip: the ip to connect to :param port: + :param src: (optional) use another source IP """ - def parse_args(self, ip, port, *args, **kargs): + def parse_args(self, ip, port, srcip=None, **kargs): from scapy.sessions import TCPSession self.dst = str(Net(ip)) self.dport = port self.sport = random.randrange(0, 2**16) - self.l4 = IP(dst=ip) / TCP(sport=self.sport, dport=self.dport, flags=0, - seq=random.randrange(0, 2**32)) + self.l4 = IP(dst=ip, src=srcip) / TCP( + sport=self.sport, dport=self.dport, + flags=0, seq=random.randrange(0, 2**32) + ) self.src = self.l4.src self.sack = self.l4[TCP].ack self.rel_seq = None @@ -2160,6 +2171,66 @@ def fragleak2(target, timeout=0.4, onlyasc=0, count=None): pass +@conf.commands.register +class connect_from_ip: + """ + Open a TCP socket to a host:port while spoofing another IP. + + :param host: the host to connect to + :param port: the port to connect to + :param srcip: the IP to spoof. the cache of the gateway will + be poisonned with this IP. + :param poison: (optional, default True) ARP poison the gateway (or next hop), + so that it answers us. + :param timeout: (optional) the socket timeout. + + Example - Connect to 192.168.0.1:80 spoofing 192.168.0.2:: + + from scapy.layers.http import HTTP, HTTPRequest + client = connect_from_ip("192.168.0.1", 80, "192.168.0.2") + sock = SSLStreamSocket(client.sock, HTTP) + resp = sock.sr1(HTTP() / HTTPRequest(Path="/")) + + Example - Connect to 192.168.0.1:443 with TLS wrapping spoofing 192.168.0.2:: + + import ssl + from scapy.layers.http import HTTP, HTTPRequest + context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + context.check_hostname = False + context.verify_mode = ssl.CERT_NONE + client = connect_from_ip("192.168.0.1", 443, "192.168.0.2") + sock = context.wrap_socket(client.sock) + sock = SSLStreamSocket(client.sock, HTTP) + resp = sock.sr1(HTTP() / HTTPRequest(Path="/")) + """ + + def __init__(self, host, port, srcip, poison=True, timeout=1): + host = str(Net(host)) + # poison the next hop + if poison: + gateway = conf.route.route(host)[2] + if gateway == "0.0.0.0": + # on lan + gateway = host + arpcachepoison(gateway, srcip, count=1, interval=0, verbose=0) + # create a socket pair + self._sock, self.sock = socket.socketpair() + self.sock.settimeout(timeout) + self.client = TCP_client( + host, port, + srcip=srcip, + external_fd={"tcp": self._sock}, + ) + # start the TCP_client + self.client.runbg() + + def close(self): + self.client.stop() + self.client.destroy() + self.sock.close() + self._sock.close() + + class ICMPEcho_am(AnsweringMachine): """Responds to ICMP Echo-Requests (ping)""" function_name = "icmpechod" diff --git a/scapy/layers/l2.py b/scapy/layers/l2.py index ea3e05d808c..5ac5db7ce91 100644 --- a/scapy/layers/l2.py +++ b/scapy/layers/l2.py @@ -773,7 +773,9 @@ def arpcachepoison( target, # type: Union[str, List[str]] addresses, # type: Union[str, Tuple[str, str], List[Tuple[str, str]]] broadcast=False, # type: bool + count=None, # type: Optional[int] interval=15, # type: int + **kwargs, # type: Any ): # type: (...) -> None """Poison targets' ARP cache @@ -815,9 +817,12 @@ def arpcachepoison( hwsrc=y, hwdst="00:00:00:00:00:00") for x, y in couple_list ] + if count is not None: + sendp(p, iface_hint=str_target, count=count, inter=interval, **kwargs) + return try: while True: - sendp(p, iface_hint=str_target) + sendp(p, iface_hint=str_target, **kwargs) time.sleep(interval) except KeyboardInterrupt: pass diff --git a/scapy/supersocket.py b/scapy/supersocket.py index 32526d78476..eff4589cf55 100644 --- a/scapy/supersocket.py +++ b/scapy/supersocket.py @@ -373,6 +373,7 @@ def send(self, x): class SimpleSocket(SuperSocket): desc = "wrapper around a classic socket" + __selectable_force_select__ = True def __init__(self, sock): # type: (socket.socket) -> None