Ejemplo n.º 1
0
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')
Ejemplo n.º 2
0
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
Ejemplo n.º 3
0
        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")
Ejemplo n.º 4
0
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