Merge pull request #78 from speidy/mitm

rdpy-rdpmitm: pep8 fixes, argument parsing improvements
This commit is contained in:
Sylvain Peyrefitte
2018-08-05 16:23:28 +02:00
committed by GitHub

View File

@@ -29,7 +29,10 @@ Client RDP -> | ProxyServer | ProxyClient | -> Server RDP
----------------- -----------------
""" """
import sys, os, getopt, time import sys
import os
import argparse
import time
from rdpy.core import log, error, rss from rdpy.core import log, error, rss
from rdpy.protocol.rdp import rdp from rdpy.protocol.rdp import rdp
@@ -37,10 +40,12 @@ from twisted.internet import reactor
log._LOG_LEVEL = log.Level.INFO log._LOG_LEVEL = log.Level.INFO
class ProxyServer(rdp.RDPServerObserver): class ProxyServer(rdp.RDPServerObserver):
""" """
@summary: Server side of proxy @summary: Server side of proxy
""" """
def __init__(self, controller, target, clientSecurityLevel, rssRecorder): def __init__(self, controller, target, clientSecurityLevel, rssRecorder):
""" """
@param controller: {RDPServerController} @param controller: {RDPServerController}
@@ -52,14 +57,14 @@ class ProxyServer(rdp.RDPServerObserver):
self._client = None self._client = None
self._rss = rssRecorder self._rss = rssRecorder
self._clientSecurityLevel = clientSecurityLevel self._clientSecurityLevel = clientSecurityLevel
def setClient(self, client): def setClient(self, client):
""" """
@summary: Event throw by client when it's ready @summary: Event throw by client when it's ready
@param client: {ProxyClient} @param client: {ProxyClient}
""" """
self._client = client self._client = client
def onReady(self): def onReady(self):
""" """
@summary: Event use to inform state of server stack @summary: Event use to inform state of server stack
@@ -69,29 +74,30 @@ class ProxyServer(rdp.RDPServerObserver):
@see: rdp.RDPServerObserver.onReady @see: rdp.RDPServerObserver.onReady
""" """
if self._client is None: if self._client is None:
#try a connection # try a connection
domain, username, password = self._controller.getCredentials() domain, username, password = self._controller.getCredentials()
self._rss.credentials(username, password, domain, self._controller.getHostname()) self._rss.credentials(username, password,
domain, self._controller.getHostname())
width, height = self._controller.getScreen() width, height = self._controller.getScreen()
self._rss.screen(width, height, self._controller.getColorDepth()) self._rss.screen(width, height, self._controller.getColorDepth())
reactor.connectTCP(self._target[0], int(self._target[1]), ProxyClientFactory(self, width, height, reactor.connectTCP(self._target[0], int(self._target[1]), ProxyClientFactory(self, width, height,
domain, username, password,self._clientSecurityLevel)) domain, username, password, self._clientSecurityLevel))
def onClose(self): def onClose(self):
""" """
@summary: Call when human client close connection @summary: Call when human client close connection
@see: rdp.RDPServerObserver.onClose @see: rdp.RDPServerObserver.onClose
""" """
#end scenario # end scenario
self._rss.close() self._rss.close()
#close network stack # close network stack
if self._client is None: if self._client is None:
return return
self._client._controller.close() self._client._controller.close()
def onKeyEventScancode(self, code, isPressed, isExtended): def onKeyEventScancode(self, code, isPressed, isExtended):
""" """
@summary: Event call when a keyboard event is catch in scan code format @summary: Event call when a keyboard event is catch in scan code format
@@ -102,9 +108,10 @@ class ProxyServer(rdp.RDPServerObserver):
""" """
if self._client is None: if self._client is None:
return return
self._client._controller.sendKeyEventScancode(code, isPressed, isExtended) self._client._controller.sendKeyEventScancode(
code, isPressed, isExtended)
self._rss.keyScancode(code, isPressed) self._rss.keyScancode(code, isPressed)
def onKeyEventUnicode(self, code, isPressed): def onKeyEventUnicode(self, code, isPressed):
""" """
@summary: Event call when a keyboard event is catch in unicode format @summary: Event call when a keyboard event is catch in unicode format
@@ -116,7 +123,7 @@ class ProxyServer(rdp.RDPServerObserver):
return return
self._client._controller.sendKeyEventUnicode(code, isPressed) self._client._controller.sendKeyEventUnicode(code, isPressed)
self._rss.keyUnicode(code, isPressed) self._rss.keyUnicode(code, isPressed)
def onPointerEvent(self, x, y, button, isPressed): def onPointerEvent(self, x, y, button, isPressed):
""" """
@summary: Event call on mouse event @summary: Event call on mouse event
@@ -129,11 +136,13 @@ class ProxyServer(rdp.RDPServerObserver):
if self._client is None: if self._client is None:
return return
self._client._controller.sendPointerEvent(x, y, button, isPressed) self._client._controller.sendPointerEvent(x, y, button, isPressed)
class ProxyServerFactory(rdp.ServerFactory): class ProxyServerFactory(rdp.ServerFactory):
""" """
@summary: Factory on listening events @summary: Factory on listening events
""" """
def __init__(self, target, ouputDir, privateKeyFilePath, certificateFilePath, clientSecurity): def __init__(self, target, ouputDir, privateKeyFilePath, certificateFilePath, clientSecurity):
""" """
@param target: {tuple(ip, prt)} @param target: {tuple(ip, prt)}
@@ -141,13 +150,14 @@ class ProxyServerFactory(rdp.ServerFactory):
@param certificateFilePath: {str} file contain server certificate (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 @param clientSecurity: {str(ssl|rdp)} security layer use in client connection side
""" """
rdp.ServerFactory.__init__(self, 16, privateKeyFilePath, certificateFilePath) rdp.ServerFactory.__init__(
self, 16, privateKeyFilePath, certificateFilePath)
self._target = target self._target = target
self._ouputDir = ouputDir self._ouputDir = ouputDir
self._clientSecurity = clientSecurity self._clientSecurity = clientSecurity
#use produce unique file by connection # use produce unique file by connection
self._uniqueId = 0 self._uniqueId = 0
def buildObserver(self, controller, addr): def buildObserver(self, controller, addr):
""" """
@param controller: {rdp.RDPServerController} @param controller: {rdp.RDPServerController}
@@ -155,12 +165,14 @@ class ProxyServerFactory(rdp.ServerFactory):
@see: rdp.ServerFactory.buildObserver @see: rdp.ServerFactory.buildObserver
""" """
self._uniqueId += 1 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)))) 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): class ProxyClient(rdp.RDPClientObserver):
""" """
@summary: Client side of proxy @summary: Client side of proxy
""" """
def __init__(self, controller, server): def __init__(self, controller, server):
""" """
@param controller: {rdp.RDPClientController} @param controller: {rdp.RDPClientController}
@@ -168,7 +180,7 @@ class ProxyClient(rdp.RDPClientObserver):
""" """
rdp.RDPClientObserver.__init__(self, controller) rdp.RDPClientObserver.__init__(self, controller)
self._server = server self._server = server
def onReady(self): def onReady(self):
""" """
@summary: Event use to signal that RDP stack is ready @summary: Event use to signal that RDP stack is ready
@@ -176,25 +188,26 @@ class ProxyClient(rdp.RDPClientObserver):
@see: rdp.RDPClientObserver.onReady @see: rdp.RDPClientObserver.onReady
""" """
self._server.setClient(self) self._server.setClient(self)
#maybe color depth change # maybe color depth change
self._server._controller.setColorDepth(self._controller.getColorDepth()) self._server._controller.setColorDepth(
self._controller.getColorDepth())
def onSessionReady(self): def onSessionReady(self):
""" """
@summary: Windows session is ready @summary: Windows session is ready
@see: rdp.RDPClientObserver.onSessionReady @see: rdp.RDPClientObserver.onSessionReady
""" """
pass pass
def onClose(self): def onClose(self):
""" """
@summary: Event inform that stack is close @summary: Event inform that stack is close
@see: rdp.RDPClientObserver.onClose @see: rdp.RDPClientObserver.onClose
""" """
#end scenario # end scenario
self._server._rss.close() self._server._rss.close()
self._server._controller.close() self._server._controller.close()
def onUpdate(self, destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data): def onUpdate(self, destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data):
""" """
@summary: Event use to inform bitmap update @summary: Event use to inform bitmap update
@@ -209,13 +222,17 @@ class ProxyClient(rdp.RDPClientObserver):
@param data: {str} bitmap data @param data: {str} bitmap data
@see: rdp.RDPClientObserver.onUpdate @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._rss.update(destLeft, destTop, destRight, destBottom, width, height,
self._server._controller.sendUpdate(destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data) 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): class ProxyClientFactory(rdp.ClientFactory):
""" """
@summary: Factory for proxy client @summary: Factory for proxy client
""" """
def __init__(self, server, width, height, domain, username, password, security): def __init__(self, server, width, height, domain, username, password, security):
""" """
@param server: {ProxyServer} @param server: {ProxyServer}
@@ -233,7 +250,7 @@ class ProxyClientFactory(rdp.ClientFactory):
self._username = username self._username = username
self._password = password self._password = password
self._security = security self._security = security
def buildObserver(self, controller, addr): def buildObserver(self, controller, addr):
""" """
@summary: Build observer @summary: Build observer
@@ -242,69 +259,65 @@ class ProxyClientFactory(rdp.ClientFactory):
@see: rdp.ClientFactory.buildObserver @see: rdp.ClientFactory.buildObserver
@return: ProxyClient @return: ProxyClient
""" """
#set screen resolution # set screen resolution
controller.setScreen(self._width, self._height) controller.setScreen(self._width, self._height)
#set credential # set credential
controller.setDomain(self._domain) controller.setDomain(self._domain)
controller.setUsername(self._username) controller.setUsername(self._username)
controller.setPassword(self._password) controller.setPassword(self._password)
controller.setSecurityLevel(self._security) controller.setSecurityLevel(self._security)
controller.setPerformanceSession() controller.setPerformanceSession()
return ProxyClient(controller, self._server) return ProxyClient(controller, self._server)
def help():
"""
@summary: Print help in console
"""
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)]
[-o output directory for recoded files]
[-r RDP standard security (XP or server 2003 client or older)]
[-n For NLA Client authentication (need to provide credentials)]
"""
def parseIpPort(interface, defaultPort = "3389"):
def parseIpPort(interface, defaultPort="3389"):
if ':' in interface: if ':' in interface:
return interface.split(':') s = interface.split(':')
return s[0], int(s[1])
else: else:
return interface, defaultPort return interface, int(defaultPort)
def isDirectory(outputDirectory):
if outputDirectory is None or not os.path.dirname(outputDirectory):
log.error("{} is an invalid output directory or directory doesn't exist".format(
outputDirectory))
return outputDirectory
def mapSecurityLayer(layer):
return {
"rdp": rdp.SecurityLevel.RDP_LEVEL_RDP,
"tls": rdp.SecurityLevel.RDP_LEVEL_SSL,
"nla": rdp.SecurityLevel.RDP_LEVEL_NLA
}[layer]
if __name__ == '__main__': if __name__ == '__main__':
listen = "3389" p = argparse.ArgumentParser(
privateKeyFilePath = None formatter_class=argparse.ArgumentDefaultsHelpFormatter)
certificateFilePath = None
ouputDirectory = None p.add_argument('-l', '--listen', type=parseIpPort, default="0.0.0.0:3389",
#for anonymous authentication help="<addr>[:<port>] to bind the server")
clientSecurity = rdp.SecurityLevel.RDP_LEVEL_SSL p.add_argument('-t', '--target', type=parseIpPort, required=True,
help="<addr>[:<port>] of the target you want to connect to via proxy")
try: p.add_argument('-o', '--output', type=isDirectory,
opts, args = getopt.getopt(sys.argv[1:], "hl:k:c:o:rn") help="output directory", required=True)
except getopt.GetoptError: p.add_argument('-s', '--sec', choices=["rdp", "tls", "nla"],
help() default="rdp", help="set protocol security layer")
for opt, arg in opts: ssl = p.add_argument_group()
if opt == "-h": ssl.add_argument('-c', '--certificate', help="certificate for TLS connections")
help() ssl.add_argument('-k', '--key', help="private key of the given certificate for TLS connections")
sys.exit()
elif opt == "-l": args = p.parse_args()
listen = arg
elif opt == "-k": if args.certificate and args.key and not args.sec == "nla":
privateKeyFilePath = arg args.sec = "tls"
elif opt == "-c":
certificateFilePath = arg log.info("running server on {addr}, using {sec} security layer, proxying to {target}".format(
elif opt == "-o": addr=args.listen, sec=args.sec.upper(), target=args.target))
ouputDirectory = arg reactor.listenTCP(args.listen[1], ProxyServerFactory(
elif opt == "-r": args.target, args.output, args.key, args.certificate, mapSecurityLayer(args.sec)),
clientSecurity = rdp.SecurityLevel.RDP_LEVEL_RDP interface=args.listen[0])
elif opt == "-n":
clientSecurity = rdp.SecurityLevel.RDP_LEVEL_NLA reactor.run()
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(listen), ProxyServerFactory(parseIpPort(args[0]), ouputDirectory, privateKeyFilePath, certificateFilePath, clientSecurity))
reactor.run()