From 9a4f5f254c97b1e894310698794349ff5adfc578 Mon Sep 17 00:00:00 2001 From: speyrefitte Date: Wed, 7 Jan 2015 18:32:16 +0100 Subject: [PATCH] add first step of rsr support --- bin/rdpy-rdpmitm.py | 27 ++++++++--- bin/rdpy-rdpshare.py | 3 +- bin/rdpy-rsrplayer.py | 93 ++++++++++++++++++++++++++++++++++++ rdpy/core/rsr.py | 106 ++++++++++++++++++++++++++++++++++++++++++ rdpy/ui/qt4.py | 4 +- 5 files changed, 223 insertions(+), 10 deletions(-) create mode 100755 bin/rdpy-rsrplayer.py create mode 100644 rdpy/core/rsr.py diff --git a/bin/rdpy-rdpmitm.py b/bin/rdpy-rdpmitm.py index 188fce9..5371172 100755 --- a/bin/rdpy-rdpmitm.py +++ b/bin/rdpy-rdpmitm.py @@ -21,20 +21,20 @@ """ RDP proxy with Man in the middle capabilities Save bitmap in file and keylogging - --------------------------- + ---------------------------- Client RDP -> | ProxyServer | ProxyClient | -> Server RDP - --------------------------- - | Save Session | - -------------- + ---------------------------- + | Record Session | + ----------------- """ -import sys, os, getopt, json +import sys, os, getopt, time -from rdpy.core import log, error +from rdpy.core import log, error, type, rsr from rdpy.protocol.rdp import rdp from twisted.internet import reactor -log._LOG_LEVEL = log.Level.DEBUG +log._LOG_LEVEL = log.Level.INFO class ProxyServer(rdp.RDPServerObserver): """ @@ -161,6 +161,7 @@ class ProxyClient(rdp.RDPClientObserver): rdp.RDPClientObserver.__init__(self, controller) self._server = server self._connected = False + self._rsr = rsr.createRecorder("/tmp/toto") def onReady(self): """ @@ -199,6 +200,17 @@ class ProxyClient(rdp.RDPClientObserver): @param data: bitmap data @see: rdp.RDPClientObserver.onUpdate """ + e = rsr.UpdateEvent() + e.destLeft.value = destLeft + e.destTop.value = destTop + e.destRight.value = destRight + e.destBottom.value = destBottom + e.width.value = width + e.height.value = height + e.bpp.value = bitsPerPixel + e.format.value = rsr.UpdateFormat.BMP if isCompress else rsr.UpdateFormat.RAW + e.data.value = data + self._rsr.add(e) self._server._controller.sendUpdate(destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data) class ProxyClientFactory(rdp.ClientFactory): @@ -237,6 +249,7 @@ class ProxyClientFactory(rdp.ClientFactory): controller.setUsername(self._username) controller.setPassword(self._password) controller.setSecurityLevel(self._security) + controller.setPerformanceSession() return ProxyClient(controller, self._server) def help(): diff --git a/bin/rdpy-rdpshare.py b/bin/rdpy-rdpshare.py index 4f228ba..d9e9851 100755 --- a/bin/rdpy-rdpshare.py +++ b/bin/rdpy-rdpshare.py @@ -30,7 +30,7 @@ Client RDP -> | ProxyServer | ProxyClient | -> Server RDP Shadow client ------------| """ -import sys, os, getopt, json +import sys, os, getopt from rdpy.core import log, error from rdpy.protocol.rdp import rdp @@ -241,6 +241,7 @@ class ProxyClientFactory(rdp.ClientFactory): controller.setDomain(self._domain) controller.setUsername(self._username) controller.setPassword(self._password) + controller.setPerformanceSession() return ProxyClient(controller, self._server) class Shadow(rdp.RDPServerObserver): diff --git a/bin/rdpy-rsrplayer.py b/bin/rdpy-rsrplayer.py new file mode 100755 index 0000000..f53a0f6 --- /dev/null +++ b/bin/rdpy-rsrplayer.py @@ -0,0 +1,93 @@ +#!/usr/bin/python +# +# Copyright (c) 2014 Sylvain Peyrefitte +# +# This file is part of rdpy. +# +# rdpy is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +""" +rsr file player +""" + +import sys, os, getopt, socket + +from PyQt4 import QtGui, QtCore + +from rdpy.core import log, rsr +from rdpy.ui.qt4 import RDPBitmapToQtImage +log._LOG_LEVEL = log.Level.INFO + +class QRsrPlayer(QtGui.QWidget): + def __init__(self): + self._refresh = [] + #buffer image + self._buffer = QtGui.QImage(width, height, QtGui.QImage.Format_RGB32) + self._f = rsr.createReader("/tmp/toto") + self._nextEvent = self._f.next() + + def next(self): + #if self._nextEvent.type.value = rsr.EventType.UPDATE: + # self.notifyImage(self._nextEvent.event., y, RDPBitmapToQtImage(width, height, bitsPerPixel, isCompress, data), width, height) + self._nextEvent = self._f.next() + QtCore.QTimer.singleShot(0,) + + def notifyImage(self, x, y, qimage, width, height): + """ + @summary: Function call from QAdaptor + @param x: x position of new image + @param y: y position of new image + @param qimage: new QImage + """ + #save in refresh list (order is important) + self._refresh.append((x, y, qimage, width, height)) + #force update + self.update() + + def paintEvent(self, e): + """ + @summary: Call when Qt renderer engine estimate that is needed + @param e: QEvent + """ + #fill buffer image + with QtGui.QPainter(self._buffer) as qp: + #draw image + for (x, y, image, width, height) in self._refresh: + qp.drawImage(x, y, image, 0, 0, width, height) + #draw in widget + with QtGui.QPainter(self) as qp: + qp.drawImage(0, 0, self._buffer) + + self._refresh = [] + +def help(): + print "Usage: rdpy-rsrplayer [options] ip[:port]" + +if __name__ == '__main__': + + #default script argument + + try: + opts, args = getopt.getopt(sys.argv[1:], "h") + except getopt.GetoptError: + help() + for opt, arg in opts: + if opt == "-h": + help() + sys.exit() + + filepath = args[0] + #create application + app = QtGui.QApplication(sys.argv) + app.exec_() \ No newline at end of file diff --git a/rdpy/core/rsr.py b/rdpy/core/rsr.py new file mode 100644 index 0000000..06143ac --- /dev/null +++ b/rdpy/core/rsr.py @@ -0,0 +1,106 @@ +# +# Copyright (c) 2014-2015 Sylvain Peyrefitte +# +# This file is part of rdpy. +# +# rdpy is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# + +""" +Remote Session Recorder File format +Private protocol format to save events +""" + +from rdpy.core.type import CompositeType, FactoryType, UInt8, UInt16Le, UInt32Le, String, sizeof, Stream +from rdpy.core import log, error +import time + +class EventType(object): + UPDATE = 0x00000001 + +class UpdateFormat(object): + RAW = 0x01 + BMP = 0x02 + +class Event(CompositeType): + """ + @summary: A recorded event + """ + def __init__(self, event = None): + CompositeType.__init__(self) + self.type = UInt16Le(lambda:event.__class__._TYPE_) + self.timestamp = UInt32Le() + self.length = UInt32Le(lambda:(sizeof(self) - 12)) + + def EventFactory(): + """ + @summary: Closure for event factory + """ + for c in [UpdateEvent]: + if self.type.value == c._TYPE_: + return c(readLen = self.length) + log.debug("unknown event type : %s"%hex(self.type.value)) + #read entire packet + return String(readLen = self.length - 4) + + if event is None: + event = FactoryType(EventFactory) + elif not "_TYPE_" in event.__class__.__dict__: + raise error.InvalidExpectedDataException("Try to send an invalid event block") + + self.event = event + +class UpdateEvent(CompositeType): + _TYPE_ = EventType.UPDATE + def __init__(self, readLen = None): + CompositeType.__init__(self, readLen = readLen) + self.destLeft = UInt16Le() + self.destTop = UInt16Le() + self.destRight = UInt16Le() + self.destBottom = UInt16Le() + self.width = UInt16Le() + self.height = UInt16Le() + self.bpp = UInt8() + self.format = UInt8() + self.length = UInt32Le(lambda:sizeof(self.data)) + self.data = String(readLen = self.length) + +class FileRecorder(object): + def __init__(self, f): + self._file = f + self._createTime = int(time.time() * 1000) + + def add(self, event): + e = Event(event) + e.timestamp.value = int(time.time() * 1000) - self._createTime + + s = Stream() + s.writeType(e) + + self._file.write(s.getvalue()) + +class FileReader(object): + def __init__(self, f): + self._s = Stream(f) + + def next(self): + e = Event() + self._s.readType(e) + return e + +def createRecorder(path): + return FileRecorder(open(path, "wb")) + +def createReader(path): + return FileReader(open(path, "rb")) \ No newline at end of file diff --git a/rdpy/ui/qt4.py b/rdpy/ui/qt4.py index 4cdac0b..f3297f3 100644 --- a/rdpy/ui/qt4.py +++ b/rdpy/ui/qt4.py @@ -185,7 +185,7 @@ class RFBClientQt(RFBClientObserver, QAdaptor): #do something maybe a message pass -def RDPBitmapToQtImage(destLeft, width, height, bitsPerPixel, isCompress, data): +def RDPBitmapToQtImage(width, height, bitsPerPixel, isCompress, data): """ @summary: Bitmap transformation to Qt object @param width: width of bitmap @@ -309,7 +309,7 @@ class RDPClientQt(RDPClientObserver, QAdaptor): @param isCompress: use RLE compression @param data: bitmap data """ - image = RDPBitmapToQtImage(destLeft, width, height, bitsPerPixel, isCompress, data); + image = RDPBitmapToQtImage(width, height, bitsPerPixel, isCompress, data); #if image need to be cut #For bit alignement server may send more than image pixel self._widget.notifyImage(destLeft, destTop, image, destRight - destLeft + 1, destBottom - destTop + 1)