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