mirror of
https://github.com/lgandx/Responder.git
synced 2025-07-16 10:02:53 -07:00
Improvements around Browser listener
This commit is contained in:
parent
8923016cbc
commit
3ef3c23ed6
4 changed files with 225 additions and 71 deletions
|
@ -16,29 +16,12 @@
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
import socket
|
import socket
|
||||||
import settings
|
import settings
|
||||||
import string
|
|
||||||
import fingerprint
|
import fingerprint
|
||||||
|
|
||||||
from packets import NBT_Ans
|
from packets import NBT_Ans
|
||||||
from SocketServer import BaseRequestHandler
|
from SocketServer import BaseRequestHandler
|
||||||
from utils import *
|
from utils import *
|
||||||
|
|
||||||
def NBT_NS_Role(data):
|
|
||||||
Role = {
|
|
||||||
"\x41\x41\x00":"Workstation/Redirector",
|
|
||||||
"\x42\x4c\x00":"Domain Master Browser",
|
|
||||||
"\x42\x4d\x00":"Domain Controller",
|
|
||||||
"\x42\x4e\x00":"Local Master Browser",
|
|
||||||
"\x42\x4f\x00":"Browser Election",
|
|
||||||
"\x43\x41\x00":"File Server",
|
|
||||||
"\x41\x42\x00":"Browser",
|
|
||||||
}
|
|
||||||
|
|
||||||
if data in Role:
|
|
||||||
return Role[data]
|
|
||||||
else:
|
|
||||||
return "Service not known."
|
|
||||||
|
|
||||||
# Define what are we answering to.
|
# Define what are we answering to.
|
||||||
def Validate_NBT_NS(data):
|
def Validate_NBT_NS(data):
|
||||||
if settings.Config.AnalyzeMode:
|
if settings.Config.AnalyzeMode:
|
||||||
|
@ -58,19 +41,6 @@ def Validate_NBT_NS(data):
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def Decode_Name(nbname):
|
|
||||||
#From http://code.google.com/p/dpkt/ with author's permission.
|
|
||||||
try:
|
|
||||||
if len(nbname) != 32:
|
|
||||||
return nbname
|
|
||||||
l = []
|
|
||||||
for i in range(0, 32, 2):
|
|
||||||
l.append(chr(((ord(nbname[i]) - 0x41) << 4) |
|
|
||||||
((ord(nbname[i+1]) - 0x41) & 0xf)))
|
|
||||||
return filter(lambda x: x in string.printable, ''.join(l).split('\x00', 1)[0].replace(' ', ''))
|
|
||||||
except:
|
|
||||||
return "Illegal NetBIOS name"
|
|
||||||
|
|
||||||
# NBT_NS Server class.
|
# NBT_NS Server class.
|
||||||
class NBTNS(BaseRequestHandler):
|
class NBTNS(BaseRequestHandler):
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,7 @@ def WorkstationFingerPrint(data):
|
||||||
"\x06\x01" :"Windows 7/Server 2008R2",
|
"\x06\x01" :"Windows 7/Server 2008R2",
|
||||||
}
|
}
|
||||||
|
|
||||||
return Role[data] if data in Role else False
|
return Role[data] if data in Role else "Unknown"
|
||||||
|
|
||||||
def RequestType(data):
|
def RequestType(data):
|
||||||
Type = {
|
Type = {
|
||||||
|
@ -50,63 +50,52 @@ def RequestType(data):
|
||||||
"\x0f": 'Local Master Announcement',
|
"\x0f": 'Local Master Announcement',
|
||||||
}
|
}
|
||||||
|
|
||||||
return Type[data] if data in Type else False
|
return Type[data] if data in Type else "Unknown"
|
||||||
|
|
||||||
def PrintServerName(data, entries):
|
def PrintServerName(data, entries):
|
||||||
if entries == 0:
|
if entries > 0:
|
||||||
pass
|
|
||||||
|
|
||||||
else:
|
|
||||||
entrieslen = 26*entries
|
entrieslen = 26*entries
|
||||||
chunks, chunk_size = len(data[:entrieslen]), entrieslen/entries
|
chunks, chunk_size = len(data[:entrieslen]), entrieslen/entries
|
||||||
ServerName = [data[i:i+chunk_size] for i in range(0, chunks, chunk_size) ]
|
ServerName = [data[i:i+chunk_size] for i in range(0, chunks, chunk_size)]
|
||||||
|
|
||||||
l = []
|
l = []
|
||||||
for x in ServerName:
|
for x in ServerName:
|
||||||
if WorkstationFingerPrint(x[16:18]):
|
FP = WorkstationFingerPrint(x[16:18])
|
||||||
l.append(x[:16].replace('\x00', '') + '\n [-] Os version is: %s'%(WorkstationFingerPrint(x[16:18])))
|
Name = x[:16].replace('\x00', '')
|
||||||
|
|
||||||
|
if FP:
|
||||||
|
l.append(Name + ' (%s)' % FP)
|
||||||
else:
|
else:
|
||||||
l.append(x[:16].replace('\x00', ''))
|
l.append(Name)
|
||||||
|
|
||||||
return l
|
return l
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
def ParsePacket(Payload):
|
def ParsePacket(Payload):
|
||||||
PayloadOffset = struct.unpack('<H',Payload[51:53])[0]
|
PayloadOffset = struct.unpack('<H',Payload[51:53])[0]
|
||||||
StatusCode = Payload[PayloadOffset-4:PayloadOffset-2]
|
StatusCode = Payload[PayloadOffset-4:PayloadOffset-2]
|
||||||
|
|
||||||
if StatusCode == "\x00\x00":
|
if StatusCode == "\x00\x00":
|
||||||
EntriesNum = struct.unpack('<H',Payload[PayloadOffset:PayloadOffset+2])[0]
|
EntriesNum = struct.unpack('<H',Payload[PayloadOffset:PayloadOffset+2])[0]
|
||||||
ParsedNames = PrintServerName(Payload[PayloadOffset+4:], EntriesNum)
|
return PrintServerName(Payload[PayloadOffset+4:], EntriesNum)
|
||||||
return ParsedNames
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def RAPThisDomain(Client,Domain):
|
def RAPThisDomain(Client,Domain):
|
||||||
try:
|
PDC = RapFinger(Client,Domain,"\x00\x00\x00\x80")
|
||||||
l = []
|
if PDC is not None:
|
||||||
for x in range(1):
|
print text("[LANMAN] Detected Domains: %s" % ', '.join(PDC))
|
||||||
PDC = RapFinger(Client,Domain,"\x00\x00\x00\x80")
|
|
||||||
if PDC is not None:
|
SQL = RapFinger(Client,Domain,"\x04\x00\x00\x00")
|
||||||
l.append('[Analyze mode LANMAN] ')
|
if SQL is not None:
|
||||||
l.append('[*] Domain detected on this network:')
|
print text("[LANMAN] Detected SQL Servers on domain %s: %s" % (Domain, ', '.join(SQL)))
|
||||||
for x in PDC:
|
|
||||||
l.append(' - '+x)
|
|
||||||
SQL = RapFinger(Client,Domain,"\x04\x00\x00\x00")
|
|
||||||
if SQL is not None:
|
|
||||||
l.append('[*] SQL Server detected on Domain %s:'%(Domain))
|
|
||||||
for x in SQL:
|
|
||||||
l.append(' - '+x)
|
|
||||||
WKST = RapFinger(Client,Domain,"\xff\xff\xff\xff")
|
|
||||||
|
|
||||||
if WKST is not None:
|
WKST = RapFinger(Client,Domain,"\xff\xff\xff\xff")
|
||||||
l.append('[*] Workstations/Servers detected on Domain %s:'%(Domain))
|
if WKST is not None:
|
||||||
for x in WKST:
|
print text("[LANMAN] Detected Workstations/Servers on domain %s: %s" % (Domain, ', '.join(WKST)))
|
||||||
l.append(' - '+x)
|
|
||||||
else:
|
|
||||||
pass
|
|
||||||
return text('\n'.join(l))
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def RapFinger(Host, Domain, Type):
|
def RapFinger(Host, Domain, Type):
|
||||||
try:
|
try:
|
||||||
|
@ -165,7 +154,7 @@ def RapFinger(Host, Domain, Type):
|
||||||
s.close()
|
s.close()
|
||||||
return ParsePacket(data)
|
return ParsePacket(data)
|
||||||
except:
|
except:
|
||||||
return None
|
pass
|
||||||
|
|
||||||
def BecomeBackup(data,Client):
|
def BecomeBackup(data,Client):
|
||||||
try:
|
try:
|
||||||
|
@ -194,10 +183,9 @@ def ParseDatagramNBTNames(data,Client):
|
||||||
Role2 = NBT_NS_Role(data[79:82])
|
Role2 = NBT_NS_Role(data[79:82])
|
||||||
|
|
||||||
|
|
||||||
if Role2 == "Domain controller service. This name is a domain controller." or Role2 == "Browser Election Service." or Role2 == "Local Master Browser.":
|
if Role2 == "Domain Controller" or Role2 == "Browser Election" or Role2 == "Local Master Browser" and settings.Config.AnalyzeMode:
|
||||||
if settings.Config.AnalyzeMode:
|
print text('[Analyze mode: Browser] Datagram Request from IP: %s hostname: %s via the: %s to: %s. Service: %s' % (Client, Name, Role1, Domain, Role2))
|
||||||
print text('[Analyze mode: Browser] Datagram Request from IP: %s hostname: %s via the: %s to: %s. Service: %s'%(Client, Name, Role1, Domain, Role2))
|
print RAPThisDomain(Client, Domain)
|
||||||
print RAPThisDomain(Client, Domain)
|
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
158
tools/BrowserListener.py
Normal file
158
tools/BrowserListener.py
Normal file
|
@ -0,0 +1,158 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
# This file is part of Responder
|
||||||
|
# Original work by Laurent Gaffie - Trustwave Holdings
|
||||||
|
#
|
||||||
|
# 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/>.
|
||||||
|
import sys, os
|
||||||
|
import socket
|
||||||
|
import thread
|
||||||
|
import struct
|
||||||
|
import time
|
||||||
|
|
||||||
|
BASEDIR = os.path.realpath(os.path.join(os.path.dirname(__file__), '..'))
|
||||||
|
sys.path.insert(0, BASEDIR)
|
||||||
|
|
||||||
|
from servers.Browser import WorkstationFingerPrint, RequestType, RAPThisDomain, RapFinger
|
||||||
|
from SocketServer import UDPServer, ThreadingMixIn, BaseRequestHandler
|
||||||
|
from threading import Lock
|
||||||
|
from utils import *
|
||||||
|
|
||||||
|
def ParseRoles(data):
|
||||||
|
|
||||||
|
if len(data) != 4:
|
||||||
|
return ''
|
||||||
|
|
||||||
|
AllRoles = {
|
||||||
|
'Workstation': (ord(data[0]) >> 0) & 1,
|
||||||
|
'Server': (ord(data[0]) >> 1) & 1,
|
||||||
|
'SQL': (ord(data[0]) >> 2) & 1,
|
||||||
|
'Domain Controller': (ord(data[0]) >> 3) & 1,
|
||||||
|
'Backup Controller': (ord(data[0]) >> 4) & 1,
|
||||||
|
'Time Source': (ord(data[0]) >> 5) & 1,
|
||||||
|
'Apple': (ord(data[0]) >> 6) & 1,
|
||||||
|
'Novell': (ord(data[0]) >> 7) & 1,
|
||||||
|
'Member': (ord(data[1]) >> 0) & 1,
|
||||||
|
'Print': (ord(data[1]) >> 1) & 1,
|
||||||
|
'Dialin': (ord(data[1]) >> 2) & 1,
|
||||||
|
'Xenix': (ord(data[1]) >> 3) & 1,
|
||||||
|
'NT Workstation': (ord(data[1]) >> 4) & 1,
|
||||||
|
'WfW': (ord(data[1]) >> 5) & 1,
|
||||||
|
'Unused': (ord(data[1]) >> 6) & 1,
|
||||||
|
'NT Server': (ord(data[1]) >> 7) & 1,
|
||||||
|
'Potential Browser': (ord(data[2]) >> 0) & 1,
|
||||||
|
'Backup Browser': (ord(data[2]) >> 1) & 1,
|
||||||
|
'Master Browser': (ord(data[2]) >> 2) & 1,
|
||||||
|
'Domain Master Browser': (ord(data[2]) >> 3) & 1,
|
||||||
|
'OSF': (ord(data[2]) >> 4) & 1,
|
||||||
|
'VMS': (ord(data[2]) >> 5) & 1,
|
||||||
|
'Windows 95+': (ord(data[2]) >> 6) & 1,
|
||||||
|
'DFS': (ord(data[2]) >> 7) & 1,
|
||||||
|
'Local': (ord(data[3]) >> 6) & 1,
|
||||||
|
'Domain Enum': (ord(data[3]) >> 7) & 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
#print 'Workstation : ', AllRoles['Workstation']
|
||||||
|
#print 'Server : ', AllRoles['Server']
|
||||||
|
#print 'SQL : ', AllRoles['SQL']
|
||||||
|
#print 'Domain Controller : ', AllRoles['Domain Controller']
|
||||||
|
#print 'Backup Controller : ', AllRoles['Backup Controller']
|
||||||
|
#print 'Time Source : ', AllRoles['Time Source']
|
||||||
|
#print 'Apple : ', AllRoles['Apple']
|
||||||
|
#print 'Novell : ', AllRoles['Novell']
|
||||||
|
#print 'Member : ', AllRoles['Member']
|
||||||
|
#print 'Print : ', AllRoles['Print']
|
||||||
|
#print 'Dialin : ', AllRoles['Dialin']
|
||||||
|
#print 'Xenix : ', AllRoles['Xenix']
|
||||||
|
#print 'NT Workstation : ', AllRoles['NT Workstation']
|
||||||
|
#print 'WfW : ', AllRoles['WfW']
|
||||||
|
#print 'Unused : ', AllRoles['Unused']
|
||||||
|
#print 'NT Server : ', AllRoles['NT Server']
|
||||||
|
#print 'Potential Browser : ', AllRoles['Potential Browser']
|
||||||
|
#print 'Backup Browser : ', AllRoles['Backup Browser']
|
||||||
|
#print 'Master Browser : ', AllRoles['Master Browser']
|
||||||
|
#print 'Domain Master Browser : ', AllRoles['Domain Master Browser']
|
||||||
|
#print 'OSF : ', AllRoles['OSF']
|
||||||
|
#print 'VMS : ', AllRoles['VMS']
|
||||||
|
#print 'Windows 95+ : ', AllRoles['Windows 95+']
|
||||||
|
#print 'DFS : ', AllRoles['DFS']
|
||||||
|
#print 'Local : ', AllRoles['Local']
|
||||||
|
#print 'Domain Enum : ', AllRoles['Domain Enum']
|
||||||
|
|
||||||
|
Roles = []
|
||||||
|
for k,v in AllRoles.iteritems():
|
||||||
|
if v == 1:
|
||||||
|
Roles.append(k)
|
||||||
|
|
||||||
|
return ', '.join(Roles)
|
||||||
|
|
||||||
|
class BrowserListener(BaseRequestHandler):
|
||||||
|
|
||||||
|
def handle(self):
|
||||||
|
#try:
|
||||||
|
data, socket = self.request
|
||||||
|
|
||||||
|
lock = Lock()
|
||||||
|
lock.acquire()
|
||||||
|
|
||||||
|
DataOffset = struct.unpack('<H',data[139:141])[0]
|
||||||
|
BrowserPacket = data[82+DataOffset:]
|
||||||
|
ReqType = RequestType(BrowserPacket[0])
|
||||||
|
|
||||||
|
Domain = Decode_Name(data[49:81])
|
||||||
|
Name = Decode_Name(data[15:47])
|
||||||
|
Role1 = NBT_NS_Role(data[45:48])
|
||||||
|
Role2 = NBT_NS_Role(data[79:82])
|
||||||
|
Fprint = WorkstationFingerPrint(data[190:192])
|
||||||
|
Roles = ParseRoles(data[192:196])
|
||||||
|
|
||||||
|
print text("[BROWSER] Request Type : %s" % ReqType)
|
||||||
|
print text("[BROWSER] Address : %s" % self.client_address[0])
|
||||||
|
print text("[BROWSER] Domain : %s" % Domain)
|
||||||
|
print text("[BROWSER] Name : %s" % Name)
|
||||||
|
print text("[BROWSER] Main Role : %s" % Role1)
|
||||||
|
print text("[BROWSER] 2nd Role : %s" % Role2)
|
||||||
|
print text("[BROWSER] Fingerprint : %s" % Fprint)
|
||||||
|
print text("[BROWSER] Role List : %s" % Roles)
|
||||||
|
|
||||||
|
RAPThisDomain(self.client_address[0], Domain)
|
||||||
|
|
||||||
|
lock.release()
|
||||||
|
|
||||||
|
#except Exception:
|
||||||
|
# pass
|
||||||
|
|
||||||
|
|
||||||
|
class ThreadingUDPServer(ThreadingMixIn, UDPServer):
|
||||||
|
def server_bind(self):
|
||||||
|
self.allow_reuse_address = 1
|
||||||
|
#self.socket.setsockopt(socket.SOL_SOCKET, 25, 'eth0\0')
|
||||||
|
UDPServer.server_bind(self)
|
||||||
|
|
||||||
|
def serve_thread_udp_broadcast(host, port, handler):
|
||||||
|
try:
|
||||||
|
server = ThreadingUDPServer(('', port), handler)
|
||||||
|
server.serve_forever()
|
||||||
|
except:
|
||||||
|
print "Error starting UDP server on port " + str(port) + ", check permissions or other servers running."
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
try:
|
||||||
|
print "Listening for BROWSER datagrams..."
|
||||||
|
thread.start_new(serve_thread_udp_broadcast,('', 138, BrowserListener))
|
||||||
|
|
||||||
|
while True:
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
sys.exit("\r Exiting...")
|
38
utils.py
38
utils.py
|
@ -36,10 +36,18 @@ def color(txt, code = 1, modifier = 0):
|
||||||
elif 'Analyze' in txt:
|
elif 'Analyze' in txt:
|
||||||
settings.Config.AnalyzeLogger.warning(txt)
|
settings.Config.AnalyzeLogger.warning(txt)
|
||||||
|
|
||||||
|
# No colors for windows...
|
||||||
|
if os.name == 'nt':
|
||||||
|
return txt
|
||||||
|
|
||||||
return "\033[%d;3%dm%s\033[0m" % (modifier, code, txt)
|
return "\033[%d;3%dm%s\033[0m" % (modifier, code, txt)
|
||||||
|
|
||||||
def text(txt):
|
def text(txt):
|
||||||
logging.info(txt)
|
logging.info(txt)
|
||||||
|
|
||||||
|
if os.name == 'nt':
|
||||||
|
return txt
|
||||||
|
|
||||||
return '\r'+re.sub(r'\[([^]]*)\]', "\033[1;34m[\\1]\033[0m", txt)
|
return '\r'+re.sub(r'\[([^]]*)\]', "\033[1;34m[\\1]\033[0m", txt)
|
||||||
|
|
||||||
def RespondToThisIP(ClientIp):
|
def RespondToThisIP(ClientIp):
|
||||||
|
@ -195,6 +203,36 @@ def Parse_IPV6_Addr(data):
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def Decode_Name(nbname):
|
||||||
|
#From http://code.google.com/p/dpkt/ with author's permission.
|
||||||
|
try:
|
||||||
|
from string import printable
|
||||||
|
|
||||||
|
if len(nbname) != 32:
|
||||||
|
return nbname
|
||||||
|
|
||||||
|
l = []
|
||||||
|
for i in range(0, 32, 2):
|
||||||
|
l.append(chr(((ord(nbname[i]) - 0x41) << 4) | ((ord(nbname[i+1]) - 0x41) & 0xf)))
|
||||||
|
|
||||||
|
return filter(lambda x: x in printable, ''.join(l).split('\x00', 1)[0].replace(' ', ''))
|
||||||
|
|
||||||
|
except:
|
||||||
|
return "Illegal NetBIOS name"
|
||||||
|
|
||||||
|
def NBT_NS_Role(data):
|
||||||
|
Role = {
|
||||||
|
"\x41\x41\x00":"Workstation/Redirector",
|
||||||
|
"\x42\x4c\x00":"Domain Master Browser",
|
||||||
|
"\x42\x4d\x00":"Domain Controller",
|
||||||
|
"\x42\x4e\x00":"Local Master Browser",
|
||||||
|
"\x42\x4f\x00":"Browser Election",
|
||||||
|
"\x43\x41\x00":"File Server",
|
||||||
|
"\x41\x42\x00":"Browser",
|
||||||
|
}
|
||||||
|
|
||||||
|
return Role[data] if data in Role else "Service not known"
|
||||||
|
|
||||||
def banner():
|
def banner():
|
||||||
|
|
||||||
banner = "\n".join([
|
banner = "\n".join([
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue