From ebb4d3e405eaadb147ab624ca4b8f863df24e719 Mon Sep 17 00:00:00 2001 From: Deft_ Date: Fri, 11 Oct 2024 16:52:04 +0200 Subject: [PATCH 01/10] [SMB] Add the Signed-off-by: Deft_ --- nxc/protocols/smb.py | 146 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 146 insertions(+) diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index f404c305..cdef68b0 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -28,6 +28,7 @@ from impacket.dcerpc.v5.dtypes import NULL from impacket.dcerpc.v5.dcomrt import DCOMConnection from impacket.dcerpc.v5.dcom.wmi import CLSID_WbemLevel1Login, IID_IWbemLevel1Login, IWbemLevel1Login from impacket.smb3structs import FILE_SHARE_WRITE, FILE_SHARE_DELETE +from impacket.dcerpc.v5 import tsts as TSTS from nxc.config import process_secret, host_info_colors from nxc.connection import connection, sem, requires_admin, dcom_FirewallChecker @@ -792,6 +793,151 @@ class smb(connection): self.logger.debug(f"ps_execute response: {response}") return response + def get_session_list(self): + with TSTS.TermSrvEnumeration(self.conn, self.host) 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':'' + } + + def enumerate_sessions_info(self): + if len(self.sessions): + with TSTS.TermSrvSession(self.conn, self.host) as TermSrvSession: + for SessionId in self.sessions.keys(): + sessdata = TermSrvSession.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 qwinsta(self): + desktop_states = { + 'WTS_SESSIONSTATE_UNKNOWN': '', + 'WTS_SESSIONSTATE_LOCK' : 'Locked', + 'WTS_SESSIONSTATE_UNLOCK' : 'Unlocked', + } + self.get_session_list() + if not len(self.sessions): + return + self.enumerate_sessions_info() + + 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) + + 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 = '' + result.append(header+header_verbose) + result.append(header2+header2_verbose+'\n') + + 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 = '' + result.append(row+row_verbose) + + self.logger.success("Enumerated qwinsta sessions") + for row in result: + self.logger.highlight(row) + + def tasklist(self): + with TSTS.LegacyAPI(self.conn, self.host) as legacy: + try: + handle = legacy.hRpcWinStationOpenServer() + r = legacy.hRpcWinStationGetAllProcesses(handle) + except: + # TODO: Issue https://github.com/fortra/impacket/issues/1816 + self.logger.debug("Exception while calling hRpcWinStationGetAllProcesses") + return + if not len(r): + return None + self.logger.success("Enumerated processes") + maxImageNameLen = max([len(i['ImageName']) for i in r]) + maxSidLen = max([len(i['pSid']) for i in r]) + template = '{: <%d} {: <8} {: <11} {: <%d} {: >12}' % (maxImageNameLen, maxSidLen) + self.logger.highlight(template.format('Image Name', 'PID', 'Session#', 'SID', 'Mem Usage')) + self.logger.highlight(template.replace(': ',':=').format('','','','','')) + for procInfo in r: + row = template.format( + procInfo['ImageName'], + procInfo['UniqueProcessId'], + procInfo['SessionId'], + procInfo['pSid'], + '{:,} K'.format(procInfo['WorkingSetSize']//1000), + ) + self.logger.highlight(row) + def shares(self): temp_dir = ntpath.normpath("\\" + gen_random_string()) temp_file = ntpath.normpath("\\" + gen_random_string() + ".txt") From a804041140cfa018d950c65dc4ef18266bf727ce Mon Sep 17 00:00:00 2001 From: Deft_ Date: Fri, 11 Oct 2024 16:52:45 +0200 Subject: [PATCH 02/10] [SMB] Add the --qwinsta and --tasklist options Signed-off-by: Deft_ --- nxc/protocols/smb/proto_args.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/nxc/protocols/smb/proto_args.py b/nxc/protocols/smb/proto_args.py index 35dd8fe2..e45194e8 100644 --- a/nxc/protocols/smb/proto_args.py +++ b/nxc/protocols/smb/proto_args.py @@ -47,6 +47,8 @@ def proto_args(parser, parents): mapping_enum_group.add_argument("--local-groups", nargs="?", const="", metavar="GROUP", help="enumerate local groups, if a group is specified then its members are enumerated") mapping_enum_group.add_argument("--pass-pol", action="store_true", help="dump password policy") mapping_enum_group.add_argument("--rid-brute", nargs="?", type=int, const=4000, metavar="MAX_RID", help="enumerate users by bruteforcing RIDs") + mapping_enum_group.add_argument("--qwinsta", action="store_true", help="Enumerate RDP connections") + mapping_enum_group.add_argument("--tasklist", action="store_true", help="Enumerate running processes") wmi_group = smb_parser.add_argument_group("WMI", "Options for WMI Queries") wmi_group.add_argument("--wmi", metavar="QUERY", type=str, help="issues the specified WMI query") @@ -101,4 +103,4 @@ def get_conditional_action(baseAction): x.required = True super().__call__(parser, namespace, values, option_string) - return ConditionalAction \ No newline at end of file + return ConditionalAction From cc5016f75a8b5f8920d494460651e73cabc61f88 Mon Sep 17 00:00:00 2001 From: Deft_ Date: Fri, 11 Oct 2024 16:55:31 +0200 Subject: [PATCH 03/10] [SMB] Signed-off-by: Deft_ --- nxc/protocols/smb.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index cdef68b0..5b3f3783 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -829,7 +829,8 @@ class smb(connection): 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'] - + + @requires_admin def qwinsta(self): desktop_states = { 'WTS_SESSIONSTATE_UNKNOWN': '', @@ -910,7 +911,8 @@ class smb(connection): self.logger.success("Enumerated qwinsta sessions") for row in result: self.logger.highlight(row) - + + @requires_admin def tasklist(self): with TSTS.LegacyAPI(self.conn, self.host) as legacy: try: From 98c92077cc55557765152154b25e75d06f7f6f27 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Wed, 26 Feb 2025 11:14:40 -0500 Subject: [PATCH 04/10] Replace single quote with double quote --- nxc/protocols/smb.py | 167 +++++++++++++++++++++---------------------- 1 file changed, 83 insertions(+), 84 deletions(-) diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index b5bdcfce..2717eddf 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -839,116 +839,115 @@ class smb(connection): def get_session_list(self): with TSTS.TermSrvEnumeration(self.conn, self.host) as lsm: handle = lsm.hRpcOpenEnum() - rsessions = lsm.hRpcGetEnumResult(handle, Level=1)['ppSessionEnumResult'] + 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':'' + 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): if len(self.sessions): with TSTS.TermSrvSession(self.conn, self.host) as TermSrvSession: - for SessionId in self.sessions.keys(): + for SessionId in self.sessions: sessdata = TermSrvSession.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'] + 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"] @requires_admin def qwinsta(self): desktop_states = { - 'WTS_SESSIONSTATE_UNKNOWN': '', - 'WTS_SESSIONSTATE_LOCK' : 'Locked', - 'WTS_SESSIONSTATE_UNLOCK' : 'Unlocked', + "WTS_SESSIONSTATE_UNKNOWN": "", + "WTS_SESSIONSTATE_LOCK": "Locked", + "WTS_SESSIONSTATE_UNLOCK": "Unlocked", } self.get_session_list() if not len(self.sessions): return self.enumerate_sessions_info() - 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 + 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) + 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) result = [] header = template.format( - SESSIONNAME = 'SESSIONNAME', - USERNAME = 'USERNAME', - ID = 'ID', - STATE = 'STATE', - DSTATE = 'Desktop', - CONNTIME = 'ConnectTime', - DISCTIME = 'DisconnectTime', + 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 = '', + header2 = template.replace(" <", "=<").format( + SESSIONNAME = "", + USERNAME = "", + ID = "", + STATE = "", + DSTATE = "", + CONNTIME = "", + DISCTIME = "", ) - header_verbose = '' - header2_verbose = '' - result.append(header+header_verbose) - result.append(header2+header2_verbose+'\n') + header_verbose = "" + header2_verbose = "" + result.extend((header + header_verbose, header2 + header2_verbose + "\n")) 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' + 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 '' + 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'], + SESSIONNAME = self.sessions[i]["SessionName"], USERNAME = userName, ID = i, - STATE = self.sessions[i]['state'], - DSTATE = desktop_states[self.sessions[i]['flags']], + STATE = self.sessions[i]["state"], + DSTATE = desktop_states[self.sessions[i]["flags"]], CONNTIME = connectTime, DISCTIME = disconnectTime, ) - row_verbose = '' + row_verbose = "" result.append(row+row_verbose) self.logger.success("Enumerated qwinsta sessions") @@ -966,20 +965,20 @@ class smb(connection): self.logger.debug("Exception while calling hRpcWinStationGetAllProcesses") return if not len(r): - return None + return self.logger.success("Enumerated processes") - maxImageNameLen = max([len(i['ImageName']) for i in r]) - maxSidLen = max([len(i['pSid']) for i in r]) - template = '{: <%d} {: <8} {: <11} {: <%d} {: >12}' % (maxImageNameLen, maxSidLen) - self.logger.highlight(template.format('Image Name', 'PID', 'Session#', 'SID', 'Mem Usage')) - self.logger.highlight(template.replace(': ',':=').format('','','','','')) + maxImageNameLen = max([len(i["ImageName"]) for i in r]) + maxSidLen = max([len(i["pSid"]) for i in r]) + template = "{: <%d} {: <8} {: <11} {: <%d} {: >12}" % (maxImageNameLen, maxSidLen) + self.logger.highlight(template.format("Image Name", "PID", "Session#", "SID", "Mem Usage")) + self.logger.highlight(template.replace(": ", ":=").format("", "", "", "", "")) for procInfo in r: row = template.format( - procInfo['ImageName'], - procInfo['UniqueProcessId'], - procInfo['SessionId'], - procInfo['pSid'], - '{:,} K'.format(procInfo['WorkingSetSize']//1000), + procInfo["ImageName"], + procInfo["UniqueProcessId"], + procInfo["SessionId"], + procInfo["pSid"], + "{:,} K".format(procInfo["WorkingSetSize"]//1000), ) self.logger.highlight(row) From 724401af7aa57a31cbc5c16f0427488a77a9804b Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Wed, 26 Feb 2025 11:15:56 -0500 Subject: [PATCH 05/10] Formating --- nxc/protocols/smb.py | 95 ++++++++++++++++++++++---------------------- 1 file changed, 48 insertions(+), 47 deletions(-) diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index 2717eddf..f258830f 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -845,15 +845,16 @@ class smb(connection): 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_info(self): if len(self.sessions): @@ -861,7 +862,7 @@ class smb(connection): for SessionId in self.sessions: sessdata = TermSrvSession.hRpcGetSessionInformationEx(SessionId) sessflags = TSTS.enum2value(TSTS.SESSIONFLAGS, sessdata["LSMSessionInfoExPtr"]["LSM_SessionInfo_Level1"]["SessionFlags"]) - self.sessions[SessionId]["flags"] = sessflags + 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 @@ -872,7 +873,7 @@ class smb(connection): 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"] - + @requires_admin def qwinsta(self): desktop_states = { @@ -884,7 +885,7 @@ class smb(connection): if not len(self.sessions): return self.enumerate_sessions_info() - + 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 @@ -907,29 +908,29 @@ class smb(connection): result = [] header = template.format( - SESSIONNAME = "SESSIONNAME", - USERNAME = "USERNAME", - ID = "ID", - STATE = "STATE", - DSTATE = "Desktop", - CONNTIME = "ConnectTime", - DISCTIME = "DisconnectTime", - ) - + 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 = "", - ) + SESSIONNAME="", + USERNAME="", + ID="", + STATE="", + DSTATE="", + CONNTIME="", + DISCTIME="", + ) header_verbose = "" header2_verbose = "" result.extend((header + header_verbose, header2 + header2_verbose + "\n")) - + 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" @@ -939,28 +940,28 @@ class smb(connection): 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, + 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 = "" + row_verbose = "" result.append(row+row_verbose) self.logger.success("Enumerated qwinsta sessions") for row in result: self.logger.highlight(row) - + @requires_admin def tasklist(self): with TSTS.LegacyAPI(self.conn, self.host) as legacy: try: - handle = legacy.hRpcWinStationOpenServer() + handle = legacy.hRpcWinStationOpenServer() r = legacy.hRpcWinStationGetAllProcesses(handle) - except: + except: # TODO: Issue https://github.com/fortra/impacket/issues/1816 self.logger.debug("Exception while calling hRpcWinStationGetAllProcesses") return @@ -974,12 +975,12 @@ class smb(connection): self.logger.highlight(template.replace(": ", ":=").format("", "", "", "", "")) for procInfo in r: row = template.format( - procInfo["ImageName"], - procInfo["UniqueProcessId"], - procInfo["SessionId"], - procInfo["pSid"], - "{:,} K".format(procInfo["WorkingSetSize"]//1000), - ) + procInfo["ImageName"], + procInfo["UniqueProcessId"], + procInfo["SessionId"], + procInfo["pSid"], + "{:,} K".format(procInfo["WorkingSetSize"]//1000), + ) self.logger.highlight(row) def shares(self): From 23de82520f42876fded284b149a9bdecc7d07bab Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Wed, 26 Feb 2025 11:28:57 -0500 Subject: [PATCH 06/10] Add missing kerberos parameter --- nxc/protocols/smb.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index f258830f..16a0d8b4 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -837,7 +837,7 @@ class smb(connection): return response def get_session_list(self): - with TSTS.TermSrvEnumeration(self.conn, self.host) as lsm: + with TSTS.TermSrvEnumeration(self.conn, self.host, self.kerberos) as lsm: handle = lsm.hRpcOpenEnum() rsessions = lsm.hRpcGetEnumResult(handle, Level=1)["ppSessionEnumResult"] lsm.hRpcCloseEnum(handle) @@ -858,7 +858,7 @@ class smb(connection): def enumerate_sessions_info(self): if len(self.sessions): - with TSTS.TermSrvSession(self.conn, self.host) as TermSrvSession: + with TSTS.TermSrvSession(self.conn, self.host, self.kerberos) as TermSrvSession: for SessionId in self.sessions: sessdata = TermSrvSession.hRpcGetSessionInformationEx(SessionId) sessflags = TSTS.enum2value(TSTS.SESSIONFLAGS, sessdata["LSMSessionInfoExPtr"]["LSM_SessionInfo_Level1"]["SessionFlags"]) @@ -957,7 +957,7 @@ class smb(connection): @requires_admin def tasklist(self): - with TSTS.LegacyAPI(self.conn, self.host) as legacy: + with TSTS.LegacyAPI(self.conn, self.host, self.kerberos) as legacy: try: handle = legacy.hRpcWinStationOpenServer() r = legacy.hRpcWinStationGetAllProcesses(handle) From 74e6aa03c161ef43a6cee33f2d47aff9621da5aa Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Wed, 26 Feb 2025 12:54:51 -0500 Subject: [PATCH 07/10] Pass local object to functions istead of using a class variable, alread in use --- nxc/protocols/smb.py | 75 ++++++++++++++++++++++---------------------- 1 file changed, 38 insertions(+), 37 deletions(-) diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index 16a0d8b4..ed0b82f2 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -841,11 +841,11 @@ class smb(connection): handle = lsm.hRpcOpenEnum() rsessions = lsm.hRpcGetEnumResult(handle, Level=1)["ppSessionEnumResult"] lsm.hRpcCloseEnum(handle) - self.sessions = {} + sessions = {} for i in rsessions: sess = i["SessionInfo"]["SessionEnum_Level1"] state = TSTS.enum2value(TSTS.WINSTATIONSTATECLASS, sess["State"]).split("_")[-1] - self.sessions[sess["SessionId"]] = { + sessions[sess["SessionId"]] = { "state": state, "SessionName": sess["Name"], "RemoteIp": "", @@ -855,24 +855,25 @@ class smb(connection): "Resolution": "", "ClientTimeZone": "" } + return sessions - def enumerate_sessions_info(self): - if len(self.sessions): + def enumerate_sessions_info(self, sessions): + if len(sessions): with TSTS.TermSrvSession(self.conn, self.host, self.kerberos) as TermSrvSession: - for SessionId in self.sessions: + for SessionId in sessions: sessdata = TermSrvSession.hRpcGetSessionInformationEx(SessionId) sessflags = TSTS.enum2value(TSTS.SESSIONFLAGS, sessdata["LSMSessionInfoExPtr"]["LSM_SessionInfo_Level1"]["SessionFlags"]) - self.sessions[SessionId]["flags"] = sessflags + 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 + if not len(sessions[SessionId]["Domain"]) and len(domain): + 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"] + if not len(sessions[SessionId]["Username"]) and len(username): + sessions[SessionId]["Username"] = username + sessions[SessionId]["ConnectTime"] = sessdata["LSMSessionInfoExPtr"]["LSM_SessionInfo_Level1"]["ConnectTime"] + sessions[SessionId]["DisconnectTime"] = sessdata["LSMSessionInfoExPtr"]["LSM_SessionInfo_Level1"]["DisconnectTime"] + sessions[SessionId]["LogonTime"] = sessdata["LSMSessionInfoExPtr"]["LSM_SessionInfo_Level1"]["LogonTime"] + sessions[SessionId]["LastInputTime"] = sessdata["LSMSessionInfoExPtr"]["LSM_SessionInfo_Level1"]["LastInputTime"] @requires_admin def qwinsta(self): @@ -881,22 +882,22 @@ class smb(connection): "WTS_SESSIONSTATE_LOCK": "Locked", "WTS_SESSIONSTATE_UNLOCK": "Unlocked", } - self.get_session_list() - if not len(self.sessions): + sessions = self.get_session_list() + if not len(sessions): return - self.enumerate_sessions_info() + self.enumerate_sessions_info(sessions) - maxSessionNameLen = max([len(self.sessions[i]["SessionName"])+1 for i in self.sessions]) + maxSessionNameLen = max([len(sessions[i]["SessionName"])+1 for i in 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 = max([len(sessions[i]["Username"]+sessions[i]["Domain"])+1 for i in sessions])+1 maxUsernameLen = maxUsernameLen if len("Username") < maxUsernameLen else len("Username")+1 - maxIdLen = max([len(str(i)) for i in self.sessions]) + maxIdLen = max([len(str(i)) for i in 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 = max([len(sessions[i]["state"])+1 for i in 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 = max([len(sessions[i]["RemoteIp"])+1 for i in 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 = max([len(sessions[i]["ClientName"])+1 for i in sessions]) maxClientName = maxClientName if len("ClientName") < maxClientName else len("ClientName")+1 template = ("{SESSIONNAME: <%d} " "{USERNAME: <%d} " @@ -931,20 +932,20 @@ class smb(connection): header2_verbose = "" result.extend((header + header_verbose, header2 + header2_verbose + "\n")) - for i in self.sessions: - connectTime = self.sessions[i]["ConnectTime"] + for i in sessions: + connectTime = 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 = 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 "" + userName = sessions[i]["Domain"] + "\\" + sessions[i]["Username"] if len(sessions[i]["Username"]) else "" row = template.format( - SESSIONNAME=self.sessions[i]["SessionName"], + SESSIONNAME=sessions[i]["SessionName"], USERNAME=userName, ID=i, - STATE=self.sessions[i]["state"], - DSTATE=desktop_states[self.sessions[i]["flags"]], + STATE=sessions[i]["state"], + DSTATE=desktop_states[sessions[i]["flags"]], CONNTIME=connectTime, DISCTIME=disconnectTime, ) @@ -960,20 +961,20 @@ class smb(connection): with TSTS.LegacyAPI(self.conn, self.host, self.kerberos) as legacy: try: handle = legacy.hRpcWinStationOpenServer() - r = legacy.hRpcWinStationGetAllProcesses(handle) - except: + res = legacy.hRpcWinStationGetAllProcesses(handle) + except Exception as e: # TODO: Issue https://github.com/fortra/impacket/issues/1816 - self.logger.debug("Exception while calling hRpcWinStationGetAllProcesses") + self.logger.debug(f"Exception while calling hRpcWinStationGetAllProcesses: {e}") return - if not len(r): + if not res: return self.logger.success("Enumerated processes") - maxImageNameLen = max([len(i["ImageName"]) for i in r]) - maxSidLen = max([len(i["pSid"]) for i in r]) + maxImageNameLen = max([len(i["ImageName"]) for i in res]) + maxSidLen = max([len(i["pSid"]) for i in res]) template = "{: <%d} {: <8} {: <11} {: <%d} {: >12}" % (maxImageNameLen, maxSidLen) self.logger.highlight(template.format("Image Name", "PID", "Session#", "SID", "Mem Usage")) self.logger.highlight(template.replace(": ", ":=").format("", "", "", "", "")) - for procInfo in r: + for procInfo in res: row = template.format( procInfo["ImageName"], procInfo["UniqueProcessId"], From 53b42df414d3fae9cc60d7fc2e87e99e14147f52 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Wed, 26 Feb 2025 13:53:08 -0500 Subject: [PATCH 08/10] Add IPv4 to qwinsta output --- nxc/protocols/smb.py | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index ed0b82f2..bea5c66c 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -874,6 +874,15 @@ class smb(connection): sessions[SessionId]["DisconnectTime"] = sessdata["LSMSessionInfoExPtr"]["LSM_SessionInfo_Level1"]["DisconnectTime"] sessions[SessionId]["LogonTime"] = sessdata["LSMSessionInfoExPtr"]["LSM_SessionInfo_Level1"]["LogonTime"] sessions[SessionId]["LastInputTime"] = sessdata["LSMSessionInfoExPtr"]["LSM_SessionInfo_Level1"]["LastInputTime"] + with TSTS.RCMPublic(self.conn, self.host, self.kerberos) as rcm: + for SessionId in sessions: + try: + client = rcm.hRpcGetRemoteAddress(SessionId) + if not client: + continue + sessions[SessionId]["RemoteIp"] = client["pRemoteAddress"]["ipv4"]["in_addr"] + except Exception as e: + self.logger.debug(f"Error getting client address for session {SessionId}: {e}") @requires_admin def qwinsta(self): @@ -902,6 +911,7 @@ class smb(connection): template = ("{SESSIONNAME: <%d} " "{USERNAME: <%d} " "{ID: <%d} " + "{IPv4: <16} " "{STATE: <%d} " "{DSTATE: <9} " "{CONNTIME: <20} " @@ -912,6 +922,7 @@ class smb(connection): SESSIONNAME="SESSIONNAME", USERNAME="USERNAME", ID="ID", + IPv4="RemoteAddress", STATE="STATE", DSTATE="Desktop", CONNTIME="ConnectTime", @@ -922,15 +933,13 @@ class smb(connection): SESSIONNAME="", USERNAME="", ID="", + IPv4="", STATE="", DSTATE="", CONNTIME="", DISCTIME="", ) - - header_verbose = "" - header2_verbose = "" - result.extend((header + header_verbose, header2 + header2_verbose + "\n")) + result.extend((header, header2)) for i in sessions: connectTime = sessions[i]["ConnectTime"] @@ -940,17 +949,16 @@ class smb(connection): disconnectTime = disconnectTime.strftime(r"%Y/%m/%d %H:%M:%S") if disconnectTime.year > 1601 else "None" userName = sessions[i]["Domain"] + "\\" + sessions[i]["Username"] if len(sessions[i]["Username"]) else "" - row = template.format( + result.append(template.format( SESSIONNAME=sessions[i]["SessionName"], USERNAME=userName, ID=i, + IPv4=sessions[i]["RemoteIp"], STATE=sessions[i]["state"], DSTATE=desktop_states[sessions[i]["flags"]], CONNTIME=connectTime, DISCTIME=disconnectTime, - ) - row_verbose = "" - result.append(row+row_verbose) + )) self.logger.success("Enumerated qwinsta sessions") for row in result: From ca5a076f1ed40c1c056b9617e8a69705d2fce97a Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Wed, 26 Feb 2025 16:09:41 -0500 Subject: [PATCH 09/10] Add IPv4 to qwinsta output --- nxc/protocols/smb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index bea5c66c..e1b2c7f6 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -922,7 +922,7 @@ class smb(connection): SESSIONNAME="SESSIONNAME", USERNAME="USERNAME", ID="ID", - IPv4="RemoteAddress", + IPv4="IPv4 Address", STATE="STATE", DSTATE="Desktop", CONNTIME="ConnectTime", From 5da61176b4e3603d56406415f5913d205585c27e Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Wed, 26 Feb 2025 17:46:23 -0500 Subject: [PATCH 10/10] Linting --- nxc/protocols/smb.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index 7ad04378..b74f007d 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -930,18 +930,18 @@ class smb(connection): return self.enumerate_sessions_info(sessions) - maxSessionNameLen = max([len(sessions[i]["SessionName"])+1 for i in sessions]) - maxSessionNameLen = maxSessionNameLen if len("SESSIONNAME") < maxSessionNameLen else len("SESSIONNAME")+1 - maxUsernameLen = max([len(sessions[i]["Username"]+sessions[i]["Domain"])+1 for i in sessions])+1 - maxUsernameLen = maxUsernameLen if len("Username") < maxUsernameLen else len("Username")+1 + maxSessionNameLen = max([len(sessions[i]["SessionName"]) + 1 for i in sessions]) + maxSessionNameLen = maxSessionNameLen if len("SESSIONNAME") < maxSessionNameLen else len("SESSIONNAME") + 1 + maxUsernameLen = max([len(sessions[i]["Username"] + sessions[i]["Domain"]) + 1 for i in sessions]) + 1 + maxUsernameLen = maxUsernameLen if len("Username") < maxUsernameLen else len("Username") + 1 maxIdLen = max([len(str(i)) for i in sessions]) - maxIdLen = maxIdLen if len("ID") < maxIdLen else len("ID")+1 - maxStateLen = max([len(sessions[i]["state"])+1 for i in sessions]) - maxStateLen = maxStateLen if len("STATE") < maxStateLen else len("STATE")+1 - maxRemoteIp = max([len(sessions[i]["RemoteIp"])+1 for i in sessions]) - maxRemoteIp = maxRemoteIp if len("RemoteAddress") < maxRemoteIp else len("RemoteAddress")+1 - maxClientName = max([len(sessions[i]["ClientName"])+1 for i in sessions]) - maxClientName = maxClientName if len("ClientName") < maxClientName else len("ClientName")+1 + maxIdLen = maxIdLen if len("ID") < maxIdLen else len("ID") + 1 + maxStateLen = max([len(sessions[i]["state"]) + 1 for i in sessions]) + maxStateLen = maxStateLen if len("STATE") < maxStateLen else len("STATE") + 1 + maxRemoteIp = max([len(sessions[i]["RemoteIp"]) + 1 for i in sessions]) + maxRemoteIp = maxRemoteIp if len("RemoteAddress") < maxRemoteIp else len("RemoteAddress") + 1 + maxClientName = max([len(sessions[i]["ClientName"]) + 1 for i in sessions]) + maxClientName = maxClientName if len("ClientName") < maxClientName else len("ClientName") + 1 template = ("{SESSIONNAME: <%d} " "{USERNAME: <%d} " "{ID: <%d} " @@ -1022,7 +1022,7 @@ class smb(connection): procInfo["UniqueProcessId"], procInfo["SessionId"], procInfo["pSid"], - "{:,} K".format(procInfo["WorkingSetSize"]//1000), + "{:,} K".format(procInfo["WorkingSetSize"] // 1000), ) self.logger.highlight(row)