Skip to content

Commit

Permalink
tstool.py update & kerberos issue fix (#1420)
Browse files Browse the repository at this point in the history
* kerberos issue fix & refactoring

* tstool.py: resolve SID to Username
  • Loading branch information
nopernik authored Aug 7, 2024
1 parent d458fce commit 829239e
Show file tree
Hide file tree
Showing 2 changed files with 107 additions and 53 deletions.
120 changes: 82 additions & 38 deletions examples/tstool.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,9 @@
from impacket.examples.utils import parse_target
from impacket.smbconnection import SMBConnection
from impacket import LOG
from impacket.dcerpc.v5 import transport
from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_GSS_NEGOTIATE, RPC_C_AUTHN_LEVEL_PKT_PRIVACY
from impacket.dcerpc.v5 import transport, lsat, lsad
from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_GSS_NEGOTIATE, RPC_C_AUTHN_LEVEL_PKT_PRIVACY, DCERPCException
from impacket.dcerpc.v5.dtypes import MAXIMUM_ALLOWED

from impacket.dcerpc.v5 import tsts as TSTS
import traceback
Expand All @@ -58,12 +59,12 @@ def __init__(self, username, password, domain, options):
self.__doKerberos = options.k
self.__kdcHost = options.dc_ip
self.__smbConnection = None
self.__remoteOps = None

if options.hashes is not None:
self.__lmhash, self.__nthash = options.hashes.split(':')

def connect(self, remoteName, remoteHost):
self.remoteName = remoteName
self.__smbConnection = SMBConnection(remoteName, remoteHost, sess_port=int(self.__options.port))

if self.__doKerberos:
Expand All @@ -83,53 +84,47 @@ def run(self, remoteName, remoteHost):

def get_session_list(self):
# Retreive session list
smb = self.__smbConnection
target_ip = self.__options.target_ip
with TSTS.TermSrvEnumeration(self.__smbConnection, self.__options.target_ip) as lsm:
with TSTS.TermSrvEnumeration(self.__smbConnection, self.__options.target_ip, self.__doKerberos) as lsm:
handle = lsm.hRpcOpenEnum()
rsessions = lsm.hRpcGetEnumResult(handle, Level=1)['ppSessionEnumResult']
lsm.hRpcCloseEnum(handle)
self.sessions = {}
for i in rsessions:
sess = i['SessionInfo']['SessionEnum_Level1']
state = TSTS.enum2value(TSTS.WINSTATIONSTATECLASS, sess['State']).split('_')[-1]
self.sessions[sess['SessionId']] = { 'state' :state,
'SessionName' :sess['Name'],
'RemoteIp' :'',
'ClientName' :'',
'Username' :'',
'Domain' :'',
'Resolution' :'',
'ClientTimeZone':''
}
self.sessions[sess['SessionId']] = { 'state' :state,
'SessionName' :sess['Name'],
'RemoteIp' :'',
'ClientName' :'',
'Username' :'',
'Domain' :'',
'Resolution' :'',
'ClientTimeZone':''
}

def enumerate_sessions_config(self):
# Get session config one by one
smb = self.__smbConnection
target_ip = self.__options.target_ip
if len(self.sessions):
with TSTS.RCMPublic(self.__smbConnection, self.__options.target_ip) as termsrv:
with TSTS.RCMPublic(self.__smbConnection, self.__options.target_ip, self.__doKerberos) as termsrv:
for SessionId in self.sessions:
resp = termsrv.hRpcGetClientData(SessionId)
if resp is not None:
self.sessions[SessionId]['RemoteIp'] = resp['ppBuff']['ClientAddress']
self.sessions[SessionId]['ClientName'] = resp['ppBuff']['ClientName']
if len(resp['ppBuff']['UserName']) and not len(self.sessions[SessionId]['Username']):
self.sessions[SessionId]['Username'] = resp['ppBuff']['UserName']
self.sessions[SessionId]['Username'] = resp['ppBuff']['UserName']
if len(resp['ppBuff']['Domain']) and not len(self.sessions[SessionId]['Domain']):
self.sessions[SessionId]['Domain'] = resp['ppBuff']['Domain']
self.sessions[SessionId]['Domain'] = resp['ppBuff']['Domain']
self.sessions[SessionId]['Resolution'] = '{}x{}'.format(
resp['ppBuff']['HRes'],
resp['ppBuff']['VRes']
)
resp['ppBuff']['HRes'],
resp['ppBuff']['VRes']
)
self.sessions[SessionId]['ClientTimeZone'] = resp['ppBuff']['ClientTimeZone']['StandardName']

def enumerate_sessions_info(self):
# Get session info one by one
smb = self.__smbConnection
target_ip = self.__options.target_ip
if len(self.sessions):
with TSTS.TermSrvSession(self.__smbConnection, self.__options.target_ip) as TermSrvSession:
with TSTS.TermSrvSession(self.__smbConnection, self.__options.target_ip, self.__doKerberos) as TermSrvSession:
for SessionId in self.sessions.keys():
sessdata = TermSrvSession.hRpcGetSessionInformationEx(SessionId)
sessflags = TSTS.enum2value(TSTS.SESSIONFLAGS, sessdata['LSMSessionInfoExPtr']['LSM_SessionInfo_Level1']['SessionFlags'])
Expand Down Expand Up @@ -263,13 +258,64 @@ def do_qwinsta(self):
for row in result:
print(row)

def lookupSids(self):
# Slightly modified code from lookupsid.py
try:
stringbinding = r'ncacn_np:%s[\pipe\lsarpc]' % self.__options.target_ip
rpctransport = transport.DCERPCTransportFactory(stringbinding)
rpctransport.set_smb_connection(self.__smbConnection)
dce = rpctransport.get_dce_rpc()
if self.__doKerberos:
dce.set_auth_type(RPC_C_AUTHN_GSS_NEGOTIATE)
dce.set_auth_level(RPC_C_AUTHN_LEVEL_PKT_PRIVACY)
dce.connect()

dce.bind(lsat.MSRPC_UUID_LSAT)
sids = list(self.sids.keys())
if len(sids) > 32:
sids = sids[:32] # TODO in future update
resp = lsad.hLsarOpenPolicy2(dce, MAXIMUM_ALLOWED | lsat.POLICY_LOOKUP_NAMES)
policyHandle = resp['PolicyHandle']
try:
resp = lsat.hLsarLookupSids(dce, policyHandle, sids, lsat.LSAP_LOOKUP_LEVEL.LsapLookupWksta)
except DCERPCException as e:
if str(e).find('STATUS_SOME_NOT_MAPPED') >= 0:
resp = e.get_packet()
else:
raise
for sid, item in zip(sids,resp['TranslatedNames']['Names']):
# if item['Use'] != SID_NAME_USE.SidTypeUnknown:
domainIndex = item['DomainIndex']
if domainIndex == -1: # Unknown domain
self.sids[sid] = '{}\\{}'.format('???', item['Name'])
elif domainIndex >= 0:
name = '{}\\{}'.format(resp['ReferencedDomains']['Domains'][item['DomainIndex']]['Name'], item['Name'])
self.sids[sid] = name
dce.disconnect()
except:
logging.debug(traceback.format_exc())

def sidToUser(self, sid):
if sid[:2] == 'S-' and sid in self.sids:
return self.sids[sid]
return sid

def do_tasklist(self):
options = self.__options
with TSTS.LegacyAPI(self.__smbConnection, options.target_ip) as legacy:
with TSTS.LegacyAPI(self.__smbConnection, options.target_ip, self.__doKerberos) as legacy:
handle = legacy.hRpcWinStationOpenServer()
r = legacy.hRpcWinStationGetAllProcesses(handle)
if not len(r):
return None

self.sids = {}
for procInfo in r:
sid = procInfo['pSid']
if sid[:2] == 'S-' and sid not in self.sids:
self.sids[sid] = sid

self.lookupSids()

maxImageNameLen = max([len(i['ImageName']) for i in r])
maxSidLen = max([len(i['pSid']) for i in r])
if options.verbose:
Expand Down Expand Up @@ -321,8 +367,8 @@ def do_tasklist(self):
pid = procInfo['UniqueProcessId'],
sessionName = self.sessions[sessId]['SessionName'],
sessid = procInfo['SessionId'],
sessstate = self.sessions[sessId]['state'].replace('Disconnected','Disc'),
sid = procInfo['pSid'],
sessstate = self.sessions[sessId]['state'].replace('Disconnected','Disc'),
sid = self.sidToUser(procInfo['pSid']),
sessionuser = fullUserName,
workingset = procInfo['WorkingSetSize']//1000
)
Expand All @@ -336,19 +382,18 @@ def do_tasklist(self):
procInfo['ImageName'],
procInfo['UniqueProcessId'],
procInfo['SessionId'],
procInfo['pSid'],
self.sidToUser(procInfo['pSid']),
'{:,} K'.format(procInfo['WorkingSetSize']//1000),
)
print(row)


def do_taskkill(self):
options = self.__options
if options.pid is None and options.name is None:
LOG.error('One of the following is required: -pid, -name')
return
pidList = []
with TSTS.LegacyAPI(self.__smbConnection, options.target_ip) as legacy:
with TSTS.LegacyAPI(self.__smbConnection, options.target_ip, self.__doKerberos) as legacy:
handle = legacy.hRpcWinStationOpenServer()
if options.pid is None and options.name is not None:
r = legacy.hRpcWinStationGetAllProcesses(handle)
Expand All @@ -375,7 +420,7 @@ def do_taskkill(self):

def do_tscon(self):
options = self.__options
with TSTS.TermSrvSession(self.__smbConnection, options.target_ip) as TSSession:
with TSTS.TermSrvSession(self.__smbConnection, options.target_ip, self.__doKerberos) as TSSession:
try:
session_handle = None
print('Connecting SessionID %d to %d ...' % (options.source, options.dest), end='')
Expand Down Expand Up @@ -405,7 +450,7 @@ def do_tscon(self):

def do_tsdiscon(self):
options = self.__options
with TSTS.TermSrvSession(self.__smbConnection, options.target_ip) as TSSession:
with TSTS.TermSrvSession(self.__smbConnection, options.target_ip, self.__doKerberos) as TSSession:
try:
print('Disconnecting SessionID: %d ...' % options.session, end='')
session_handle = TSSession.hRpcOpenSession(options.session)
Expand All @@ -424,7 +469,7 @@ def do_tsdiscon(self):

def do_logoff(self):
options = self.__options
with TSTS.TermSrvSession(self.__smbConnection, options.target_ip) as TSSession:
with TSTS.TermSrvSession(self.__smbConnection, options.target_ip, self.__doKerberos) as TSSession:
try:
print('Signing-out SessionID: %d ...' % options.session, end='')
session_handle = TSSession.hRpcOpenSession(options.session)
Expand All @@ -445,7 +490,7 @@ def do_logoff(self):

def do_shutdown(self):
options = self.__options
with TSTS.LegacyAPI(self.__smbConnection, options.target_ip) as legacy:
with TSTS.LegacyAPI(self.__smbConnection, options.target_ip, self.__doKerberos) as legacy:
handle = legacy.hRpcWinStationOpenServer()
flags = 0
flagsList = []
Expand All @@ -472,8 +517,7 @@ def do_shutdown(self):

def do_msg(self):
options = self.__options

with TSTS.TermSrvSession(self.__smbConnection, options.target_ip) as TSSession:
with TSTS.TermSrvSession(self.__smbConnection, options.target_ip, self.__doKerberos) as TSSession:
try:
print('Sending message to SessionID: %d ...' % options.session, end='')
session_handle = TSSession.hRpcOpenSession(options.session)
Expand Down
40 changes: 25 additions & 15 deletions impacket/dcerpc/v5/tsts.py
Original file line number Diff line number Diff line change
Expand Up @@ -3667,7 +3667,9 @@ def hRpcWinStationOpenSessionDirectory(dce, hServer, pszServerName):
################################################################################

class TSTSEndpoint:
def __init__(self, smb, target_ip, stringbinding, endpoint, kerberos = False):
def __init__(self, smb, target_ip, stringbinding, endpoint, kerberos):
self.__doKerberos = kerberos
self._target_ip = target_ip
self._stringbinding = stringbinding.format(target_ip)
self._endpoint = endpoint
self._smbconnection = smb
Expand All @@ -3679,9 +3681,11 @@ def _bind(self):
self._rpctransport = transport.DCERPCTransportFactory(self._stringbinding)
self._rpctransport.set_smb_connection(self._smbconnection)
self._dce = self._rpctransport.get_dce_rpc()
self._dce.set_credentials(*self._rpctransport.get_credentials())
self._dce.connect()
if self.__doKerberos:
self._dce.set_auth_type(RPC_C_AUTHN_GSS_NEGOTIATE)
self._dce.set_auth_level(RPC_C_AUTHN_LEVEL_PKT_PRIVACY)

self._dce.connect()
self._dce.bind(self._endpoint)
return self._dce
def _disconnect(self):
Expand All @@ -3692,10 +3696,11 @@ def __exit__(self, type, value, traceback):
self._disconnect()

class TermSrvSession(TSTSEndpoint):
def __init__(self, smb, target_ip):
def __init__(self, smb, target_ip, kerberos):
super().__init__(smb, target_ip,
stringbinding = r'ncacn_np:{}[\pipe\LSM_API_service]',
endpoint = TermSrvSession_UUID
endpoint = TermSrvSession_UUID,
kerberos = kerberos
)
hRpcOpenSession = hRpcOpenSession
hRpcCloseSession = hRpcCloseSession
Expand All @@ -3715,21 +3720,23 @@ def __init__(self, smb, target_ip):
hRpcGetSessionInformationEx = hRpcGetSessionInformationEx

class TermSrvNotification(TSTSEndpoint):
def __init__(self, smb, target_ip):
def __init__(self, smb, target_ip, kerberos):
super().__init__(smb, target_ip,
stringbinding = r'ncacn_np:{}[\pipe\LSM_API_service]',
endpoint = TermSrvNotification_UUID
endpoint = TermSrvNotification_UUID,
kerberos = kerberos
)
hRpcWaitForSessionState = hRpcWaitForSessionState
hRpcRegisterAsyncNotification = hRpcRegisterAsyncNotification
hRpcWaitAsyncNotification = hRpcWaitAsyncNotification
hRpcUnRegisterAsyncNotification = hRpcUnRegisterAsyncNotification

class TermSrvEnumeration(TSTSEndpoint):
def __init__(self, smb, target_ip):
def __init__(self, smb, target_ip, kerberos):
super().__init__(smb, target_ip,
stringbinding = r'ncacn_np:{}[\pipe\LSM_API_service]',
endpoint = TermSrvEnumeration_UUID
endpoint = TermSrvEnumeration_UUID,
kerberos = kerberos
)
hRpcOpenEnum = hRpcOpenEnum
hRpcCloseEnum = hRpcCloseEnum
Expand All @@ -3738,10 +3745,11 @@ def __init__(self, smb, target_ip):
hRpcGetAllSessions = hRpcGetAllSessions

class RCMPublic(TSTSEndpoint):
def __init__(self, smb, target_ip):
def __init__(self, smb, target_ip, kerberos):
super().__init__(smb, target_ip,
stringbinding = r'ncacn_np:{}[\pipe\TermSrv_API_service]',
endpoint = RCMPublic_UUID
endpoint = RCMPublic_UUID,
kerberos = kerberos
)
hRpcGetClientData = hRpcGetClientData
hRpcGetConfigData = hRpcGetConfigData
Expand All @@ -3751,10 +3759,11 @@ def __init__(self, smb, target_ip):


class RcmListener(TSTSEndpoint):
def __init__(self, smb, target_ip):
def __init__(self, smb, target_ip, kerberos):
super().__init__(smb, target_ip,
stringbinding = r'ncacn_np:{}[\pipe\TermSrv_API_service]',
endpoint = RcmListener_UUID
endpoint = RcmListener_UUID,
kerberos = kerberos
)
hRpcOpenListener = hRpcOpenListener
hRpcCloseListener = hRpcCloseListener
Expand All @@ -3763,10 +3772,11 @@ def __init__(self, smb, target_ip):
hRpcIsListening = hRpcIsListening

class LegacyAPI(TSTSEndpoint):
def __init__(self, smb, target_ip):
def __init__(self, smb, target_ip, kerberos):
super().__init__(smb, target_ip,
stringbinding = r'ncacn_np:{}[\pipe\Ctx_WinStation_API_service]',
endpoint = LegacyAPI_UUID
endpoint = LegacyAPI_UUID,
kerberos = kerberos
)
hRpcWinStationOpenServer = hRpcWinStationOpenServer
hRpcWinStationCloseServer = hRpcWinStationCloseServer
Expand Down

0 comments on commit 829239e

Please sign in to comment.