• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1## This file is part of Scapy
2## See http://www.secdev.org/projects/scapy for more informations
3## Copyright (C) Philippe Biondi <phil@secdev.org>
4## Copyright (C) Gabriel Potter <gabriel@potter.fr>
5## This program is published under a GPLv2 license
6
7"""
8Customizations needed to support Microsoft Windows.
9"""
10from __future__ import absolute_import
11from __future__ import print_function
12import os, re, sys, socket, time, itertools, platform
13import subprocess as sp
14from glob import glob
15import tempfile
16from threading import Thread, Event
17
18import scapy
19from scapy.config import conf, ConfClass
20from scapy.error import Scapy_Exception, log_loading, log_runtime, warning
21from scapy.utils import atol, itom, inet_aton, inet_ntoa, PcapReader, pretty_list
22from scapy.utils6 import construct_source_candidate_set
23from scapy.base_classes import Gen, Net, SetGen
24from scapy.data import MTU, ETHER_BROADCAST, ETH_P_ARP
25
26import scapy.modules.six as six
27from scapy.modules.six.moves import range, zip, input
28from scapy.compat import plain_str
29
30conf.use_pcap = False
31conf.use_dnet = False
32conf.use_winpcapy = True
33
34WINDOWS = (os.name == 'nt')
35NEW_RELEASE = None
36
37#hot-patching socket for missing variables on Windows
38import socket
39if not hasattr(socket, 'IPPROTO_IPIP'):
40    socket.IPPROTO_IPIP=4
41if not hasattr(socket, 'IPPROTO_AH'):
42    socket.IPPROTO_AH=51
43if not hasattr(socket, 'IPPROTO_ESP'):
44    socket.IPPROTO_ESP=50
45if not hasattr(socket, 'IPPROTO_GRE'):
46    socket.IPPROTO_GRE=47
47
48from scapy.arch import pcapdnet
49from scapy.arch.pcapdnet import *
50
51_WlanHelper = NPCAP_PATH + "\\WlanHelper.exe"
52
53import scapy.consts
54
55def is_new_release(ignoreVBS=False):
56    if NEW_RELEASE and conf.prog.powershell is not None:
57        return True
58    release = platform.release()
59    if conf.prog.powershell is None and not ignoreVBS:
60        return False
61    try:
62         if float(release) >= 8:
63             return True
64    except ValueError:
65        if (release=="post2008Server"):
66            return True
67    return False
68
69def _encapsulate_admin(cmd):
70    """Encapsulate a command with an Administrator flag"""
71    # To get admin access, we start a new powershell instance with admin
72    # rights, which will execute the command
73    return "Start-Process PowerShell -windowstyle hidden -Wait -Verb RunAs -ArgumentList '-command &{%s}'" % cmd
74
75class _PowershellManager(Thread):
76    """Instance used to send multiple commands on the same Powershell process.
77    Will be instantiated on loading and automatically stopped.
78    """
79    def __init__(self):
80        # Start & redirect input
81        if conf.prog.powershell:
82            self.process = sp.Popen([conf.prog.powershell,
83                                     "-NoLogo", "-NonInteractive",  # Do not print headers
84                                     "-Command", "-"],  # Listen commands from stdin
85                             stdout=sp.PIPE,
86                             stdin=sp.PIPE,
87                             stderr=sp.STDOUT)
88            self.cmd = False
89        else:  # Fallback on CMD (powershell-only commands will fail, but scapy use the VBS fallback)
90            self.process = sp.Popen([conf.prog.cmd],
91                             stdout=sp.PIPE,
92                             stdin=sp.PIPE,
93                             stderr=sp.STDOUT)
94            self.cmd = True
95        self.buffer = []
96        self.running = True
97        self.query_complete = Event()
98        Thread.__init__(self)
99        self.daemon = True
100        self.start()
101        if self.cmd:
102            self.query(["echo @off"])  # Remove header
103        else:
104            self.query(["$FormatEnumerationLimit=-1"])  # Do not crop long IP lists
105
106    def run(self):
107        while self.running:
108            read_line = self.process.stdout.readline().strip()
109            if read_line == b"scapy_end":
110                self.query_complete.set()
111            else:
112                self.buffer.append(read_line.decode("utf8", "ignore") if six.PY3 else read_line)
113
114    def query(self, command):
115        self.query_complete.clear()
116        if not self.running:
117            self.__init__(self)
118        # Call powershell query using running process
119        self.buffer = []
120        # 'scapy_end' is used as a marker of the end of execution
121        query = " ".join(command) + ("&" if self.cmd else ";") + " echo scapy_end\n"
122        self.process.stdin.write(query.encode())
123        self.process.stdin.flush()
124        self.query_complete.wait()
125        return self.buffer[1:]  # Crops first line: the command
126
127    def close(self):
128        self.running = False
129        try:
130            self.process.stdin.write("exit\n")
131            self.process.terminate()
132        except:
133            pass
134
135def _exec_query_ps(cmd, fields):
136    """Execute a PowerShell query, using the cmd command,
137    and select and parse the provided fields.
138    """
139    if not conf.prog.powershell:
140        raise OSError("Scapy could not detect powershell !")
141    # Build query
142    query_cmd = cmd + ['|', 'select %s' % ', '.join(fields),  # select fields
143                       '|', 'fl',  # print as a list
144                       '|', 'out-string', '-Width', '4096']  # do not crop
145    l=[]
146    # Ask the powershell manager to process the query
147    stdout = POWERSHELL_PROCESS.query(query_cmd)
148    # Process stdout
149    for line in stdout:
150        if not line.strip(): # skip empty lines
151            continue
152        sl = line.split(':', 1)
153        if len(sl) == 1:
154            l[-1] += sl[0].strip()
155            continue
156        else:
157            l.append(sl[1].strip())
158        if len(l) == len(fields):
159            yield l
160            l=[]
161
162def _vbs_exec_code(code, split_tag="@"):
163    if not conf.prog.cscript:
164        raise OSError("Scapy could not detect cscript !")
165    tmpfile = tempfile.NamedTemporaryFile(mode="wb", suffix=".vbs", delete=False)
166    tmpfile.write(raw(code))
167    tmpfile.close()
168    ps = sp.Popen([conf.prog.cscript, tmpfile.name],
169                  stdout=sp.PIPE, stderr=open(os.devnull),
170                  universal_newlines=True)
171    for _ in range(3):
172        # skip 3 first lines
173        ps.stdout.readline()
174    for line in ps.stdout:
175        data = line.replace("\n", "").split(split_tag)
176        for l in data:
177            yield l
178    os.unlink(tmpfile.name)
179
180def _vbs_get_hardware_iface_guid(devid):
181    try:
182        devid = str(int(devid) + 1)
183        guid = next(iter(_vbs_exec_code("""WScript.Echo CreateObject("WScript.Shell").RegRead("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\NetworkCards\\%s\\ServiceName")
184""" % devid)))
185        guid = guid[:-1] if guid.endswith('}\n') else guid
186        if guid.startswith('{') and guid.endswith('}'):
187            return guid
188    except StopIteration:
189        return None
190
191# Some names differ between VBS and PS
192## None: field will not be returned under VBS
193_VBS_WMI_FIELDS = {
194    "Win32_NetworkAdapter": {
195        "InterfaceDescription": "Description",
196        # Note: when using VBS, the GUID is not the same than with Powershell
197        # So we use get the device ID instead, then use _vbs_get_hardware_iface_guid
198        # To get its real GUID
199        "GUID": "DeviceID"
200    },
201    "*": {
202        "Status": "State"
203    }
204}
205
206_VBS_WMI_REPLACE = {
207    "Win32_NetworkAdapterConfiguration": {
208        "line.IPAddress": "\"{\" & Join( line.IPAddress, \", \" ) & \"}\"",
209    }
210}
211
212_VBS_WMI_OUTPUT = {
213    "Win32_NetworkAdapter": {
214        "DeviceID": _vbs_get_hardware_iface_guid,
215    }
216}
217
218def _exec_query_vbs(cmd, fields):
219    """Execute a query using VBS. Currently Get-WmiObject, Get-Service
220    queries are supported.
221
222    """
223    if not(len(cmd) == 2 and cmd[0] in ["Get-WmiObject", "Get-Service"]):
224        return
225    action = cmd[0]
226    fields = [_VBS_WMI_FIELDS.get(cmd[1], _VBS_WMI_FIELDS.get("*", {})).get(fld, fld) for fld in fields]
227    parsed_command = "WScript.Echo " + " & \" @ \" & ".join("line.%s" % fld for fld in fields
228                           if fld is not None)
229    # The IPAddress is an array: convert it to a string
230    for key,val in _VBS_WMI_REPLACE.get(cmd[1], {}).items():
231        parsed_command = parsed_command.replace(key, val)
232    if action == "Get-WmiObject":
233        values = _vbs_exec_code("""Set wmi = GetObject("winmgmts:")
234Set lines = wmi.InstancesOf("%s")
235On Error Resume Next
236Err.clear
237For Each line in lines
238  %s
239Next
240""" % (cmd[1], parsed_command), "@")
241    elif action == "Get-Service":
242        values = _vbs_exec_code("""serviceName = "%s"
243Set wmi = GetObject("winmgmts://./root/cimv2")
244Set line = wmi.Get("Win32_Service.Name='" & serviceName & "'")
245%s
246""" % (cmd[1], parsed_command), "@")
247
248    while True:
249        yield [None if fld is None else
250               _VBS_WMI_OUTPUT.get(cmd[1], {}).get(fld, lambda x: x)(
251                   next(values).strip()
252               )
253               for fld in fields]
254
255def exec_query(cmd, fields):
256    """Execute a system query using PowerShell if it is available, and
257    using VBS/cscript as a fallback.
258
259    """
260    if conf.prog.powershell is None:
261        return _exec_query_vbs(cmd, fields)
262    return _exec_query_ps(cmd, fields)
263
264def _where(filename, dirs=None, env="PATH"):
265    """Find file in current dir, in deep_lookup cache or in system path"""
266    if dirs is None:
267        dirs = []
268    if not isinstance(dirs, list):
269        dirs = [dirs]
270    if glob(filename):
271        return filename
272    paths = [os.curdir] + os.environ[env].split(os.path.pathsep) + dirs
273    for path in paths:
274        for match in glob(os.path.join(path, filename)):
275            if match:
276                return os.path.normpath(match)
277    raise IOError("File not found: %s" % filename)
278
279def win_find_exe(filename, installsubdir=None, env="ProgramFiles"):
280    """Find executable in current dir, system path or given ProgramFiles subdir"""
281    fns = [filename] if filename.endswith(".exe") else [filename+".exe", filename]
282    for fn in fns:
283        try:
284            if installsubdir is None:
285                path = _where(fn)
286            else:
287                path = _where(fn, dirs=[os.path.join(os.environ[env], installsubdir)])
288        except IOError:
289            path = None
290        else:
291            break
292    return path
293
294
295class WinProgPath(ConfClass):
296    _default = "<System default>"
297    def __init__(self):
298        self._reload()
299
300    def _reload(self):
301        # We try some magic to find the appropriate executables
302        self.pdfreader = win_find_exe("AcroRd32")
303        self.psreader = win_find_exe("gsview32")
304        self.dot = win_find_exe("dot")
305        self.tcpdump = win_find_exe("windump")
306        self.tshark = win_find_exe("tshark")
307        self.tcpreplay = win_find_exe("tcpreplay")
308        self.display = self._default
309        self.hexedit = win_find_exe("hexer")
310        self.sox = win_find_exe("sox")
311        self.wireshark = win_find_exe("wireshark", "wireshark")
312        self.powershell = win_find_exe(
313            "powershell",
314            installsubdir="System32\\WindowsPowerShell\\v1.0",
315            env="SystemRoot"
316        )
317        self.cscript = win_find_exe("cscript", installsubdir="System32",
318                               env="SystemRoot")
319        self.cmd = win_find_exe("cmd", installsubdir="System32",
320                               env="SystemRoot")
321        if self.wireshark:
322            manu_path = load_manuf(os.path.sep.join(self.wireshark.split(os.path.sep)[:-1])+os.path.sep+"manuf")
323            scapy.data.MANUFDB = conf.manufdb = manu_path
324
325        self.os_access = (self.powershell is not None) or (self.cscript is not None)
326
327conf.prog = WinProgPath()
328if not conf.prog.os_access:
329    warning("Scapy did not detect powershell and cscript ! Routes, interfaces and much more won't work !", onlyOnce=True)
330
331if conf.prog.tcpdump and conf.use_npcap and conf.prog.os_access:
332    def test_windump_npcap():
333        """Return wether windump version is correct or not"""
334        try:
335            p_test_windump = sp.Popen([conf.prog.tcpdump, "-help"], stdout=sp.PIPE, stderr=sp.STDOUT)
336            stdout, err = p_test_windump.communicate()
337            _output = stdout.lower()
338            return b"npcap" in _output and not b"winpcap" in _output
339        except:
340            return False
341    windump_ok = test_windump_npcap()
342    if not windump_ok:
343        warning("The installed Windump version does not work with Npcap ! Refer to 'Winpcap/Npcap conflicts' in scapy's doc", onlyOnce=True)
344    del windump_ok
345
346# Auto-detect release
347NEW_RELEASE = is_new_release()
348
349class PcapNameNotFoundError(Scapy_Exception):
350    pass
351
352def is_interface_valid(iface):
353    if "guid" in iface and iface["guid"]:
354        # Fix '-' instead of ':'
355        if "mac" in iface:
356            iface["mac"] = iface["mac"].replace("-", ":")
357        return True
358    return False
359
360def get_windows_if_list():
361    """Returns windows interfaces."""
362    if not conf.prog.os_access:
363        return []
364    if is_new_release():
365        # This works only starting from Windows 8/2012 and up. For older Windows another solution is needed
366        # Careful: this is weird, but Get-NetAdaptater works like: (Name isn't the interface name)
367        # Name                      InterfaceDescription                    ifIndex Status       MacAddress             LinkSpeed
368        # ----                      --------------------                    ------- ------       ----------             ---------
369        # Ethernet                  Killer E2200 Gigabit Ethernet Contro...      13 Up           D0-50-99-56-DD-F9         1 Gbps
370        query = exec_query(['Get-NetAdapter'],
371                           ['InterfaceDescription', 'InterfaceIndex', 'Name',
372                            'InterfaceGuid', 'MacAddress', 'InterfaceAlias']) # It is normal that it is in this order
373    else:
374        query = exec_query(['Get-WmiObject', 'Win32_NetworkAdapter'],
375                           ['Name', 'InterfaceIndex', 'InterfaceDescription',
376                            'GUID', 'MacAddress', 'NetConnectionID'])
377    return [
378        iface for iface in
379        (dict(zip(['name', 'win_index', 'description', 'guid', 'mac', 'netid'], line))
380         for line in query)
381        if is_interface_valid(iface)
382    ]
383
384def get_ips(v6=False):
385    """Returns all available IPs matching to interfaces, using the windows system.
386    Should only be used as a WinPcapy fallback."""
387    res = {}
388    for descr, ipaddr in exec_query(['Get-WmiObject',
389                                     'Win32_NetworkAdapterConfiguration'],
390                                    ['Description', 'IPAddress']):
391        if ipaddr.strip():
392            res[descr] = ipaddr.split(",", 1)[v6].strip('{}').strip()
393    return res
394
395def get_ip_from_name(ifname, v6=False):
396    """Backward compatibility: indirectly calls get_ips
397    Deprecated."""
398    return get_ips(v6=v6).get(ifname, "")
399
400class NetworkInterface(object):
401    """A network interface of your local host"""
402
403    def __init__(self, data=None):
404        self.name = None
405        self.ip = None
406        self.mac = None
407        self.pcap_name = None
408        self.description = None
409        self.data = data
410        self.invalid = False
411        self.raw80211 = None
412        if data is not None:
413            self.update(data)
414
415    def update(self, data):
416        """Update info about network interface according to given dnet dictionary"""
417        if 'netid' in data and data['netid'] == scapy.consts.LOOPBACK_NAME:
418            # Force LOOPBACK_NAME: Some Windows systems overwrite 'name'
419            self.name = scapy.consts.LOOPBACK_NAME
420        else:
421            self.name = data['name']
422        self.description = data['description']
423        self.win_index = data['win_index']
424        self.guid = data['guid']
425        if 'invalid' in data:
426            self.invalid = data['invalid']
427        # Other attributes are optional
428        self._update_pcapdata()
429
430        try:
431            # Npcap loopback interface
432            if self.name == scapy.consts.LOOPBACK_NAME and conf.use_npcap:
433                # https://nmap.org/npcap/guide/npcap-devguide.html
434                self.mac = "00:00:00:00:00:00"
435                self.ip = "127.0.0.1"
436                conf.cache_ipaddrs[self.pcap_name] = socket.inet_aton(self.ip)
437                return
438            else:
439                self.mac = data['mac']
440        except KeyError:
441            pass
442
443        try:
444            self.ip = socket.inet_ntoa(get_if_raw_addr(self))
445        except (TypeError, NameError):
446            pass
447
448        try:
449            # Windows native loopback interface
450            if not self.ip and self.name == scapy.consts.LOOPBACK_NAME:
451                self.ip = "127.0.0.1"
452                conf.cache_ipaddrs[self.pcap_name] = socket.inet_aton(self.ip)
453        except (KeyError, AttributeError, NameError) as e:
454            print(e)
455
456    def _update_pcapdata(self):
457        if self.is_invalid():
458            return
459        for i in get_if_list():
460            if i.endswith(self.data['guid']):
461                self.pcap_name = i
462                return
463
464        raise PcapNameNotFoundError
465
466    def is_invalid(self):
467        return self.invalid
468
469    def _check_npcap_requirement(self):
470        if not conf.use_npcap:
471            raise OSError("This operation requires Npcap.")
472        if self.raw80211 is None:
473            # This checks if npcap has Dot11 enabled and if the interface is compatible,
474            # by looking for the npcap/Parameters/Dot11Adapters key in the registry.
475            try:
476                dot11adapters = next(iter(_vbs_exec_code("""WScript.Echo CreateObject("WScript.Shell").RegRead("HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\npcap\\Parameters\\Dot11Adapters")""")))
477            except StopIteration:
478                pass
479            else:
480                self.raw80211 = ("\\Device\\" + self.guid).lower() in dot11adapters.lower()
481        if not self.raw80211:
482            raise Scapy_Exception("This interface does not support raw 802.11")
483
484    def mode(self):
485        """Get the interface operation mode.
486        Only available with Npcap."""
487        self._check_npcap_requirement()
488        return sp.Popen([_WlanHelper, self.guid[1:-1], "mode"], stdout=sp.PIPE).communicate()[0].strip()
489
490    def availablemodes(self):
491        """Get all available interface modes.
492        Only available with Npcap."""
493        # According to https://nmap.org/npcap/guide/npcap-devguide.html#npcap-feature-dot11
494        self._check_npcap_requirement()
495        return sp.Popen([_WlanHelper, self.guid[1:-1], "modes"], stdout=sp.PIPE).communicate()[0].strip().split(",")
496
497    def setmode(self, mode):
498        """Set the interface mode. It can be:
499        - 0 or managed: Managed Mode (aka "Extensible Station Mode")
500        - 1 or monitor: Monitor Mode (aka "Network Monitor Mode")
501        - 2 or master: Master Mode (aka "Extensible Access Point") (supported from Windows 7 and later)
502        - 3 or wfd_device: The Wi-Fi Direct Device operation mode (supported from Windows 8 and later)
503        - 4 or wfd_owner: The Wi-Fi Direct Group Owner operation mode (supported from Windows 8 and later)
504        - 5 or wfd_client: The Wi-Fi Direct Client operation mode (supported from Windows 8 and later)
505        Only available with Npcap."""
506        # According to https://nmap.org/npcap/guide/npcap-devguide.html#npcap-feature-dot11
507        self._check_npcap_requirement()
508        _modes = {
509            0: "managed",
510            1: "monitor",
511            2: "master",
512            3: "wfd_device",
513            4: "wfd_owner",
514            5: "wfd_client"
515        }
516        m = _modes.get(mode, "unknown") if isinstance(mode, int) else mode
517        return sp.call(_WlanHelper + " " + self.guid[1:-1] + " mode " + m)
518
519    def channel(self):
520        """Get the channel of the interface.
521        Only available with Npcap."""
522        # According to https://nmap.org/npcap/guide/npcap-devguide.html#npcap-feature-dot11
523        self._check_npcap_requirement()
524        return sp.Popen([_WlanHelper, self.guid[1:-1], "channel"],
525                        stdout=sp.PIPE).communicate()[0].strip()
526
527    def setchannel(self, channel):
528        """Set the channel of the interface (1-14):
529        Only available with Npcap."""
530        # According to https://nmap.org/npcap/guide/npcap-devguide.html#npcap-feature-dot11
531        self._check_npcap_requirement()
532        return sp.call(_WlanHelper + " " + self.guid[1:-1] + " channel " + str(channel))
533
534    def frequence(self):
535        """Get the frequence of the interface.
536        Only available with Npcap."""
537        # According to https://nmap.org/npcap/guide/npcap-devguide.html#npcap-feature-dot11
538        self._check_npcap_requirement()
539        return sp.Popen([_WlanHelper, self.guid[1:-1], "freq"], stdout=sp.PIPE).communicate()[0].strip()
540
541    def setfrequence(self, freq):
542        """Set the channel of the interface (1-14):
543        Only available with Npcap."""
544        # According to https://nmap.org/npcap/guide/npcap-devguide.html#npcap-feature-dot11
545        self._check_npcap_requirement()
546        return sp.call(_WlanHelper + " " + self.guid[1:-1] + " freq " + str(freq))
547
548    def availablemodulations(self):
549        """Get all available 802.11 interface modulations.
550        Only available with Npcap."""
551        # According to https://nmap.org/npcap/guide/npcap-devguide.html#npcap-feature-dot11
552        self._check_npcap_requirement()
553        return sp.Popen([_WlanHelper, self.guid[1:-1], "modus"], stdout=sp.PIPE).communicate()[0].strip().split(",")
554
555    def modulation(self):
556        """Get the 802.11 modulation of the interface.
557        Only available with Npcap."""
558        # According to https://nmap.org/npcap/guide/npcap-devguide.html#npcap-feature-dot11
559        self._check_npcap_requirement()
560        return sp.Popen([_WlanHelper, self.guid[1:-1], "modu"], stdout=sp.PIPE).communicate()[0].strip()
561
562    def setmodulation(self, modu):
563        """Set the interface modulation. It can be:
564           - 0: dsss
565           - 1: fhss
566           - 2: irbaseband
567           - 3: ofdm
568           - 4: hrdss
569           - 5: erp
570           - 6: ht
571           - 7: vht
572           - 8: ihv
573           - 9: mimo-ofdm
574           - 10: mimo-ofdm
575        Only available with Npcap."""
576        # According to https://nmap.org/npcap/guide/npcap-devguide.html#npcap-feature-dot11
577        self._check_npcap_requirement()
578        _modus = {
579            0: "dsss",
580            1: "fhss",
581            2: "irbaseband",
582            3: "ofdm",
583            4: "hrdss",
584            5: "erp",
585            6: "ht",
586            7: "vht",
587            8: "ihv",
588            9: "mimo-ofdm",
589            10: "mimo-ofdm",
590        }
591        m = _modus.get(modu, "unknown") if isinstance(modu, int) else modu
592        return sp.call(_WlanHelper + " " + self.guid[1:-1] + " mode " + m)
593
594    def __repr__(self):
595        return "<%s %s %s>" % (self.__class__.__name__, self.name, self.guid)
596
597def pcap_service_name():
598    """Return the pcap adapter service's name"""
599    return "npcap" if conf.use_npcap else "npf"
600
601def pcap_service_status():
602    """Returns a tuple (name, description, started) of the windows pcap adapter"""
603    for i in exec_query(['Get-Service', pcap_service_name()], ['Name', 'DisplayName', 'Status']):
604        name = i[0]
605        description = i[1]
606        started = (i[2].lower().strip() == 'running')
607        if name == pcap_service_name():
608            return (name, description, started)
609    return (None, None, None)
610
611def pcap_service_control(action, askadmin=True):
612    """Util to run pcap control command"""
613    if not conf.prog.powershell:
614        return False
615    command = action + ' ' + pcap_service_name()
616    stdout = POWERSHELL_PROCESS.query([_encapsulate_admin(command) if askadmin else command])
617    return "error" not in "".join(stdout).lower()
618
619def pcap_service_start(askadmin=True):
620    """Starts the pcap adapter. Will ask for admin. Returns True if success"""
621    return pcap_service_control('Start-Service', askadmin=askadmin)
622
623def pcap_service_stop(askadmin=True):
624    """Stops the pcap adapter. Will ask for admin. Returns True if success"""
625    return pcap_service_control('Stop-Service', askadmin=askadmin)
626
627from scapy.modules.six.moves import UserDict
628
629class NetworkInterfaceDict(UserDict):
630    """Store information about network interfaces and convert between names"""
631    def load_from_powershell(self):
632        if not conf.prog.os_access:
633            return
634        ifaces_ips = None
635        for i in get_windows_if_list():
636            try:
637                interface = NetworkInterface(i)
638                self.data[interface.guid] = interface
639                # If no IP address was detected using winpcap and if
640                # the interface is not the loopback one, look for
641                # internal windows interfaces
642                if not interface.ip:
643                    if not ifaces_ips:  # ifaces_ips is used as a cache
644                        ifaces_ips = get_ips()
645                    # If it exists, retrieve the interface's IP from the cache
646                    interface.ip = ifaces_ips.get(interface.name, "")
647            except (KeyError, PcapNameNotFoundError):
648                pass
649
650        if not self.data and conf.use_winpcapy:
651            _detect = pcap_service_status()
652            def _ask_user():
653                if not conf.interactive:
654                    return False
655                while True:
656                    _confir = input("Do you want to start it ? (yes/no) [y]: ").lower().strip()
657                    if _confir in ["yes", "y", ""]:
658                        return True
659                    elif _confir in ["no", "n"]:
660                        return False
661                return False
662            _error_msg = "No match between your pcap and windows network interfaces found. "
663            if _detect[0] and not _detect[2] and not (hasattr(self, "restarted_adapter") and self.restarted_adapter):
664                warning("Scapy has detected that your pcap service is not running !")
665                if not conf.interactive or _ask_user():
666                    succeed = pcap_service_start(askadmin=conf.interactive)
667                    self.restarted_adapter = True
668                    if succeed:
669                        log_loading.info("Pcap service started !")
670                        self.load_from_powershell()
671                        return
672                _error_msg = "Could not start the pcap service ! "
673            warning(_error_msg +
674                    "You probably won't be able to send packets. "
675                    "Deactivating unneeded interfaces and restarting Scapy might help. "
676                    "Check your winpcap and powershell installation, and access rights.", onlyOnce=True)
677        else:
678            # Loading state: remove invalid interfaces
679            self.remove_invalid_ifaces()
680            # Replace LOOPBACK_INTERFACE
681            try:
682                scapy.consts.LOOPBACK_INTERFACE = self.dev_from_name(
683                    scapy.consts.LOOPBACK_NAME,
684                )
685            except:
686                pass
687
688    def dev_from_name(self, name):
689        """Return the first pcap device name for a given Windows
690        device name.
691        """
692        for iface in six.itervalues(self):
693            if iface.name == name:
694                return iface
695        raise ValueError("Unknown network interface %r" % name)
696
697    def dev_from_pcapname(self, pcap_name):
698        """Return Windows device name for given pcap device name."""
699        for iface in six.itervalues(self):
700            if iface.pcap_name == pcap_name:
701                return iface
702        raise ValueError("Unknown pypcap network interface %r" % pcap_name)
703
704    def dev_from_index(self, if_index):
705        """Return interface name from interface index"""
706        for devname, iface in six.iteritems(self):
707            if iface.win_index == str(if_index):
708                return iface
709        if str(if_index) == "1":
710            # Test if the loopback interface is set up
711            if isinstance(scapy.consts.LOOPBACK_INTERFACE, NetworkInterface):
712                return scapy.consts.LOOPBACK_INTERFACE
713        raise ValueError("Unknown network interface index %r" % if_index)
714
715    def remove_invalid_ifaces(self):
716        """Remove all invalid interfaces"""
717        for devname in list(self.keys()):
718            iface = self.data[devname]
719            if iface.is_invalid():
720                self.data.pop(devname)
721
722    def reload(self):
723        """Reload interface list"""
724        self.restarted_adapter = False
725        self.data.clear()
726        self.load_from_powershell()
727
728    def show(self, resolve_mac=True, print_result=True):
729        """Print list of available network interfaces in human readable form"""
730        res = []
731        for iface_name in sorted(self.data):
732            dev = self.data[iface_name]
733            mac = dev.mac
734            if resolve_mac and conf.manufdb:
735                mac = conf.manufdb._resolve_MAC(mac)
736            res.append((str(dev.win_index).ljust(5), str(dev.name).ljust(35), str(dev.ip).ljust(15), mac))
737
738        res = pretty_list(res, [("INDEX", "IFACE", "IP", "MAC")])
739        if print_result:
740            print(res)
741        else:
742            return res
743
744    def __repr__(self):
745        return self.show(print_result=False)
746
747# Init POWERSHELL_PROCESS
748POWERSHELL_PROCESS = _PowershellManager()
749
750IFACES = NetworkInterfaceDict()
751IFACES.load_from_powershell()
752
753def pcapname(dev):
754    """Return pypcap device name for given interface or libdnet/Scapy
755    device name.
756
757    """
758    if isinstance(dev, NetworkInterface):
759        if dev.is_invalid():
760            return None
761        return dev.pcap_name
762    try:
763        return IFACES.dev_from_name(dev).pcap_name
764    except ValueError:
765        if conf.use_pcap:
766            # pcap.pcap() will choose a sensible default for sniffing if
767            # iface=None
768            return None
769        raise
770
771def dev_from_pcapname(pcap_name):
772    """Return libdnet/Scapy device name for given pypcap device name"""
773    return IFACES.dev_from_pcapname(pcap_name)
774
775def dev_from_index(if_index):
776    """Return Windows adapter name for given Windows interface index"""
777    return IFACES.dev_from_index(if_index)
778
779def show_interfaces(resolve_mac=True):
780    """Print list of available network interfaces"""
781    return IFACES.show(resolve_mac)
782
783_orig_open_pcap = pcapdnet.open_pcap
784pcapdnet.open_pcap = lambda iface,*args,**kargs: _orig_open_pcap(pcapname(iface),*args,**kargs)
785
786get_if_raw_hwaddr = pcapdnet.get_if_raw_hwaddr = lambda iface, *args, **kargs: (
787    ARPHDR_ETHER, mac2str(IFACES.dev_from_pcapname(pcapname(iface)).mac)
788)
789
790def _read_routes_xp():
791    # The InterfaceIndex in Win32_IP4RouteTable does not match the
792    # InterfaceIndex in Win32_NetworkAdapter under some platforms
793    # (namely Windows XP): let's try an IP association
794    routes = []
795    partial_routes = []
796    # map local IP addresses to interfaces
797    local_addresses = {iface.ip: iface for iface in six.itervalues(IFACES)}
798    iface_indexes = {}
799    for line in exec_query(['Get-WmiObject', 'Win32_IP4RouteTable'],
800                           ['Name', 'Mask', 'NextHop', 'InterfaceIndex', 'Metric1']):
801        if line[2] in local_addresses:
802            iface = local_addresses[line[2]]
803            # This gives us an association InterfaceIndex <-> interface
804            iface_indexes[line[3]] = iface
805            routes.append((atol(line[0]), atol(line[1]), "0.0.0.0", iface,
806                           iface.ip, int(line[4])))
807        else:
808            partial_routes.append((atol(line[0]), atol(line[1]), line[2],
809                                   line[3], int(line[4])))
810    for dst, mask, gw, ifidx, metric in partial_routes:
811        if ifidx in iface_indexes:
812            iface = iface_indexes[ifidx]
813            routes.append((dst, mask, gw, iface, iface.ip, metric))
814    return routes
815
816def _read_routes_7():
817    routes=[]
818    for line in exec_query(['Get-WmiObject', 'Win32_IP4RouteTable'],
819                           ['Name', 'Mask', 'NextHop', 'InterfaceIndex', 'Metric1']):
820        try:
821            iface = dev_from_index(line[3])
822            ip = "127.0.0.1" if line[3] == "1" else iface.ip # Force loopback on iface 1
823            routes.append((atol(line[0]), atol(line[1]), line[2], iface, ip, int(line[4])))
824        except ValueError:
825            continue
826    return routes
827
828def read_routes():
829    routes = []
830    if not conf.prog.os_access:
831        return routes
832    release = platform.release()
833    try:
834        if is_new_release():
835            routes = _read_routes_post2008()
836        elif release == "XP":
837            routes = _read_routes_xp()
838        else:
839            routes = _read_routes_7()
840    except Exception as e:
841        warning("Error building scapy IPv4 routing table : %s", e, onlyOnce=True)
842    else:
843        if not routes:
844            warning("No default IPv4 routes found. Your Windows release may no be supported and you have to enter your routes manually", onlyOnce=True)
845    return routes
846
847def _get_metrics(ipv6=False):
848    """Returns a dict containing all IPv4 or IPv6 interfaces' metric,
849    ordered by their interface index.
850    """
851    query_cmd = "netsh interface " + ("ipv6" if ipv6 else "ipv4") + " show interfaces level=verbose"
852    stdout = POWERSHELL_PROCESS.query([query_cmd])
853    res = {}
854    _buffer = []
855    _pattern = re.compile(".*:\s+(\d+)")
856    for _line in stdout:
857        if not _line.strip() and len(_buffer) > 0:
858            if_index = re.search(_pattern, _buffer[3]).group(1)
859            if_metric = int(re.search(_pattern, _buffer[5]).group(1))
860            res[if_index] = if_metric
861            _buffer = []
862        else:
863            _buffer.append(_line)
864    return res
865
866def _read_routes_post2008():
867    routes = []
868    if4_metrics = None
869    # This works only starting from Windows 8/2012 and up. For older Windows another solution is needed
870    # Get-NetRoute -AddressFamily IPV4 | select ifIndex, DestinationPrefix, NextHop, RouteMetric, InterfaceMetric | fl
871    for line in exec_query(['Get-NetRoute', '-AddressFamily IPV4'], ['ifIndex', 'DestinationPrefix', 'NextHop', 'RouteMetric', 'InterfaceMetric']):
872        try:
873            iface = dev_from_index(line[0])
874            if iface.ip == "0.0.0.0":
875                continue
876        except:
877            continue
878        # try:
879        #     intf = pcapdnet.dnet.intf().get_dst(pcapdnet.dnet.addr(type=2, addrtxt=dest))
880        # except OSError:
881        #     log_loading.warning("Building Scapy's routing table: Couldn't get outgoing interface for destination %s", dest)
882        #     continue
883        dest, mask = line[1].split('/')
884        ip = "127.0.0.1" if line[0] == "1" else iface.ip # Force loopback on iface 1
885        if not line[4].strip():  # InterfaceMetric is not available. Load it from netsh
886            if not if4_metrics:
887                 if4_metrics = _get_metrics()
888            metric = int(line[3]) + if4_metrics.get(iface.win_index, 0)  # RouteMetric + InterfaceMetric
889        else:
890            metric = int(line[3]) + int(line[4])  # RouteMetric + InterfaceMetric
891        routes.append((atol(dest), itom(int(mask)),
892                       line[2], iface, ip, metric))
893    return routes
894
895############
896### IPv6 ###
897############
898
899def in6_getifaddr():
900    """
901    Returns all IPv6 addresses found on the computer
902    """
903    ifaddrs = []
904    for ifaddr in in6_getifaddr_raw():
905        try:
906            ifaddrs.append((ifaddr[0], ifaddr[1], dev_from_pcapname(ifaddr[2])))
907        except ValueError:
908            pass
909    # Appends Npcap loopback if available
910    if conf.use_npcap and scapy.consts.LOOPBACK_INTERFACE:
911        ifaddrs.append(("::1", 0, scapy.consts.LOOPBACK_INTERFACE))
912    return ifaddrs
913
914def _append_route6(routes, dpref, dp, nh, iface, lifaddr, metric):
915    cset = [] # candidate set (possible source addresses)
916    if iface.name == scapy.consts.LOOPBACK_NAME:
917        if dpref == '::':
918            return
919        cset = ['::1']
920    else:
921        devaddrs = (x for x in lifaddr if x[2] == iface)
922        cset = construct_source_candidate_set(dpref, dp, devaddrs)
923    if not cset:
924        return
925    # APPEND (DESTINATION, NETMASK, NEXT HOP, IFACE, CANDIDATS, METRIC)
926    routes.append((dpref, dp, nh, iface, cset, metric))
927
928def _read_routes6_post2008():
929    routes6 = []
930    # This works only starting from Windows 8/2012 and up. For older Windows another solution is needed
931    # Get-NetRoute -AddressFamily IPV6 | select ifIndex, DestinationPrefix, NextHop | fl
932    lifaddr = in6_getifaddr()
933    for line in exec_query(['Get-NetRoute', '-AddressFamily IPV6'], ['ifIndex', 'DestinationPrefix', 'NextHop', 'RouteMetric', 'InterfaceMetric']):
934        try:
935            if_index = line[0]
936            iface = dev_from_index(if_index)
937        except:
938            continue
939
940        dpref, dp = line[1].split('/')
941        dp = int(dp)
942        nh = line[2]
943        metric = int(line[3])+int(line[4])
944
945        _append_route6(routes6, dpref, dp, nh, iface, lifaddr, metric)
946    return routes6
947
948def _read_routes6_7():
949    # Not supported in powershell, we have to use netsh
950    routes = []
951    query_cmd = "netsh interface ipv6 show route level=verbose"
952    stdout = POWERSHELL_PROCESS.query([query_cmd])
953    lifaddr = in6_getifaddr()
954    if6_metrics = _get_metrics(ipv6=True)
955    # Define regexes
956    r_int = [".*:\s+(\d+)"]
957    r_all = ["(.*)"]
958    r_ipv6 = [".*:\s+([A-z|0-9|:]+(\/\d+)?)"]
959    # Build regex list for each object
960    regex_list = r_ipv6*2 + r_int + r_all*3 + r_int + r_all*3
961    current_object =  []
962    index = 0
963    for l in stdout:
964        if not l.strip():
965            if not current_object:
966                continue
967
968            if len(current_object) == len(regex_list):
969                try:
970                    if_index = current_object[2]
971                    iface = dev_from_index(if_index)
972                except:
973                    current_object = []
974                    index = 0
975                    continue
976                _ip = current_object[0].split("/")
977                dpref = _ip[0]
978                dp = int(_ip[1])
979                _match = re.search(r_ipv6[0], current_object[3])
980                nh = "::"
981                if _match: # Detect if Next Hop is specified (if not, it will be the IFName)
982                    _nhg1 = _match.group(1)
983                    nh = _nhg1 if re.match(".*:.*:.*", _nhg1) else "::"
984                metric = int(current_object[6]) + if6_metrics.get(if_index, 0)
985                _append_route6(routes, dpref, dp, nh, iface, lifaddr, metric)
986
987            # Reset current object
988            current_object = []
989            index = 0
990        else:
991            pattern = re.compile(regex_list[index])
992            match = re.search(pattern, l)
993            if match:
994                current_object.append(match.group(1))
995                index = index + 1
996    return routes
997
998def read_routes6():
999    routes6 = []
1000    if not conf.prog.os_access:
1001        return routes6
1002    try:
1003        if is_new_release():
1004            routes6 = _read_routes6_post2008()
1005        else:
1006            routes6 = _read_routes6_7()
1007    except Exception as e:
1008        warning("Error building scapy IPv6 routing table : %s", e, onlyOnce=True)
1009    return routes6
1010
1011def get_working_if():
1012    try:
1013        # return the interface associated with the route with smallest
1014        # mask (route by default if it exists)
1015        return min(conf.route.routes, key=lambda x: x[1])[3]
1016    except ValueError:
1017        # no route
1018        return scapy.consts.LOOPBACK_INTERFACE
1019
1020def _get_valid_guid():
1021    if scapy.consts.LOOPBACK_INTERFACE:
1022        return scapy.consts.LOOPBACK_INTERFACE.guid
1023    else:
1024        for i in six.itervalues(IFACES):
1025            if not i.is_invalid():
1026                return i.guid
1027
1028def route_add_loopback(routes=None, ipv6=False, iflist=None):
1029    """Add a route to 127.0.0.1 and ::1 to simplify unit tests on Windows"""
1030    if not WINDOWS:
1031        warning("Not available")
1032        return
1033    warning("This will completly mess up the routes. Testing purpose only !")
1034    # Add only if some adpaters already exist
1035    if ipv6:
1036        if not conf.route6.routes:
1037            return
1038    else:
1039        if not conf.route.routes:
1040            return
1041    data = {
1042        'name': scapy.consts.LOOPBACK_NAME,
1043        'description': "Loopback",
1044        'win_index': -1,
1045        'guid': _get_valid_guid(),
1046        'invalid': False,
1047        'mac': '00:00:00:00:00:00',
1048    }
1049    data['pcap_name'] = six.text_type("\\Device\\NPF_" + data['guid'])
1050    adapter = NetworkInterface(data)
1051    adapter.ip = "127.0.0.1"
1052    if iflist:
1053        iflist.append(adapter.pcap_name)
1054        return
1055    # Remove all LOOPBACK_NAME routes
1056    for route in list(conf.route.routes):
1057        iface = route[3]
1058        if iface.name == scapy.consts.LOOPBACK_NAME:
1059            conf.route.routes.remove(route)
1060    # Remove LOOPBACK_NAME interface
1061    for devname, iface in list(IFACES.items()):
1062        if iface.name == scapy.consts.LOOPBACK_NAME:
1063            IFACES.pop(devname)
1064    # Inject interface
1065    IFACES["{0XX00000-X000-0X0X-X00X-00XXXX000XXX}"] = adapter
1066    scapy.consts.LOOPBACK_INTERFACE = adapter
1067    if isinstance(conf.iface, NetworkInterface):
1068        if conf.iface.name == LOOPBACK_NAME:
1069            conf.iface = adapter
1070    if isinstance(conf.iface6, NetworkInterface):
1071        if conf.iface6.name == LOOPBACK_NAME:
1072            conf.iface6 = adapter
1073    # Build the packed network addresses
1074    loop_net = struct.unpack("!I", socket.inet_aton("127.0.0.0"))[0]
1075    loop_mask = struct.unpack("!I", socket.inet_aton("255.0.0.0"))[0]
1076    # Build the fake routes
1077    loopback_route = (loop_net, loop_mask, "0.0.0.0", adapter, "127.0.0.1", 1)
1078    loopback_route6 = ('::1', 128, '::', adapter, ["::1"], 1)
1079    loopback_route6_custom = ("fe80::", 128, "::", adapter, ["::1"], 1)
1080    if routes == None:
1081        # Injection
1082        conf.route6.routes.append(loopback_route6)
1083        conf.route6.routes.append(loopback_route6_custom)
1084        conf.route.routes.append(loopback_route)
1085        # Flush the caches
1086        conf.route6.invalidate_cache()
1087        conf.route.invalidate_cache()
1088    else:
1089        if ipv6:
1090            routes.append(loopback_route6)
1091            routes.append(loopback_route6_custom)
1092        else:
1093            routes.append(loopback_route)
1094
1095
1096if not conf.use_winpcapy:
1097
1098    class NotAvailableSocket(SuperSocket):
1099        desc = "wpcap.dll missing"
1100        def __init__(self, *args, **kargs):
1101            raise RuntimeError("Sniffing and sending packets is not available: "
1102                               "winpcap is not installed")
1103
1104    conf.L2socket = NotAvailableSocket
1105    conf.L2listen = NotAvailableSocket
1106    conf.L3socket = NotAvailableSocket
1107