Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1# Impacket - Collection of Python classes for working with network protocols. 

2# 

3# SECUREAUTH LABS. Copyright (C) 2021 SecureAuth Corporation. All rights reserved. 

4# 

5# This software is provided under a slightly modified version 

6# of the Apache Software License. See the accompanying LICENSE file 

7# for more information. 

8# 

9# Description: 

10# Mini shell using some of the SMB funcionality of the library 

11# 

12# Author: 

13# Alberto Solino (@agsolino) 

14# 

15# Reference for: 

16# SMB DCE/RPC 

17# 

18from __future__ import division 

19from __future__ import print_function 

20from io import BytesIO 

21import sys 

22import time 

23import cmd 

24import os 

25import ntpath 

26 

27from six import PY2 

28from impacket.dcerpc.v5 import samr, transport, srvs 

29from impacket.dcerpc.v5.dtypes import NULL 

30from impacket import LOG 

31from impacket.smbconnection import SMBConnection, SMB2_DIALECT_002, SMB2_DIALECT_21, SMB_DIALECT, SessionError, \ 

32 FILE_READ_DATA, FILE_SHARE_READ, FILE_SHARE_WRITE 

33from impacket.smb3structs import FILE_DIRECTORY_FILE, FILE_LIST_DIRECTORY 

34 

35import chardet 

36 

37 

38# If you wanna have readline like functionality in Windows, install pyreadline 

39try: 

40 import pyreadline as readline 

41except ImportError: 

42 import readline 

43 

44class MiniImpacketShell(cmd.Cmd): 

45 def __init__(self, smbClient, tcpShell=None): 

46 #If the tcpShell parameter is passed (used in ntlmrelayx), 

47 # all input and output is redirected to a tcp socket 

48 # instead of to stdin / stdout 

49 if tcpShell is not None: 

50 cmd.Cmd.__init__(self, stdin=tcpShell.stdin, stdout=tcpShell.stdout) 

51 sys.stdout = tcpShell.stdout 

52 sys.stdin = tcpShell.stdin 

53 sys.stderr = tcpShell.stdout 

54 self.use_rawinput = False 

55 self.shell = tcpShell 

56 else: 

57 cmd.Cmd.__init__(self) 

58 self.shell = None 

59 

60 self.prompt = '# ' 

61 self.smb = smbClient 

62 self.username, self.password, self.domain, self.lmhash, self.nthash, self.aesKey, self.TGT, self.TGS = smbClient.getCredentials() 

63 self.tid = None 

64 self.intro = 'Type help for list of commands' 

65 self.pwd = '' 

66 self.share = None 

67 self.loggedIn = True 

68 self.last_output = None 

69 self.completion = [] 

70 

71 def emptyline(self): 

72 pass 

73 

74 def precmd(self,line): 

75 # switch to unicode 

76 if PY2: 

77 return line.decode('utf-8') 

78 return line 

79 

80 def onecmd(self,s): 

81 retVal = False 

82 try: 

83 retVal = cmd.Cmd.onecmd(self,s) 

84 except Exception as e: 

85 LOG.error(e) 

86 LOG.debug('Exception info', exc_info=True) 

87 

88 return retVal 

89 

90 def do_exit(self,line): 

91 if self.shell is not None: 

92 self.shell.close() 

93 return True 

94 

95 def do_shell(self, line): 

96 output = os.popen(line).read() 

97 print(output) 

98 self.last_output = output 

99 

100 def do_help(self,line): 

101 print(""" 

102 open {host,port=445} - opens a SMB connection against the target host/port 

103 login {domain/username,passwd} - logs into the current SMB connection, no parameters for NULL connection. If no password specified, it'll be prompted 

104 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 

105 login_hash {domain/username,lmhash:nthash} - logs into the current SMB connection using the password hashes 

106 logoff - logs off 

107 shares - list available shares 

108 use {sharename} - connect to an specific share 

109 cd {path} - changes the current directory to {path} 

110 lcd {path} - changes the current local directory to {path} 

111 pwd - shows current remote directory 

112 password - changes the user password, the new password will be prompted for input 

113 ls {wildcard} - lists all the files in the current directory 

114 rm {file} - removes the selected file 

115 mkdir {dirname} - creates the directory under the current path 

116 rmdir {dirname} - removes the directory under the current path 

117 put {filename} - uploads the filename into the current path 

118 get {filename} - downloads the filename from the current path 

119 mget {mask} - downloads all files from the current directory matching the provided mask 

120 cat {filename} - reads the filename from the current path 

121 mount {target,path} - creates a mount point from {path} to {target} (admin required) 

122 umount {path} - removes the mount point at {path} without deleting the directory (admin required) 

123 list_snapshots {path} - lists the vss snapshots for the specified path 

124 info - returns NetrServerInfo main results 

125 who - returns the sessions currently connected at the target host (admin required) 

126 close - closes the current SMB Session 

127 exit - terminates the server process (and this session) 

128 

129""") 

130 

131 def do_password(self, line): 

132 if self.loggedIn is False: 

133 LOG.error("Not logged in") 

134 return 

135 from getpass import getpass 

136 newPassword = getpass("New Password:") 

137 rpctransport = transport.SMBTransport(self.smb.getRemoteHost(), filename = r'\samr', smb_connection = self.smb) 

138 dce = rpctransport.get_dce_rpc() 

139 dce.connect() 

140 dce.bind(samr.MSRPC_UUID_SAMR) 

141 samr.hSamrUnicodeChangePasswordUser2(dce, '\x00', self.username, self.password, newPassword, self.lmhash, self.nthash) 

142 self.password = newPassword 

143 self.lmhash = None 

144 self.nthash = None 

145 

146 def do_open(self,line): 

147 l = line.split(' ') 

148 port = 445 

149 if len(l) > 0: 

150 host = l[0] 

151 if len(l) > 1: 

152 port = int(l[1]) 

153 

154 

155 if port == 139: 

156 self.smb = SMBConnection('*SMBSERVER', host, sess_port=port) 

157 else: 

158 self.smb = SMBConnection(host, host, sess_port=port) 

159 

160 dialect = self.smb.getDialect() 

161 if dialect == SMB_DIALECT: 

162 LOG.info("SMBv1 dialect used") 

163 elif dialect == SMB2_DIALECT_002: 

164 LOG.info("SMBv2.0 dialect used") 

165 elif dialect == SMB2_DIALECT_21: 

166 LOG.info("SMBv2.1 dialect used") 

167 else: 

168 LOG.info("SMBv3.0 dialect used") 

169 

170 self.share = None 

171 self.tid = None 

172 self.pwd = '' 

173 self.loggedIn = False 

174 self.password = None 

175 self.lmhash = None 

176 self.nthash = None 

177 self.username = None 

178 

179 def do_login(self,line): 

180 if self.smb is None: 

181 LOG.error("No connection open") 

182 return 

183 l = line.split(' ') 

184 username = '' 

185 password = '' 

186 domain = '' 

187 if len(l) > 0: 

188 username = l[0] 

189 if len(l) > 1: 

190 password = l[1] 

191 

192 if username.find('/') > 0: 

193 domain, username = username.split('/') 

194 

195 if password == '' and username != '': 

196 from getpass import getpass 

197 password = getpass("Password:") 

198 

199 self.smb.login(username, password, domain=domain) 

200 self.password = password 

201 self.username = username 

202 

203 if self.smb.isGuestSession() > 0: 

204 LOG.info("GUEST Session Granted") 

205 else: 

206 LOG.info("USER Session Granted") 

207 self.loggedIn = True 

208 

209 def do_kerberos_login(self,line): 

210 if self.smb is None: 

211 LOG.error("No connection open") 

212 return 

213 l = line.split(' ') 

214 username = '' 

215 password = '' 

216 domain = '' 

217 if len(l) > 0: 

218 username = l[0] 

219 if len(l) > 1: 

220 password = l[1] 

221 

222 if username.find('/') > 0: 

223 domain, username = username.split('/') 

224 

225 if domain == '': 

226 LOG.error("Domain must be specified for Kerberos login") 

227 return 

228 

229 if password == '' and username != '': 

230 from getpass import getpass 

231 password = getpass("Password:") 

232 

233 self.smb.kerberosLogin(username, password, domain=domain) 

234 self.password = password 

235 self.username = username 

236 

237 if self.smb.isGuestSession() > 0: 

238 LOG.info("GUEST Session Granted") 

239 else: 

240 LOG.info("USER Session Granted") 

241 self.loggedIn = True 

242 

243 def do_login_hash(self,line): 

244 if self.smb is None: 

245 LOG.error("No connection open") 

246 return 

247 l = line.split(' ') 

248 domain = '' 

249 if len(l) > 0: 

250 username = l[0] 

251 if len(l) > 1: 

252 hashes = l[1] 

253 else: 

254 LOG.error("Hashes needed. Format is lmhash:nthash") 

255 return 

256 

257 if username.find('/') > 0: 

258 domain, username = username.split('/') 

259 

260 lmhash, nthash = hashes.split(':') 

261 

262 self.smb.login(username, '', domain,lmhash=lmhash, nthash=nthash) 

263 self.username = username 

264 self.lmhash = lmhash 

265 self.nthash = nthash 

266 

267 if self.smb.isGuestSession() > 0: 

268 LOG.info("GUEST Session Granted") 

269 else: 

270 LOG.info("USER Session Granted") 

271 self.loggedIn = True 

272 

273 def do_logoff(self, line): 

274 if self.smb is None: 

275 LOG.error("No connection open") 

276 return 

277 self.smb.logoff() 

278 del self.smb 

279 self.share = None 

280 self.smb = None 

281 self.tid = None 

282 self.pwd = '' 

283 self.loggedIn = False 

284 self.password = None 

285 self.lmhash = None 

286 self.nthash = None 

287 self.username = None 

288 

289 def do_info(self, line): 

290 if self.loggedIn is False: 

291 LOG.error("Not logged in") 

292 return 

293 rpctransport = transport.SMBTransport(self.smb.getRemoteHost(), filename = r'\srvsvc', smb_connection = self.smb) 

294 dce = rpctransport.get_dce_rpc() 

295 dce.connect() 

296 dce.bind(srvs.MSRPC_UUID_SRVS) 

297 resp = srvs.hNetrServerGetInfo(dce, 102) 

298 

299 print("Version Major: %d" % resp['InfoStruct']['ServerInfo102']['sv102_version_major']) 

300 print("Version Minor: %d" % resp['InfoStruct']['ServerInfo102']['sv102_version_minor']) 

301 print("Server Name: %s" % resp['InfoStruct']['ServerInfo102']['sv102_name']) 

302 print("Server Comment: %s" % resp['InfoStruct']['ServerInfo102']['sv102_comment']) 

303 print("Server UserPath: %s" % resp['InfoStruct']['ServerInfo102']['sv102_userpath']) 

304 print("Simultaneous Users: %d" % resp['InfoStruct']['ServerInfo102']['sv102_users']) 

305 

306 def do_who(self, line): 

307 if self.loggedIn is False: 

308 LOG.error("Not logged in") 

309 return 

310 rpctransport = transport.SMBTransport(self.smb.getRemoteHost(), filename = r'\srvsvc', smb_connection = self.smb) 

311 dce = rpctransport.get_dce_rpc() 

312 dce.connect() 

313 dce.bind(srvs.MSRPC_UUID_SRVS) 

314 resp = srvs.hNetrSessionEnum(dce, NULL, NULL, 10) 

315 

316 for session in resp['InfoStruct']['SessionInfo']['Level10']['Buffer']: 

317 print("host: %15s, user: %5s, active: %5d, idle: %5d" % ( 

318 session['sesi10_cname'][:-1], session['sesi10_username'][:-1], session['sesi10_time'], 

319 session['sesi10_idle_time'])) 

320 

321 def do_shares(self, line): 

322 if self.loggedIn is False: 

323 LOG.error("Not logged in") 

324 return 

325 resp = self.smb.listShares() 

326 for i in range(len(resp)): 

327 print(resp[i]['shi1_netname'][:-1]) 

328 

329 def do_use(self,line): 

330 if self.loggedIn is False: 

331 LOG.error("Not logged in") 

332 return 

333 self.share = line 

334 self.tid = self.smb.connectTree(line) 

335 self.pwd = '\\' 

336 self.do_ls('', False) 

337 

338 def complete_cd(self, text, line, begidx, endidx): 

339 return self.complete_get(text, line, begidx, endidx, include = 2) 

340 

341 def do_cd(self, line): 

342 if self.tid is None: 

343 LOG.error("No share selected") 

344 return 

345 p = line.replace('/','\\') 

346 oldpwd = self.pwd 

347 if p[0] == '\\': 

348 self.pwd = line 

349 else: 

350 self.pwd = ntpath.join(self.pwd, line) 

351 self.pwd = ntpath.normpath(self.pwd) 

352 # Let's try to open the directory to see if it's valid 

353 try: 

354 fid = self.smb.openFile(self.tid, self.pwd, creationOption = FILE_DIRECTORY_FILE, desiredAccess = FILE_READ_DATA | 

355 FILE_LIST_DIRECTORY, shareMode = FILE_SHARE_READ | FILE_SHARE_WRITE ) 

356 self.smb.closeFile(self.tid,fid) 

357 except SessionError: 

358 self.pwd = oldpwd 

359 raise 

360 

361 def do_lcd(self, s): 

362 print(s) 

363 if s == '': 

364 print(os.getcwd()) 

365 else: 

366 os.chdir(s) 

367 

368 def do_pwd(self,line): 

369 if self.loggedIn is False: 

370 LOG.error("Not logged in") 

371 return 

372 print(self.pwd) 

373 

374 def do_ls(self, wildcard, display = True): 

375 if self.loggedIn is False: 

376 LOG.error("Not logged in") 

377 return 

378 if self.tid is None: 

379 LOG.error("No share selected") 

380 return 

381 if wildcard == '': 

382 pwd = ntpath.join(self.pwd,'*') 

383 else: 

384 pwd = ntpath.join(self.pwd, wildcard) 

385 self.completion = [] 

386 pwd = pwd.replace('/','\\') 

387 pwd = ntpath.normpath(pwd) 

388 for f in self.smb.listPath(self.share, pwd): 

389 if display is True: 

390 print("%crw-rw-rw- %10d %s %s" % ( 

391 'd' if f.is_directory() > 0 else '-', f.get_filesize(), time.ctime(float(f.get_mtime_epoch())), 

392 f.get_longname())) 

393 self.completion.append((f.get_longname(), f.is_directory())) 

394 

395 

396 def do_rm(self, filename): 

397 if self.tid is None: 

398 LOG.error("No share selected") 

399 return 

400 f = ntpath.join(self.pwd, filename) 

401 file = f.replace('/','\\') 

402 self.smb.deleteFile(self.share, file) 

403 

404 def do_mkdir(self, path): 

405 if self.tid is None: 

406 LOG.error("No share selected") 

407 return 

408 p = ntpath.join(self.pwd, path) 

409 pathname = p.replace('/','\\') 

410 self.smb.createDirectory(self.share,pathname) 

411 

412 def do_rmdir(self, path): 

413 if self.tid is None: 

414 LOG.error("No share selected") 

415 return 

416 p = ntpath.join(self.pwd, path) 

417 pathname = p.replace('/','\\') 

418 self.smb.deleteDirectory(self.share, pathname) 

419 

420 def do_put(self, pathname): 

421 if self.tid is None: 

422 LOG.error("No share selected") 

423 return 

424 src_path = pathname 

425 dst_name = os.path.basename(src_path) 

426 

427 fh = open(pathname, 'rb') 

428 f = ntpath.join(self.pwd,dst_name) 

429 finalpath = f.replace('/','\\') 

430 self.smb.putFile(self.share, finalpath, fh.read) 

431 fh.close() 

432 

433 def complete_get(self, text, line, begidx, endidx, include = 1): 

434 # include means 

435 # 1 just files 

436 # 2 just directories 

437 p = line.replace('/','\\') 

438 if p.find('\\') < 0: 

439 items = [] 

440 if include == 1: 

441 mask = 0 

442 else: 

443 mask = 0x010 

444 for i in self.completion: 

445 if i[1] == mask: 

446 items.append(i[0]) 

447 if text: 

448 return [ 

449 item for item in items 

450 if item.upper().startswith(text.upper()) 

451 ] 

452 else: 

453 return items 

454 

455 def do_mget(self, mask): 

456 if mask == '': 

457 LOG.error("A mask must be provided") 

458 return 

459 if self.tid is None: 

460 LOG.error("No share selected") 

461 return 

462 self.do_ls(mask,display=False) 

463 if len(self.completion) == 0: 

464 LOG.error("No files found matching the provided mask") 

465 return 

466 for file_tuple in self.completion: 

467 if file_tuple[1] == 0: 

468 filename = file_tuple[0] 

469 filename = filename.replace('/', '\\') 

470 fh = open(ntpath.basename(filename), 'wb') 

471 pathname = ntpath.join(self.pwd, filename) 

472 try: 

473 LOG.info("Downloading %s" % (filename)) 

474 self.smb.getFile(self.share, pathname, fh.write) 

475 except: 

476 fh.close() 

477 os.remove(filename) 

478 raise 

479 fh.close() 

480 

481 def do_get(self, filename): 

482 if self.tid is None: 

483 LOG.error("No share selected") 

484 return 

485 filename = filename.replace('/','\\') 

486 fh = open(ntpath.basename(filename),'wb') 

487 pathname = ntpath.join(self.pwd,filename) 

488 try: 

489 self.smb.getFile(self.share, pathname, fh.write) 

490 except: 

491 fh.close() 

492 os.remove(filename) 

493 raise 

494 fh.close() 

495 

496 def do_cat(self, filename): 

497 if self.tid is None: 

498 LOG.error("No share selected") 

499 return 

500 filename = filename.replace('/','\\') 

501 fh = BytesIO() 

502 pathname = ntpath.join(self.pwd,filename) 

503 try: 

504 self.smb.getFile(self.share, pathname, fh.write) 

505 except: 

506 raise 

507 output = fh.getvalue() 

508 encoding = chardet.detect(output)["encoding"] 

509 error_msg = "[-] Output cannot be correctly decoded, are you sure the text is readable ?" 

510 if encoding: 

511 try: 

512 print(output.decode(encoding)) 

513 except: 

514 print(error_msg) 

515 finally: 

516 fh.close() 

517 else: 

518 print(error_msg) 

519 fh.close() 

520 

521 def do_close(self, line): 

522 self.do_logoff(line) 

523 

524 def do_list_snapshots(self, line): 

525 l = line.split(' ') 

526 if len(l) > 0: 

527 pathName= l[0].replace('/','\\') 

528 

529 # Relative or absolute path? 

530 if pathName.startswith('\\') is not True: 

531 pathName = ntpath.join(self.pwd, pathName) 

532 

533 snapshotList = self.smb.listSnapshots(self.tid, pathName) 

534 

535 if not snapshotList: 

536 print("No snapshots found") 

537 return 

538 

539 for timestamp in snapshotList: 

540 print(timestamp) 

541 

542 def do_mount(self, line): 

543 l = line.split(' ') 

544 if len(l) > 1: 

545 target = l[0].replace('/','\\') 

546 pathName= l[1].replace('/','\\') 

547 

548 # Relative or absolute path? 

549 if pathName.startswith('\\') is not True: 

550 pathName = ntpath.join(self.pwd, pathName) 

551 

552 self.smb.createMountPoint(self.tid, pathName, target) 

553 

554 def do_umount(self, mountpoint): 

555 mountpoint = mountpoint.replace('/','\\') 

556 

557 # Relative or absolute path? 

558 if mountpoint.startswith('\\') is not True: 

559 mountpoint = ntpath.join(self.pwd, mountpoint) 

560 

561 mountPath = ntpath.join(self.pwd, mountpoint) 

562 

563 self.smb.removeMountPoint(self.tid, mountPath) 

564 

565 def do_EOF(self, line): 

566 print('Bye!\n') 

567 return True