def onSlowPathPDU(self, pdu: PlayerPDU): parser = SlowPathParser() pdu = parser.parse(pdu.payload) if isinstance(pdu, ConfirmActivePDU): bitmapCapability = pdu.parsedCapabilitySets[ CapabilityType.CAPSTYPE_BITMAP] self.viewer.resize(bitmapCapability.desktopWidth, bitmapCapability.desktopHeight) elif isinstance( pdu, UpdatePDU ) and pdu.updateType == SlowPathUpdateType.SLOWPATH_UPDATETYPE_BITMAP: updates = BitmapParser().parseBitmapUpdateData(pdu.updateData) for bitmap in updates: self.handleBitmap(bitmap) elif isinstance(pdu, InputPDU): for event in pdu.events: if isinstance(event, MouseEvent): self.onMousePosition(event.x, event.y) elif isinstance(event, KeyboardEvent): self.onScanCode( event.keyCode, event.flags & KeyboardFlag.KBDFLAGS_DOWN == 0, event.flags & KeyboardFlag.KBDFLAGS_EXTENDED != 0)
def onSlowPathPDU(self, pdu: PlayerPDU): parser = SlowPathParser() pdu = parser.parse(pdu.payload) if isinstance(pdu, ConfirmActivePDU): self.onCapabilities(pdu.parsedCapabilitySets) elif isinstance(pdu, UpdatePDU): self.onSlowPathUpdate(pdu) elif isinstance(pdu, InputPDU): for event in pdu.events: if isinstance(event, MouseEvent): self.onMousePosition(event.x, event.y) elif isinstance(event, KeyboardEvent): self.onScanCode( event.keyCode, event.flags & KeyboardFlag.KBDFLAGS_DOWN == 0, event.flags & KeyboardFlag.KBDFLAGS_EXTENDED != 0)
class HeadlessEventHandler(Observer): """ Handle events from a replay file without rendering to a surface. This event handler does not require any graphical dependencies. """ def __init__(self, output: TextIOBase = stdout): super().__init__() self.output = output self.shiftPressed = False self.capsLockOn = False self.buffer = b"" # Instantiate parsers. self.slowpath = SlowPathParser() # self.fastpath = FastPathOutputParser() self.clipboard = ClipboardParser() self.handlers = { PlayerPDUType.CLIENT_DATA: self.onClientData, PlayerPDUType.CLIENT_INFO: self.onClientInfo, PlayerPDUType.CONNECTION_CLOSE: self.onConnectionClose, PlayerPDUType.CLIPBOARD_DATA: self.onClipboardData, PlayerPDUType.SLOW_PATH_PDU: self.onSlowPathPDU, PlayerPDUType.FAST_PATH_INPUT: self.onFastPathInput, PlayerPDUType.DEVICE_MAPPING: self.onDeviceMapping, } def writeText(self, text: str): self.output.write(text.rstrip("\x00")) def writeSeparator(self): self.output.write("\n--------------------\n") def onPDUReceived(self, pdu: PlayerPDU, isMainThread=False): log.debug("Received %(pdu)s", {"pdu": pdu}) if pdu.header in self.handlers: self.handlers[pdu.header](pdu) def onClientData(self, pdu: PlayerPDU): parser = ClientConnectionParser() clientDataPDU = parser.parse(pdu.payload) clientName = clientDataPDU.coreData.clientName.strip("\x00") self.writeSeparator() self.writeText(f"HOST: {clientName}\n") self.writeSeparator() def onClientInfo(self, pdu: PlayerPDU): parser = ClientInfoParser() clientInfoPDU = parser.parse(pdu.payload) self.writeSeparator() self.writeText("USERNAME: {}\nPASSWORD: {}\nDOMAIN: {}\n".format( clientInfoPDU.username.replace("\x00", ""), clientInfoPDU.password.replace("\x00", ""), clientInfoPDU.domain.replace("\x00", ""))) self.writeSeparator() def onConnectionClose(self, _: PlayerPDU): self.writeText("\n<Connection closed>") def onClipboardData(self, pdu: PlayerPDU): parser = ClipboardParser() pdu = parser.parse(pdu.payload) if not isinstance(pdu, FormatDataResponsePDU): return clipboardData = decodeUTF16LE(pdu.requestedFormatData) self.writeSeparator() self.writeText(f"CLIPBOARD DATA: {clipboardData}") self.writeSeparator() def onSlowPathPDU(self, pdu: PlayerPDU): pdu = self.slowpath.parse(pdu.payload) if not isinstance(pdu, InputPDU): return for event in pdu.events: if isinstance(event, MouseEvent): self.onMousePosition(event.x, event.y) elif isinstance(event, KeyboardEvent): down = event.flags & KeyboardFlag.KBDFLAGS_DOWN == 0 ext = event.flags & KeyboardFlag.KBDFLAGS_EXTENDED != 0 self.onScanCode(event.keyCode, down, ext) def onFastPathInput(self, pdu: PlayerPDU): parser = BasicFastPathParser(ParserMode.SERVER) pdu = parser.parse(pdu.payload) for event in pdu.events: if isinstance(event, FastPathUnicodeEvent): if not event.released: self.onUnicode(event) elif isinstance(event, FastPathMouseEvent): self.onMouse(event) elif isinstance(event, FastPathScanCodeEvent): ext = event.rawHeaderByte & KBDFLAGS_EXTENDED != 0 self.onScanCode(event.scanCode, event.isReleased, ext) def onUnicode(self, event: FastPathUnicodeEvent): self.writeText(str(event.text)) def onMouse(self, event: FastPathMouseEvent): if event.pointerFlags & PointerFlag.PTRFLAGS_DOWN: if event.pointerFlags & PointerFlag.PTRFLAGS_BUTTON1: button = 'Left' elif event.pointerFlags & PointerFlag.PTRFLAGS_BUTTON2: button = 'Right' elif event.pointerFlags & PointerFlag.PTRFLAGS_BUTTON3: button = 'Middle' else: button = 'Unknown' self.writeText( f'\n<Click ({button}) @ ({event.mouseX}, {event.mouseY})>') self.onMousePosition(event.mouseX, event.mouseY) def onMousePosition(self, x: int, y: int): pass def onScanCode(self, scanCode: int, isReleased: bool, isExtended: bool): keyName = getKeyName(scanCode, isExtended, self.shiftPressed, self.capsLockOn) if len(keyName) == 1: if not isReleased: self.writeText(keyName) else: self.writeText( f"\n<{keyName} {'released' if isReleased else 'pressed'}>") # Handle shift. if scanCode in [0x2A, 0x36]: self.shiftPressed = not isReleased # Caps lock elif scanCode == 0x3A and not isReleased: self.capsLockOn = not self.capsLockOn def onDeviceMapping(self, pdu: PlayerDeviceMappingPDU): self.writeText( f"\n<{DeviceType.getPrettyName(pdu.deviceType)} mapped: {pdu.name}>" )
class PlayerMessageHandler(PlayerMessageObserver): """ Class to manage the display of the RDP player when reading events. """ def __init__(self, viewer, text): super().__init__() self.viewer = viewer self.text = text self.writeInCaps = False self.inputParser = BasicFastPathParser(ParserMode.SERVER) self.outputParser = BasicFastPathParser(ParserMode.CLIENT) self.clientInfoParser = ClientInfoParser() self.dataParser = SlowPathParser() self.clipboardParser = ClipboardParser() self.outputEventParser = FastPathOutputParser() self.clientConnectionParser = ClientConnectionParser() self.buffer = b"" def onConnectionClose(self, pdu: PlayerMessagePDU): self.text.moveCursor(QTextCursor.End) self.text.insertPlainText("\n<Connection closed>") def onOutput(self, pdu: PlayerMessagePDU): pdu = self.outputParser.parse(pdu.payload) for event in pdu.events: reassembledEvent = self.reassembleEvent(event) if reassembledEvent is not None: if isinstance(reassembledEvent, FastPathOrdersEvent): log.debug("Not handling orders event, not coded :)") elif isinstance(reassembledEvent, FastPathBitmapEvent): log.debug("Handling bitmap event %(arg1)s", {"arg1": type(reassembledEvent)}) self.onBitmap(reassembledEvent) else: log.debug("Can't handle output event: %(arg1)s", {"arg1": type(reassembledEvent)}) else: log.debug("Reassembling output event...") def onInput(self, pdu: PlayerMessagePDU): pdu = self.inputParser.parse(pdu.payload) for event in pdu.events: if isinstance(event, FastPathScanCodeEvent): log.debug("handling %(arg1)s", {"arg1": event}) self.onScanCode(event.scancode, not event.isReleased) elif isinstance(event, FastPathMouseEvent): self.onMousePosition(event.mouseX, event.mouseY) else: log.debug("Can't handle input event: %(arg1)s", {"arg1": event}) def onScanCode(self, code: int, isPressed: bool): """ Handle scan code. """ log.debug("Reading scancode %(arg1)s", {"arg1": code}) if code in [0x2A, 0x36]: self.text.moveCursor(QTextCursor.End) self.text.insertPlainText( "\n<LSHIFT PRESSED>" if isPressed else "\n<LSHIFT RELEASED>") self.writeInCaps = not self.writeInCaps elif code == 0x3A and isPressed: self.text.moveCursor(QTextCursor.End) self.text.insertPlainText("\n<CAPSLOCK>") self.writeInCaps = not self.writeInCaps elif isPressed: char = scancodeToChar(code) self.text.moveCursor(QTextCursor.End) self.text.insertPlainText( char if self.writeInCaps else char.lower()) def onMousePosition(self, x: int, y: int): self.viewer.setMousePosition(x, y) def onBitmap(self, event: FastPathBitmapEvent): parsedEvent = self.outputEventParser.parseBitmapEvent(event) for bitmapData in parsedEvent.bitmapUpdateData: self.handleBitmap(bitmapData) def handleBitmap(self, bitmapData: BitmapUpdateData): image = RDPBitmapToQtImage( bitmapData.width, bitmapData.heigth, bitmapData.bitsPerPixel, bitmapData.flags & BitmapFlags.BITMAP_COMPRESSION != 0, bitmapData.bitmapData) self.viewer.notifyImage(bitmapData.destLeft, bitmapData.destTop, image, bitmapData.destRight - bitmapData.destLeft + 1, bitmapData.destBottom - bitmapData.destTop + 1) def onClientInfo(self, pdu: PlayerMessagePDU): clientInfoPDU = self.clientInfoParser.parse(pdu.payload) self.text.insertPlainText( "USERNAME: {}\nPASSWORD: {}\nDOMAIN: {}\n".format( clientInfoPDU.username.replace("\0", ""), clientInfoPDU.password.replace("\0", ""), clientInfoPDU.domain.replace("\0", ""))) self.text.insertPlainText("--------------------\n") def onSlowPathPDU(self, pdu: PlayerMessagePDU): pdu = self.dataParser.parse(pdu.payload) if isinstance(pdu, ConfirmActivePDU): self.viewer.resize( pdu.parsedCapabilitySets[ CapabilityType.CAPSTYPE_BITMAP].desktopWidth, pdu.parsedCapabilitySets[ CapabilityType.CAPSTYPE_BITMAP].desktopHeight) elif isinstance( pdu, UpdatePDU ) and pdu.updateType == SlowPathUpdateType.SLOWPATH_UPDATETYPE_BITMAP: updates = BitmapParser().parseBitmapUpdateData(pdu.updateData) for bitmap in updates: self.handleBitmap(bitmap) elif isinstance(pdu, InputPDU): for event in pdu.events: if isinstance(event, MouseEvent): self.onMousePosition(event.x, event.y) elif isinstance(event, KeyboardEvent): self.onScanCode( event.keyCode, event.flags & KeyboardFlag.KBDFLAGS_DOWN != 0) def onClipboardData(self, pdu: PlayerMessagePDU): formatDataResponsePDU: FormatDataResponsePDU = self.clipboardParser.parse( pdu.payload) self.text.moveCursor(QTextCursor.End) self.text.insertPlainText("\n=============\n") self.text.insertPlainText("CLIPBOARD DATA: {}".format( decodeUTF16LE(formatDataResponsePDU.requestedFormatData))) self.text.insertPlainText("\n=============\n") def onClientData(self, pdu: PlayerMessagePDU): """ Prints the clientName on the screen """ clientDataPDU = self.clientConnectionParser.parse(pdu.payload) self.text.moveCursor(QTextCursor.End) self.text.insertPlainText("--------------------\n") self.text.insertPlainText( f"HOST: {clientDataPDU.coreData.clientName.strip(chr(0))}\n") def reassembleEvent( self, event: FastPathOutputEvent ) -> Optional[Union[FastPathBitmapEvent, FastPathOutputEvent]]: """ Handles FastPath event reassembly as described in https://msdn.microsoft.com/en-us/library/cc240622.aspx :param event: A potentially segmented fastpath output event :return: a FastPathBitmapEvent if a complete PDU has been reassembled, otherwise None. If the event is not fragmented, returns the original event. """ fragmentationFlag = FastPathFragmentation((event.header & 0b00110000) >> 4) if fragmentationFlag == FastPathFragmentation.FASTPATH_FRAGMENT_SINGLE: return event elif fragmentationFlag == FastPathFragmentation.FASTPATH_FRAGMENT_FIRST: self.buffer = event.payload elif fragmentationFlag == FastPathFragmentation.FASTPATH_FRAGMENT_NEXT: self.buffer += event.payload elif fragmentationFlag == FastPathFragmentation.FASTPATH_FRAGMENT_LAST: self.buffer += event.payload event.payload = self.buffer return self.outputEventParser.parseBitmapEvent(event) return None