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# SECUREAUTH LABS. Copyright 2018 SecureAuth Corporation. All rights reserved. 

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# 

16from __future__ import division 

17from __future__ import print_function 

18from io import BytesIO 

19import sys 

20import time 

21import cmd 

22import os 

23import ntpath 

24 

25from six import PY2 

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

27from impacket.dcerpc.v5.dtypes import NULL 

28from impacket import LOG 

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

30 FILE_READ_DATA, FILE_SHARE_READ, FILE_SHARE_WRITE 

31from impacket.smb3structs import FILE_DIRECTORY_FILE, FILE_LIST_DIRECTORY 

32 

33import chardet 

34 

35 

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

37try: 

38 import pyreadline as readline 

39except ImportError: 

40 import readline 

41 

42class MiniImpacketShell(cmd.Cmd): 

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

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

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

46 # instead of to stdin / stdout 

47 if tcpShell is not None: 

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

49 sys.stdout = tcpShell.stdout 

50 sys.stdin = tcpShell.stdin 

51 sys.stderr = tcpShell.stdout 

52 self.use_rawinput = False 

53 self.shell = tcpShell 

54 else: 

55 cmd.Cmd.__init__(self) 

56 self.shell = None 

57 

58 self.prompt = '# ' 

59 self.smb = smbClient 

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

61 self.tid = None 

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

63 self.pwd = '' 

64 self.share = None 

65 self.loggedIn = True 

66 self.last_output = None 

67 self.completion = [] 

68 

69 def emptyline(self): 

70 pass 

71 

72 def precmd(self,line): 

73 # switch to unicode 

74 if PY2: 

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

76 return line 

77 

78 def onecmd(self,s): 

79 retVal = False 

80 try: 

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

82 except Exception as e: 

83 LOG.error(e) 

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

85 

86 return retVal 

87 

88 def do_exit(self,line): 

89 if self.shell is not None: 

90 self.shell.close() 

91 return True 

92 

93 def do_shell(self, line): 

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

95 print(output) 

96 self.last_output = output 

97 

98 def do_help(self,line): 

99 print(""" 

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

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

102 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 

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

104 logoff - logs off 

105 shares - list available shares 

106 use {sharename} - connect to an specific share 

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

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

109 pwd - shows current remote directory 

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

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

112 rm {file} - removes the selected file 

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

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

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

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

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

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

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

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

121 info - returns NetrServerInfo main results 

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

123 close - closes the current SMB Session 

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

125 

126""") 

127 

128 def do_password(self, line): 

129 if self.loggedIn is False: 

130 LOG.error("Not logged in") 

131 return 

132 from getpass import getpass 

133 newPassword = getpass("New Password:") 

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

135 dce = rpctransport.get_dce_rpc() 

136 dce.connect() 

137 dce.bind(samr.MSRPC_UUID_SAMR) 

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

139 self.password = newPassword 

140 self.lmhash = None 

141 self.nthash = None 

142 

143 def do_open(self,line): 

144 l = line.split(' ') 

145 port = 445 

146 if len(l) > 0: 

147 host = l[0] 

148 if len(l) > 1: 

149 port = int(l[1]) 

150 

151 

152 if port == 139: 

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

154 else: 

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

156 

157 dialect = self.smb.getDialect() 

158 if dialect == SMB_DIALECT: 

159 LOG.info("SMBv1 dialect used") 

160 elif dialect == SMB2_DIALECT_002: 

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

162 elif dialect == SMB2_DIALECT_21: 

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

164 else: 

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

166 

167 self.share = None 

168 self.tid = None 

169 self.pwd = '' 

170 self.loggedIn = False 

171 self.password = None 

172 self.lmhash = None 

173 self.nthash = None 

174 self.username = None 

175 

176 def do_login(self,line): 

177 if self.smb is None: 

178 LOG.error("No connection open") 

179 return 

180 l = line.split(' ') 

181 username = '' 

182 password = '' 

183 domain = '' 

184 if len(l) > 0: 

185 username = l[0] 

186 if len(l) > 1: 

187 password = l[1] 

188 

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

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

191 

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

193 from getpass import getpass 

194 password = getpass("Password:") 

195 

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

197 self.password = password 

198 self.username = username 

199 

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

201 LOG.info("GUEST Session Granted") 

202 else: 

203 LOG.info("USER Session Granted") 

204 self.loggedIn = True 

205 

206 def do_kerberos_login(self,line): 

207 if self.smb is None: 

208 LOG.error("No connection open") 

209 return 

210 l = line.split(' ') 

211 username = '' 

212 password = '' 

213 domain = '' 

214 if len(l) > 0: 

215 username = l[0] 

216 if len(l) > 1: 

217 password = l[1] 

218 

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

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

221 

222 if domain == '': 

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

224 return 

225 

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

227 from getpass import getpass 

228 password = getpass("Password:") 

229 

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

231 self.password = password 

232 self.username = username 

233 

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

235 LOG.info("GUEST Session Granted") 

236 else: 

237 LOG.info("USER Session Granted") 

238 self.loggedIn = True 

239 

240 def do_login_hash(self,line): 

241 if self.smb is None: 

242 LOG.error("No connection open") 

243 return 

244 l = line.split(' ') 

245 domain = '' 

246 if len(l) > 0: 

247 username = l[0] 

248 if len(l) > 1: 

249 hashes = l[1] 

250 else: 

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

252 return 

253 

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

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

256 

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

258 

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

260 self.username = username 

261 self.lmhash = lmhash 

262 self.nthash = nthash 

263 

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

265 LOG.info("GUEST Session Granted") 

266 else: 

267 LOG.info("USER Session Granted") 

268 self.loggedIn = True 

269 

270 def do_logoff(self, line): 

271 if self.smb is None: 

272 LOG.error("No connection open") 

273 return 

274 self.smb.logoff() 

275 del self.smb 

276 self.share = None 

277 self.smb = None 

278 self.tid = None 

279 self.pwd = '' 

280 self.loggedIn = False 

281 self.password = None 

282 self.lmhash = None 

283 self.nthash = None 

284 self.username = None 

285 

286 def do_info(self, line): 

287 if self.loggedIn is False: 

288 LOG.error("Not logged in") 

289 return 

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

291 dce = rpctransport.get_dce_rpc() 

292 dce.connect() 

293 dce.bind(srvs.MSRPC_UUID_SRVS) 

294 resp = srvs.hNetrServerGetInfo(dce, 102) 

295 

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

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

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

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

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

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

302 

303 def do_who(self, line): 

304 if self.loggedIn is False: 

305 LOG.error("Not logged in") 

306 return 

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

308 dce = rpctransport.get_dce_rpc() 

309 dce.connect() 

310 dce.bind(srvs.MSRPC_UUID_SRVS) 

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

312 

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

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

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

316 session['sesi10_idle_time'])) 

317 

318 def do_shares(self, line): 

319 if self.loggedIn is False: 

320 LOG.error("Not logged in") 

321 return 

322 resp = self.smb.listShares() 

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

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

325 

326 def do_use(self,line): 

327 if self.loggedIn is False: 

328 LOG.error("Not logged in") 

329 return 

330 self.share = line 

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

332 self.pwd = '\\' 

333 self.do_ls('', False) 

334 

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

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

337 

338 def do_cd(self, line): 

339 if self.tid is None: 

340 LOG.error("No share selected") 

341 return 

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

343 oldpwd = self.pwd 

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

345 self.pwd = line 

346 else: 

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

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

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

350 try: 

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

352 FILE_LIST_DIRECTORY, shareMode = FILE_SHARE_READ | FILE_SHARE_WRITE ) 

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

354 except SessionError: 

355 self.pwd = oldpwd 

356 raise 

357 

358 def do_lcd(self, s): 

359 print(s) 

360 if s == '': 

361 print(os.getcwd()) 

362 else: 

363 os.chdir(s) 

364 

365 def do_pwd(self,line): 

366 if self.loggedIn is False: 

367 LOG.error("Not logged in") 

368 return 

369 print(self.pwd) 

370 

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

372 if self.loggedIn is False: 

373 LOG.error("Not logged in") 

374 return 

375 if self.tid is None: 

376 LOG.error("No share selected") 

377 return 

378 if wildcard == '': 

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

380 else: 

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

382 self.completion = [] 

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

384 pwd = ntpath.normpath(pwd) 

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

386 if display is True: 

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

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

389 f.get_longname())) 

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

391 

392 

393 def do_rm(self, filename): 

394 if self.tid is None: 

395 LOG.error("No share selected") 

396 return 

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

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

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

400 

401 def do_mkdir(self, path): 

402 if self.tid is None: 

403 LOG.error("No share selected") 

404 return 

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

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

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

408 

409 def do_rmdir(self, path): 

410 if self.tid is None: 

411 LOG.error("No share selected") 

412 return 

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

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

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

416 

417 def do_put(self, pathname): 

418 if self.tid is None: 

419 LOG.error("No share selected") 

420 return 

421 src_path = pathname 

422 dst_name = os.path.basename(src_path) 

423 

424 fh = open(pathname, 'rb') 

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

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

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

428 fh.close() 

429 

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

431 # include means 

432 # 1 just files 

433 # 2 just directories 

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

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

436 items = [] 

437 if include == 1: 

438 mask = 0 

439 else: 

440 mask = 0x010 

441 for i in self.completion: 

442 if i[1] == mask: 

443 items.append(i[0]) 

444 if text: 

445 return [ 

446 item for item in items 

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

448 ] 

449 else: 

450 return items 

451 

452 def do_get(self, filename): 

453 if self.tid is None: 

454 LOG.error("No share selected") 

455 return 

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

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

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

459 try: 

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

461 except: 

462 fh.close() 

463 os.remove(filename) 

464 raise 

465 fh.close() 

466 

467 def do_cat(self, filename): 

468 if self.tid is None: 

469 LOG.error("No share selected") 

470 return 

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

472 fh = BytesIO() 

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

474 try: 

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

476 except: 

477 raise 

478 output = fh.getvalue() 

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

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

481 if encoding: 

482 try: 

483 print(output.decode(encoding)) 

484 except: 

485 print(error_msg) 

486 finally: 

487 fh.close() 

488 else: 

489 print(error_msg) 

490 fh.close() 

491 

492 def do_close(self, line): 

493 self.do_logoff(line) 

494 

495 def do_list_snapshots(self, line): 

496 l = line.split(' ') 

497 if len(l) > 0: 

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

499 

500 # Relative or absolute path? 

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

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

503 

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

505 

506 if not snapshotList: 

507 print("No snapshots found") 

508 return 

509 

510 for timestamp in snapshotList: 

511 print(timestamp) 

512 

513 def do_mount(self, line): 

514 l = line.split(' ') 

515 if len(l) > 1: 

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

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

518 

519 # Relative or absolute path? 

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

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

522 

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

524 

525 def do_umount(self, mountpoint): 

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

527 

528 # Relative or absolute path? 

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

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

531 

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

533 

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

535 

536 def do_EOF(self, line): 

537 print('Bye!\n') 

538 return True