def main(): """ Parse the provided command line arguments and launch the GUI. :return: The app exit code (0 for normal exit, non-zero for errors) """ parser = argparse.ArgumentParser() parser.add_argument("replay", help="Replay files to open on launch (optional)", nargs="*") parser.add_argument("-b", "--bind", help="Bind address (default: 127.0.0.1)", default="127.0.0.1") parser.add_argument("-p", "--port", help="Bind port (default: 3000)", default=3000) parser.add_argument("-o", "--output", help="Output folder", default="pyrdp_output") parser.add_argument("-L", "--log-level", help="Log level", default=None, choices=["INFO", "DEBUG", "WARNING", "ERROR", "CRITICAL"], nargs="?") parser.add_argument("-F", "--log-filter", help="Only show logs from this logger name (accepts '*' wildcards)", default=None) parser.add_argument("--headless", help="Parse a replay without rendering the user interface.", action="store_true") args = parser.parse_args() cfg = settings.load(f'{settings.CONFIG_DIR}/player.ini', DEFAULTS) # Modify configuration with switches. if args.log_level: cfg.set('vars', 'level', args.log_level) if args.log_filter: cfg.set('logs', 'filter', args.log_filter) if args.output: cfg.set('vars', 'output_dir', args.output) outDir = Path(cfg.get('vars', 'output_dir')) outDir.mkdir(exist_ok=True) configureLoggers(cfg) logger = logging.getLogger(LOGGER_NAMES.PYRDP) if cfg.getboolean('logs', 'notifications', fallback=False) and not args.headless: enableNotifications(logger) if not HAS_GUI and not args.headless: logger.error('Headless mode is not specified and PySide2 is not installed.' ' Install PySide2 to use the graphical user interface.') sys.exit(127) if not args.headless: app = QApplication(sys.argv) mainWindow = MainWindow(args.bind, int(args.port), args.replay) mainWindow.showMaximized() mainWindow.show() return app.exec_() else: logger.info('Starting PyRDP Player in headless mode.') from pyrdp.player import HeadlessEventHandler from pyrdp.player.Replay import Replay processEvents = HeadlessEventHandler() for replay in args.replay: processEvents.output.write(f'== REPLAY FILE: {replay}\n') fd = open(replay, "rb") replay = Replay(fd, handler=processEvents) processEvents.output.write('\n-- END --------------------------------\n')
def configure(cmdline=None) -> MITMConfig: parser = buildArgParser() if cmdline: args = parser.parse_args(cmdline) else: args = parser.parse_args() # Load configuration file. cfg = settings.load(settings.CONFIG_DIR + '/mitm.ini', DEFAULTS) # Override some of the switches based on command line arguments. if args.output: cfg.set('vars', 'output_dir', args.output) if args.log_filter: cfg.set('logs', 'filter', args.log_filter) if args.log_level: cfg.set('vars', 'level', args.log_level) if args.sensor_id: cfg.set('vars', 'sensor_id', args.sensor_id) outDir = Path(cfg.get('vars', 'output_dir')) outDir.mkdir(exist_ok=True) configureLoggers(cfg) logger = logging.getLogger(LOGGER_NAMES.PYRDP) if args.target is None and not args.transparent: parser.print_usage() sys.stderr.write( 'error: A relay target is required unless running in transparent proxy mode.\n' ) sys.exit(1) if (args.nla_redirection_host is None) != (args.nla_redirection_port is None): sys.stderr.write( 'Error: please provide both --nla-redirection-host and --nla-redirection-port' ) sys.exit(1) if args.target: targetHost, targetPort = parseTarget(args.target) else: targetHost = None targetPort = 3389 # FIXME: Allow to set transparent port as well. key, certificate = validateKeyAndCertificate(args.private_key, args.certificate) config = MITMConfig() config.targetHost = targetHost config.targetPort = targetPort config.privateKeyFileName = key config.listenPort = int(args.listen) config.certificateFileName = certificate config.attackerHost = args.destination_ip config.attackerPort = int(args.destination_port) config.replacementUsername = args.username config.replacementPassword = args.password config.outDir = outDir config.enableCrawler = args.crawl config.crawlerMatchFileName = args.crawler_match_file config.crawlerIgnoreFileName = args.crawler_ignore_file config.recordReplays = not args.no_replay config.downgrade = not args.no_downgrade config.transparent = args.transparent config.extractFiles = not args.no_files config.disableActiveClipboardStealing = args.disable_active_clipboard config.useGdi = not args.no_gdi config.redirectionHost = args.nla_redirection_host config.redirectionPort = args.nla_redirection_port payload = None powershell = None npayloads = int(args.payload is not None) + \ int(args.payload_powershell is not None) + \ int(args.payload_powershell_file is not None) if npayloads > 1: logger.error( "Only one of --payload, --payload-powershell and --payload-powershell-file may be supplied." ) sys.exit(1) if args.payload is not None: payload = args.payload logger.info("Using payload: %(payload)s", {"payload": args.payload}) elif args.payload_powershell is not None: powershell = args.payload_powershell logger.info("Using powershell payload: %(payload)s", {"payload": args.payload_powershell}) elif args.payload_powershell_file is not None: if not os.path.exists(args.payload_powershell_file): logger.error("Powershell file %(path)s does not exist.", {"path": args.payload_powershell_file}) sys.exit(1) try: with open(args.payload_powershell_file, "r") as f: powershell = f.read() except IOError as e: logger.error( "Error when trying to read powershell file: %(error)s", {"error": e}) sys.exit(1) logger.info("Using payload from powershell file: %(path)s", {"path": args.payload_powershell_file}) if powershell is not None: payload = "powershell -EncodedCommand " + b64encode( powershell.encode("utf-16le")).decode() if payload is not None: if args.payload_delay is None: logger.error( "--payload-delay must be provided if a payload is provided.") sys.exit(1) if args.payload_duration is None: logger.error( "--payload-duration must be provided if a payload is provided." ) sys.exit(1) try: config.payloadDelay = int(args.payload_delay) except ValueError: logger.error( "Invalid payload delay. Payload delay must be an integral number of milliseconds." ) sys.exit(1) if config.payloadDelay < 0: logger.error("Payload delay must not be negative.") sys.exit(1) if config.payloadDelay < 1000: logger.warning( "You have provided a payload delay of less than 1 second." " We recommend you use a slightly longer delay to make sure it runs properly." ) try: config.payloadDuration = int(args.payload_duration) except ValueError: logger.error( "Invalid payload duration. Payload duration must be an integral number of milliseconds." ) sys.exit(1) if config.payloadDuration < 0: logger.error("Payload duration must not be negative.") sys.exit(1) config.payload = payload elif args.payload_delay is not None: logger.error("--payload-delay was provided but no payload was set.") sys.exit(1) # Configure allowed authentication protocols. for auth in args.auth.split(','): auth = auth.strip() if auth == "tls": config.authMethods |= NegotiationProtocols.SSL elif auth == "ssp": # CredSSP implies TLS. config.authMethods |= (NegotiationProtocols.SSL | NegotiationProtocols.CRED_SSP) showConfiguration(config) return config
self.authMethods: NegotiationProtocols = NegotiationProtocols.SSL """Specifies the list of authentication protocols that PyRDP accepts.""" @property def replayDir(self) -> Path: """ Get the directory for replay files. """ return self.outDir / "replays" @property def fileDir(self) -> Path: """ Get the directory for intercepted files. """ return self.outDir / "files" @property def certDir(self) -> Path: """ Get the directory for dynamically generated certificates. """ return self.outDir / "certs" """ The default MITM configuration. """ DEFAULTS = settings.load(Path(__file__).parent.absolute() / "mitm.default.ini")
def configure(cmdline=None) -> MITMConfig: parser = buildArgParser() if cmdline: args = parser.parse_args(cmdline) else: args = parser.parse_args() # Load configuration file. cfg = settings.load(settings.CONFIG_DIR + '/mitm.ini', DEFAULTS) # Override some of the switches based on command line arguments. if args.output: cfg.set('vars', 'output_dir', args.output) if args.log_filter: cfg.set('logs', 'filter', args.log_filter) if args.log_level: cfg.set('vars', 'level', args.log_level) configureLoggers(cfg) logger = logging.getLogger(LOGGER_NAMES.PYRDP) outDir = Path(cfg.get('vars', 'output_dir')) outDir.mkdir(exist_ok=True) targetHost, targetPort = parseTarget(args.target) key, certificate = validateKeyAndCertificate(args.private_key, args.certificate) config = MITMConfig() config.targetHost = targetHost config.targetPort = targetPort config.privateKeyFileName = key config.listenPort = int(args.listen) config.certificateFileName = certificate config.attackerHost = args.destination_ip config.attackerPort = int(args.destination_port) config.replacementUsername = args.username config.replacementPassword = args.password config.outDir = outDir config.enableCrawler = args.crawl config.crawlerMatchFileName = args.crawler_match_file config.crawlerIgnoreFileName = args.crawler_ignore_file config.recordReplays = not args.no_replay config.downgrade = not args.no_downgrade config.transparent = args.transparent config.extractFiles = not args.no_files config.disableActiveClipboardStealing = args.disable_active_clipboard payload = None powershell = None if int(args.payload is not None) + int( args.payload_powershell is not None) + int( args.payload_powershell_file is not None) > 1: logger.error( "Only one of --payload, --payload-powershell and --payload-powershell-file may be supplied." ) sys.exit(1) if args.payload is not None: payload = args.payload logger.info("Using payload: %(payload)s", {"payload": args.payload}) elif args.payload_powershell is not None: powershell = args.payload_powershell logger.info("Using powershell payload: %(payload)s", {"payload": args.payload_powershell}) elif args.payload_powershell_file is not None: if not os.path.exists(args.payload_powershell_file): logger.error("Powershell file %(path)s does not exist.", {"path": args.payload_powershell_file}) sys.exit(1) try: with open(args.payload_powershell_file, "r") as f: powershell = f.read() except IOError as e: logger.error( "Error when trying to read powershell file: %(error)s", {"error": e}) sys.exit(1) logger.info("Using payload from powershell file: %(path)s", {"path": args.payload_powershell_file}) if powershell is not None: payload = "powershell -EncodedCommand " + b64encode( powershell.encode("utf-16le")).decode() if payload is not None: if args.payload_delay is None: logger.error( "--payload-delay must be provided if a payload is provided.") sys.exit(1) if args.payload_duration is None: logger.error( "--payload-duration must be provided if a payload is provided." ) sys.exit(1) try: config.payloadDelay = int(args.payload_delay) except ValueError: logger.error( "Invalid payload delay. Payload delay must be an integral number of milliseconds." ) sys.exit(1) if config.payloadDelay < 0: logger.error("Payload delay must not be negative.") sys.exit(1) if config.payloadDelay < 1000: logger.warning( "You have provided a payload delay of less than 1 second. We recommend you use a slightly longer delay to make sure it runs properly." ) try: config.payloadDuration = int(args.payload_duration) except ValueError: logger.error( "Invalid payload duration. Payload duration must be an integral number of milliseconds." ) sys.exit(1) if config.payloadDuration < 0: logger.error("Payload duration must not be negative.") sys.exit(1) config.payload = payload elif args.payload_delay is not None: logger.error("--payload-delay was provided but no payload was set.") sys.exit(1) showConfiguration(config) return config