示例#1
0
文件: cli.py 项目: redcat8850/pyrdp
def validateKeyAndCertificate(private_key: str,
                              certificate: str) -> Tuple[str, str]:
    if (private_key is None) != (certificate is None):
        sys.stderr.write(
            "You must provide both the private key and the certificate")
        sys.exit(1)

    if private_key is None:
        # Certificates will be generated automatically.
        return None, None
    else:
        key, cert = private_key, certificate

    try:
        # Check if OpenSSL accepts the private key and certificate.
        ServerTLSContext(key, cert)
    except OpenSSL.SSL.Error as error:
        from pyrdp.logging import log
        log.error(
            "An error occurred when creating the server TLS context. " +
            "There may be a problem with your private key or certificate (e.g: signature algorithm too weak). "
            + "Here is the exception: %(error)s", {"error": error})
        sys.exit(1)

    return key, cert
示例#2
0
    def send(self, data: bytes):
        """
        Save data to the file.
        """

        if not self.file_descriptor.closed:
            self.file_descriptor.write(data)
        else:
            log.error(
                "Recording file handle closed, cannot write message: %(message)s",
                {"message": data})
示例#3
0
    def recv(self, data: bytes):
        pdu: SecurityPDU = self.mainParser.parse(data)

        try:
            self.dispatchPDU(pdu)
        except KeyboardInterrupt:
            raise
        except Exception:
            if isinstance(pdu, SecurityExchangePDU):
                log.error("Exception occurred when receiving Security Exchange. Data: %(securityExchangeData)s",
                          {"securityExchangeData": hexlify(data)})
            raise
示例#4
0
    def recv(self, data: bytes):
        virtualChannelPDU = self.mainParser.parse(data)

        if virtualChannelPDU.flags & VirtualChannelPDUFlag.CHANNEL_PACKET_COMPRESSED != 0:
            log.error("Compression flag is set on virtual channel data, it is NOT handled, crash will most likely occur.")

        flags = virtualChannelPDU.flags
        if flags & VirtualChannelPDUFlag.CHANNEL_FLAG_FIRST:
            self.pduBuffer = virtualChannelPDU.payload
        else:
            self.pduBuffer += virtualChannelPDU.payload

        if flags & VirtualChannelPDUFlag.CHANNEL_FLAG_LAST:
            # Reassembly done, change the payload of the virtualChannelPDU for processing by the observer.
            virtualChannelPDU.payload = self.pduBuffer
            self.pduReceived(virtualChannelPDU)
示例#5
0
文件: recorder.py 项目: xuyi/pyrdp
    def sendBytes(self, data: bytes):
        """
        Save data to the file.
        :param data: data to write.
        """

        if not self.fd:
            self.pending += data
            if len(self.pending) > FileLayer.FLUSH_THRESHOLD:
                self.fd = open(str(self.filename), "wb")
                self.fd.write(self.pending)
                self.pending = b''
        elif not self.fd.closed:
            self.fd.write(data)
        else:
            log.error("Recording file handle closed, cannot write message: %(message)s", {"message": data})
示例#6
0
 def send(self, data):
     """
     Send data through the socket
     :type data: bytes
     """
     if self.isConnected:
         try:
             log.debug("sending %(arg1)s to %(arg2)s", {
                 "arg1": data,
                 "arg2": self.socket.getpeername()
             })
             self.socket.send(data)
         except Exception as e:
             log.error("Cant send data over the network socket: %(data)s",
                       {"data": e})
             self.isConnected = False
示例#7
0
    def parseEvents(self, data):
        events = []

        while len(data) > 0:
            eventLength = self.readParser.getEventLength(data)
            eventData = data[: eventLength]
            data = data[eventLength :]

            try:
                event = self.readParser.parse(eventData)
            except KeyboardInterrupt:
                raise
            except Exception:
                log.error("Exception occurred when receiving: %(data)s", {"data": hexlify(eventData)})
                raise

            events.append(event)

        return events
示例#8
0
def main():
    parser = argparse.ArgumentParser()
    parser.add_argument(
        "target",
        help="IP:port of the target RDP machine (ex: 192.168.1.10:3390)")
    parser.add_argument("-l",
                        "--listen",
                        help="Port number to listen on (default: 3389)",
                        default=3389)
    parser.add_argument("-o",
                        "--output",
                        help="Output folder",
                        default="pyrdp_output")
    parser.add_argument(
        "-i",
        "--destination-ip",
        help=
        "Destination IP address of the PyRDP player.If not specified, RDP events are not sent over the network."
    )
    parser.add_argument(
        "-d",
        "--destination-port",
        help="Listening port of the PyRDP player (default: 3000).",
        default=3000)
    parser.add_argument("-k",
                        "--private-key",
                        help="Path to private key (for SSL)")
    parser.add_argument("-c",
                        "--certificate",
                        help="Path to certificate (for SSL)")
    parser.add_argument(
        "-n",
        "--nla",
        help="For NLA client authentication (need to provide credentials)",
        action="store_true")
    parser.add_argument(
        "-u",
        "--username",
        help="Username that will replace the client's username",
        default=None)
    parser.add_argument(
        "-p",
        "--password",
        help="Password that will replace the client's password",
        default=None)
    parser.add_argument(
        "-L",
        "--log-level",
        help="Console logging level. Logs saved to file are always verbose.",
        default="INFO",
        choices=["INFO", "DEBUG", "WARNING", "ERROR", "CRITICAL"])
    parser.add_argument(
        "-F",
        "--log-filter",
        help="Only show logs from this logger name (accepts '*' wildcards)",
        default="")
    parser.add_argument(
        "-s",
        "--sensor-id",
        help=
        "Sensor ID (to differentiate multiple instances of the MITM where logs are aggregated at one place)",
        default="PyRDP")
    parser.add_argument("--payload",
                        help="Command to run automatically upon connection",
                        default=None)
    parser.add_argument(
        "--payload-powershell",
        help="PowerShell command to run automatically upon connection",
        default=None)
    parser.add_argument(
        "--payload-powershell-file",
        help=
        "PowerShell script to run automatically upon connection (as -EncodedCommand)",
        default=None)
    parser.add_argument(
        "--payload-delay",
        help=
        "Time to wait after a new connection before sending the payload, in milliseconds",
        default=None)
    parser.add_argument(
        "--payload-duration",
        help=
        "Amount of time for which input / output should be dropped, in milliseconds. This can be used to hide the payload screen.",
        default=None)
    parser.add_argument("--crawl",
                        help="Enable automatic shared drive scraping",
                        action="store_true")
    parser.add_argument(
        "--crawler-match-file",
        help=
        "File to be used by the crawler to chose what to download when scraping the client shared drives.",
        default=None)
    parser.add_argument(
        "--crawler-ignore-file",
        help=
        "File to be used by the crawler to chose what folders to avoid when scraping the client shared drives.",
        default=None)
    parser.add_argument("--no-replay",
                        help="Disable replay recording",
                        action="store_true")

    args = parser.parse_args()
    outDir = Path(args.output)
    outDir.mkdir(exist_ok=True)

    logLevel = getattr(logging, args.log_level)

    prepareLoggers(logLevel, args.log_filter, args.sensor_id, outDir)
    pyrdpLogger = logging.getLogger(LOGGER_NAMES.MITM)

    target = args.target

    if ":" in target:
        targetHost = target[:target.index(":")]
        targetPort = int(target[target.index(":") + 1:])
    else:
        targetHost = target
        targetPort = 3389

    if (args.private_key is None) != (args.certificate is None):
        pyrdpLogger.error(
            "You must provide both the private key and the certificate")
        sys.exit(1)
    elif args.private_key is None:
        key, certificate = getSSLPaths()
        handleKeyAndCertificate(key, certificate)
    else:
        key, certificate = args.private_key, args.certificate

    listenPort = int(args.listen)

    config = MITMConfig()
    config.targetHost = targetHost
    config.targetPort = targetPort
    config.privateKeyFileName = key
    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

    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:
        pyrdpLogger.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
        pyrdpLogger.info("Using payload: %(payload)s",
                         {"payload": args.payload})
    elif args.payload_powershell is not None:
        powershell = args.payload_powershell
        pyrdpLogger.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):
            pyrdpLogger.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:
            pyrdpLogger.error(
                "Error when trying to read powershell file: %(error)s",
                {"error": e})
            sys.exit(1)

        pyrdpLogger.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:
            pyrdpLogger.error(
                "--payload-delay must be provided if a payload is provided.")
            sys.exit(1)

        if args.payload_duration is None:
            pyrdpLogger.error(
                "--payload-duration must be provided if a payload is provided."
            )
            sys.exit(1)

        try:
            config.payloadDelay = int(args.payload_delay)
        except ValueError:
            pyrdpLogger.error(
                "Invalid payload delay. Payload delay must be an integral number of milliseconds."
            )
            sys.exit(1)

        if config.payloadDelay < 0:
            pyrdpLogger.error("Payload delay must not be negative.")
            sys.exit(1)

        if config.payloadDelay < 1000:
            pyrdpLogger.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:
            pyrdpLogger.error(
                "Invalid payload duration. Payload duration must be an integral number of milliseconds."
            )
            sys.exit(1)

        if config.payloadDuration < 0:
            pyrdpLogger.error("Payload duration must not be negative.")
            sys.exit(1)

        config.payload = payload
    elif args.payload_delay is not None:
        pyrdpLogger.error(
            "--payload-delay was provided but no payload was set.")
        sys.exit(1)

    try:
        # Check if OpenSSL accepts the private key and certificate.
        ServerTLSContext(config.privateKeyFileName, config.certificateFileName)
    except OpenSSL.SSL.Error as error:
        log.error(
            "An error occurred when creating the server TLS context. " +
            "There may be a problem with your private key or certificate (e.g: signature algorithm too weak). "
            + "Here is the exception: %(error)s", {"error": error})

        sys.exit(1)

    logConfiguration(config)

    reactor.listenTCP(listenPort, MITMServerFactory(config))
    pyrdpLogger.info("MITM Server listening on port %(port)d",
                     {"port": listenPort})
    reactor.run()

    pyrdpLogger.info("MITM terminated")
    logConfiguration(config)
示例#9
0
def RDPBitmapToQtImage(width, height, bitsPerPixel, isCompressed, data):
    """
    @summary: Bitmap transformation to Qt object
    @param width: width of bitmap
    @param height: height of bitmap
    @param bitsPerPixel: number of bit per pixel
    @param isCompressed: use RLE compression
    @param data: bitmap data
    """
    image = None
    #allocate

    if bitsPerPixel == 15:
        if isCompressed:
            buf = bytearray(width * height * 2)
            rle.bitmap_decompress(buf, width, height, data, 2)
            image = QtGui.QImage(buf, width, height,
                                 QtGui.QImage.Format_RGB555)
        else:
            image = QtGui.QImage(data, width, height,
                                 QtGui.QImage.Format_RGB555).transformed(
                                     QtGui.QMatrix(1.0, 0.0, 0.0, -1.0, 0.0,
                                                   0.0))

    elif bitsPerPixel == 16:
        if isCompressed:
            buf = bytearray(width * height * 2)
            rle.bitmap_decompress(buf, width, height, data, 2)
            image = QtGui.QImage(buf, width, height, QtGui.QImage.Format_RGB16)
        else:
            image = QtGui.QImage(data, width, height,
                                 QtGui.QImage.Format_RGB16).transformed(
                                     QtGui.QMatrix(1.0, 0.0, 0.0, -1.0, 0.0,
                                                   0.0))

    elif bitsPerPixel == 24:
        if isCompressed:
            buf = bytearray(width * height * 3)
            rle.bitmap_decompress(buf, width, height, data, 3)

            # This is a ugly patch because there is a bug in the 24bpp decompression in rle.c
            # where the red and the blue colors are inverted. Fixing this in python causes a performance
            # issue, but at least it shows the good colors.
            buf2 = BytesIO(buf)
            while buf2.tell() < len(buf2.getvalue()):
                pixel = buf2.read(3)
                buf[buf2.tell() - 3] = pixel[2]
                buf[buf2.tell() - 1] = pixel[0]

            image = QtGui.QImage(buf, width, height,
                                 QtGui.QImage.Format_RGB888)
        else:
            image = QtGui.QImage(data, width, height,
                                 QtGui.QImage.Format_RGB888).transformed(
                                     QtGui.QMatrix(1.0, 0.0, 0.0, -1.0, 0.0,
                                                   0.0))

    elif bitsPerPixel == 32:
        if isCompressed:
            buf = bytearray(width * height * 4)
            rle.bitmap_decompress(buf, width, height, data, 4)
            image = QtGui.QImage(buf, width, height, QtGui.QImage.Format_RGB32)
        else:
            image = QtGui.QImage(data, width, height,
                                 QtGui.QImage.Format_RGB32).transformed(
                                     QtGui.QMatrix(1.0, 0.0, 0.0, -1.0, 0.0,
                                                   0.0))
    elif bitsPerPixel == 8:
        if isCompressed:
            buf = bytearray(width * height * 1)
            rle.bitmap_decompress(buf, width, height, data, 1)
            buf2 = convert8bppTo16bpp(buf)
            image = QtGui.QImage(buf2, width, height,
                                 QtGui.QImage.Format_RGB16)
        else:
            buf2 = convert8bppTo16bpp(data)
            image = QtGui.QImage(buf2, width, height,
                                 QtGui.QImage.Format_RGB16).transformed(
                                     QtGui.QMatrix(1.0, 0.0, 0.0, -1.0, 0.0,
                                                   0.0))
    else:
        log.error("Receive image in bad format")
        image = QtGui.QImage(width, height, QtGui.QImage.Format_RGB32)
    return image
示例#10
0
def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("target", help="IP:port of the target RDP machine (ex: 192.168.1.10:3390)")
    parser.add_argument("-l", "--listen", help="Port number to listen on (default: 3389)", default=3389)
    parser.add_argument("-o", "--output", help="Output folder", default="pyrdp_output")
    parser.add_argument("-i", "--destination-ip", help="Destination IP address of the PyRDP player.If not specified, RDP events are not sent over the network.")
    parser.add_argument("-d", "--destination-port", help="Listening port of the PyRDP player (default: 3000).", default=3000)
    parser.add_argument("-k", "--private-key", help="Path to private key (for SSL)")
    parser.add_argument("-c", "--certificate", help="Path to certificate (for SSL)")
    parser.add_argument("-n", "--nla", help="For NLA client authentication (need to provide credentials)", action="store_true")
    parser.add_argument("-u", "--username", help="Username that will replace the client's username", default=None)
    parser.add_argument("-p", "--password", help="Password that will replace the client's password", default=None)
    parser.add_argument("-L", "--log-level", help="Console logging level. Logs saved to file are always verbose.", default="INFO", choices=["INFO", "DEBUG", "WARNING", "ERROR", "CRITICAL"])
    parser.add_argument("-F", "--log-filter", help="Only show logs from this logger name (accepts '*' wildcards)", default="")
    parser.add_argument("-s", "--sensor-id", help="Sensor ID (to differentiate multiple instances of the MITM where logs are aggregated at one place)", default="PyRDP")
    parser.add_argument("--no-replay", help="Disable replay recording", action="store_true")

    args = parser.parse_args()
    outDir = Path(args.output)
    outDir.mkdir(exist_ok = True)

    logLevel = getattr(logging, args.log_level)

    prepareLoggers(logLevel, args.log_filter, args.sensor_id, outDir)
    pyrdpLogger = logging.getLogger(LOGGER_NAMES.MITM)

    target = args.target

    if ":" in target:
        targetHost = target[: target.index(":")]
        targetPort = int(target[target.index(":") + 1:])
    else:
        targetHost = target
        targetPort = 3389

    if (args.private_key is None) != (args.certificate is None):
        pyrdpLogger.error("You must provide both the private key and the certificate")
        sys.exit(1)
    elif args.private_key is None:
        key, certificate = getSSLPaths()
        handleKeyAndCertificate(key, certificate)
    else:
        key, certificate = args.private_key, args.certificate

    listenPort = int(args.listen)

    config = MITMConfig()
    config.targetHost = targetHost
    config.targetPort = targetPort
    config.privateKeyFileName = key
    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.recordReplays = not args.no_replay

    try:
        # Check if OpenSSL accepts the private key and certificate.
        ServerTLSContext(config.privateKeyFileName, config.certificateFileName)
    except OpenSSL.SSL.Error as error:
        log.error(
            "An error occurred when creating the server TLS context. " +
            "There may be a problem with your private key or certificate (e.g: signature algorithm too weak). " +
            "Here is the exception: %(error)s",
            {"error": error}
        )

        sys.exit(1)

    logConfiguration(config)

    reactor.listenTCP(listenPort, MITMServerFactory(config))
    pyrdpLogger.info("MITM Server listening on port %(port)d", {"port": listenPort})
    reactor.run()

    pyrdpLogger.info("MITM terminated")
    logConfiguration(config)
示例#11
0
def RDPBitmapToQtImage(width: int, height: int, bitsPerPixel: int,
                       isCompressed: bool, data: bytes):
    """
    Bitmap transformation to Qt object
    :param width: width of bitmap
    :param height: height of bitmap
    :param bitsPerPixel: number of bit per pixel
    :param isCompressed: use RLE compression
    :param data: bitmap data
    """
    image = None
    buf = None

    if bitsPerPixel == 15:
        if isCompressed:
            buf = rle.bitmap_decompress(data, width, height, 2)
            image = QImage(buf, width, height, QImage.Format_RGB555)
        else:
            buf = data
            image = QImage(buf, width, height,
                           QImage.Format_RGB555).transformed(
                               QMatrix(1.0, 0.0, 0.0, -1.0, 0.0, 0.0))

    elif bitsPerPixel == 16:
        if isCompressed:
            buf = rle.bitmap_decompress(data, width, height, 2)
            image = QImage(buf, width, height, QImage.Format_RGB16)
        else:
            buf = data
            image = QImage(buf, width, height,
                           QImage.Format_RGB16).transformed(
                               QMatrix(1.0, 0.0, 0.0, -1.0, 0.0, 0.0))

    elif bitsPerPixel == 24:
        if isCompressed:
            buf = rle.bitmap_decompress(data, width, height, 3)

            # This is a ugly patch because there is a bug in the 24bpp decompression in rle.c
            # where the red and the blue colors are inverted. Fixing this in python causes a performance
            # issue, but at least it shows the good colors.
            buf2 = BytesIO(buf)
            while buf2.tell() < len(buf2.getvalue()):
                pixel = buf2.read(3)
                buf[buf2.tell() - 3] = pixel[2]
                buf[buf2.tell() - 1] = pixel[0]

            image = QImage(buf, width, height, QImage.Format_RGB888)
        else:
            buf = data
            image = QImage(buf, width, height,
                           QImage.Format_RGB888).transformed(
                               QMatrix(1.0, 0.0, 0.0, -1.0, 0.0, 0.0))

    elif bitsPerPixel == 32:
        if isCompressed:
            buf = rle.bitmap_decompress(data, width, height, 4)
            image = QImage(buf, width, height, QImage.Format_RGB32)
        else:
            buf = data
            image = QImage(buf, width, height,
                           QImage.Format_RGB32).transformed(
                               QMatrix(1.0, 0.0, 0.0, -1.0, 0.0, 0.0))
    elif bitsPerPixel == 8:
        if isCompressed:
            _buf = rle.bitmap_decompress(data, width, height, 1)
            buf = convert8bppTo16bpp(_buf)
            image = QImage(buf, width, height, QImage.Format_RGB16)
        else:
            buf = convert8bppTo16bpp(data)
            image = QImage(buf, width, height,
                           QImage.Format_RGB16).transformed(
                               QMatrix(1.0, 0.0, 0.0, -1.0, 0.0, 0.0))
    else:
        log.error("Receive image in bad format")
        image = QImage(width, height, QImage.Format_RGB32)
    return (image, buf)