Skip to content

Commit

Permalink
Merge pull request #195 from GoSecure/no-files
Browse files Browse the repository at this point in the history
feat: Added --no-files switch to disable  file carving.
  • Loading branch information
obilodeau committed Mar 26, 2020
2 parents 3780de0 + d41aaf4 commit 6d1623a
Show file tree
Hide file tree
Showing 5 changed files with 30 additions and 15 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ For a detailed view of what has changed, refer to the {uri-repo}/commits/master[
* Documentation updates and fixes ({uri-issue}165[#165], {uri-issue}166[#166], {uri-issue}172[#172])
* Added `--disable-active-clipboard` switch to prevent clipboard request injection
* Added `--no-downgrade` switch to prevent protocol downgrading where possible {uri-issue}189[#189]
* Added `--no-files` switch to prevent extracting transferred files {uri-issue}195[#195]

=== Bug fixes

Expand Down
2 changes: 2 additions & 0 deletions bin/pyrdp-mitm.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ def main():
parser.add_argument("--crawler-ignore-file", help="File to be used by the crawler to chose what folders to avoid when scraping the client shared drives.", default=None)
parser.add_argument("--no-replay", help="Disable replay recording", action="store_true")
parser.add_argument("--no-downgrade", help="Disables downgrading of unsupported extensions. This makes PyRDP harder to fingerprint but might impact the player's ability to replay captured traffic.", action="store_true")
parser.add_argument("--no-files", help="Do not extract files transferred between the client and server.", action="store_true")

args = parser.parse_args()
outDir = Path(args.output)
Expand Down Expand Up @@ -78,6 +79,7 @@ def main():
config.crawlerIgnoreFileName = args.crawler_ignore_file
config.recordReplays = not args.no_replay
config.downgrade = not args.no_downgrade
config.extractFiles = not args.no_files
config.disableActiveClipboardStealing = args.disable_active_clipboard


Expand Down
37 changes: 23 additions & 14 deletions pyrdp/mitm/DeviceRedirectionMITM.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#
# This file is part of the PyRDP project.
# Copyright (C) 2019 GoSecure Inc.
# Copyright (C) 2019-2020 GoSecure Inc.
# Licensed under the GPLv3 or later.
#

Expand Down Expand Up @@ -56,7 +56,7 @@ class DeviceRedirectionMITM(Subject):


def __init__(self, client: DeviceRedirectionLayer, server: DeviceRedirectionLayer, log: LoggerAdapter,
config: MITMConfig, statCounter: StatCounter, state: RDPMITMState):
statCounter: StatCounter, state: RDPMITMState):
"""
:param client: device redirection layer for the client side
:param server: device redirection layer for the server side
Expand All @@ -70,19 +70,21 @@ def __init__(self, client: DeviceRedirectionLayer, server: DeviceRedirectionLaye
self.state = state
self.log = log
self.statCounter = statCounter
self.config = config
self.currentIORequests: Dict[int, DeviceIORequestPDU] = {}
self.openedFiles: Dict[int, FileProxy] = {}
self.openedMappings: Dict[int, FileMapping] = {}
self.fileMap: Dict[str, FileMapping] = {}
self.fileMapPath = self.config.outDir / "mapping.json"
self.forgedRequests: Dict[int, DeviceRedirectionMITM.ForgedRequest] = {}

self.responseHandlers: Dict[MajorFunction, callable] = {
MajorFunction.IRP_MJ_CREATE: self.handleCreateResponse,
MajorFunction.IRP_MJ_READ: self.handleReadResponse,
MajorFunction.IRP_MJ_CLOSE: self.handleCloseResponse,
}
if self.config.extractFiles:
self.responseHandlers: Dict[MajorFunction, callable] = {
MajorFunction.IRP_MJ_CREATE: self.handleCreateResponse,
MajorFunction.IRP_MJ_READ: self.handleReadResponse,
MajorFunction.IRP_MJ_CLOSE: self.handleCloseResponse,
}
else:
self.responseHandlers = {}

self.client.createObserver(
onPDUReceived=self.onClientPDUReceived,
Expand All @@ -102,6 +104,10 @@ def __init__(self, client: DeviceRedirectionLayer, server: DeviceRedirectionLaye
except json.JSONDecodeError:
self.log.error("Failed to decode file mapping, overwriting previous file")

@property
def config(self):
return self.state.config

def saveMapping(self):
"""
Save the file mapping to a file in JSON format.
Expand Down Expand Up @@ -220,7 +226,6 @@ def handleCreateResponse(self, request: DeviceCreateRequestPDU, response: Device
onFileClosed = lambda _: self.log.debug("Closing file %(path)s", {"path": mapping.localPath})
)


def handleReadResponse(self, request: DeviceReadRequestPDU, response: DeviceReadResponsePDU):
"""
Write the data that was read at the appropriate offset in the file proxy.
Expand Down Expand Up @@ -286,7 +291,6 @@ def handleCloseResponse(self, request: DeviceCloseRequestPDU, _: DeviceCloseResp

self.saveMapping()


def handleClientLogin(self):
"""
Handle events that should be triggered when a client logs in.
Expand All @@ -302,7 +306,6 @@ def handleClientLogin(self):
self.state.credentialsCandidate = ""
self.state.inputBuffer = ""


def findNextRequestID(self) -> int:
"""
Find the next request ID to be returned for a forged request. Request ID's start from a different base than the
Expand All @@ -316,7 +319,6 @@ def findNextRequestID(self) -> int:

return completionID


def sendForgedFileRead(self, deviceID: int, path: str) -> int:
"""
Send a forged requests for reading a file. Returns a request ID that can be used by the caller to keep track of
Expand All @@ -325,6 +327,10 @@ def sendForgedFileRead(self, deviceID: int, path: str) -> int:
:param path: path of the file to download. The path should use '\' instead of '/' to separate directories.
"""

if not self.config.extractFiles:
self.log.info('Ignored attempt to forge file reads because file extraction is disabled.')
return

self.statCounter.increment(STAT.DEVICE_REDIRECTION_FORGED_FILE_READ)

completionID = self.findNextRequestID()
Expand All @@ -334,7 +340,6 @@ def sendForgedFileRead(self, deviceID: int, path: str) -> int:
request.send()
return completionID


def sendForgedDirectoryListing(self, deviceID: int, path: str) -> int:
"""
Send a forged directory listing request. Returns a request ID that can be used by the caller to keep track of which
Expand All @@ -345,6 +350,10 @@ def sendForgedDirectoryListing(self, deviceID: int, path: str) -> int:
\Documents\*
"""

if not self.config.extractFiles:
self.log.info('Ignored attempt to forge directory listing because file extraction is disabled.')
return

self.statCounter.increment(STAT.DEVICE_REDIRECTION_FORGED_DIRECTORY_LISTING)

completionID = self.findNextRequestID()
Expand Down Expand Up @@ -578,4 +587,4 @@ def handleDirectoryListingComplete(self, _: DeviceQueryDirectoryResponsePDU):
self.mitm.observer.onDirectoryListingComplete(self.deviceID, self.requestID)

# Once we're done, we can close the file.
self.sendCloseRequest()
self.sendCloseRequest()
2 changes: 1 addition & 1 deletion pyrdp/mitm/RDPMITM.py
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,7 @@ def buildDeviceChannel(self, client: MCSServerChannel, server: MCSClientChannel)
LayerChainItem.chain(client, clientSecurity, clientVirtualChannel, clientLayer)
LayerChainItem.chain(server, serverSecurity, serverVirtualChannel, serverLayer)

deviceRedirection = DeviceRedirectionMITM(clientLayer, serverLayer, self.getLog(MCSChannelName.DEVICE_REDIRECTION), self.config, self.statCounter, self.state)
deviceRedirection = DeviceRedirectionMITM(clientLayer, serverLayer, self.getLog(MCSChannelName.DEVICE_REDIRECTION), self.statCounter, self.state)
self.channelMITMs[client.channelID] = deviceRedirection

if self.config.enableCrawler:
Expand Down
3 changes: 3 additions & 0 deletions pyrdp/mitm/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ def __init__(self):
self.outDir: Path = None
"""The output directory"""

self.extractFiles: bool = True
"""Whether to extract file transferred between the client and server."""

self.recordReplays: bool = True
"""Whether replays should be recorded or not"""

Expand Down

0 comments on commit 6d1623a

Please sign in to comment.