def _find_primary_adapter(self): adapters = self._device['network_tool'].adapters_in_priority_order() primary_adapter = [ adapter for adapter in adapters if adapter.pingable() ][0] L.info("Primary network adapter is {}".format(primary_adapter.name())) return primary_adapter
def remote_mkdir(self, path, mode=None): args = [path] if mode: args += ["--mode={}".format(str(mode))] L.debug("Making remote directory: {}".format(path)) self.execute_scriptlet('remote_mkdir.py', args)
def detect(self): L.debug("Trying to determine if we're using the l2tp protocol") vpn_info = VPNInfo() # Assume that no-process definitively means no openvpn if not self._l2tp_processes(): L.debug('Not using l2tp') return None L.info("Detected l2tp as the current VPN protocol") vpn_info.vpn_processes = self._l2tp_processes() vpn_info.protocol = 'l2tp' # TODO: Do tunnel_interface vpn_info.vpn_server_ip = self._get_vpn_server_ip() vpn_info.dns_server_ips = [] vpn_info.tunnel_interface = None if vpn_info.vpn_server_ip == ipaddress.ip_address('127.0.0.1'): L.warning( "Got vpn server IP 127.0.0.1. The app is likely using a proxy. Removing this IP" ) vpn_info.vpn_server_ip = None # Prevent anyone who's holding on to this class from using stale data. self._reset() L.debug( "Detected the following info about openvpn: {}".format(vpn_info)) return vpn_info
def ngrok_main(self, port): cmd = [ 'ngrok', 'http', str(port), '-log', 'stdout', '--log-level', 'debug' ] self._ngrok_proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=None) theq = queue.Queue() thread = threading.Thread(target=WebServer.enqueue_ngrok_output, args=(self._ngrok_proc.stdout, theq)) thread.daemon = True # thread dies with the program thread.start() prog = re.compile(r".*URL:([^\s]+)\s.*") while 1: try: line = theq.get(timeout=1) match = prog.match(str(line)) if not match: continue url = match.group(1) L.debug("ngrok secure url is {}".format(url)) return match.group(1) except queue.Empty: # Add a timeout for getting URL pass
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 _try_vpn_server_ip_from_config_file(self): L.debug("Trying to determine the VPN server IP from the config file") if not self._config_file(): L.debug( "Couldn't get config file so can't determine the VPN server IP" ) return None # TODO: DRY with open(self._config_file()) as file_: for line in file_.readlines(): match = OpenVPNDetector.PROG_REMOTE.match(line) if match: ip = match.group(1) L.debug( "Got VPN server IP {} from the --remote".format(ip)) return ip match = OpenVPNDetector.PROG_ROUTE.match(line) if match: ip = match.group(1) L.debug("Got VPN server IP {} from the --route".format(ip)) return ip L.debug("Couldn't determine VPN server IP from config file {}".format( self._config_file())) return None
def do_dns_requests(self): while not self.thread_should_exit(): try: hostname = random.choice(TestDNSVanillaAggressivePacketCapture.HOSTNAMES) self.localhost['dns_tool'].lookup(hostname) except XVEx as ex: L.debug("DNS lookup failed. Not considering this an error: {}".format(ex))
def start_checking_dns(self): self._stop_dns = False L.info("Starting {} background DNS threads".format( self._background_threads)) for _ in range(0, self._background_threads): self._thread_results.append( self._thread_pool.apply_async(self.dns_check_main))
def dns_server_ips(self): info = self._vpn_info() if info is not None and info.dns_server_ips: return info.dns_server_ips L.warning("Couldn't find VPN DNS server IPs. Will fallback to default method") return super().dns_server_ips()
def vpn_processes(self): info = self._vpn_info() if info is not None and info.vpn_processes: return info.vpn_processes L.warning("Couldn't find VPN processes. Will fallback to default method") return super().vpn_processes()
def protocol(self): info = self._vpn_info() if info is not None and info.protocol: return info.protocol L.warning("Couldn't determine the VPN protocol. Will fallback to default method") return super().protocol()
def remote_makedirs(self, path, mode=None): args = [path] if mode: args += ["--mode={}".format(str(mode))] L.debug("Making remote directories: {}".format(path)) return self.execute_scriptlet('remote_makedirs.py', args)
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 release_devices(self): # TODO: Merge this into device teardown. Probably do it with a component, e.g. vm_manager. # However, we could do it via the connector and make it part of that objects responsibility. # The advantage with a component is that it's configurable, so we can configure things like # whether to update to a snapshot or whether to pause. for vmx in self._vms_in_use: L.debug("Pausing VM: {}".format(vmx))
def block_application(full_path, direction="out"): full_path = windows_real_path(full_path) L.info( "Creating firewall rule to block application {}".format(full_path)) return WindowsAdvFirewall.create_rule("block", program=full_path, direction=direction)
def _create_chain(self, chain): L.debug("Creating iptables chain {}".format(chain)) if self._chain_exists(chain): L.debug("iptables chain {} exists".format(chain)) return self._connector_helper.check_command(["iptables", "-w", "-N", chain], root=True)
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 delete_rule(name): L.info("Deleting firewall rule {}".format(name)) cmd = [ 'netsh', 'advfirewall', 'firewall', 'delete', 'rule', "name={}".format(name) ] check_subprocess(cmd)
def setup(self): super().setup() # TODO: Hardcoded 1 and 1000. Does it matter? self._adapter1.set_interface_metric(1) L.describe("Set interface metric for adapter {} to {}".format(self._adapter1.name(), 1)) self._adapter2.set_interface_metric(1000) L.describe("Set interface metric for adapter {} to {}".format(self._adapter2.name(), 1000))
def pingable(self): if not self.enabled(): return False ips = self.ip_addresses() if not ips: return False cmd = ['ping', '-n', '1', '-w', '2', '-S', ips[0].exploded, '8.8.8.8'] output = "" 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 test_with_packet_capture(self): L.describe("Create disruption...") self.disrupter.create_disruption() L.describe("Generate traffic") self.webdriver = self.localhost['webdriver'].driver( self.parameters['browser']) self.webdriver.get("https://www.expressvpn.com/dns-leak-test")
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 set_lan_ip(self, ip): L.info("Setting LAN IP for router to: {}".format(ip)) self._connector_helper.check_command( ['uci', 'set', "network.lan.ipaddr='{}'".format(ip.exploded)]) self._connector_helper.check_command(['uci', 'commit', 'network']) self._connector_helper.check_command(['/etc/init.d/network', 'restart', '&'])
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 open(self): if self._app_path: L.info("Opening VPN application {} (it might take a moment to appear)".format( self._config["name"])) self._device.open_app(self._app_path) else: super().open()
def report_info(self): info = super().report_info() try: info += self._connector_helper.check_command(['systeminfo'])[0] except XVProcessException as ex: L.warning("Couldn't get OS info from systeminfo:\n{}".format(ex)) return info
def _create_rules_file(rules): filehandle, path = tempfile.mkstemp(suffix='_pf_rules.conf', prefix='xv_leak_test_rules_') with os.fdopen(filehandle, 'w') as _file: for rule in rules: _file.write("{}\n".format(rule)) L.debug("Wrote pf file {} with rules:\n{}".format(path, rules)) return path
def _create_rule(self, rule_args): L.debug("Creating iptables rule {}".format(rule_args)) if self._rule_exists(rule_args): L.debug("iptables rule {} already exists".format(rule_args)) self._connector_helper.check_command(["iptables", "-w", "-A"] + rule_args, root=True)
def setup(self): super().setup() L.describe('Ensure no VPN apps are connected or open') self.target_device['cleanup'].cleanup() L.describe('Configure VPN application') self.target_device['vpn_application'].configure()
def disrupt(self): L.describe('Find the VPN processes and kill them (not the main application)') pids = self._device['vpn_application'].vpn_processes() self.assertNotEmpty(pids, 'Found no VPN processes. This should not happen') for pid in pids: self._device.kill_process(pid) L.info("Killed VPN process (PID {})".format(pid))