From 8fcdc590f559d08b5049f147ee263a8396151555 Mon Sep 17 00:00:00 2001 From: Sam Free5ide Date: Thu, 11 Aug 2022 01:38:41 +0300 Subject: [PATCH 1/3] Add tstool modules --- cme/modules/qwinsta.py | 220 ++++++++++++++++++++++++++++++++++++++++ cme/modules/tasklist.py | 168 ++++++++++++++++++++++++++++++ 2 files changed, 388 insertions(+) create mode 100644 cme/modules/qwinsta.py create mode 100644 cme/modules/tasklist.py diff --git a/cme/modules/qwinsta.py b/cme/modules/qwinsta.py new file mode 100644 index 000000000..fc70f2fc1 --- /dev/null +++ b/cme/modules/qwinsta.py @@ -0,0 +1,220 @@ +from impacket.dcerpc.v5 import tsts as TSTS + + +class CMEModule: + ''' + Display information about Remote Desktop Services sessions. + + Module by snovvcrash (@snovvcrash), based on tstool.py by Alexander Korznikov (@nopernik) + ''' + name = 'qwinsta' + description = 'Displays information about Remote Desktop Services sessions' + supported_protocols = ['smb'] + opsec_safe = True + multiple_hosts = True + + def options(self, context, module_options): + ''' + VERBOSE Turn verbose output ON. + ''' + self.context = context + self.sessions = {} + + self.verbose = None + if module_options and 'VERBOSE' in module_options: + self.verbose = True + + def get_session_list(self, connection): + ''' + Retreive session list. + ''' + with TSTS.TermSrvEnumeration(connection.conn, connection.host) as tse: + handle = tse.hRpcOpenEnum() + rsessions = tse.hRpcGetEnumResult(handle, Level=1)['ppSessionEnumResult'] + tse.hRpcCloseEnum(handle) + + 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': '' + } + + def enumerate_sessions_info(self, connection): + ''' + Get session info one by one. + ''' + if len(self.sessions): + with TSTS.TermSrvSession(connection.conn, connection.host) as tss: + for SessionId in self.sessions.keys(): + sessdata = tss.hRpcGetSessionInformationEx(SessionId) + sessflags = TSTS.enum2value(TSTS.SESSIONFLAGS, sessdata['LSMSessionInfoExPtr']['LSM_SessionInfo_Level1']['SessionFlags']) + + self.sessions[SessionId]['flags'] = sessflags + domain = sessdata['LSMSessionInfoExPtr']['LSM_SessionInfo_Level1']['DomainName'] + + if not len(self.sessions[SessionId]['Domain']) and len(domain): + self.sessions[SessionId]['Domain'] = domain + + username = sessdata['LSMSessionInfoExPtr']['LSM_SessionInfo_Level1']['UserName'] + + if not len(self.sessions[SessionId]['Username']) and len(username): + self.sessions[SessionId]['Username'] = username + + self.sessions[SessionId]['ConnectTime'] = sessdata['LSMSessionInfoExPtr']['LSM_SessionInfo_Level1']['ConnectTime'] + self.sessions[SessionId]['DisconnectTime'] = sessdata['LSMSessionInfoExPtr']['LSM_SessionInfo_Level1']['DisconnectTime'] + self.sessions[SessionId]['LogonTime'] = sessdata['LSMSessionInfoExPtr']['LSM_SessionInfo_Level1']['LogonTime'] + self.sessions[SessionId]['LastInputTime'] = sessdata['LSMSessionInfoExPtr']['LSM_SessionInfo_Level1']['LastInputTime'] + + def enumerate_sessions_config(self, connection): + ''' + Get session config one by one. + ''' + if len(self.sessions): + with TSTS.RCMPublic(connection.conn, connection.host) as rcmp: + for SessionId in self.sessions: + resp = rcmp.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'] + + if len(resp['ppBuff']['Domain']) and not len(self.sessions[SessionId]['Domain']): + self.sessions[SessionId]['Domain'] = resp['ppBuff']['Domain'] + + self.sessions[SessionId]['Resolution'] = '{}x{}'.format( + resp['ppBuff']['HRes'], + resp['ppBuff']['VRes'] + ) + + self.sessions[SessionId]['ClientTimeZone'] = resp['ppBuff']['ClientTimeZone']['StandardName'] + + def on_login(self, context, connection): + ''' + Display information about Remote Desktop Services sessions. + ''' + desktop_states = { + 'WTS_SESSIONSTATE_UNKNOWN': '', + 'WTS_SESSIONSTATE_LOCK': 'Locked', + 'WTS_SESSIONSTATE_UNLOCK': 'Unlocked' + } + + self.get_session_list(connection) + if not len(self.sessions): + return 'No sessions found' + + self.enumerate_sessions_info(connection) + if self.verbose: + self.enumerate_sessions_config(connection) + + maxSessionNameLen = max([len(self.sessions[i]['SessionName']) + 1 for i in self.sessions]) + maxSessionNameLen = maxSessionNameLen if len('SESSIONNAME') < maxSessionNameLen else len('SESSIONNAME') + 1 + maxUsernameLen = max([len(self.sessions[i]['Username'] + self.sessions[i]['Domain']) + 1 for i in self.sessions]) + 1 + maxUsernameLen = maxUsernameLen if len('Username') < maxUsernameLen else len('Username') + 1 + maxIdLen = max([len(str(i)) for i in self.sessions]) + maxIdLen = maxIdLen if len('ID') < maxIdLen else len('ID') + 1 + maxStateLen = max([len(self.sessions[i]['state']) + 1 for i in self.sessions]) + maxStateLen = maxStateLen if len('STATE') < maxStateLen else len('STATE') + 1 + maxRemoteIp = max([len(self.sessions[i]['RemoteIp']) + 1 for i in self.sessions]) + maxRemoteIp = maxRemoteIp if len('RemoteAddress') < maxRemoteIp else len('RemoteAddress') + 1 + maxClientName = max([len(self.sessions[i]['ClientName']) + 1 for i in self.sessions]) + maxClientName = maxClientName if len('ClientName') < maxClientName else len('ClientName') + 1 + + template = ( + '{SESSIONNAME: <%d} ' + '{USERNAME: <%d} ' + '{ID: <%d} ' + '{STATE: <%d} ' + '{DSTATE: <9} ' + '{CONNTIME: <20} ' + '{DISCTIME: <20} ' + ) % (maxSessionNameLen, maxUsernameLen, maxIdLen, maxStateLen) + + template_verbose = ( + '{CLIENTNAME: <%d} ' + '{REMOTEIP: <%d} ' + '{RESOLUTION: <11} ' + '{TIMEZONE: <15}' + ) % (maxClientName, maxRemoteIp) + + result = [] + header = template.format( + SESSIONNAME='SESSIONNAME', + USERNAME='USERNAME', + ID='ID', + STATE='STATE', + DSTATE='Desktop', + CONNTIME='ConnectTime', + DISCTIME='DisconnectTime', + ) + + header2 = template.replace(' <', '=<').format( + SESSIONNAME='', + USERNAME='', + ID='', + STATE='', + DSTATE='', + CONNTIME='', + DISCTIME='', + ) + + header_verbose = '' + header2_verbose = '' + if self.verbose: + header_verbose = template_verbose.format( + CLIENTNAME='ClientName', + REMOTEIP='RemoteAddress', + RESOLUTION='Resolution', + TIMEZONE='ClientTimeZone' + ) + + header2_verbose = template_verbose.replace(' <', '=<').format( + CLIENTNAME='', + REMOTEIP='', + RESOLUTION='', + TIMEZONE='' + ) + + result.append(header + header_verbose) + result.append(header2 + header2_verbose) + + for i in self.sessions: + connectTime = self.sessions[i]['ConnectTime'] + connectTime = connectTime.strftime(r'%Y/%m/%d %H:%M:%S') if connectTime.year > 1601 else 'None' + + disconnectTime = self.sessions[i]['DisconnectTime'] + disconnectTime = disconnectTime.strftime(r'%Y/%m/%d %H:%M:%S') if disconnectTime.year > 1601 else 'None' + userName = self.sessions[i]['Domain'] + '\\' + self.sessions[i]['Username'] if len(self.sessions[i]['Username']) else '' + + row = template.format( + SESSIONNAME=self.sessions[i]['SessionName'], + USERNAME=userName, + ID=i, + STATE=self.sessions[i]['state'], + DSTATE=desktop_states[self.sessions[i]['flags']], + CONNTIME=connectTime, + DISCTIME=disconnectTime, + ) + + row_verbose = '' + if self.verbose: + row_verbose = template_verbose.format( + CLIENTNAME=self.sessions[i]['ClientName'], + REMOTEIP=self.sessions[i]['RemoteIp'], + RESOLUTION=self.sessions[i]['Resolution'], + TIMEZONE=self.sessions[i]['ClientTimeZone'] + ) + + result.append(row + row_verbose) + + for row in result: + self.context.log.highlight(row) diff --git a/cme/modules/tasklist.py b/cme/modules/tasklist.py new file mode 100644 index 000000000..83d9ac766 --- /dev/null +++ b/cme/modules/tasklist.py @@ -0,0 +1,168 @@ +from impacket.dcerpc.v5 import tsts as TSTS + + +class CMEModule: + ''' + Display a list of currently running processes on the system. + + Module by snovvcrash (@snovvcrash), based on tstool.py by Alexander Korznikov (@nopernik) + ''' + name = 'tasklist' + description = 'Displays a list of currently running processes on the system' + supported_protocols = ['smb'] + opsec_safe = True + multiple_hosts = True + + def options(self, context, module_options): + ''' + VERBOSE Turn verbose output ON. + ''' + self.context = context + self.sessions = {} + + self.verbose = None + if module_options and 'VERBOSE' in module_options: + self.verbose = True + + def get_session_list(self, connection): + ''' + Retreive session list. + ''' + with TSTS.TermSrvEnumeration(connection.conn, connection.host) as tse: + handle = tse.hRpcOpenEnum() + rsessions = tse.hRpcGetEnumResult(handle, Level=1)['ppSessionEnumResult'] + tse.hRpcCloseEnum(handle) + + 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': '' + } + + def enumerate_sessions_config(self, connection): + ''' + Get session config one by one. + ''' + if len(self.sessions): + with TSTS.RCMPublic(connection.conn, connection.host) as rcmp: + for SessionId in self.sessions: + resp = rcmp.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'] + + if len(resp['ppBuff']['Domain']) and not len(self.sessions[SessionId]['Domain']): + self.sessions[SessionId]['Domain'] = resp['ppBuff']['Domain'] + + self.sessions[SessionId]['Resolution'] = '{}x{}'.format( + resp['ppBuff']['HRes'], + resp['ppBuff']['VRes'] + ) + + self.sessions[SessionId]['ClientTimeZone'] = resp['ppBuff']['ClientTimeZone']['StandardName'] + + def on_login(self, context, connection): + ''' + Display a list of currently running processes on the system. + ''' + with TSTS.LegacyAPI(connection.conn, connection.host) as lapi: + handle = lapi.hRpcWinStationOpenServer() + + r = lapi.hRpcWinStationGetAllProcesses(handle) + if not len(r): + return None + + maxImageNameLen = max([len(i['ImageName']) for i in r]) + maxSidLen = max([len(i['pSid']) for i in r]) + + if self.verbose: + self.get_session_list(connection) + self.enumerate_sessions_config(connection) + + maxUserNameLen = max([len(self.sessions[i]['Username']+self.sessions[i]['Domain'])+1 for i in self.sessions])+1 + if maxUserNameLen < 11: + maxUserNameLen = 11 + + template = ( + '{imagename: <%d} ' + '{pid: <6} ' + '{sessid: <6} ' + '{sessionName: <16} ' + '{sessstate: <11} ' + '{sessionuser: <%d} ' + '{sid: <%d} ' + '{workingset: <12}' + ) % (maxImageNameLen, maxUserNameLen, maxSidLen) + + context.log.highlight(template.format( + imagename='Image Name', + pid='PID', + sessionName='SessName', + sessid='SessID', + sessionuser = 'SessUser', + sessstate = 'State', + sid = 'SID', + workingset = 'Mem Usage' + ) + ) + + context.log.highlight(template.replace(' <', '=<').format( + imagename='', + pid='', + sessionName='', + sessid='', + sessionuser='', + sessstate='', + sid='', + workingset='' + ) + ) + + for procInfo in r: + sessId = procInfo['SessionId'] + fullUserName = '' + + if len(self.sessions[sessId]['Domain']): + fullUserName += self.sessions[sessId]['Domain'] + '\\' + + if len(self.sessions[sessId]['Username']): + fullUserName += self.sessions[sessId]['Username'] + + row = template.replace('{workingset: <12}', '{workingset: >10,} K').format( + imagename = procInfo['ImageName'], + pid = procInfo['UniqueProcessId'], + sessionName = self.sessions[sessId]['SessionName'], + sessid = procInfo['SessionId'], + sessstate = self.sessions[sessId]['state'].replace('Disconnected','Disc'), + sid = procInfo['pSid'], + sessionuser = fullUserName, + workingset = procInfo['WorkingSetSize'] // 1000 + ) + + context.log.highlight(row) + else: + template = '{: <%d} {: <8} {: <11} {: <%d} {: >12}' % (maxImageNameLen, maxSidLen) + context.log.highlight(template.format('Image Name', 'PID', 'Session#', 'SID', 'Mem Usage')) + context.log.highlight(template.replace(': ',':=').format('','','','','')) + + for procInfo in r: + row = template.format( + procInfo['ImageName'], + procInfo['UniqueProcessId'], + procInfo['SessionId'], + procInfo['pSid'], + '{:,} K'.format(procInfo['WorkingSetSize'] // 1000), + ) + + context.log.highlight(row) From aa99d29b964e3707193f2925c7c3b93f341c44f6 Mon Sep 17 00:00:00 2001 From: Sam Free5ide Date: Thu, 11 Aug 2022 01:50:33 +0300 Subject: [PATCH 2/3] Code linting --- cme/modules/tasklist.py | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/cme/modules/tasklist.py b/cme/modules/tasklist.py index 83d9ac766..9116eafa6 100644 --- a/cme/modules/tasklist.py +++ b/cme/modules/tasklist.py @@ -42,7 +42,7 @@ def get_session_list(self, connection): 'RemoteIp': '', 'ClientName': '', 'Username': '', - 'Domain':'', + 'Domain': '', 'Resolution': '', 'ClientTimeZone': '' } @@ -90,7 +90,7 @@ def on_login(self, context, connection): self.get_session_list(connection) self.enumerate_sessions_config(connection) - maxUserNameLen = max([len(self.sessions[i]['Username']+self.sessions[i]['Domain'])+1 for i in self.sessions])+1 + maxUserNameLen = max([len(self.sessions[i]['Username'] + self.sessions[i]['Domain']) + 1 for i in self.sessions]) + 1 if maxUserNameLen < 11: maxUserNameLen = 11 @@ -110,10 +110,10 @@ def on_login(self, context, connection): pid='PID', sessionName='SessName', sessid='SessID', - sessionuser = 'SessUser', - sessstate = 'State', - sid = 'SID', - workingset = 'Mem Usage' + sessionuser='SessUser', + sessstate='State', + sid='SID', + workingset='Mem Usage' ) ) @@ -140,21 +140,21 @@ def on_login(self, context, connection): fullUserName += self.sessions[sessId]['Username'] row = template.replace('{workingset: <12}', '{workingset: >10,} K').format( - imagename = procInfo['ImageName'], - pid = procInfo['UniqueProcessId'], - sessionName = self.sessions[sessId]['SessionName'], - sessid = procInfo['SessionId'], - sessstate = self.sessions[sessId]['state'].replace('Disconnected','Disc'), - sid = procInfo['pSid'], - sessionuser = fullUserName, - workingset = procInfo['WorkingSetSize'] // 1000 + imagename=procInfo['ImageName'], + pid=procInfo['UniqueProcessId'], + sessionName=self.sessions[sessId]['SessionName'], + sessid=procInfo['SessionId'], + sessstate=self.sessions[sessId]['state'].replace('Disconnected', 'Disc'), + sid=procInfo['pSid'], + sessionuser=fullUserName, + workingset=procInfo['WorkingSetSize'] // 1000 ) context.log.highlight(row) else: template = '{: <%d} {: <8} {: <11} {: <%d} {: >12}' % (maxImageNameLen, maxSidLen) context.log.highlight(template.format('Image Name', 'PID', 'Session#', 'SID', 'Mem Usage')) - context.log.highlight(template.replace(': ',':=').format('','','','','')) + context.log.highlight(template.replace(': ', ':=').format('', '', '', '', '')) for procInfo in r: row = template.format( From f23388b6287a3b8676a702278af8b67920f0a9bb Mon Sep 17 00:00:00 2001 From: Sam Free5ide Date: Thu, 18 Aug 2022 15:04:27 +0300 Subject: [PATCH 3/3] Fix log highlighting --- cme/modules/qwinsta.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cme/modules/qwinsta.py b/cme/modules/qwinsta.py index fc70f2fc1..8a40376df 100644 --- a/cme/modules/qwinsta.py +++ b/cme/modules/qwinsta.py @@ -217,4 +217,4 @@ def on_login(self, context, connection): result.append(row + row_verbose) for row in result: - self.context.log.highlight(row) + context.log.highlight(row)