Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 89 additions & 0 deletions examples/tstool.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
# tslogoff: Signs-out a Remote Desktop Services session
# shutdown: Remote shutdown
# msg: Send a message to Remote Desktop Services session (MSGBOX)
# shadow: Shadow a Remote Desktop Services session
#
# Author:
# Alexander Korznikov (@nopernik)
Expand All @@ -33,6 +34,8 @@
import codecs
import logging
import sys
from xml.etree.ElementTree import tostring
import xml.etree.ElementTree as ET
from struct import unpack

from impacket import version
Expand All @@ -45,6 +48,11 @@
from impacket.dcerpc.v5.dtypes import MAXIMUM_ALLOWED

from impacket.dcerpc.v5 import tsts as TSTS
from impacket.dcerpc.v5.tsts import (
SHADOW_CONTROL_REQUEST,
SHADOW_PERMISSION_REQUEST,
SHADOW_REQUEST_RESPONSE
)
import traceback


Expand Down Expand Up @@ -533,6 +541,81 @@ def do_msg(self):
LOG.error('Could not find SessionID: %d' % options.session)
else:
LOG.error(str(e))

def do_shadow(self):
"""
Request a Remote Connection String to shadow a Remote Desktop Services session.
Author: Ilya Yatsenko (@fulc2um)
"""
control = (SHADOW_CONTROL_REQUEST.enumItems.SHADOW_CONTROL_REQUEST_TAKECONTROL
if self.__options.control
else SHADOW_CONTROL_REQUEST.enumItems.SHADOW_CONTROL_REQUEST_VIEW)

perm = (SHADOW_PERMISSION_REQUEST.enumItems.SHADOW_PERMISSION_REQUEST_REQUESTPERMISSION
if self.__options.prompt
else SHADOW_PERMISSION_REQUEST.enumItems.SHADOW_PERMISSION_REQUEST_SILENT)

LOG.info(f"Calling RpcShadow2 (SessionId={self.__options.session}, Control={self.__options.control}, Permission={self.__options.prompt})")

try:
with TSTS.SessEnvPublicRpc(self.__smbConnection, self.__options.target_ip, self.__doKerberos) as sErpc:
response = sErpc.hRpcShadow2(self.__options.session, control, perm, 8192)

if self.__options.debug:
LOG.debug(f"Response: {response.getData()}")

permission = response['pePermission']
invitation = response['pszInvitation']

except DCERPCException as e:
LOG.error(f"RPC Exception: {e}")
return

if permission is not None:
try:
desc = TSTS.enum2value(SHADOW_REQUEST_RESPONSE, permission)
except (KeyError, AttributeError):
desc = "Unknown"
LOG.info(f"Permission: {permission} ({desc})")

if permission == SHADOW_REQUEST_RESPONSE.enumItems.SHADOW_REQUEST_RESPONSE_ALLOW.value:
LOG.info("RpcShadow2 call succeeded!")

if not invitation:
LOG.error("RpcShadow2 failed: No invitation received")
sys.exit(1)

LOG.info(f"Invitation received ({len(invitation)} characters)")

try:
invitation = invitation.rstrip('\x00\r\n').strip()

invitation = ET.fromstring(invitation)
except ET.ParseError:
if invitation.startswith('<') and not invitation.endswith('>'):
if '</E>' in invitation:
end_pos = invitation.rfind('</E>') + 4
invitation = invitation[:end_pos]
try:
invitation = ET.fromstring(invitation)
except ET.ParseError:
invitation = None
else:
invitation = None
else:
invitation = None

if invitation:
invitation = tostring(invitation, encoding='utf-8', method='xml').decode('utf-8')
LOG.info("Invitation is well-formed XML")
with open(self.__options.file, 'w', encoding='utf-8') as f:
f.write(invitation)
LOG.info(f"Saved to {self.__options.file} file")
else:
LOG.error("Invitation does not appear to be well-formed XML")
else:
LOG.error("RpcShadow2 failed: Permission denied")
sys.exit(1)


if __name__ == '__main__':
Expand Down Expand Up @@ -593,6 +676,12 @@ def do_msg(self):
msg_parser.add_argument('-title', action='store', metavar="'Your Title'", type=str, required=False, help='Title of the MessageBox [Optional]')
msg_parser.add_argument('-message', action='store', metavar="'Your Message'", type=str, required=True, help='Contents of the MessageBox')

shadow_parser = subparsers.add_parser('shadow', help='Shadow a Remote Desktop Services session.')
shadow_parser.add_argument('-session', action='store', metavar="SessionID", type=int, required=True, help='SessionId to shadow')
shadow_parser.add_argument('-control', action='store_true', help='Request control of the session (default is view only)')
shadow_parser.add_argument('-prompt', action='store_true', help='Request user permission (default is silent)')
shadow_parser.add_argument('-file', type=str, help='Save invitation to file', default='invite.msrcIncident')

# Authentication options
group = parser.add_argument_group('authentication')

Expand Down
38 changes: 38 additions & 0 deletions impacket/dcerpc/v5/tsts.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
TermSrvEnumeration_UUID = uuidtup_to_bin(('88143fd0-c28d-4b2b-8fef-8d882f6a9390','1.0'))
RCMPublic_UUID = uuidtup_to_bin(('bde95fdf-eee0-45de-9e12-e5a61cd0d4fe','1.0'))
RcmListener_UUID = uuidtup_to_bin(('497d95a6-2d27-4bf5-9bbd-a6046957133c','1.0'))
SessEnvPublicRpc_UUID = uuidtup_to_bin(('1257b580-ce2f-4109-82d6-a9459d0bf6bc','1.0'))
LegacyAPI_UUID = uuidtup_to_bin(('5ca4a760-ebb1-11cf-8611-00a0245420ed','1.0'))

AUDIODRIVENAME_LENGTH = 9
Expand Down Expand Up @@ -1899,6 +1900,24 @@ class RpcGetRemoteAddressResponse(NDRCALL):
('ErrorCode', ULONG),
)


# 3.5.4.1.6 RpcShadow2 (Opnum 0)
class RpcShadow2(NDRCALL):
opnum = 0
structure = (
('TargetSessionId', ULONG),
('eRequestControl', SHADOW_CONTROL_REQUEST),
('eRequestPermission', SHADOW_PERMISSION_REQUEST),
('cchInvitation', ULONG),
)

class RpcShadow2Response(NDRCALL):
structure = (
('pePermission', SHADOW_REQUEST_RESPONSE),
('pszInvitation', WSTR),
('ErrorCode', ULONG),
)

#OLD 3.4.4.1.6 RpcShadow (Opnum 5)
# Probably deprecated. Taken from [MS-TSTS] – v20080207
class RpcShadow(NDRCALL):
Expand Down Expand Up @@ -3663,6 +3682,14 @@ def hRpcWinStationOpenSessionDirectory(dce, hServer, pszServerName):
request['pszServerName'] = pszServerName
return dce.request(request, checkError=False)

# 3.10.4.1.1 RpcShadow2 (Opnum 0)
def hRpcShadow2(dce, TargetSessionId, eRequestControl, eRequestPermission, cchInvitation = 8192):
request = RpcShadow2()
request['TargetSessionId'] = TargetSessionId
request['eRequestControl'] = eRequestControl
request['eRequestPermission'] = eRequestPermission
request['cchInvitation'] = cchInvitation
return dce.request(request, checkError=False)

################################################################################
# Initialization Classes and Helper classes
Expand Down Expand Up @@ -3773,6 +3800,17 @@ def __init__(self, smb, target_ip, kerberos):
hRpcStartListener = hRpcStartListener
hRpcIsListening = hRpcIsListening

class SessEnvPublicRpc(TSTSEndpoint):
def __init__(self, smb, target_ip, kerberos):
super().__init__(smb, target_ip,
stringbinding = r'ncacn_np:{}[\pipe\SessEnvPublicRpc]',
endpoint = SessEnvPublicRpc_UUID,
kerberos = kerberos
)

hRpcShadow2 = hRpcShadow2


class LegacyAPI(TSTSEndpoint):
def __init__(self, smb, target_ip, kerberos):
super().__init__(smb, target_ip,
Expand Down