smbclientng.core.InteractiveShell
1#!/usr/bin/env python3 2# -*- coding: utf-8 -*- 3# File name : InteractiveShell.py 4# Author : Podalirius (@podalirius_) 5# Date created : 23 may 2024 6 7 8import charset_normalizer 9import datetime 10import impacket 11from importlib import import_module 12import ntpath 13import os 14import readline 15import shutil 16import shlex 17import sys 18import traceback 19from rich.console import Console 20from rich.table import Table 21from rich.syntax import Syntax 22from smbclientng.core.CommandCompleter import CommandCompleter 23from smbclientng.core.utils import b_filesize, unix_permissions, windows_ls_entry, local_tree, resolve_local_files, resolve_remote_files 24 25 26## Decorators 27 28def command_arguments_required(func): 29 def wrapper(*args, **kwargs): 30 self, arguments,command = args[0], args[1], args[2] 31 if len(arguments) != 0: 32 return func(*args, **kwargs) 33 else: 34 self.commandCompleterObject.print_help(command=command) 35 return None 36 return wrapper 37 38def active_smb_connection_needed(func): 39 def wrapper(*args, **kwargs): 40 self, arguments,command = args[0], args[1], args[2] 41 # 42 self.sessionsManager.current_session.ping_smb_session() 43 if self.sessionsManager.current_session.connected: 44 return func(*args, **kwargs) 45 else: 46 print("[!] SMB Session is disconnected.") 47 return None 48 return wrapper 49 50def smb_share_is_set(func): 51 def wrapper(*args, **kwargs): 52 self, arguments,command = args[0], args[1], args[2] 53 if self.sessionsManager.current_session.smb_share is not None: 54 return func(*args, **kwargs) 55 else: 56 print("[!] You must open a share first, try the 'use <share>' command.") 57 return None 58 return wrapper 59 60 61class InteractiveShell(object): 62 """ 63 Class InteractiveShell is designed to manage the interactive command line interface for smbclient-ng. 64 65 This class handles user input, executes commands, and manages the state of the SMB session. It provides 66 a command line interface for users to interact with SMB shares, execute commands like directory listing, 67 file transfer, and more. 68 69 Attributes: 70 smbSession (SMBConnection): The active SMB connection session. 71 debug (bool): Flag to enable or disable debug mode. 72 smb_share (str): The current SMB share in use. 73 smb_path (str): The current path within the SMB share. 74 commandCompleterObject (CommandCompleter): Object to handle command completion and help generation. 75 76 Methods: 77 __init__(self, smbSession, debug=False): Initializes the InteractiveShell with the given SMB session and debug mode. 78 run(self): Starts the command line interface loop, processing user input until exit. 79 """ 80 81 running = True 82 modules = {} 83 84 def __init__(self, sessionsManager, config, logger): 85 # Objects 86 self.sessionsManager = sessionsManager 87 self.config = config 88 self.logger = logger 89 # Internals 90 self.commandCompleterObject = CommandCompleter( 91 smbSession=self.sessionsManager.current_session, 92 config=self.config, 93 logger=self.logger, 94 ) 95 readline.set_completer(self.commandCompleterObject.complete) 96 readline.parse_and_bind("tab: complete") 97 readline.set_completer_delims("\n") 98 # Additional modules 99 self.__load_modules() 100 101 def run(self): 102 # Read commands from script file first 103 if self.config.startup_script: 104 f = open(self.config.startup_script, 'r') 105 for line in f.readlines(): 106 try: 107 self.logger.print("%s%s" % (self.__prompt(), line.strip())) 108 readline.add_history(line.strip()) 109 self.process_line(commandLine=line.strip()) 110 except KeyboardInterrupt as e: 111 self.logger.print() 112 113 except EOFError as e: 114 self.logger.print() 115 running = False 116 117 except Exception as err: 118 if self.config.debug: 119 traceback.print_exc() 120 self.logger.error(str(err)) 121 122 # Then interactive console 123 if not self.config.not_interactive: 124 while self.running: 125 try: 126 user_input = input(self.__prompt()).strip() 127 self.process_line(commandLine=user_input) 128 except KeyboardInterrupt as e: 129 self.logger.print() 130 131 except EOFError as e: 132 self.logger.print() 133 running = False 134 135 except Exception as err: 136 if self.config.debug: 137 traceback.print_exc() 138 self.logger.error(str(err)) 139 140 def process_line(self, commandLine): 141 # Split and parse the commandLine 142 tokens = shlex.split(commandLine) 143 if len(tokens) == 0: 144 command = "" 145 arguments = [] 146 elif len(tokens) == 1: 147 command = tokens[0].lower() 148 arguments = [] 149 else: 150 command = tokens[0].lower() 151 arguments = tokens[1:] 152 153 # Skip 154 if command.strip() == "": 155 pass 156 # Execute the command 157 elif command in self.commandCompleterObject.commands.keys(): 158 159 # Exit the command line 160 if command in ["exit", "quit"]: 161 self.running = False 162 163 # Display help 164 elif command == "help": 165 self.command_help(arguments, command) 166 167 # Cat the contents of a file 168 elif command == "bat": 169 self.command_bat(arguments, command) 170 171 # Cat the contents of a file 172 elif command == "cat": 173 self.command_cat(arguments, command) 174 175 # Closes the current SMB session 176 elif command == "close": 177 self.command_close(arguments, command) 178 179 # Change directory in the current share 180 elif command == "cd": 181 self.command_cd(arguments, command) 182 183 # debug 184 elif command == "debug": 185 self.command_debug(arguments, command) 186 187 # Get a file 188 elif command == "get": 189 self.command_get(arguments, command) 190 191 # SMB server info 192 elif command == "info": 193 self.command_info(arguments, command) 194 195 # List directory contents in a share 196 elif command in ["ls", "dir"]: 197 self.command_ls(arguments, command) 198 199 # Creates a new remote directory 200 elif command == "mkdir": 201 self.command_mkdir(arguments, command) 202 203 # Put a file 204 elif command == "put": 205 self.command_put(arguments, command) 206 207 # Shows the content of a local file 208 elif command == "lcat": 209 self.command_lcat(arguments, command) 210 211 # Changes the current local directory 212 elif command == "lcd": 213 self.command_lcd(arguments, command) 214 215 # Creates a copy of a local file 216 elif command == "lcp": 217 self.command_lcp(arguments, command) 218 219 # Pretty prints the content of a local file 220 elif command == "lbat": 221 self.command_lbat(arguments, command) 222 223 # Lists the contents of the current local directory 224 elif command == "lls": 225 self.command_lls(arguments, command) 226 227 # Creates a new local directory 228 elif command == "lmkdir": 229 self.command_lmkdir(arguments, command) 230 231 # Shows the current local directory 232 elif command == "lpwd": 233 self.command_lpwd(arguments, command) 234 235 # Renames a local file 236 elif command == "lrename": 237 self.command_lrename(arguments, command) 238 239 # Removes a local file 240 elif command == "lrm": 241 self.command_lrm(arguments, command) 242 243 # Removes a local directory 244 elif command == "lrmdir": 245 self.command_lrmdir(arguments, command) 246 247 # Shows the current local directory 248 elif command == "ltree": 249 self.command_ltree(arguments, command) 250 251 # Modules 252 elif command == "module": 253 self.command_module(arguments, command) 254 255 # Creates a mount point of the remote share on the local machine 256 elif command == "mount": 257 self.command_mount(arguments, command) 258 259 # Reconnects the current SMB session 260 elif command in ["connect", "reconnect"]: 261 self.command_reconnect(arguments, command) 262 263 # Reset the TTY output 264 elif command == "reset": 265 self.command_reset(arguments, command) 266 267 # Removes a remote file 268 elif command == "rm": 269 self.command_rm(arguments, command) 270 271 # Removes a remote directory 272 elif command == "rmdir": 273 self.command_rmdir(arguments, command) 274 275 # Sessions management 276 elif command == "sessions": 277 self.sessionsManager.process_command_line(arguments) 278 279 # List shares 280 elif command == "sizeof": 281 self.command_sizeof(arguments, command) 282 283 # List shares 284 elif command == "shares": 285 self.command_shares(arguments, command) 286 287 # Displays a tree view of the CWD 288 elif command == "tree": 289 self.command_tree(arguments, command) 290 291 # Use a share 292 elif command == "use": 293 self.command_use(arguments, command) 294 295 # Fallback to unknown command 296 else: 297 self.logger.print("Unknown command. Type \"help\" for help.") 298 299 # Commands ================================================================ 300 301 def command_debug(self, arguments, command): 302 try: 303 self.logger.print("[debug] command = '%s'" % command) 304 self.logger.print("[debug] arguments = %s" % arguments) 305 except Exception as e: 306 traceback.print_exc() 307 308 @command_arguments_required 309 @active_smb_connection_needed 310 @smb_share_is_set 311 def command_bat(self, arguments, command): 312 # Command arguments required : Yes 313 # Active SMB connection needed : Yes 314 # SMB share needed : Yes 315 316 # Parse wildcards 317 files_and_directories = resolve_local_files(arguments) 318 319 for path_to_file in files_and_directories: 320 if self.sessionsManager.current_session.path_isfile(pathFromRoot=path_to_file): 321 # Read the file 322 try: 323 rawcontents = self.sessionsManager.current_session.read_file(path=path_to_file) 324 if rawcontents is not None: 325 encoding = charset_normalizer.detect(rawcontents)["encoding"] 326 if encoding is not None: 327 filecontent = rawcontents.decode(encoding).rstrip() 328 lexer = Syntax.guess_lexer(path=ntpath.basename(path_to_file), code=filecontent) 329 # Some trickery for the files undetected by the lexer 330 if lexer == "default": 331 if '<?xml' in filecontent: 332 lexer = "xml" 333 elif '<html>' in filecontent: 334 lexer = "html" 335 syntax = Syntax(code=filecontent, line_numbers=True, lexer=lexer) 336 if len(files_and_directories) > 1: 337 self.logger.print("\x1b[1;93m[>] %s\x1b[0m" % (path_to_file+' ').ljust(80,'=')) 338 Console().print(syntax) 339 else: 340 self.logger.error("[!] Could not detect charset of '%s'." % path_to_file) 341 except impacket.smbconnection.SessionError as e: 342 self.logger.error("[!] SMB Error: %s" % e) 343 344 @command_arguments_required 345 @active_smb_connection_needed 346 @smb_share_is_set 347 def command_cd(self, arguments, command): 348 # Command arguments required : Yes 349 # Active SMB connection needed : Yes 350 # SMB share needed : Yes 351 352 try: 353 self.sessionsManager.current_session.set_cwd(path=arguments[0]) 354 except impacket.smbconnection.SessionError as e: 355 self.logger.error("[!] SMB Error: %s" % e) 356 357 @command_arguments_required 358 @active_smb_connection_needed 359 @smb_share_is_set 360 def command_cat(self, arguments, command): 361 # Command arguments required : Yes 362 # Active SMB connection needed : Yes 363 # SMB share needed : Yes 364 365 # Parse wildcards 366 files_and_directories = resolve_local_files(arguments) 367 368 for path_to_file in files_and_directories: 369 if self.sessionsManager.current_session.path_isfile(pathFromRoot=path_to_file): 370 # Read the file 371 try: 372 rawcontents = self.sessionsManager.current_session.read_file(path=path_to_file) 373 if rawcontents is not None: 374 encoding = charset_normalizer.detect(rawcontents)["encoding"] 375 if encoding is not None: 376 filecontent = rawcontents.decode(encoding).rstrip() 377 if len(files_and_directories) > 1: 378 self.logger.print("\x1b[1;93m[>] %s\x1b[0m" % (path_to_file+' ').ljust(80,'=')) 379 self.logger.print(filecontent) 380 else: 381 self.logger.error("[!] Could not detect charset of '%s'." % path_to_file) 382 except impacket.smbconnection.SessionError as e: 383 self.logger.error("[!] SMB Error: %s" % e) 384 385 def command_close(self, arguments, command): 386 # Command arguments required : No 387 # Active SMB connection needed : No 388 # SMB share needed : No 389 390 self.sessionsManager.current_session.ping_smb_session() 391 if self.sessionsManager.current_session.connected: 392 self.sessionsManager.current_session.close_smb_session() 393 394 @command_arguments_required 395 @active_smb_connection_needed 396 @smb_share_is_set 397 def command_get(self, arguments, command): 398 # Command arguments required : Yes 399 # Active SMB connection needed : Yes 400 # SMB share needed : Yes 401 402 is_recursive = False 403 while '-r' in arguments: 404 is_recursive = True 405 arguments.remove('-r') 406 407 # Parse wildcards 408 files_and_directories = resolve_remote_files(self.sessionsManager.current_session, arguments) 409 410 # 411 for remotepath in files_and_directories: 412 try: 413 if is_recursive and self.sessionsManager.current_session.path_isdir(remotepath): 414 # Get files recursively 415 self.sessionsManager.current_session.get_file_recursively(path=remotepath) 416 else: 417 # Get this single file 418 self.sessionsManager.current_session.get_file(path=remotepath) 419 except impacket.smbconnection.SessionError as e: 420 self.logger.error("[!] SMB Error: %s" % e) 421 422 def command_help(self, arguments, command): 423 # Command arguments required : No 424 # Active SMB connection needed : No 425 # SMB share needed : No 426 427 if len(arguments) != 0: 428 self.commandCompleterObject.print_help(command=arguments[0]) 429 else: 430 self.commandCompleterObject.print_help(command=None) 431 432 @active_smb_connection_needed 433 def command_info(self, arguments, command): 434 # Command arguments required : No 435 # Active SMB connection needed : Yes 436 # SMB share needed : No 437 438 print_server_info = False 439 print_share_info = False 440 if len(arguments) != 0: 441 print_server_info = (arguments[0].lower() == "server") 442 print_share_info = (arguments[0].lower() == "share") 443 else: 444 print_server_info = True 445 print_share_info = True 446 447 try: 448 self.sessionsManager.current_session.info( 449 share=print_share_info, 450 server=print_server_info 451 ) 452 except impacket.smbconnection.SessionError as e: 453 self.logger.error("[!] SMB Error: %s" % e) 454 455 @command_arguments_required 456 def command_lbat(self, arguments, command): 457 # Command arguments required : Yes 458 # Active SMB connection needed : No 459 # SMB share needed : No 460 461 # Parse wildcards 462 files_and_directories = resolve_remote_files(self.sessionsManager.current_session, arguments) 463 464 for path_to_file in files_and_directories: 465 # Read the file 466 try: 467 if os.path.exists(path=path_to_file): 468 f = open(path_to_file, 'rb') 469 rawcontents = f.read() 470 # 471 if rawcontents is not None: 472 encoding = charset_normalizer.detect(rawcontents)["encoding"] 473 if encoding is not None: 474 filecontent = rawcontents.decode(encoding).rstrip() 475 lexer = Syntax.guess_lexer(path=ntpath.basename(path_to_file), code=filecontent) 476 # Some trickery for the files undetected by the lexer 477 if lexer == "default": 478 if '<?xml' in filecontent: 479 lexer = "xml" 480 elif '<html>' in filecontent: 481 lexer = "html" 482 syntax = Syntax(code=filecontent, line_numbers=True, lexer=lexer) 483 if len(files_and_directories) > 1: 484 self.logger.print("\x1b[1;93m[>] %s\x1b[0m" % (path_to_file+' ').ljust(80,'=')) 485 Console().print(syntax) 486 else: 487 self.logger.error("[!] Could not detect charset of '%s'." % path_to_file) 488 else: 489 self.logger.error("[!] Local file '%s' does not exist." % path_to_file) 490 except impacket.smbconnection.SessionError as e: 491 self.logger.error("[!] SMB Error: %s" % e) 492 493 @command_arguments_required 494 def command_lcat(self, arguments, command): 495 # Command arguments required : Yes 496 # Active SMB connection needed : No 497 # SMB share needed : No 498 499 # Parse wildcards 500 files_and_directories = resolve_remote_files(self.sessionsManager.current_session, arguments) 501 502 for path_to_file in files_and_directories: 503 # Read the file 504 try: 505 if os.path.exists(path=path_to_file): 506 f = open(path_to_file, 'rb') 507 rawcontents = f.read() 508 # 509 if rawcontents is not None: 510 encoding = charset_normalizer.detect(rawcontents)["encoding"] 511 if encoding is not None: 512 filecontent = rawcontents.decode(encoding).rstrip() 513 if len(files_and_directories) > 1: 514 self.logger.print("\x1b[1;93m[>] %s\x1b[0m" % (path_to_file+' ').ljust(80,'=')) 515 self.logger.print(filecontent) 516 else: 517 self.logger.error("[!] Could not detect charset of '%s'." % path_to_file) 518 else: 519 self.logger.error("[!] Local file '%s' does not exist." % path_to_file) 520 except impacket.smbconnection.SessionError as e: 521 self.logger.error("[!] SMB Error: %s" % e) 522 523 @command_arguments_required 524 def command_lcd(self, arguments, command): 525 # Command arguments required : Yes 526 # Active SMB connection needed : No 527 # SMB share needed : No 528 529 path = arguments[0] 530 531 if os.path.exists(path=path): 532 if os.path.isdir(s=path): 533 os.chdir(path=path) 534 else: 535 self.logger.error("Path '%s' is not a directory." % path) 536 else: 537 self.logger.error("Directory '%s' does not exists." % path) 538 539 @command_arguments_required 540 def command_lcp(self, arguments, command): 541 # Command arguments required : Yes 542 # Active SMB connection needed : No 543 # SMB share needed : No 544 545 if len(arguments) == 2: 546 src_path = arguments[0] 547 dst_path = arguments[1] 548 if os.path.exists(path=src_path): 549 try: 550 shutil.copyfile(src=src_path, dst=dst_path) 551 except shutil.SameFileError as err: 552 self.logger.error("[!] Error: %s" % err) 553 else: 554 self.logger.error("[!] File '%s' does not exists." % src_path) 555 else: 556 self.commandCompleterObject.print_help(command=command) 557 558 def command_lls(self, arguments, command): 559 # Command arguments required : No 560 # Active SMB connection needed : No 561 # SMB share needed : No 562 563 if len(arguments) == 0: 564 arguments = ['.'] 565 else: 566 arguments = resolve_local_files(arguments) 567 568 for path in arguments: 569 if len(arguments) > 1: 570 self.logger.print("%s:" % path) 571 # lls <directory> 572 if os.path.isdir(path): 573 directory_contents = os.listdir(path=path) 574 for entryname in sorted(directory_contents): 575 path_to_file = path + os.path.sep + entryname 576 rights_str = unix_permissions(path_to_file) 577 size_str = b_filesize(os.path.getsize(filename=path_to_file)) 578 date_str = datetime.datetime.fromtimestamp(os.path.getmtime(filename=path_to_file)).strftime("%Y-%m-%d %H:%M") 579 580 if os.path.isdir(s=entryname): 581 if self.config.no_colors: 582 self.logger.print("%s %10s %s %s%s" % (rights_str, size_str, date_str, entryname, os.path.sep)) 583 else: 584 self.logger.print("%s %10s %s \x1b[1;96m%s\x1b[0m%s" % (rights_str, size_str, date_str, entryname, os.path.sep)) 585 else: 586 if self.config.no_colors: 587 self.logger.print("%s %10s %s %s" % (rights_str, size_str, date_str, entryname)) 588 else: 589 self.logger.print("%s %10s %s \x1b[1m%s\x1b[0m" % (rights_str, size_str, date_str, entryname)) 590 # lls <file> 591 elif os.path.isfile(path): 592 rights_str = unix_permissions(path) 593 size_str = b_filesize(os.path.getsize(filename=path)) 594 date_str = datetime.datetime.fromtimestamp(os.path.getmtime(filename=path)).strftime("%Y-%m-%d %H:%M") 595 if self.config.no_colors: 596 self.logger.print("%s %10s %s %s" % (rights_str, size_str, date_str, os.path.basename(path))) 597 else: 598 self.logger.print("%s %10s %s \x1b[1m%s\x1b[0m" % (rights_str, size_str, date_str, os.path.basename(path))) 599 600 if len(arguments) > 1: 601 self.logger.print() 602 603 @command_arguments_required 604 def command_lmkdir(self, arguments, command): 605 # Command arguments required : Yes 606 # Active SMB connection needed : No 607 # SMB share needed : No 608 609 for path in arguments: 610 if os.path.sep in path: 611 path = path.strip(os.path.sep).split(os.path.sep) 612 else: 613 path = [path] 614 615 # Create each dir in the path 616 for depth in range(1, len(path)+1): 617 tmp_path = os.path.sep.join(path[:depth]) 618 if not os.path.exists(tmp_path): 619 os.mkdir(path=tmp_path) 620 621 def command_lpwd(self, arguments, command): 622 # Command arguments required : No 623 # Active SMB connection needed : No 624 # SMB share needed : No 625 626 self.logger.print(os.getcwd()) 627 628 @command_arguments_required 629 def command_lrename(self, arguments, command): 630 # Command arguments required : Yes 631 # Active SMB connection needed : No 632 # SMB share needed : No 633 634 if len(arguments) == 2: 635 os.rename(src=arguments[0], dst=arguments[1]) 636 else: 637 self.commandCompleterObject.print_help(command=command) 638 639 @command_arguments_required 640 def command_lrm(self, arguments, command): 641 # Command arguments required : Yes 642 # Active SMB connection needed : No 643 # SMB share needed : No 644 645 path = arguments[0] 646 647 if os.path.exists(path): 648 if not os.path.isdir(s=path): 649 try: 650 os.remove(path=path) 651 except Exception as e: 652 self.logger.error("Error removing file '%s' : %s" % path) 653 else: 654 self.logger.error("Cannot delete '%s'. It is a directory, use 'lrmdir <directory>' instead." % path) 655 else: 656 self.logger.error("Path '%s' does not exist." % path) 657 658 @command_arguments_required 659 def command_lrmdir(self, arguments, command): 660 # Command arguments required : Yes 661 # Active SMB connection needed : No 662 # SMB share needed : No 663 664 if len(arguments) == 0: 665 path = '.' 666 else: 667 path = arguments[0] 668 669 if os.path.exists(path): 670 if os.path.isdir(s=path): 671 try: 672 shutil.rmtree(path=path) 673 except Exception as e: 674 self.logger.error("Error removing directory '%s' : %s" % path) 675 else: 676 self.logger.error("Cannot delete '%s'. It is a file, use 'lrm <file>' instead." % path) 677 else: 678 self.logger.error("Path '%s' does not exist." % path) 679 680 def command_ltree(self, arguments, command): 681 # Command arguments required : No 682 # Active SMB connection needed : No 683 # SMB share needed : No 684 685 if len(arguments) == 0: 686 path = '.' 687 else: 688 path = arguments[0] 689 690 if len(arguments) == 0: 691 local_tree(path='.', config=self.config) 692 else: 693 local_tree(path=path, config=self.config) 694 695 @active_smb_connection_needed 696 @smb_share_is_set 697 def command_ls(self, arguments, command): 698 # Command arguments required : No 699 # Active SMB connection needed : Yes 700 # SMB share needed : Yes 701 702 if len(arguments) == 0: 703 arguments = ['.'] 704 else: 705 arguments = resolve_remote_files(self.sessionsManager.current_session, arguments) 706 707 for path in arguments: 708 if len(arguments) > 1: 709 self.logger.print("%s:" % path) 710 711 if self.sessionsManager.current_session.path_isdir(pathFromRoot=path): 712 # Read the files 713 directory_contents = self.sessionsManager.current_session.list_contents(path=path) 714 else: 715 entry = self.sessionsManager.current_session.get_entry(path=path) 716 if entry is not None: 717 directory_contents = {entry.get_longname(): entry} 718 else: 719 directory_contents = {} 720 721 for longname in sorted(directory_contents.keys(), key=lambda x:x.lower()): 722 windows_ls_entry(directory_contents[longname], self.config) 723 724 if len(arguments) > 1: 725 self.logger.print() 726 727 @command_arguments_required 728 @active_smb_connection_needed 729 @smb_share_is_set 730 def command_mkdir(self, arguments, command): 731 # Command arguments required : Yes 732 # Active SMB connection needed : Yes 733 # SMB share needed : Yes 734 735 self.sessionsManager.current_session.mkdir(path=arguments[0]) 736 737 @command_arguments_required 738 @active_smb_connection_needed 739 @smb_share_is_set 740 def command_module(self, arguments, command): 741 module_name = arguments[0] 742 743 if module_name in self.modules.keys(): 744 module = self.modules[module_name](self.sessionsManager.current_session, self.config, self.logger) 745 arguments_string = ' '.join(arguments[1:]) 746 module.run(arguments_string) 747 else: 748 self.logger.error("Module '%s' does not exist." % module_name) 749 750 @command_arguments_required 751 @active_smb_connection_needed 752 @smb_share_is_set 753 def command_mount(self, arguments, command): 754 # Command arguments required : Yes 755 # Active SMB connection needed : Yes 756 # SMB share needed : Yes 757 758 if len(arguments) == 2: 759 remote_path = arguments[0] 760 if not remote_path.startswith(ntpath.sep): 761 remote_path = self.sessionsManager.current_session.smb_cwd + ntpath.sep + remote_path 762 763 local_mount_point = arguments[1] 764 765 self.logger.debug("Trying to mount remote '%s' onto local '%s'" % (remote_path, local_mount_point)) 766 767 try: 768 self.sessionsManager.current_session.mount(local_mount_point, remote_path) 769 except (impacket.smbconnection.SessionError, impacket.smb3.SessionError) as e: 770 self.sessionsManager.current_session.umount(local_mount_point) 771 else: 772 self.commandCompleterObject.print_help(command=command) 773 774 @command_arguments_required 775 @active_smb_connection_needed 776 @smb_share_is_set 777 def command_put(self, arguments, command): 778 # Command arguments required : Yes 779 # Active SMB connection needed : Yes 780 # SMB share needed : Yes 781 782 is_recursive = False 783 while '-r' in arguments: 784 is_recursive = True 785 arguments.remove('-r') 786 787 # Parse wildcards 788 files_and_directories = resolve_local_files(arguments) 789 790 # 791 for localpath in files_and_directories: 792 try: 793 self.logger.print(localpath) 794 if is_recursive and os.path.isdir(s=localpath): 795 # Put files recursively 796 self.sessionsManager.current_session.put_file_recursively(localpath=localpath) 797 else: 798 # Put this single file 799 self.sessionsManager.current_session.put_file(localpath=localpath) 800 except impacket.smbconnection.SessionError as e: 801 self.logger.error("[!] SMB Error: %s" % e) 802 803 def command_reconnect(self, arguments, command): 804 # Command arguments required : No 805 # Active SMB connection needed : No 806 # SMB share needed : No 807 808 self.sessionsManager.current_session.ping_smb_session() 809 if self.sessionsManager.current_session.connected: 810 self.sessionsManager.current_session.close_smb_session() 811 self.sessionsManager.current_session.init_smb_session() 812 else: 813 self.sessionsManager.current_session.init_smb_session() 814 815 def command_reset(self, arguments, command): 816 # Command arguments required : No 817 # Active SMB connection needed : No 818 # SMB share needed : No 819 sys.stdout.write('\x1b[?25h') # Sets the cursor to on 820 sys.stdout.write('\x1b[v') 821 sys.stdout.write('\x1b[o') # Reset 822 sys.stdout.flush() 823 824 @command_arguments_required 825 @active_smb_connection_needed 826 @smb_share_is_set 827 def command_rm(self, arguments, command): 828 # Command arguments required : Yes 829 # Active SMB connection needed : Yes 830 # SMB share needed : Yes 831 832 for path_to_file in arguments: 833 # Wildcard 834 if '*' in path_to_file: 835 self.sessionsManager.current_session.rm(path=path_to_file) 836 # File 837 elif self.sessionsManager.current_session.path_exists(path_to_file): 838 if self.sessionsManager.current_session.path_isfile(path_to_file): 839 try: 840 self.sessionsManager.current_session.rm(path=path_to_file) 841 except Exception as e: 842 self.logger.error("Error removing file '%s' : %s" % path_to_file) 843 else: 844 self.logger.error("Cannot delete '%s': This is a directory, use 'rmdir <directory>' instead." % path_to_file) 845 # File does not exist 846 else: 847 self.logger.error("Remote file '%s' does not exist." % path_to_file) 848 849 @command_arguments_required 850 @active_smb_connection_needed 851 @smb_share_is_set 852 def command_rmdir(self, arguments, command): 853 # Command arguments required : Yes 854 # Active SMB connection needed : Yes 855 # SMB share needed : Yes 856 857 for path_to_directory in arguments: 858 if self.sessionsManager.current_session.path_exists(path_to_directory): 859 if self.sessionsManager.current_session.path_isdir(path_to_directory): 860 try: 861 self.sessionsManager.current_session.rmdir(path=path_to_directory) 862 except Exception as e: 863 self.logger.error("Error removing directory '%s' : %s" % path_to_directory) 864 else: 865 self.logger.error("Cannot delete '%s': This is a file, use 'rm <file>' instead." % path_to_directory) 866 else: 867 self.logger.error("Remote directory '%s' does not exist." % path_to_directory) 868 869 @active_smb_connection_needed 870 @smb_share_is_set 871 def command_sizeof(self, arguments, command): 872 # Command arguments required : Yes 873 # Active SMB connection needed : Yes 874 # SMB share needed : Yes 875 876 class RecursiveSizeOfPrint(object): 877 def __init__(self, entry, sessionsManager, config, logger): 878 self.entry = entry 879 self.config = config 880 self.logger = logger 881 self.sessionsManager = sessionsManager 882 self.size = 0 883 884 def update(self, entry, fullpath, depth): 885 self.size += entry.get_filesize() 886 self.print(end='\r') 887 888 def print(self, end='\n'): 889 # Directory 890 if self.entry.is_directory(): 891 if self.config.no_colors: 892 path = "%s\\" % self.entry.get_longname() 893 else: 894 path = "\x1b[1;96m%s\x1b[0m\\" % self.entry.get_longname() 895 # File 896 else: 897 if self.config.no_colors: 898 path = "%s" % self.entry.get_longname() 899 else: 900 path = "\x1b[1m%s\x1b[0m" % self.entry.get_longname() 901 self.logger.print("%10s %s" % (b_filesize(self.size), path), end=end) 902 903 entries = [] 904 if len(arguments) == 0: 905 entries = self.sessionsManager.current_session.list_contents() 906 entries = [entry for name, entry in entries.items() if name not in ['.', '..']] 907 else: 908 entry = self.sessionsManager.current_session.get_entry(path=' '.join(arguments)) 909 entries = [] 910 if entry is not None: 911 entries = [entry] 912 else: 913 self.logger.print("[!] Path '%s' does not exist." % ' '.join(arguments)) 914 915 total = 0 916 for entry in entries: 917 rsop = RecursiveSizeOfPrint( 918 entry=entry, 919 sessionsManager=self.sessionsManager, 920 config=self.config, 921 logger=self.logger 922 ) 923 # Directory 924 if entry.is_directory(): 925 self.sessionsManager.current_session.find( 926 paths=[entry.get_longname()], 927 callback=rsop.update 928 ) 929 # File 930 else: 931 rsop.update(entry=entry, fullpath=entry.get_longname(), depth=0) 932 # Close the print 933 rsop.print() 934 total += rsop.size 935 936 if len(entries) > 1: 937 self.logger.print("──────────────────────") 938 self.logger.print(" total %s" % b_filesize(total)) 939 940 @active_smb_connection_needed 941 def command_shares(self, arguments, command): 942 # Command arguments required : No 943 # Active SMB connection needed : Yes 944 # SMB share needed : No 945 946 do_check_rights = False 947 if len(arguments) != 0: 948 if arguments[0] == "rights": 949 do_check_rights = True 950 951 shares = self.sessionsManager.current_session.list_shares() 952 if len(shares.keys()) != 0: 953 table = Table(title=None) 954 table.add_column("Share") 955 table.add_column("Visibility") 956 table.add_column("Type") 957 table.add_column("Description", justify="left") 958 if do_check_rights: 959 table.add_column("Rights") 960 961 for sharename in sorted(shares.keys()): 962 types = ', '.join([s.replace("STYPE_","") for s in shares[sharename]["type"]]) 963 964 is_hidden = bool(sharename.endswith('$')) 965 if is_hidden: 966 str_hidden = "[bold bright_blue]Hidden[/bold bright_blue]" 967 str_sharename = "[bold bright_blue]" + shares[sharename]["name"] + "[/bold bright_blue]" 968 str_types = "[bold bright_blue]" + types + "[/bold bright_blue]" 969 str_comment = "[bold bright_blue]" + shares[sharename]["comment"] + "[/bold bright_blue]" 970 else: 971 str_hidden = "[bold bright_yellow]Visible[/bold bright_yellow]" 972 str_sharename = "[bold bright_yellow]" + shares[sharename]["name"] + "[/bold bright_yellow]" 973 str_types = "[bold bright_yellow]" + types + "[/bold bright_yellow]" 974 str_comment = "[bold bright_yellow]" + shares[sharename]["comment"] + "[/bold bright_yellow]" 975 976 if do_check_rights: 977 access_rights = self.sessionsManager.current_session.test_rights(sharename=shares[sharename]["name"]) 978 str_access_rights = "[bold yellow]NO ACCESS[/bold yellow]" 979 if access_rights["readable"] and access_rights["writable"]: 980 str_access_rights = "[bold green]READ[/bold green], [bold red]WRITE[/bold red]" 981 elif access_rights["readable"]: 982 str_access_rights = "[bold green]READ[/bold green]" 983 elif access_rights["writable"]: 984 # Without READ?? This should not happen IMHO 985 str_access_rights = "[bold red]WRITE[/bold red]" 986 else: 987 str_access_rights = "[bold yellow]NO ACCESS[/bold yellow]" 988 989 if do_check_rights: 990 table.add_row(str_sharename, str_hidden, str_types, str_comment, str_access_rights) 991 else: 992 table.add_row(str_sharename, str_hidden, str_types, str_comment) 993 994 Console().print(table) 995 else: 996 self.logger.error("No share served on '%s'" % self.sessionsManager.current_session.host) 997 998 @active_smb_connection_needed 999 @smb_share_is_set 1000 def command_tree(self, arguments, command): 1001 # Command arguments required : No 1002 # Active SMB connection needed : Yes 1003 # SMB share needed : Yes 1004 1005 if len(arguments) == 0: 1006 self.sessionsManager.current_session.tree(path='.') 1007 else: 1008 self.sessionsManager.current_session.tree(path=arguments[0]) 1009 1010 @command_arguments_required 1011 @active_smb_connection_needed 1012 @smb_share_is_set 1013 def command_umount(self, arguments, command): 1014 # Command arguments required : Yes 1015 # Active SMB connection needed : Yes 1016 # SMB share needed : Yes 1017 1018 local_mount_point = arguments[0] 1019 1020 self.logger.debug("Trying to unmount local mount point '%s'" % (local_mount_point)) 1021 1022 self.sessionsManager.current_session.umount(local_mount_point) 1023 1024 @command_arguments_required 1025 @active_smb_connection_needed 1026 def command_use(self, arguments, command): 1027 # Command arguments required : Yes 1028 # Active SMB connection needed : Yes 1029 # SMB share needed : No 1030 1031 sharename = arguments[0] 1032 1033 # Reload the list of shares 1034 shares = self.sessionsManager.current_session.list_shares() 1035 shares = [s.lower() for s in shares.keys()] 1036 1037 if sharename.lower() in shares: 1038 self.sessionsManager.current_session.set_share(sharename) 1039 else: 1040 self.logger.error("No share named '%s' on '%s'" % (sharename, self.sessionsManager.current_session.host)) 1041 1042 # Private functions ======================================================= 1043 1044 def __load_modules(self): 1045 """ 1046 Dynamically loads all Python modules from the 'modules' directory and stores them in the 'modules' dictionary. 1047 Each module is expected to be a Python file that contains a class with the same name as the file (minus the .py extension). 1048 The class must have at least two attributes: 'name' and 'description'. 1049 1050 This method clears any previously loaded modules, constructs the path to the modules directory, and iterates over 1051 each file in that directory. If the file is a Python file (ends with .py and is not '__init__.py'), it attempts to 1052 import the module and access the class within it to add to the 'modules' dictionary. 1053 1054 If debug mode is enabled in the configuration, it prints debug information about the loading process and the loaded modules. 1055 """ 1056 1057 self.modules.clear() 1058 1059 modules_dir = os.path.normpath(os.path.dirname(__file__) + os.path.sep + ".." + os.path.sep + "modules") 1060 self.logger.debug("Loading modules from %s ..." % modules_dir) 1061 sys.path.extend([modules_dir]) 1062 1063 for file in os.listdir(modules_dir): 1064 filepath = os.path.normpath(modules_dir + os.path.sep + file) 1065 if file.endswith('.py'): 1066 if os.path.isfile(filepath) and file not in ["__init__.py"]: 1067 try: 1068 module_file = import_module('smbclientng.modules.%s' % (file[:-3])) 1069 module = module_file.__getattribute__(file[:-3]) 1070 self.modules[module.name.lower()] = module 1071 except AttributeError as e: 1072 pass 1073 1074 if self.config.debug: 1075 if len(self.modules.keys()) == 0: 1076 self.logger.debug("Loaded 0 modules.") 1077 elif len(self.modules.keys()) == 1: 1078 self.logger.debug("Loaded 1 module:") 1079 else: 1080 self.logger.debug("Loaded %d modules:" % len(self.modules.keys())) 1081 for modulename in sorted(self.modules.keys()): 1082 self.logger.debug("%s : \"%s\" (%s)" % (self.modules[modulename].name, self.modules[modulename].description, self.modules[modulename])) 1083 1084 if self.commandCompleterObject is not None: 1085 self.commandCompleterObject.commands["module"]["subcommands"] = list(self.modules.keys()) 1086 1087 def __prompt(self): 1088 """ 1089 Prints the command prompt for the interactive shell. 1090 1091 This method constructs and returns the command prompt string based on the current state of the SMB session. 1092 The prompt indicates the connection status with a visual symbol and displays the current working directory 1093 or the SMB share path. The prompt appearance changes based on whether colors are enabled in the configuration. 1094 1095 Returns: 1096 str: The formatted command prompt string. 1097 """ 1098 1099 # A session exists 1100 if self.sessionsManager.current_session is not None: 1101 # Check if the session is still active 1102 self.sessionsManager.current_session.ping_smb_session() 1103 if self.sessionsManager.current_session.connected: 1104 if self.config.no_colors: 1105 connected_dot = "[v]" 1106 else: 1107 connected_dot = "\x1b[1;92m⏺\x1b[0m" 1108 else: 1109 if self.config.no_colors: 1110 connected_dot = "[x]" 1111 else: 1112 connected_dot = "\x1b[1;91m⏺\x1b[0m" 1113 1114 # Session ID if 1115 session_prompt = "" 1116 if len(self.sessionsManager.sessions.keys()) >= 2: 1117 session_prompt = "[#%d]" % self.sessionsManager.current_session_id 1118 1119 # No share set yet 1120 if self.sessionsManager.current_session.smb_share is None: 1121 str_path = "\\\\%s\\" % self.sessionsManager.current_session.host 1122 # A share is set 1123 else: 1124 if len(self.sessionsManager.current_session.smb_cwd) == 0: 1125 current_path = "" 1126 else: 1127 current_path = self.sessionsManager.current_session.smb_cwd.strip(ntpath.sep) + ntpath.sep 1128 1129 str_path = "\\\\%s\\%s\\%s" % (self.sessionsManager.current_session.host, self.sessionsManager.current_session.smb_share, current_path) 1130 # No active session 1131 else: 1132 connected_dot = "" 1133 session_prompt = "" 1134 str_path = "No active session" 1135 1136 # Build final prompt string 1137 if self.config.no_colors: 1138 str_prompt = "%s%s[%s]> " % (connected_dot, session_prompt, str_path) 1139 else: 1140 str_prompt = "%s%s[\x1b[1;94m%s\x1b[0m]> " % (connected_dot, session_prompt, str_path) 1141 1142 return str_prompt
39def active_smb_connection_needed(func): 40 def wrapper(*args, **kwargs): 41 self, arguments,command = args[0], args[1], args[2] 42 # 43 self.sessionsManager.current_session.ping_smb_session() 44 if self.sessionsManager.current_session.connected: 45 return func(*args, **kwargs) 46 else: 47 print("[!] SMB Session is disconnected.") 48 return None 49 return wrapper
62class InteractiveShell(object): 63 """ 64 Class InteractiveShell is designed to manage the interactive command line interface for smbclient-ng. 65 66 This class handles user input, executes commands, and manages the state of the SMB session. It provides 67 a command line interface for users to interact with SMB shares, execute commands like directory listing, 68 file transfer, and more. 69 70 Attributes: 71 smbSession (SMBConnection): The active SMB connection session. 72 debug (bool): Flag to enable or disable debug mode. 73 smb_share (str): The current SMB share in use. 74 smb_path (str): The current path within the SMB share. 75 commandCompleterObject (CommandCompleter): Object to handle command completion and help generation. 76 77 Methods: 78 __init__(self, smbSession, debug=False): Initializes the InteractiveShell with the given SMB session and debug mode. 79 run(self): Starts the command line interface loop, processing user input until exit. 80 """ 81 82 running = True 83 modules = {} 84 85 def __init__(self, sessionsManager, config, logger): 86 # Objects 87 self.sessionsManager = sessionsManager 88 self.config = config 89 self.logger = logger 90 # Internals 91 self.commandCompleterObject = CommandCompleter( 92 smbSession=self.sessionsManager.current_session, 93 config=self.config, 94 logger=self.logger, 95 ) 96 readline.set_completer(self.commandCompleterObject.complete) 97 readline.parse_and_bind("tab: complete") 98 readline.set_completer_delims("\n") 99 # Additional modules 100 self.__load_modules() 101 102 def run(self): 103 # Read commands from script file first 104 if self.config.startup_script: 105 f = open(self.config.startup_script, 'r') 106 for line in f.readlines(): 107 try: 108 self.logger.print("%s%s" % (self.__prompt(), line.strip())) 109 readline.add_history(line.strip()) 110 self.process_line(commandLine=line.strip()) 111 except KeyboardInterrupt as e: 112 self.logger.print() 113 114 except EOFError as e: 115 self.logger.print() 116 running = False 117 118 except Exception as err: 119 if self.config.debug: 120 traceback.print_exc() 121 self.logger.error(str(err)) 122 123 # Then interactive console 124 if not self.config.not_interactive: 125 while self.running: 126 try: 127 user_input = input(self.__prompt()).strip() 128 self.process_line(commandLine=user_input) 129 except KeyboardInterrupt as e: 130 self.logger.print() 131 132 except EOFError as e: 133 self.logger.print() 134 running = False 135 136 except Exception as err: 137 if self.config.debug: 138 traceback.print_exc() 139 self.logger.error(str(err)) 140 141 def process_line(self, commandLine): 142 # Split and parse the commandLine 143 tokens = shlex.split(commandLine) 144 if len(tokens) == 0: 145 command = "" 146 arguments = [] 147 elif len(tokens) == 1: 148 command = tokens[0].lower() 149 arguments = [] 150 else: 151 command = tokens[0].lower() 152 arguments = tokens[1:] 153 154 # Skip 155 if command.strip() == "": 156 pass 157 # Execute the command 158 elif command in self.commandCompleterObject.commands.keys(): 159 160 # Exit the command line 161 if command in ["exit", "quit"]: 162 self.running = False 163 164 # Display help 165 elif command == "help": 166 self.command_help(arguments, command) 167 168 # Cat the contents of a file 169 elif command == "bat": 170 self.command_bat(arguments, command) 171 172 # Cat the contents of a file 173 elif command == "cat": 174 self.command_cat(arguments, command) 175 176 # Closes the current SMB session 177 elif command == "close": 178 self.command_close(arguments, command) 179 180 # Change directory in the current share 181 elif command == "cd": 182 self.command_cd(arguments, command) 183 184 # debug 185 elif command == "debug": 186 self.command_debug(arguments, command) 187 188 # Get a file 189 elif command == "get": 190 self.command_get(arguments, command) 191 192 # SMB server info 193 elif command == "info": 194 self.command_info(arguments, command) 195 196 # List directory contents in a share 197 elif command in ["ls", "dir"]: 198 self.command_ls(arguments, command) 199 200 # Creates a new remote directory 201 elif command == "mkdir": 202 self.command_mkdir(arguments, command) 203 204 # Put a file 205 elif command == "put": 206 self.command_put(arguments, command) 207 208 # Shows the content of a local file 209 elif command == "lcat": 210 self.command_lcat(arguments, command) 211 212 # Changes the current local directory 213 elif command == "lcd": 214 self.command_lcd(arguments, command) 215 216 # Creates a copy of a local file 217 elif command == "lcp": 218 self.command_lcp(arguments, command) 219 220 # Pretty prints the content of a local file 221 elif command == "lbat": 222 self.command_lbat(arguments, command) 223 224 # Lists the contents of the current local directory 225 elif command == "lls": 226 self.command_lls(arguments, command) 227 228 # Creates a new local directory 229 elif command == "lmkdir": 230 self.command_lmkdir(arguments, command) 231 232 # Shows the current local directory 233 elif command == "lpwd": 234 self.command_lpwd(arguments, command) 235 236 # Renames a local file 237 elif command == "lrename": 238 self.command_lrename(arguments, command) 239 240 # Removes a local file 241 elif command == "lrm": 242 self.command_lrm(arguments, command) 243 244 # Removes a local directory 245 elif command == "lrmdir": 246 self.command_lrmdir(arguments, command) 247 248 # Shows the current local directory 249 elif command == "ltree": 250 self.command_ltree(arguments, command) 251 252 # Modules 253 elif command == "module": 254 self.command_module(arguments, command) 255 256 # Creates a mount point of the remote share on the local machine 257 elif command == "mount": 258 self.command_mount(arguments, command) 259 260 # Reconnects the current SMB session 261 elif command in ["connect", "reconnect"]: 262 self.command_reconnect(arguments, command) 263 264 # Reset the TTY output 265 elif command == "reset": 266 self.command_reset(arguments, command) 267 268 # Removes a remote file 269 elif command == "rm": 270 self.command_rm(arguments, command) 271 272 # Removes a remote directory 273 elif command == "rmdir": 274 self.command_rmdir(arguments, command) 275 276 # Sessions management 277 elif command == "sessions": 278 self.sessionsManager.process_command_line(arguments) 279 280 # List shares 281 elif command == "sizeof": 282 self.command_sizeof(arguments, command) 283 284 # List shares 285 elif command == "shares": 286 self.command_shares(arguments, command) 287 288 # Displays a tree view of the CWD 289 elif command == "tree": 290 self.command_tree(arguments, command) 291 292 # Use a share 293 elif command == "use": 294 self.command_use(arguments, command) 295 296 # Fallback to unknown command 297 else: 298 self.logger.print("Unknown command. Type \"help\" for help.") 299 300 # Commands ================================================================ 301 302 def command_debug(self, arguments, command): 303 try: 304 self.logger.print("[debug] command = '%s'" % command) 305 self.logger.print("[debug] arguments = %s" % arguments) 306 except Exception as e: 307 traceback.print_exc() 308 309 @command_arguments_required 310 @active_smb_connection_needed 311 @smb_share_is_set 312 def command_bat(self, arguments, command): 313 # Command arguments required : Yes 314 # Active SMB connection needed : Yes 315 # SMB share needed : Yes 316 317 # Parse wildcards 318 files_and_directories = resolve_local_files(arguments) 319 320 for path_to_file in files_and_directories: 321 if self.sessionsManager.current_session.path_isfile(pathFromRoot=path_to_file): 322 # Read the file 323 try: 324 rawcontents = self.sessionsManager.current_session.read_file(path=path_to_file) 325 if rawcontents is not None: 326 encoding = charset_normalizer.detect(rawcontents)["encoding"] 327 if encoding is not None: 328 filecontent = rawcontents.decode(encoding).rstrip() 329 lexer = Syntax.guess_lexer(path=ntpath.basename(path_to_file), code=filecontent) 330 # Some trickery for the files undetected by the lexer 331 if lexer == "default": 332 if '<?xml' in filecontent: 333 lexer = "xml" 334 elif '<html>' in filecontent: 335 lexer = "html" 336 syntax = Syntax(code=filecontent, line_numbers=True, lexer=lexer) 337 if len(files_and_directories) > 1: 338 self.logger.print("\x1b[1;93m[>] %s\x1b[0m" % (path_to_file+' ').ljust(80,'=')) 339 Console().print(syntax) 340 else: 341 self.logger.error("[!] Could not detect charset of '%s'." % path_to_file) 342 except impacket.smbconnection.SessionError as e: 343 self.logger.error("[!] SMB Error: %s" % e) 344 345 @command_arguments_required 346 @active_smb_connection_needed 347 @smb_share_is_set 348 def command_cd(self, arguments, command): 349 # Command arguments required : Yes 350 # Active SMB connection needed : Yes 351 # SMB share needed : Yes 352 353 try: 354 self.sessionsManager.current_session.set_cwd(path=arguments[0]) 355 except impacket.smbconnection.SessionError as e: 356 self.logger.error("[!] SMB Error: %s" % e) 357 358 @command_arguments_required 359 @active_smb_connection_needed 360 @smb_share_is_set 361 def command_cat(self, arguments, command): 362 # Command arguments required : Yes 363 # Active SMB connection needed : Yes 364 # SMB share needed : Yes 365 366 # Parse wildcards 367 files_and_directories = resolve_local_files(arguments) 368 369 for path_to_file in files_and_directories: 370 if self.sessionsManager.current_session.path_isfile(pathFromRoot=path_to_file): 371 # Read the file 372 try: 373 rawcontents = self.sessionsManager.current_session.read_file(path=path_to_file) 374 if rawcontents is not None: 375 encoding = charset_normalizer.detect(rawcontents)["encoding"] 376 if encoding is not None: 377 filecontent = rawcontents.decode(encoding).rstrip() 378 if len(files_and_directories) > 1: 379 self.logger.print("\x1b[1;93m[>] %s\x1b[0m" % (path_to_file+' ').ljust(80,'=')) 380 self.logger.print(filecontent) 381 else: 382 self.logger.error("[!] Could not detect charset of '%s'." % path_to_file) 383 except impacket.smbconnection.SessionError as e: 384 self.logger.error("[!] SMB Error: %s" % e) 385 386 def command_close(self, arguments, command): 387 # Command arguments required : No 388 # Active SMB connection needed : No 389 # SMB share needed : No 390 391 self.sessionsManager.current_session.ping_smb_session() 392 if self.sessionsManager.current_session.connected: 393 self.sessionsManager.current_session.close_smb_session() 394 395 @command_arguments_required 396 @active_smb_connection_needed 397 @smb_share_is_set 398 def command_get(self, arguments, command): 399 # Command arguments required : Yes 400 # Active SMB connection needed : Yes 401 # SMB share needed : Yes 402 403 is_recursive = False 404 while '-r' in arguments: 405 is_recursive = True 406 arguments.remove('-r') 407 408 # Parse wildcards 409 files_and_directories = resolve_remote_files(self.sessionsManager.current_session, arguments) 410 411 # 412 for remotepath in files_and_directories: 413 try: 414 if is_recursive and self.sessionsManager.current_session.path_isdir(remotepath): 415 # Get files recursively 416 self.sessionsManager.current_session.get_file_recursively(path=remotepath) 417 else: 418 # Get this single file 419 self.sessionsManager.current_session.get_file(path=remotepath) 420 except impacket.smbconnection.SessionError as e: 421 self.logger.error("[!] SMB Error: %s" % e) 422 423 def command_help(self, arguments, command): 424 # Command arguments required : No 425 # Active SMB connection needed : No 426 # SMB share needed : No 427 428 if len(arguments) != 0: 429 self.commandCompleterObject.print_help(command=arguments[0]) 430 else: 431 self.commandCompleterObject.print_help(command=None) 432 433 @active_smb_connection_needed 434 def command_info(self, arguments, command): 435 # Command arguments required : No 436 # Active SMB connection needed : Yes 437 # SMB share needed : No 438 439 print_server_info = False 440 print_share_info = False 441 if len(arguments) != 0: 442 print_server_info = (arguments[0].lower() == "server") 443 print_share_info = (arguments[0].lower() == "share") 444 else: 445 print_server_info = True 446 print_share_info = True 447 448 try: 449 self.sessionsManager.current_session.info( 450 share=print_share_info, 451 server=print_server_info 452 ) 453 except impacket.smbconnection.SessionError as e: 454 self.logger.error("[!] SMB Error: %s" % e) 455 456 @command_arguments_required 457 def command_lbat(self, arguments, command): 458 # Command arguments required : Yes 459 # Active SMB connection needed : No 460 # SMB share needed : No 461 462 # Parse wildcards 463 files_and_directories = resolve_remote_files(self.sessionsManager.current_session, arguments) 464 465 for path_to_file in files_and_directories: 466 # Read the file 467 try: 468 if os.path.exists(path=path_to_file): 469 f = open(path_to_file, 'rb') 470 rawcontents = f.read() 471 # 472 if rawcontents is not None: 473 encoding = charset_normalizer.detect(rawcontents)["encoding"] 474 if encoding is not None: 475 filecontent = rawcontents.decode(encoding).rstrip() 476 lexer = Syntax.guess_lexer(path=ntpath.basename(path_to_file), code=filecontent) 477 # Some trickery for the files undetected by the lexer 478 if lexer == "default": 479 if '<?xml' in filecontent: 480 lexer = "xml" 481 elif '<html>' in filecontent: 482 lexer = "html" 483 syntax = Syntax(code=filecontent, line_numbers=True, lexer=lexer) 484 if len(files_and_directories) > 1: 485 self.logger.print("\x1b[1;93m[>] %s\x1b[0m" % (path_to_file+' ').ljust(80,'=')) 486 Console().print(syntax) 487 else: 488 self.logger.error("[!] Could not detect charset of '%s'." % path_to_file) 489 else: 490 self.logger.error("[!] Local file '%s' does not exist." % path_to_file) 491 except impacket.smbconnection.SessionError as e: 492 self.logger.error("[!] SMB Error: %s" % e) 493 494 @command_arguments_required 495 def command_lcat(self, arguments, command): 496 # Command arguments required : Yes 497 # Active SMB connection needed : No 498 # SMB share needed : No 499 500 # Parse wildcards 501 files_and_directories = resolve_remote_files(self.sessionsManager.current_session, arguments) 502 503 for path_to_file in files_and_directories: 504 # Read the file 505 try: 506 if os.path.exists(path=path_to_file): 507 f = open(path_to_file, 'rb') 508 rawcontents = f.read() 509 # 510 if rawcontents is not None: 511 encoding = charset_normalizer.detect(rawcontents)["encoding"] 512 if encoding is not None: 513 filecontent = rawcontents.decode(encoding).rstrip() 514 if len(files_and_directories) > 1: 515 self.logger.print("\x1b[1;93m[>] %s\x1b[0m" % (path_to_file+' ').ljust(80,'=')) 516 self.logger.print(filecontent) 517 else: 518 self.logger.error("[!] Could not detect charset of '%s'." % path_to_file) 519 else: 520 self.logger.error("[!] Local file '%s' does not exist." % path_to_file) 521 except impacket.smbconnection.SessionError as e: 522 self.logger.error("[!] SMB Error: %s" % e) 523 524 @command_arguments_required 525 def command_lcd(self, arguments, command): 526 # Command arguments required : Yes 527 # Active SMB connection needed : No 528 # SMB share needed : No 529 530 path = arguments[0] 531 532 if os.path.exists(path=path): 533 if os.path.isdir(s=path): 534 os.chdir(path=path) 535 else: 536 self.logger.error("Path '%s' is not a directory." % path) 537 else: 538 self.logger.error("Directory '%s' does not exists." % path) 539 540 @command_arguments_required 541 def command_lcp(self, arguments, command): 542 # Command arguments required : Yes 543 # Active SMB connection needed : No 544 # SMB share needed : No 545 546 if len(arguments) == 2: 547 src_path = arguments[0] 548 dst_path = arguments[1] 549 if os.path.exists(path=src_path): 550 try: 551 shutil.copyfile(src=src_path, dst=dst_path) 552 except shutil.SameFileError as err: 553 self.logger.error("[!] Error: %s" % err) 554 else: 555 self.logger.error("[!] File '%s' does not exists." % src_path) 556 else: 557 self.commandCompleterObject.print_help(command=command) 558 559 def command_lls(self, arguments, command): 560 # Command arguments required : No 561 # Active SMB connection needed : No 562 # SMB share needed : No 563 564 if len(arguments) == 0: 565 arguments = ['.'] 566 else: 567 arguments = resolve_local_files(arguments) 568 569 for path in arguments: 570 if len(arguments) > 1: 571 self.logger.print("%s:" % path) 572 # lls <directory> 573 if os.path.isdir(path): 574 directory_contents = os.listdir(path=path) 575 for entryname in sorted(directory_contents): 576 path_to_file = path + os.path.sep + entryname 577 rights_str = unix_permissions(path_to_file) 578 size_str = b_filesize(os.path.getsize(filename=path_to_file)) 579 date_str = datetime.datetime.fromtimestamp(os.path.getmtime(filename=path_to_file)).strftime("%Y-%m-%d %H:%M") 580 581 if os.path.isdir(s=entryname): 582 if self.config.no_colors: 583 self.logger.print("%s %10s %s %s%s" % (rights_str, size_str, date_str, entryname, os.path.sep)) 584 else: 585 self.logger.print("%s %10s %s \x1b[1;96m%s\x1b[0m%s" % (rights_str, size_str, date_str, entryname, os.path.sep)) 586 else: 587 if self.config.no_colors: 588 self.logger.print("%s %10s %s %s" % (rights_str, size_str, date_str, entryname)) 589 else: 590 self.logger.print("%s %10s %s \x1b[1m%s\x1b[0m" % (rights_str, size_str, date_str, entryname)) 591 # lls <file> 592 elif os.path.isfile(path): 593 rights_str = unix_permissions(path) 594 size_str = b_filesize(os.path.getsize(filename=path)) 595 date_str = datetime.datetime.fromtimestamp(os.path.getmtime(filename=path)).strftime("%Y-%m-%d %H:%M") 596 if self.config.no_colors: 597 self.logger.print("%s %10s %s %s" % (rights_str, size_str, date_str, os.path.basename(path))) 598 else: 599 self.logger.print("%s %10s %s \x1b[1m%s\x1b[0m" % (rights_str, size_str, date_str, os.path.basename(path))) 600 601 if len(arguments) > 1: 602 self.logger.print() 603 604 @command_arguments_required 605 def command_lmkdir(self, arguments, command): 606 # Command arguments required : Yes 607 # Active SMB connection needed : No 608 # SMB share needed : No 609 610 for path in arguments: 611 if os.path.sep in path: 612 path = path.strip(os.path.sep).split(os.path.sep) 613 else: 614 path = [path] 615 616 # Create each dir in the path 617 for depth in range(1, len(path)+1): 618 tmp_path = os.path.sep.join(path[:depth]) 619 if not os.path.exists(tmp_path): 620 os.mkdir(path=tmp_path) 621 622 def command_lpwd(self, arguments, command): 623 # Command arguments required : No 624 # Active SMB connection needed : No 625 # SMB share needed : No 626 627 self.logger.print(os.getcwd()) 628 629 @command_arguments_required 630 def command_lrename(self, arguments, command): 631 # Command arguments required : Yes 632 # Active SMB connection needed : No 633 # SMB share needed : No 634 635 if len(arguments) == 2: 636 os.rename(src=arguments[0], dst=arguments[1]) 637 else: 638 self.commandCompleterObject.print_help(command=command) 639 640 @command_arguments_required 641 def command_lrm(self, arguments, command): 642 # Command arguments required : Yes 643 # Active SMB connection needed : No 644 # SMB share needed : No 645 646 path = arguments[0] 647 648 if os.path.exists(path): 649 if not os.path.isdir(s=path): 650 try: 651 os.remove(path=path) 652 except Exception as e: 653 self.logger.error("Error removing file '%s' : %s" % path) 654 else: 655 self.logger.error("Cannot delete '%s'. It is a directory, use 'lrmdir <directory>' instead." % path) 656 else: 657 self.logger.error("Path '%s' does not exist." % path) 658 659 @command_arguments_required 660 def command_lrmdir(self, arguments, command): 661 # Command arguments required : Yes 662 # Active SMB connection needed : No 663 # SMB share needed : No 664 665 if len(arguments) == 0: 666 path = '.' 667 else: 668 path = arguments[0] 669 670 if os.path.exists(path): 671 if os.path.isdir(s=path): 672 try: 673 shutil.rmtree(path=path) 674 except Exception as e: 675 self.logger.error("Error removing directory '%s' : %s" % path) 676 else: 677 self.logger.error("Cannot delete '%s'. It is a file, use 'lrm <file>' instead." % path) 678 else: 679 self.logger.error("Path '%s' does not exist." % path) 680 681 def command_ltree(self, arguments, command): 682 # Command arguments required : No 683 # Active SMB connection needed : No 684 # SMB share needed : No 685 686 if len(arguments) == 0: 687 path = '.' 688 else: 689 path = arguments[0] 690 691 if len(arguments) == 0: 692 local_tree(path='.', config=self.config) 693 else: 694 local_tree(path=path, config=self.config) 695 696 @active_smb_connection_needed 697 @smb_share_is_set 698 def command_ls(self, arguments, command): 699 # Command arguments required : No 700 # Active SMB connection needed : Yes 701 # SMB share needed : Yes 702 703 if len(arguments) == 0: 704 arguments = ['.'] 705 else: 706 arguments = resolve_remote_files(self.sessionsManager.current_session, arguments) 707 708 for path in arguments: 709 if len(arguments) > 1: 710 self.logger.print("%s:" % path) 711 712 if self.sessionsManager.current_session.path_isdir(pathFromRoot=path): 713 # Read the files 714 directory_contents = self.sessionsManager.current_session.list_contents(path=path) 715 else: 716 entry = self.sessionsManager.current_session.get_entry(path=path) 717 if entry is not None: 718 directory_contents = {entry.get_longname(): entry} 719 else: 720 directory_contents = {} 721 722 for longname in sorted(directory_contents.keys(), key=lambda x:x.lower()): 723 windows_ls_entry(directory_contents[longname], self.config) 724 725 if len(arguments) > 1: 726 self.logger.print() 727 728 @command_arguments_required 729 @active_smb_connection_needed 730 @smb_share_is_set 731 def command_mkdir(self, arguments, command): 732 # Command arguments required : Yes 733 # Active SMB connection needed : Yes 734 # SMB share needed : Yes 735 736 self.sessionsManager.current_session.mkdir(path=arguments[0]) 737 738 @command_arguments_required 739 @active_smb_connection_needed 740 @smb_share_is_set 741 def command_module(self, arguments, command): 742 module_name = arguments[0] 743 744 if module_name in self.modules.keys(): 745 module = self.modules[module_name](self.sessionsManager.current_session, self.config, self.logger) 746 arguments_string = ' '.join(arguments[1:]) 747 module.run(arguments_string) 748 else: 749 self.logger.error("Module '%s' does not exist." % module_name) 750 751 @command_arguments_required 752 @active_smb_connection_needed 753 @smb_share_is_set 754 def command_mount(self, arguments, command): 755 # Command arguments required : Yes 756 # Active SMB connection needed : Yes 757 # SMB share needed : Yes 758 759 if len(arguments) == 2: 760 remote_path = arguments[0] 761 if not remote_path.startswith(ntpath.sep): 762 remote_path = self.sessionsManager.current_session.smb_cwd + ntpath.sep + remote_path 763 764 local_mount_point = arguments[1] 765 766 self.logger.debug("Trying to mount remote '%s' onto local '%s'" % (remote_path, local_mount_point)) 767 768 try: 769 self.sessionsManager.current_session.mount(local_mount_point, remote_path) 770 except (impacket.smbconnection.SessionError, impacket.smb3.SessionError) as e: 771 self.sessionsManager.current_session.umount(local_mount_point) 772 else: 773 self.commandCompleterObject.print_help(command=command) 774 775 @command_arguments_required 776 @active_smb_connection_needed 777 @smb_share_is_set 778 def command_put(self, arguments, command): 779 # Command arguments required : Yes 780 # Active SMB connection needed : Yes 781 # SMB share needed : Yes 782 783 is_recursive = False 784 while '-r' in arguments: 785 is_recursive = True 786 arguments.remove('-r') 787 788 # Parse wildcards 789 files_and_directories = resolve_local_files(arguments) 790 791 # 792 for localpath in files_and_directories: 793 try: 794 self.logger.print(localpath) 795 if is_recursive and os.path.isdir(s=localpath): 796 # Put files recursively 797 self.sessionsManager.current_session.put_file_recursively(localpath=localpath) 798 else: 799 # Put this single file 800 self.sessionsManager.current_session.put_file(localpath=localpath) 801 except impacket.smbconnection.SessionError as e: 802 self.logger.error("[!] SMB Error: %s" % e) 803 804 def command_reconnect(self, arguments, command): 805 # Command arguments required : No 806 # Active SMB connection needed : No 807 # SMB share needed : No 808 809 self.sessionsManager.current_session.ping_smb_session() 810 if self.sessionsManager.current_session.connected: 811 self.sessionsManager.current_session.close_smb_session() 812 self.sessionsManager.current_session.init_smb_session() 813 else: 814 self.sessionsManager.current_session.init_smb_session() 815 816 def command_reset(self, arguments, command): 817 # Command arguments required : No 818 # Active SMB connection needed : No 819 # SMB share needed : No 820 sys.stdout.write('\x1b[?25h') # Sets the cursor to on 821 sys.stdout.write('\x1b[v') 822 sys.stdout.write('\x1b[o') # Reset 823 sys.stdout.flush() 824 825 @command_arguments_required 826 @active_smb_connection_needed 827 @smb_share_is_set 828 def command_rm(self, arguments, command): 829 # Command arguments required : Yes 830 # Active SMB connection needed : Yes 831 # SMB share needed : Yes 832 833 for path_to_file in arguments: 834 # Wildcard 835 if '*' in path_to_file: 836 self.sessionsManager.current_session.rm(path=path_to_file) 837 # File 838 elif self.sessionsManager.current_session.path_exists(path_to_file): 839 if self.sessionsManager.current_session.path_isfile(path_to_file): 840 try: 841 self.sessionsManager.current_session.rm(path=path_to_file) 842 except Exception as e: 843 self.logger.error("Error removing file '%s' : %s" % path_to_file) 844 else: 845 self.logger.error("Cannot delete '%s': This is a directory, use 'rmdir <directory>' instead." % path_to_file) 846 # File does not exist 847 else: 848 self.logger.error("Remote file '%s' does not exist." % path_to_file) 849 850 @command_arguments_required 851 @active_smb_connection_needed 852 @smb_share_is_set 853 def command_rmdir(self, arguments, command): 854 # Command arguments required : Yes 855 # Active SMB connection needed : Yes 856 # SMB share needed : Yes 857 858 for path_to_directory in arguments: 859 if self.sessionsManager.current_session.path_exists(path_to_directory): 860 if self.sessionsManager.current_session.path_isdir(path_to_directory): 861 try: 862 self.sessionsManager.current_session.rmdir(path=path_to_directory) 863 except Exception as e: 864 self.logger.error("Error removing directory '%s' : %s" % path_to_directory) 865 else: 866 self.logger.error("Cannot delete '%s': This is a file, use 'rm <file>' instead." % path_to_directory) 867 else: 868 self.logger.error("Remote directory '%s' does not exist." % path_to_directory) 869 870 @active_smb_connection_needed 871 @smb_share_is_set 872 def command_sizeof(self, arguments, command): 873 # Command arguments required : Yes 874 # Active SMB connection needed : Yes 875 # SMB share needed : Yes 876 877 class RecursiveSizeOfPrint(object): 878 def __init__(self, entry, sessionsManager, config, logger): 879 self.entry = entry 880 self.config = config 881 self.logger = logger 882 self.sessionsManager = sessionsManager 883 self.size = 0 884 885 def update(self, entry, fullpath, depth): 886 self.size += entry.get_filesize() 887 self.print(end='\r') 888 889 def print(self, end='\n'): 890 # Directory 891 if self.entry.is_directory(): 892 if self.config.no_colors: 893 path = "%s\\" % self.entry.get_longname() 894 else: 895 path = "\x1b[1;96m%s\x1b[0m\\" % self.entry.get_longname() 896 # File 897 else: 898 if self.config.no_colors: 899 path = "%s" % self.entry.get_longname() 900 else: 901 path = "\x1b[1m%s\x1b[0m" % self.entry.get_longname() 902 self.logger.print("%10s %s" % (b_filesize(self.size), path), end=end) 903 904 entries = [] 905 if len(arguments) == 0: 906 entries = self.sessionsManager.current_session.list_contents() 907 entries = [entry for name, entry in entries.items() if name not in ['.', '..']] 908 else: 909 entry = self.sessionsManager.current_session.get_entry(path=' '.join(arguments)) 910 entries = [] 911 if entry is not None: 912 entries = [entry] 913 else: 914 self.logger.print("[!] Path '%s' does not exist." % ' '.join(arguments)) 915 916 total = 0 917 for entry in entries: 918 rsop = RecursiveSizeOfPrint( 919 entry=entry, 920 sessionsManager=self.sessionsManager, 921 config=self.config, 922 logger=self.logger 923 ) 924 # Directory 925 if entry.is_directory(): 926 self.sessionsManager.current_session.find( 927 paths=[entry.get_longname()], 928 callback=rsop.update 929 ) 930 # File 931 else: 932 rsop.update(entry=entry, fullpath=entry.get_longname(), depth=0) 933 # Close the print 934 rsop.print() 935 total += rsop.size 936 937 if len(entries) > 1: 938 self.logger.print("──────────────────────") 939 self.logger.print(" total %s" % b_filesize(total)) 940 941 @active_smb_connection_needed 942 def command_shares(self, arguments, command): 943 # Command arguments required : No 944 # Active SMB connection needed : Yes 945 # SMB share needed : No 946 947 do_check_rights = False 948 if len(arguments) != 0: 949 if arguments[0] == "rights": 950 do_check_rights = True 951 952 shares = self.sessionsManager.current_session.list_shares() 953 if len(shares.keys()) != 0: 954 table = Table(title=None) 955 table.add_column("Share") 956 table.add_column("Visibility") 957 table.add_column("Type") 958 table.add_column("Description", justify="left") 959 if do_check_rights: 960 table.add_column("Rights") 961 962 for sharename in sorted(shares.keys()): 963 types = ', '.join([s.replace("STYPE_","") for s in shares[sharename]["type"]]) 964 965 is_hidden = bool(sharename.endswith('$')) 966 if is_hidden: 967 str_hidden = "[bold bright_blue]Hidden[/bold bright_blue]" 968 str_sharename = "[bold bright_blue]" + shares[sharename]["name"] + "[/bold bright_blue]" 969 str_types = "[bold bright_blue]" + types + "[/bold bright_blue]" 970 str_comment = "[bold bright_blue]" + shares[sharename]["comment"] + "[/bold bright_blue]" 971 else: 972 str_hidden = "[bold bright_yellow]Visible[/bold bright_yellow]" 973 str_sharename = "[bold bright_yellow]" + shares[sharename]["name"] + "[/bold bright_yellow]" 974 str_types = "[bold bright_yellow]" + types + "[/bold bright_yellow]" 975 str_comment = "[bold bright_yellow]" + shares[sharename]["comment"] + "[/bold bright_yellow]" 976 977 if do_check_rights: 978 access_rights = self.sessionsManager.current_session.test_rights(sharename=shares[sharename]["name"]) 979 str_access_rights = "[bold yellow]NO ACCESS[/bold yellow]" 980 if access_rights["readable"] and access_rights["writable"]: 981 str_access_rights = "[bold green]READ[/bold green], [bold red]WRITE[/bold red]" 982 elif access_rights["readable"]: 983 str_access_rights = "[bold green]READ[/bold green]" 984 elif access_rights["writable"]: 985 # Without READ?? This should not happen IMHO 986 str_access_rights = "[bold red]WRITE[/bold red]" 987 else: 988 str_access_rights = "[bold yellow]NO ACCESS[/bold yellow]" 989 990 if do_check_rights: 991 table.add_row(str_sharename, str_hidden, str_types, str_comment, str_access_rights) 992 else: 993 table.add_row(str_sharename, str_hidden, str_types, str_comment) 994 995 Console().print(table) 996 else: 997 self.logger.error("No share served on '%s'" % self.sessionsManager.current_session.host) 998 999 @active_smb_connection_needed 1000 @smb_share_is_set 1001 def command_tree(self, arguments, command): 1002 # Command arguments required : No 1003 # Active SMB connection needed : Yes 1004 # SMB share needed : Yes 1005 1006 if len(arguments) == 0: 1007 self.sessionsManager.current_session.tree(path='.') 1008 else: 1009 self.sessionsManager.current_session.tree(path=arguments[0]) 1010 1011 @command_arguments_required 1012 @active_smb_connection_needed 1013 @smb_share_is_set 1014 def command_umount(self, arguments, command): 1015 # Command arguments required : Yes 1016 # Active SMB connection needed : Yes 1017 # SMB share needed : Yes 1018 1019 local_mount_point = arguments[0] 1020 1021 self.logger.debug("Trying to unmount local mount point '%s'" % (local_mount_point)) 1022 1023 self.sessionsManager.current_session.umount(local_mount_point) 1024 1025 @command_arguments_required 1026 @active_smb_connection_needed 1027 def command_use(self, arguments, command): 1028 # Command arguments required : Yes 1029 # Active SMB connection needed : Yes 1030 # SMB share needed : No 1031 1032 sharename = arguments[0] 1033 1034 # Reload the list of shares 1035 shares = self.sessionsManager.current_session.list_shares() 1036 shares = [s.lower() for s in shares.keys()] 1037 1038 if sharename.lower() in shares: 1039 self.sessionsManager.current_session.set_share(sharename) 1040 else: 1041 self.logger.error("No share named '%s' on '%s'" % (sharename, self.sessionsManager.current_session.host)) 1042 1043 # Private functions ======================================================= 1044 1045 def __load_modules(self): 1046 """ 1047 Dynamically loads all Python modules from the 'modules' directory and stores them in the 'modules' dictionary. 1048 Each module is expected to be a Python file that contains a class with the same name as the file (minus the .py extension). 1049 The class must have at least two attributes: 'name' and 'description'. 1050 1051 This method clears any previously loaded modules, constructs the path to the modules directory, and iterates over 1052 each file in that directory. If the file is a Python file (ends with .py and is not '__init__.py'), it attempts to 1053 import the module and access the class within it to add to the 'modules' dictionary. 1054 1055 If debug mode is enabled in the configuration, it prints debug information about the loading process and the loaded modules. 1056 """ 1057 1058 self.modules.clear() 1059 1060 modules_dir = os.path.normpath(os.path.dirname(__file__) + os.path.sep + ".." + os.path.sep + "modules") 1061 self.logger.debug("Loading modules from %s ..." % modules_dir) 1062 sys.path.extend([modules_dir]) 1063 1064 for file in os.listdir(modules_dir): 1065 filepath = os.path.normpath(modules_dir + os.path.sep + file) 1066 if file.endswith('.py'): 1067 if os.path.isfile(filepath) and file not in ["__init__.py"]: 1068 try: 1069 module_file = import_module('smbclientng.modules.%s' % (file[:-3])) 1070 module = module_file.__getattribute__(file[:-3]) 1071 self.modules[module.name.lower()] = module 1072 except AttributeError as e: 1073 pass 1074 1075 if self.config.debug: 1076 if len(self.modules.keys()) == 0: 1077 self.logger.debug("Loaded 0 modules.") 1078 elif len(self.modules.keys()) == 1: 1079 self.logger.debug("Loaded 1 module:") 1080 else: 1081 self.logger.debug("Loaded %d modules:" % len(self.modules.keys())) 1082 for modulename in sorted(self.modules.keys()): 1083 self.logger.debug("%s : \"%s\" (%s)" % (self.modules[modulename].name, self.modules[modulename].description, self.modules[modulename])) 1084 1085 if self.commandCompleterObject is not None: 1086 self.commandCompleterObject.commands["module"]["subcommands"] = list(self.modules.keys()) 1087 1088 def __prompt(self): 1089 """ 1090 Prints the command prompt for the interactive shell. 1091 1092 This method constructs and returns the command prompt string based on the current state of the SMB session. 1093 The prompt indicates the connection status with a visual symbol and displays the current working directory 1094 or the SMB share path. The prompt appearance changes based on whether colors are enabled in the configuration. 1095 1096 Returns: 1097 str: The formatted command prompt string. 1098 """ 1099 1100 # A session exists 1101 if self.sessionsManager.current_session is not None: 1102 # Check if the session is still active 1103 self.sessionsManager.current_session.ping_smb_session() 1104 if self.sessionsManager.current_session.connected: 1105 if self.config.no_colors: 1106 connected_dot = "[v]" 1107 else: 1108 connected_dot = "\x1b[1;92m⏺\x1b[0m" 1109 else: 1110 if self.config.no_colors: 1111 connected_dot = "[x]" 1112 else: 1113 connected_dot = "\x1b[1;91m⏺\x1b[0m" 1114 1115 # Session ID if 1116 session_prompt = "" 1117 if len(self.sessionsManager.sessions.keys()) >= 2: 1118 session_prompt = "[#%d]" % self.sessionsManager.current_session_id 1119 1120 # No share set yet 1121 if self.sessionsManager.current_session.smb_share is None: 1122 str_path = "\\\\%s\\" % self.sessionsManager.current_session.host 1123 # A share is set 1124 else: 1125 if len(self.sessionsManager.current_session.smb_cwd) == 0: 1126 current_path = "" 1127 else: 1128 current_path = self.sessionsManager.current_session.smb_cwd.strip(ntpath.sep) + ntpath.sep 1129 1130 str_path = "\\\\%s\\%s\\%s" % (self.sessionsManager.current_session.host, self.sessionsManager.current_session.smb_share, current_path) 1131 # No active session 1132 else: 1133 connected_dot = "" 1134 session_prompt = "" 1135 str_path = "No active session" 1136 1137 # Build final prompt string 1138 if self.config.no_colors: 1139 str_prompt = "%s%s[%s]> " % (connected_dot, session_prompt, str_path) 1140 else: 1141 str_prompt = "%s%s[\x1b[1;94m%s\x1b[0m]> " % (connected_dot, session_prompt, str_path) 1142 1143 return str_prompt
Class InteractiveShell is designed to manage the interactive command line interface for smbclient-ng.
This class handles user input, executes commands, and manages the state of the SMB session. It provides a command line interface for users to interact with SMB shares, execute commands like directory listing, file transfer, and more.
Attributes: smbSession (SMBConnection): The active SMB connection session. debug (bool): Flag to enable or disable debug mode. smb_share (str): The current SMB share in use. smb_path (str): The current path within the SMB share. commandCompleterObject (CommandCompleter): Object to handle command completion and help generation.
Methods: __init__(self, smbSession, debug=False): Initializes the InteractiveShell with the given SMB session and debug mode. run(self): Starts the command line interface loop, processing user input until exit.
85 def __init__(self, sessionsManager, config, logger): 86 # Objects 87 self.sessionsManager = sessionsManager 88 self.config = config 89 self.logger = logger 90 # Internals 91 self.commandCompleterObject = CommandCompleter( 92 smbSession=self.sessionsManager.current_session, 93 config=self.config, 94 logger=self.logger, 95 ) 96 readline.set_completer(self.commandCompleterObject.complete) 97 readline.parse_and_bind("tab: complete") 98 readline.set_completer_delims("\n") 99 # Additional modules 100 self.__load_modules()
102 def run(self): 103 # Read commands from script file first 104 if self.config.startup_script: 105 f = open(self.config.startup_script, 'r') 106 for line in f.readlines(): 107 try: 108 self.logger.print("%s%s" % (self.__prompt(), line.strip())) 109 readline.add_history(line.strip()) 110 self.process_line(commandLine=line.strip()) 111 except KeyboardInterrupt as e: 112 self.logger.print() 113 114 except EOFError as e: 115 self.logger.print() 116 running = False 117 118 except Exception as err: 119 if self.config.debug: 120 traceback.print_exc() 121 self.logger.error(str(err)) 122 123 # Then interactive console 124 if not self.config.not_interactive: 125 while self.running: 126 try: 127 user_input = input(self.__prompt()).strip() 128 self.process_line(commandLine=user_input) 129 except KeyboardInterrupt as e: 130 self.logger.print() 131 132 except EOFError as e: 133 self.logger.print() 134 running = False 135 136 except Exception as err: 137 if self.config.debug: 138 traceback.print_exc() 139 self.logger.error(str(err))
141 def process_line(self, commandLine): 142 # Split and parse the commandLine 143 tokens = shlex.split(commandLine) 144 if len(tokens) == 0: 145 command = "" 146 arguments = [] 147 elif len(tokens) == 1: 148 command = tokens[0].lower() 149 arguments = [] 150 else: 151 command = tokens[0].lower() 152 arguments = tokens[1:] 153 154 # Skip 155 if command.strip() == "": 156 pass 157 # Execute the command 158 elif command in self.commandCompleterObject.commands.keys(): 159 160 # Exit the command line 161 if command in ["exit", "quit"]: 162 self.running = False 163 164 # Display help 165 elif command == "help": 166 self.command_help(arguments, command) 167 168 # Cat the contents of a file 169 elif command == "bat": 170 self.command_bat(arguments, command) 171 172 # Cat the contents of a file 173 elif command == "cat": 174 self.command_cat(arguments, command) 175 176 # Closes the current SMB session 177 elif command == "close": 178 self.command_close(arguments, command) 179 180 # Change directory in the current share 181 elif command == "cd": 182 self.command_cd(arguments, command) 183 184 # debug 185 elif command == "debug": 186 self.command_debug(arguments, command) 187 188 # Get a file 189 elif command == "get": 190 self.command_get(arguments, command) 191 192 # SMB server info 193 elif command == "info": 194 self.command_info(arguments, command) 195 196 # List directory contents in a share 197 elif command in ["ls", "dir"]: 198 self.command_ls(arguments, command) 199 200 # Creates a new remote directory 201 elif command == "mkdir": 202 self.command_mkdir(arguments, command) 203 204 # Put a file 205 elif command == "put": 206 self.command_put(arguments, command) 207 208 # Shows the content of a local file 209 elif command == "lcat": 210 self.command_lcat(arguments, command) 211 212 # Changes the current local directory 213 elif command == "lcd": 214 self.command_lcd(arguments, command) 215 216 # Creates a copy of a local file 217 elif command == "lcp": 218 self.command_lcp(arguments, command) 219 220 # Pretty prints the content of a local file 221 elif command == "lbat": 222 self.command_lbat(arguments, command) 223 224 # Lists the contents of the current local directory 225 elif command == "lls": 226 self.command_lls(arguments, command) 227 228 # Creates a new local directory 229 elif command == "lmkdir": 230 self.command_lmkdir(arguments, command) 231 232 # Shows the current local directory 233 elif command == "lpwd": 234 self.command_lpwd(arguments, command) 235 236 # Renames a local file 237 elif command == "lrename": 238 self.command_lrename(arguments, command) 239 240 # Removes a local file 241 elif command == "lrm": 242 self.command_lrm(arguments, command) 243 244 # Removes a local directory 245 elif command == "lrmdir": 246 self.command_lrmdir(arguments, command) 247 248 # Shows the current local directory 249 elif command == "ltree": 250 self.command_ltree(arguments, command) 251 252 # Modules 253 elif command == "module": 254 self.command_module(arguments, command) 255 256 # Creates a mount point of the remote share on the local machine 257 elif command == "mount": 258 self.command_mount(arguments, command) 259 260 # Reconnects the current SMB session 261 elif command in ["connect", "reconnect"]: 262 self.command_reconnect(arguments, command) 263 264 # Reset the TTY output 265 elif command == "reset": 266 self.command_reset(arguments, command) 267 268 # Removes a remote file 269 elif command == "rm": 270 self.command_rm(arguments, command) 271 272 # Removes a remote directory 273 elif command == "rmdir": 274 self.command_rmdir(arguments, command) 275 276 # Sessions management 277 elif command == "sessions": 278 self.sessionsManager.process_command_line(arguments) 279 280 # List shares 281 elif command == "sizeof": 282 self.command_sizeof(arguments, command) 283 284 # List shares 285 elif command == "shares": 286 self.command_shares(arguments, command) 287 288 # Displays a tree view of the CWD 289 elif command == "tree": 290 self.command_tree(arguments, command) 291 292 # Use a share 293 elif command == "use": 294 self.command_use(arguments, command) 295 296 # Fallback to unknown command 297 else: 298 self.logger.print("Unknown command. Type \"help\" for help.")
386 def command_close(self, arguments, command): 387 # Command arguments required : No 388 # Active SMB connection needed : No 389 # SMB share needed : No 390 391 self.sessionsManager.current_session.ping_smb_session() 392 if self.sessionsManager.current_session.connected: 393 self.sessionsManager.current_session.close_smb_session()
423 def command_help(self, arguments, command): 424 # Command arguments required : No 425 # Active SMB connection needed : No 426 # SMB share needed : No 427 428 if len(arguments) != 0: 429 self.commandCompleterObject.print_help(command=arguments[0]) 430 else: 431 self.commandCompleterObject.print_help(command=None)
40 def wrapper(*args, **kwargs): 41 self, arguments,command = args[0], args[1], args[2] 42 # 43 self.sessionsManager.current_session.ping_smb_session() 44 if self.sessionsManager.current_session.connected: 45 return func(*args, **kwargs) 46 else: 47 print("[!] SMB Session is disconnected.") 48 return None
559 def command_lls(self, arguments, command): 560 # Command arguments required : No 561 # Active SMB connection needed : No 562 # SMB share needed : No 563 564 if len(arguments) == 0: 565 arguments = ['.'] 566 else: 567 arguments = resolve_local_files(arguments) 568 569 for path in arguments: 570 if len(arguments) > 1: 571 self.logger.print("%s:" % path) 572 # lls <directory> 573 if os.path.isdir(path): 574 directory_contents = os.listdir(path=path) 575 for entryname in sorted(directory_contents): 576 path_to_file = path + os.path.sep + entryname 577 rights_str = unix_permissions(path_to_file) 578 size_str = b_filesize(os.path.getsize(filename=path_to_file)) 579 date_str = datetime.datetime.fromtimestamp(os.path.getmtime(filename=path_to_file)).strftime("%Y-%m-%d %H:%M") 580 581 if os.path.isdir(s=entryname): 582 if self.config.no_colors: 583 self.logger.print("%s %10s %s %s%s" % (rights_str, size_str, date_str, entryname, os.path.sep)) 584 else: 585 self.logger.print("%s %10s %s \x1b[1;96m%s\x1b[0m%s" % (rights_str, size_str, date_str, entryname, os.path.sep)) 586 else: 587 if self.config.no_colors: 588 self.logger.print("%s %10s %s %s" % (rights_str, size_str, date_str, entryname)) 589 else: 590 self.logger.print("%s %10s %s \x1b[1m%s\x1b[0m" % (rights_str, size_str, date_str, entryname)) 591 # lls <file> 592 elif os.path.isfile(path): 593 rights_str = unix_permissions(path) 594 size_str = b_filesize(os.path.getsize(filename=path)) 595 date_str = datetime.datetime.fromtimestamp(os.path.getmtime(filename=path)).strftime("%Y-%m-%d %H:%M") 596 if self.config.no_colors: 597 self.logger.print("%s %10s %s %s" % (rights_str, size_str, date_str, os.path.basename(path))) 598 else: 599 self.logger.print("%s %10s %s \x1b[1m%s\x1b[0m" % (rights_str, size_str, date_str, os.path.basename(path))) 600 601 if len(arguments) > 1: 602 self.logger.print()
681 def command_ltree(self, arguments, command): 682 # Command arguments required : No 683 # Active SMB connection needed : No 684 # SMB share needed : No 685 686 if len(arguments) == 0: 687 path = '.' 688 else: 689 path = arguments[0] 690 691 if len(arguments) == 0: 692 local_tree(path='.', config=self.config) 693 else: 694 local_tree(path=path, config=self.config)
40 def wrapper(*args, **kwargs): 41 self, arguments,command = args[0], args[1], args[2] 42 # 43 self.sessionsManager.current_session.ping_smb_session() 44 if self.sessionsManager.current_session.connected: 45 return func(*args, **kwargs) 46 else: 47 print("[!] SMB Session is disconnected.") 48 return None
804 def command_reconnect(self, arguments, command): 805 # Command arguments required : No 806 # Active SMB connection needed : No 807 # SMB share needed : No 808 809 self.sessionsManager.current_session.ping_smb_session() 810 if self.sessionsManager.current_session.connected: 811 self.sessionsManager.current_session.close_smb_session() 812 self.sessionsManager.current_session.init_smb_session() 813 else: 814 self.sessionsManager.current_session.init_smb_session()
816 def command_reset(self, arguments, command): 817 # Command arguments required : No 818 # Active SMB connection needed : No 819 # SMB share needed : No 820 sys.stdout.write('\x1b[?25h') # Sets the cursor to on 821 sys.stdout.write('\x1b[v') 822 sys.stdout.write('\x1b[o') # Reset 823 sys.stdout.flush()
40 def wrapper(*args, **kwargs): 41 self, arguments,command = args[0], args[1], args[2] 42 # 43 self.sessionsManager.current_session.ping_smb_session() 44 if self.sessionsManager.current_session.connected: 45 return func(*args, **kwargs) 46 else: 47 print("[!] SMB Session is disconnected.") 48 return None
40 def wrapper(*args, **kwargs): 41 self, arguments,command = args[0], args[1], args[2] 42 # 43 self.sessionsManager.current_session.ping_smb_session() 44 if self.sessionsManager.current_session.connected: 45 return func(*args, **kwargs) 46 else: 47 print("[!] SMB Session is disconnected.") 48 return None