def default_interfaces(self): L.info( "No interfaces specified for packet capture. Checking config to determine which to use" ) if 'capture_interface' not in self._config: raise XVEx( "Specify the 'capture_interface' in the 'packet_capturer' component config" ) if self._config['capture_interface'] == 'span': interface = self._device.config()['span_interface'] L.info("Using span interface for capture: {}".format(interface)) return [interface] elif self._config['capture_interface'] == 'primary': interface = self._get_primary_interface() L.info("Using primary interface for capture: {}".format(interface)) return [interface] elif self._config['capture_interface'] == 'non-tunnel': interfaces = self._get_non_tunnel_interfaces() L.info("Using non-tunnel interfaces for capture: {}".format( interfaces)) return interfaces elif self._config['capture_interface'] == 'all': interfaces = self._get_all_interfaces() L.info("Using all interfaces for capture: {}".format(interfaces)) return interfaces else: raise XVEx( "Don't know how to handle capture_interface '{}'".format( self._config['capture_interface']))
def _get_full_tap_adapter_name(self, tap_adapter_name): if tap_adapter_name is None: return None # The logic here is necessary because Windows can give the TAP adapters suffixes like #2. # So the name might not match exactly what we expect. I think this happens when there's a # name collision. If we have two adapters with the same name then let's error as it's # probably going to cause a problem. L.debug("Looking for TAP adapter with name '{}'".format(tap_adapter_name)) adapters = self._device['network_tool'].adapters_in_priority_order() candidates = [] for adapter in adapters: adapter_name = adapter.name() if tap_adapter_name == adapter_name: # If we find an exact match then just return it return adapter_name if adapter_name.startswith(tap_adapter_name): candidates.append(adapter_name) if len(candidates) == 1: L.debug("Found TAP adapter with name '{}'".format(candidates[0])) return candidates[0] if len(candidates) == 0: raise XVEx("Found no candidate adapters matching TAP adapter '{}' for '{}':\n{}".format( tap_adapter_name, self._config['name'], candidates)) raise XVEx("Found several candidate adapters matching TAP adapter '{}' for '{}':\n{}".format( tap_adapter_name, self._config['name'], candidates))
def _webrtc_ips(self, url_base): url = self._url(url_base) L.describe("Open ICE detection webpage {}".format(url)) self._webdriver.get(url) # TODO: This isn't working properly. Also, try to automate if self._ask_perms: message_and_await_enter( "Please grant the browser permissions to use audio/video") # Not in love with this but not sure we have a lot of choice. There's no way to know whether # the javascript code has done searching for IPs TODO: Verify that L.describe("Wait for {} seconds for IPs to report".format( WebRTCICEHelper.TIME_TO_WAIT_FOR_IPS)) time.sleep(WebRTCICEHelper.TIME_TO_WAIT_FOR_IPS) if 'xv_leak_tools local ICE IP detection' not in self._webdriver.title: raise XVEx("Got unexpected webpage title: {}".format( self._webdriver.title)) L.describe( "Check ICE detection webpage doesn't show our public IP addresses") inner_html = self._webdriver.find_element_by_id( 'ice_ips').get_attribute('innerHTML') if not inner_html: raise XVEx('ice_ips div in webrtc page contained no inner_html') json_reponse = json.loads(inner_html) if not json_reponse['webrtc_supported']: L.warning( 'WebRTC not supported for this browser. Reporting no ips.') return [] return [ipaddress.ip_address(ip) for ip in json_reponse['ips']]
def _parse_output(output): server = None lines = output.splitlines() for line in lines: matches = Dig.PROG_DIG_SERVER.match(line) if not matches: continue server = matches.group(1) break if server is None: raise XVEx("Couldn't parse dig output: {}".format(output)) ips = [] for iline, line in enumerate(lines): if 'ANSWER SECTION' not in line: continue ips = Dig._parse_answers(lines[iline + 1:]) break if len(ips) == 0: raise XVEx( "dig failed to return any IPs. Output was: {}".format(output)) return ipaddress.ip_address(server), ips
def start(self): L.debug('Starting IPResponder thread') if self._server_address is None: raise XVEx( "Server address must be set before starting IPResponder") if self._send_forever_thread is not None: raise XVEx("IP Responder already started") self._check_server() self._stop_sending = threading.Event() self._send_forever_thread = threading.Thread(target=self._send_forever) self._send_forever_thread.start()
def set_dns_servers(self, *ips): if len(ips) > 2: raise XVEx("There is only space for two DNS servers per adapter") cmd_set = [ 'netsh', 'interface', 'ip', 'set', 'dns', self.net_connection_id() ] if not ips: cmd_set.append('dhcp') L.debug("Setting DNS on {} via DHCP".format( self.net_connection_id())) check_subprocess(cmd_set) else: # TODO: this breaks if we set a server that's already set. Not sure # if that's a problem. L.debug("Setting primary DNS on {} to {}".format( self.net_connection_id(), ips[0])) check_subprocess(cmd_set + ['static', ips[0], 'index=1']) if len(ips) == 2: cmd_add = [ 'netsh', 'interface', 'ip', 'add', 'dns', self.net_connection_id() ] L.debug("Setting secondary DNS on {} to {}".format( self.net_connection_id(), ips[1])) check_subprocess(cmd_add + [ips[1], 'index=2'])
def pingable(self): if not self.enabled(): L.verbose("{} not enabled".format(self.name())) return False ips = self.ip_addresses() if not ips: L.verbose("{} has no IP address".format(self.name())) return False cmd = ['ping', '-n', '1', '-w', '2', '-S', ips[0].exploded, '8.8.8.8'] try: output = check_subprocess(cmd)[0] if 'Received = 1' in output: return True else: # Consider this a real error and propagate. It's likely a code issue. raise XVEx( "Don't know how to parse ping output: {}".format(output)) except XVProcessException as ex: if 'Lost = 1' in ex.stderr: L.debug("Couldn't ping on adapter {}".format(self)) return False L.warning("Ping failed unexpectedly with error '{}'. " "Assuming adapter {} un-pingable.".format( ex.stderr, self)) return False
def discover_device(self, discovery_keys): L.debug('Looking for device with keys {}'.format(discovery_keys)) device = self._inventory_item_for_discovery_keys(discovery_keys) if device is None: return None if 'output_root' not in device: raise XVEx("Device config didn't specify 'output_root': {}".format(device)) device['output_directory'] = os.path.join( device['output_root'], self._context['run_directory']) # TODO: Look into refactoring this. if 'dummy' in device and device['dummy']: connector = DummyConnector() elif 'adb_id' in device and device['adb_id']: connector = ADBConnector(device['adb_id']) else: connector = SimpleSSHConnector( ips=device['ips'], username=device['username'], account_password=device.get('account_password', None), ssh_key=device.get('ssh_key', None), ssh_password=device.get('ssh_password', None) ) return create_device(device['os_name'], device, connector)
def _config_file(self): if self._config_file_cache: return self._config_file_cache config_file = self._command_line_arg('--config') if not config_file: return None if os.path.exists(config_file): self._config_file_cache = config_file L.debug("Got config file {}".format(self._config_file_cache)) return self._config_file_cache # Couldn't find the config file so maybe it's a relative path to --cd working_directory = self._command_line_arg('--cd') if working_directory: config_file = os.path.join(working_directory, self._command_line_arg('--config')) if os.path.exists(config_file): self._config_file_cache = config_file L.debug("Got config file {}".format(self._config_file_cache)) return self._config_file_cache raise XVEx( "Found argument --config={} but the config files doesn't exists". format(config_file))
def __init__(self, anchor_name=None, no_disable=False): if anchor_name is None: anchor_name = PFCtl._random_anchor_name() self._anchor_name = anchor_name self._no_disable = no_disable self._token = None if self._no_disable: try: self._pfctl(['-e']) except XVProcessException: pass else: lines = self._pfctl(['-E'])[1].splitlines() for line in lines: match = PFCtl.PROG_TOKEN.match(line) if not match: continue self._token = match.group(1) L.debug("Got pfctl reference {}".format(self._token)) if not self._token: raise XVEx( "Couldn't parse token from pfctl output:\n {}".format( "\n".join(lines))) self._ensure_anchor_is_present()
def _iter_package(package, restrict_platform=True, is_pkg=True): def ignore_package(package, ignore_substrings): # Don't import anything which doesn't belong on this OS for ignore in ignore_substrings: if ignore in package: return True return False if restrict_platform and ignore_package(package, other_oses()): return try: L.debug("Importing {}".format(package)) mod = importlib.import_module(package) package_path = os.path.normpath(os.path.split(mod.__file__)[0]) yield mod except ImportError as ex: raise XVEx( "Can't import package '{}' ({}). Make sure the package directory is in your " "sys.path".format(package, ex)) if not is_pkg: return L.verbose("Walking package path {}".format(package_path)) for _, module_name, is_pkg2 in pkgutil.walk_packages([package_path]): for mod in _iter_package("{}.{}".format(package, module_name), restrict_platform, is_pkg2): yield mod
def os_version(self): try: # Use systeminfo on Windows as platform.win32_ver doesn't work for cygwin. That way the # code is agnostic to the shell. output = self._connector_helper.check_command(['systeminfo'])[0] for line in output.splitlines(): match = WindowsDevice.PROG_OS_VERSION.match(line) if match: return match.group(1) raise XVEx( "Couldn't determine Windows Version from systeminfo output:\n{}" .format(output)) except XVProcessException as ex: raise XVEx( "Couldn't determine Windows Version as systeminfo failed:\n{}". format(ex))
def discover_device(self, discovery_keys): for discoverer in self._discoverers: device = discoverer.discover_device(discovery_keys) if device is not None: return device raise XVEx( "Couldn't discover device using keys: {}".format(discovery_keys))
def _receive(self, timeout=1): L.debug("Listening on %s:%s" % self._query_sock.getsockname()) ready = select.select([self._query_sock], [], {}, timeout) if not ready[0]: raise XVEx("Didn't get a response from IP responder server") data = self._query_sock.recvfrom(4096)[0] return set(ipaddress.ip_address(ip) for ip in data.decode().split())
def _ssh_connect(self): connect_errors = "" for ip in self._ips: self._create_route_to_ip(ip) connect_dict = { 'hostname': ip, 'username': self._username, 'password': self._ssh_password, 'key_filename': self._ssh_key, 'timeout': 5, } L.debug("Connecting SSH with args {}".format(connect_dict)) self._ssh_client = paramiko.SSHClient() self._ssh_client.set_missing_host_key_policy( paramiko.AutoAddPolicy()) try: self._ssh_client.connect(**connect_dict) self._sftp_client = self._ssh_client.open_sftp() return except (paramiko.ssh_exception.NoValidConnectionsError, socket.timeout) as ex: connect_errors += "Couldn't connect to host via SSH connection at {}: {}\n".format( ip, ex) raise XVEx("Couldn't connect to host:\n{}".format(connect_errors))
def start(self): L.debug("Starting packet capture on interface {}".format( self._interface)) if self._popen: raise XVEx("Packet capture already started!") binary = os.path.join(os.path.dirname(os.path.realpath(__file__)), "..", "bin", "xv_packet_capture.exe") os.path.join(self._device.temp_directory(), WindowsGoPacketCapturer._random_pid_file()) binary = windows_real_path(binary) output_directory = windows_real_path(self._device.temp_directory()) cmd = [ 'cmd.exe', '/c', binary, '-i', str(self._interface), '-o', output_directory, '-f', self._capture_file, '-m', 'windump', '--preserve', '--debug' ] stdout = os.path.join(self._device.temp_directory(), "{}.stdout".format(self._capture_file)) stderr = os.path.join(self._device.temp_directory(), "{}.stderr".format(self._capture_file)) # TODO: Check for errors once opened? L.debug("Starting packet capture: {}".format(cmd)) makedirs_safe(self._device.temp_directory()) with open(stdout, "w") as out, open(stderr, "w") as err: self._popen = subprocess.Popen(cmd, stdout=out, stderr=err) self._pid = self._find_windows_pid()
def _openvpn_process(self): if self._openvpn_process_cache: return self._openvpn_process_cache openvpn_pids = self._device.pgrep('openvpn') if not openvpn_pids: return # This is a bit of a hack. Some providers are known to spawn two openvpn instances. We only # want the parent one which is the one where --config is specified. So if we find more than # one the we will let this slide if exactly one of them has --config in it. if len(openvpn_pids) != 1: good_pids = [] for pid in openvpn_pids: for arg in self._device.command_line_for_pid(pid): if "--config" in arg: good_pids.append(pid) if len(good_pids) != 1: raise XVEx( "Found {} openvpn processes, expected exactly 1".format( len(openvpn_pids))) openvpn_pids = good_pids self._openvpn_process_cache = openvpn_pids[0] return self._openvpn_process_cache
def stop(self): L.debug("Stopping packet capture on interface {} and getting packets". format(self._interface)) if not self._popen: raise XVEx("Packet capture not started!") old_handler = None def interrupt_func(_, __): L.debug( "Ignoring interrupt in main process when killing packet capture" ) signal.signal(signal.SIGINT, old_handler) old_handler = signal.signal(signal.SIGINT, interrupt_func) # Requires https://github.com/alirdn/windows-kill subprocess.Popen(['windows-kill', '-SIGINT', str(self._pid)], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) self._pid = None # self._popen.send_signal(2) self._popen.wait() self._popen = None return self._get_packets()
def set_current(self): self._sync_preferences() if not SCNetworkSetSetCurrent( self._cf_set()) or not self._save_preferences(): raise XVEx( "Couldn't set location '{}' to current location".format(self))
def vpn_server_ip(self): ips = self._get_ips('Please input the VPN server IP.', max_ips=1) if len(ips) > 1: raise XVEx('User provided more than one VPN server IP') if ips: return ips[0] return None
def _create_rules(**kwargs): rules = {} for key, value in kwargs.items(): if key not in TrafficFilter.VALID_RULES: raise XVEx("Invalid filter rule: {}".format(key)) L.debug("Adding rule {}: {}".format(key, value)) rules[key] = value return rules
def _find_windows_pid(self): pids = self._device.pgrep("xv_packet_capture.exe") for pid in pids: cmd_line = self._device.command_line_for_pid(pid) for arg in cmd_line: if self._capture_file in arg: return pid raise XVEx("Couldn't find PID")
def refresh(self): for row in wmic_rows(): if row['GUID'] == self.guid(): self.data = row return raise XVEx( "Couldn't find adapter with GUID {} when refreshing data".format( self.guid()))
def __getitem__(self, component_name): if component_name in self._components: return self._components[component_name] raise XVEx( "Device {} has no component {}. Available components:\n{}".format( self.device_id(), component_name, "\n".join(self._components.keys())))
def wait_for_ips(self, timeout): start = time.time() ips = self.ipv4_addresses() while not ips and time.time() - start < timeout: time.sleep(1) ips = self.ipv4_addresses() L.debug("Got ips: {}".format(ips)) if not ips: raise XVEx("Network service {} never got lease")
def _create_route_to_ip(self, ip): default_gateway = netifaces.gateways()['default'][netifaces.AF_INET][0] L.debug("Adding route to ip: {} -> {}".format(ip, default_gateway)) if current_os() == 'windows': raise XVEx("TODO: Implement route on windows") else: route = [ip, default_gateway] subprocess.check_output(['route', 'add'] + route) self._routes_to_remote.append(route)
def adapter_by_net_connection_id(id_): adapters = WindowsNetwork._adapters_by("NetConnectionID", id_, unique=True) if not adapters: raise XVEx( "Couldn't find adapter with network connection ID '{}'".format( id_)) return adapters[0]
def _check_config(self, extra_keys=None): required_keys = ['ips', 'device_id'] if extra_keys is not None: required_keys += extra_keys for key in required_keys: if key not in self._config: raise XVEx( "Device type {} requires the config key '{}'".format( self.__class__.__name__, key))
def primary_adapter(self): adapters = self.adapters_in_priority_order() pingable_adapters = [ adapter for adapter in adapters if adapter.pingable() ] if pingable_adapters: return pingable_adapters[0] raise XVEx( "No pingable adapters. All adapters are {}".format(adapters))
def class_by_name(name, base_class): available = [] for subclass in itersubclasses(base_class): if subclass.__name__ == name: return subclass available.append(subclass) raise XVEx( "Couldn't find class {} deriving from {}. Available classes:\n{}". format(name, base_class, "\n".join([clazz.__name__ for clazz in available])))