1# Copyright (c) 2003-2018 CORE Security Technologies
2#
3# This software is provided under under a slightly modified version
4# of the Apache Software License. See the accompanying LICENSE file
5# for more information.
6#
7# Description: Mini shell using some of the SMB funcionality of the library
8#
9# Author:
10#  Alberto Solino (@agsolino)
11#
12#
13# Reference for:
14#  SMB DCE/RPC
15#
16
17import sys
18import time
19import cmd
20import os
21
22from impacket import LOG
23from impacket.dcerpc.v5 import samr, transport, srvs
24from impacket.dcerpc.v5.dtypes import NULL
25from impacket.smbconnection import *
26
27
28# If you wanna have readline like functionality in Windows, install pyreadline
29try:
30  import pyreadline as readline
31except ImportError:
32  import readline
33
34class MiniImpacketShell(cmd.Cmd):
35    def __init__(self, smbClient,tcpShell=None):
36        #If the tcpShell parameter is passed (used in ntlmrelayx),
37        # all input and output is redirected to a tcp socket
38        # instead of to stdin / stdout
39        if tcpShell is not None:
40            cmd.Cmd.__init__(self,stdin=tcpShell,stdout=tcpShell)
41            sys.stdout = tcpShell
42            sys.stdin = tcpShell
43            sys.stderr = tcpShell
44            self.use_rawinput = False
45            self.shell = tcpShell
46        else:
47            cmd.Cmd.__init__(self)
48            self.shell = None
49
50        self.prompt = '# '
51        self.smb = smbClient
52        self.username, self.password, self.domain, self.lmhash, self.nthash, self.aesKey, self.TGT, self.TGS = smbClient.getCredentials()
53        self.tid = None
54        self.intro = 'Type help for list of commands'
55        self.pwd = ''
56        self.share = None
57        self.loggedIn = True
58        self.last_output = None
59        self.completion = []
60
61    def emptyline(self):
62        pass
63
64    def precmd(self,line):
65        # switch to unicode
66        return line.decode('utf-8')
67
68    def onecmd(self,s):
69        retVal = False
70        try:
71           retVal = cmd.Cmd.onecmd(self,s)
72        except Exception, e:
73           #import traceback
74           #traceback.print_exc()
75           LOG.error(e)
76
77        return retVal
78
79    def do_exit(self,line):
80        if self.shell is not None:
81            self.shell.close()
82        return True
83
84    def do_shell(self, line):
85        output = os.popen(line).read()
86        print output
87        self.last_output = output
88
89    def do_help(self,line):
90        print """
91 open {host,port=445} - opens a SMB connection against the target host/port
92 login {domain/username,passwd} - logs into the current SMB connection, no parameters for NULL connection. If no password specified, it'll be prompted
93 kerberos_login {domain/username,passwd} - logs into the current SMB connection using Kerberos. If no password specified, it'll be prompted. Use the DNS resolvable domain name
94 login_hash {domain/username,lmhash:nthash} - logs into the current SMB connection using the password hashes
95 logoff - logs off
96 shares - list available shares
97 use {sharename} - connect to an specific share
98 cd {path} - changes the current directory to {path}
99 lcd {path} - changes the current local directory to {path}
100 pwd - shows current remote directory
101 password - changes the user password, the new password will be prompted for input
102 ls {wildcard} - lists all the files in the current directory
103 rm {file} - removes the selected file
104 mkdir {dirname} - creates the directory under the current path
105 rmdir {dirname} - removes the directory under the current path
106 put {filename} - uploads the filename into the current path
107 get {filename} - downloads the filename from the current path
108 info - returns NetrServerInfo main results
109 who - returns the sessions currently connected at the target host (admin required)
110 close - closes the current SMB Session
111 exit - terminates the server process (and this session)
112
113"""
114
115    def do_password(self, line):
116        if self.loggedIn is False:
117            LOG.error("Not logged in")
118            return
119        from getpass import getpass
120        newPassword = getpass("New Password:")
121        rpctransport = transport.SMBTransport(self.smb.getRemoteHost(), filename = r'\samr', smb_connection = self.smb)
122        dce = rpctransport.get_dce_rpc()
123        dce.connect()
124        dce.bind(samr.MSRPC_UUID_SAMR)
125        samr.hSamrUnicodeChangePasswordUser2(dce, '\x00', self.username, self.password, newPassword, self.lmhash, self.nthash)
126        self.password = newPassword
127        self.lmhash = None
128        self.nthash = None
129
130    def do_open(self,line):
131        l = line.split(' ')
132        port = 445
133        if len(l) > 0:
134           host = l[0]
135        if len(l) > 1:
136           port = int(l[1])
137
138
139        if port == 139:
140            self.smb = SMBConnection('*SMBSERVER', host, sess_port=port)
141        else:
142            self.smb = SMBConnection(host, host, sess_port=port)
143
144        dialect = self.smb.getDialect()
145        if dialect == SMB_DIALECT:
146            LOG.info("SMBv1 dialect used")
147        elif dialect == SMB2_DIALECT_002:
148            LOG.info("SMBv2.0 dialect used")
149        elif dialect == SMB2_DIALECT_21:
150            LOG.info("SMBv2.1 dialect used")
151        else:
152            LOG.info("SMBv3.0 dialect used")
153
154        self.share = None
155        self.tid = None
156        self.pwd = ''
157        self.loggedIn = False
158        self.password = None
159        self.lmhash = None
160        self.nthash = None
161        self.username = None
162
163    def do_login(self,line):
164        if self.smb is None:
165            LOG.error("No connection open")
166            return
167        l = line.split(' ')
168        username = ''
169        password = ''
170        domain = ''
171        if len(l) > 0:
172           username = l[0]
173        if len(l) > 1:
174           password = l[1]
175
176        if username.find('/') > 0:
177           domain, username = username.split('/')
178
179        if password == '' and username != '':
180            from getpass import getpass
181            password = getpass("Password:")
182
183        self.smb.login(username, password, domain=domain)
184        self.password = password
185        self.username = username
186
187        if self.smb.isGuestSession() > 0:
188            LOG.info("GUEST Session Granted")
189        else:
190            LOG.info("USER Session Granted")
191        self.loggedIn = True
192
193    def do_kerberos_login(self,line):
194        if self.smb is None:
195            LOG.error("No connection open")
196            return
197        l = line.split(' ')
198        username = ''
199        password = ''
200        domain = ''
201        if len(l) > 0:
202           username = l[0]
203        if len(l) > 1:
204           password = l[1]
205
206        if username.find('/') > 0:
207           domain, username = username.split('/')
208
209        if domain == '':
210            LOG.error("Domain must be specified for Kerberos login")
211            return
212
213        if password == '' and username != '':
214            from getpass import getpass
215            password = getpass("Password:")
216
217        self.smb.kerberosLogin(username, password, domain=domain)
218        self.password = password
219        self.username = username
220
221        if self.smb.isGuestSession() > 0:
222            LOG.info("GUEST Session Granted")
223        else:
224            LOG.info("USER Session Granted")
225        self.loggedIn = True
226
227    def do_login_hash(self,line):
228        if self.smb is None:
229            LOG.error("No connection open")
230            return
231        l = line.split(' ')
232        domain = ''
233        if len(l) > 0:
234           username = l[0]
235        if len(l) > 1:
236           hashes = l[1]
237        else:
238           LOG.error("Hashes needed. Format is lmhash:nthash")
239           return
240
241        if username.find('/') > 0:
242           domain, username = username.split('/')
243
244        lmhash, nthash = hashes.split(':')
245
246        self.smb.login(username, '', domain,lmhash=lmhash, nthash=nthash)
247        self.username = username
248        self.lmhash = lmhash
249        self.nthash = nthash
250
251        if self.smb.isGuestSession() > 0:
252            LOG.info("GUEST Session Granted")
253        else:
254            LOG.info("USER Session Granted")
255        self.loggedIn = True
256
257    def do_logoff(self, line):
258        if self.smb is None:
259            LOG.error("No connection open")
260            return
261        self.smb.logoff()
262        del self.smb
263        self.share = None
264        self.smb = None
265        self.tid = None
266        self.pwd = ''
267        self.loggedIn = False
268        self.password = None
269        self.lmhash = None
270        self.nthash = None
271        self.username = None
272
273    def do_info(self, line):
274        if self.loggedIn is False:
275            LOG.error("Not logged in")
276            return
277        rpctransport = transport.SMBTransport(self.smb.getRemoteHost(), filename = r'\srvsvc', smb_connection = self.smb)
278        dce = rpctransport.get_dce_rpc()
279        dce.connect()
280        dce.bind(srvs.MSRPC_UUID_SRVS)
281        resp = srvs.hNetrServerGetInfo(dce, 102)
282
283        print "Version Major: %d" % resp['InfoStruct']['ServerInfo102']['sv102_version_major']
284        print "Version Minor: %d" % resp['InfoStruct']['ServerInfo102']['sv102_version_minor']
285        print "Server Name: %s" % resp['InfoStruct']['ServerInfo102']['sv102_name']
286        print "Server Comment: %s" % resp['InfoStruct']['ServerInfo102']['sv102_comment']
287        print "Server UserPath: %s" % resp['InfoStruct']['ServerInfo102']['sv102_userpath']
288        print "Simultaneous Users: %d" % resp['InfoStruct']['ServerInfo102']['sv102_users']
289
290    def do_who(self, line):
291        if self.loggedIn is False:
292            LOG.error("Not logged in")
293            return
294        rpctransport = transport.SMBTransport(self.smb.getRemoteHost(), filename = r'\srvsvc', smb_connection = self.smb)
295        dce = rpctransport.get_dce_rpc()
296        dce.connect()
297        dce.bind(srvs.MSRPC_UUID_SRVS)
298        resp = srvs.hNetrSessionEnum(dce, NULL, NULL, 10)
299
300        for session in resp['InfoStruct']['SessionInfo']['Level10']['Buffer']:
301            print "host: %15s, user: %5s, active: %5d, idle: %5d" % (
302            session['sesi10_cname'][:-1], session['sesi10_username'][:-1], session['sesi10_time'],
303            session['sesi10_idle_time'])
304
305    def do_shares(self, line):
306        if self.loggedIn is False:
307            LOG.error("Not logged in")
308            return
309        resp = self.smb.listShares()
310        for i in range(len(resp)):
311            print resp[i]['shi1_netname'][:-1]
312
313    def do_use(self,line):
314        if self.loggedIn is False:
315            LOG.error("Not logged in")
316            return
317        self.share = line
318        self.tid = self.smb.connectTree(line)
319        self.pwd = '\\'
320        self.do_ls('', False)
321
322    def complete_cd(self, text, line, begidx, endidx):
323        return self.complete_get(text, line, begidx, endidx, include = 2)
324
325    def do_cd(self, line):
326        if self.tid is None:
327            LOG.error("No share selected")
328            return
329        p = string.replace(line,'/','\\')
330        oldpwd = self.pwd
331        if p[0] == '\\':
332           self.pwd = line
333        else:
334           self.pwd = ntpath.join(self.pwd, line)
335        self.pwd = ntpath.normpath(self.pwd)
336        # Let's try to open the directory to see if it's valid
337        try:
338            fid = self.smb.openFile(self.tid, self.pwd, creationOption = FILE_DIRECTORY_FILE \
339                                    , desiredAccess = FILE_READ_DATA | FILE_LIST_DIRECTORY \
340                                    , shareMode = FILE_SHARE_READ | FILE_SHARE_WRITE \
341                                    )
342            self.smb.closeFile(self.tid,fid)
343        except SessionError:
344            self.pwd = oldpwd
345            raise
346
347    def do_lcd(self, s):
348        print s
349        if s == '':
350           print os.getcwd()
351        else:
352           os.chdir(s)
353
354    def do_pwd(self,line):
355        if self.loggedIn is False:
356            LOG.error("Not logged in")
357            return
358        print self.pwd
359
360    def do_ls(self, wildcard, display = True):
361        if self.loggedIn is False:
362            LOG.error("Not logged in")
363            return
364        if self.tid is None:
365            LOG.error("No share selected")
366            return
367        if wildcard == '':
368           pwd = ntpath.join(self.pwd,'*')
369        else:
370           pwd = ntpath.join(self.pwd, wildcard)
371        self.completion = []
372        pwd = string.replace(pwd,'/','\\')
373        pwd = ntpath.normpath(pwd)
374        for f in self.smb.listPath(self.share, pwd):
375            if display is True:
376                print "%crw-rw-rw- %10d  %s %s" % (
377                'd' if f.is_directory() > 0 else '-', f.get_filesize(), time.ctime(float(f.get_mtime_epoch())),
378                f.get_longname())
379            self.completion.append((f.get_longname(), f.is_directory()))
380
381
382    def do_rm(self, filename):
383        if self.tid is None:
384            LOG.error("No share selected")
385            return
386        f = ntpath.join(self.pwd, filename)
387        file = string.replace(f,'/','\\')
388        self.smb.deleteFile(self.share, file)
389
390    def do_mkdir(self, path):
391        if self.tid is None:
392            LOG.error("No share selected")
393            return
394        p = ntpath.join(self.pwd, path)
395        pathname = string.replace(p,'/','\\')
396        self.smb.createDirectory(self.share,pathname)
397
398    def do_rmdir(self, path):
399        if self.tid is None:
400            LOG.error("No share selected")
401            return
402        p = ntpath.join(self.pwd, path)
403        pathname = string.replace(p,'/','\\')
404        self.smb.deleteDirectory(self.share, pathname)
405
406    def do_put(self, pathname):
407        if self.tid is None:
408            LOG.error("No share selected")
409            return
410        src_path = pathname
411        dst_name = os.path.basename(src_path)
412
413        fh = open(pathname, 'rb')
414        f = ntpath.join(self.pwd,dst_name)
415        finalpath = string.replace(f,'/','\\')
416        self.smb.putFile(self.share, finalpath, fh.read)
417        fh.close()
418
419    def complete_get(self, text, line, begidx, endidx, include = 1):
420        # include means
421        # 1 just files
422        # 2 just directories
423        p = string.replace(line,'/','\\')
424        if p.find('\\') < 0:
425            items = []
426            if include == 1:
427                mask = 0
428            else:
429                mask = 0x010
430            for i in self.completion:
431                if i[1] == mask:
432                    items.append(i[0])
433            if text:
434                return  [
435                    item for item in items
436                    if item.upper().startswith(text.upper())
437                ]
438            else:
439                return items
440
441    def do_get(self, filename):
442        if self.tid is None:
443            LOG.error("No share selected")
444            return
445        filename = string.replace(filename,'/','\\')
446        fh = open(ntpath.basename(filename),'wb')
447        pathname = ntpath.join(self.pwd,filename)
448        try:
449            self.smb.getFile(self.share, pathname, fh.write)
450        except:
451            fh.close()
452            os.remove(filename)
453            raise
454        fh.close()
455
456    def do_close(self, line):
457        self.do_logoff(line)
458
459
460