mirror of
https://github.com/p0dalirius/smbclient-ng
synced 2026-08-09 13:02:38 +00:00
Compare commits
@@ -10,6 +10,11 @@
|
||||
<br>
|
||||
</p>
|
||||
|
||||
> [!NOTE]
|
||||
> Check out the new smbclient-ng written in Go, faster and cross platform by default!
|
||||
>
|
||||
> https://github.com/TheManticoreProject/smbclient-ng
|
||||
|
||||
## Features
|
||||
|
||||
- [x] `acls`: List ACLs of files and folders in cwd. Syntax: `acls`
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
|
||||
|
||||
[project]
|
||||
name = "smbclientng"
|
||||
version = "3.0.0"
|
||||
version = "3.1.0"
|
||||
description = "smbclient-ng, a fast and user-friendly way to interact with SMB shares."
|
||||
authors = [
|
||||
{name = "p0dalirius"}
|
||||
|
||||
@@ -4,12 +4,10 @@
|
||||
# Author: Podalirius (@podalirius_)
|
||||
# Date created: 24 May 2024
|
||||
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import sys
|
||||
|
||||
# Local library imports
|
||||
from smbclientng import console
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(console.run())
|
||||
sys.exit(console.run())
|
||||
|
||||
@@ -18,7 +18,7 @@ class Command_lcp(Command):
|
||||
HELP = {
|
||||
"description": [description, "Syntax: 'lcp <srcfile> <dstfile>'"],
|
||||
"subcommands": [],
|
||||
"autocomplete": ["remote_file"],
|
||||
"autocomplete": ["local_file"],
|
||||
}
|
||||
|
||||
def setupParser(self) -> CommandArgumentParser:
|
||||
|
||||
@@ -26,6 +26,12 @@ class Command_module(Command):
|
||||
# Active SMB connection needed : No
|
||||
# SMB share needed : No
|
||||
|
||||
if len(arguments) == 0:
|
||||
interactive_shell.logger.error(
|
||||
"Usage: module <name> [module-arguments...]"
|
||||
)
|
||||
return
|
||||
|
||||
module_name = arguments[0]
|
||||
|
||||
if module_name in interactive_shell.modules.keys():
|
||||
|
||||
@@ -67,9 +67,9 @@ class Command_rm(Command):
|
||||
interactive_shell.sessionsManager.current_session.rm(
|
||||
path=path_to_file
|
||||
)
|
||||
except Exception:
|
||||
except Exception as err:
|
||||
interactive_shell.logger.error(
|
||||
"Error removing file '%s' : %s" % path_to_file
|
||||
"Error removing file '%s': %s" % (path_to_file, err)
|
||||
)
|
||||
else:
|
||||
interactive_shell.logger.error(
|
||||
|
||||
@@ -45,9 +45,10 @@ class Command_rmdir(Command):
|
||||
interactive_shell.sessionsManager.current_session.rmdir(
|
||||
path=path_to_directory
|
||||
)
|
||||
except Exception:
|
||||
except Exception as err:
|
||||
interactive_shell.logger.error(
|
||||
"Error removing directory '%s' : %s" % path_to_directory
|
||||
"Error removing directory '%s': %s"
|
||||
% (path_to_directory, err)
|
||||
)
|
||||
else:
|
||||
interactive_shell.logger.error(
|
||||
|
||||
@@ -35,8 +35,10 @@ class Command_use(Command):
|
||||
# SMB share needed : No
|
||||
|
||||
self.options = self.processArguments(arguments=arguments)
|
||||
if self.options is None:
|
||||
return
|
||||
|
||||
sharename = arguments[0]
|
||||
sharename = self.options.sharename
|
||||
|
||||
# Reload the list of shares
|
||||
shares = interactive_shell.sessionsManager.current_session.list_shares()
|
||||
|
||||
+10
-3
@@ -9,7 +9,7 @@ from smbclientng.core.SessionsManager import SessionsManager
|
||||
from smbclientng.types.Config import Config
|
||||
from smbclientng.types.Credentials import Credentials
|
||||
|
||||
VERSION = "3.0.0"
|
||||
VERSION = "3.1.0"
|
||||
|
||||
|
||||
def parseArgs():
|
||||
@@ -90,7 +90,11 @@ def parseArgs():
|
||||
"-d", "--domain", default=".", type=str, help="Authentication domain."
|
||||
)
|
||||
group_auth.add_argument(
|
||||
"-u", "--user", type=str, default="", help="Username for authentication."
|
||||
"-u",
|
||||
"--user",
|
||||
type=str,
|
||||
default="",
|
||||
help=r"Username for authentication. Supports DOMAIN\user.",
|
||||
)
|
||||
group_auth.add_argument(
|
||||
"-k", "--kerberos", action="store_true", help="Use Kerberos authentication."
|
||||
@@ -132,8 +136,11 @@ def parseArgs():
|
||||
if options.user and not (options.password or options.no_pass or options.hashes or options.ccache_file or options.kerberos):
|
||||
from getpass import getpass
|
||||
|
||||
prompt_domain, prompt_username = Credentials.parse_domain_username(
|
||||
domain=options.domain, username=options.user
|
||||
)
|
||||
options.password = getpass(
|
||||
f" | Provide a password for '{options.domain}\\{options.user}': "
|
||||
f" | Provide a password for '{prompt_domain}\\{prompt_username}': "
|
||||
)
|
||||
|
||||
if options.aes_key or options.ccache_file:
|
||||
|
||||
@@ -7,7 +7,7 @@ from __future__ import annotations
|
||||
|
||||
import ntpath
|
||||
import os
|
||||
import shlex
|
||||
import re
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from smbclientng.commands import (Command_acls, Command_bat, Command_bhead,
|
||||
@@ -35,6 +35,18 @@ if TYPE_CHECKING:
|
||||
from smbclientng.core.SMBSession import SMBSession
|
||||
|
||||
|
||||
# Characters that must be escaped so readline can still find a common prefix
|
||||
# when some candidates contain spaces or quoting-significant characters.
|
||||
# Using backslash escaping (rather than wrapping with quotes) keeps the entry
|
||||
# name visible to readline's prefix-matching logic, so TAB completion works
|
||||
# when the user has only typed part of an entry's name.
|
||||
_SHELL_ESCAPE_PATTERN = re.compile(r"([\s\\\"'])")
|
||||
|
||||
|
||||
def _shell_escape(value: str) -> str:
|
||||
return _SHELL_ESCAPE_PATTERN.sub(r"\\\1", value)
|
||||
|
||||
|
||||
class CommandCompleter(object):
|
||||
"""
|
||||
A class to handle command completion for the smbclient-ng shell.
|
||||
@@ -156,7 +168,7 @@ class CommandCompleter(object):
|
||||
matching_entries.append(shares[sharename]["name"])
|
||||
# Final matches
|
||||
for m in matching_entries:
|
||||
self.matches.append(command + " " + shlex.quote(m))
|
||||
self.matches.append(command + " " + _shell_escape(m))
|
||||
|
||||
# Autocomplete directory
|
||||
if "remote_directory" in self.commands[command]["autocomplete"]:
|
||||
@@ -186,12 +198,11 @@ class CommandCompleter(object):
|
||||
)
|
||||
#
|
||||
for m in matching_entries:
|
||||
escaped = _shell_escape(m)
|
||||
if m.lower().startswith(
|
||||
remainder.lower()
|
||||
) or shlex.quote(m).lower().startswith(
|
||||
remainder.lower()
|
||||
):
|
||||
self.matches.append(command + " " + shlex.quote(m))
|
||||
) or escaped.lower().startswith(remainder.lower()):
|
||||
self.matches.append(command + " " + escaped)
|
||||
|
||||
# Autocomplete file
|
||||
if "remote_file" in self.commands[command]["autocomplete"]:
|
||||
@@ -218,12 +229,11 @@ class CommandCompleter(object):
|
||||
matching_entries.append(entry.get_longname())
|
||||
#
|
||||
for m in matching_entries:
|
||||
escaped = _shell_escape(m)
|
||||
if m.lower().startswith(
|
||||
remainder.lower()
|
||||
) or shlex.quote(m).lower().startswith(
|
||||
remainder.lower()
|
||||
):
|
||||
self.matches.append(command + " " + shlex.quote(m))
|
||||
) or escaped.lower().startswith(remainder.lower()):
|
||||
self.matches.append(command + " " + escaped)
|
||||
|
||||
# Autocomplete local_directory
|
||||
if "local_directory" in self.commands[command]["autocomplete"]:
|
||||
@@ -247,12 +257,11 @@ class CommandCompleter(object):
|
||||
)
|
||||
#
|
||||
for m in matching_entries:
|
||||
escaped = _shell_escape(m)
|
||||
if m.lower().startswith(
|
||||
remainder.lower()
|
||||
) or shlex.quote(m).lower().startswith(
|
||||
remainder.lower()
|
||||
):
|
||||
self.matches.append(command + " " + shlex.quote(m))
|
||||
) or escaped.lower().startswith(remainder.lower()):
|
||||
self.matches.append(command + " " + escaped)
|
||||
|
||||
# Autocomplete local_file
|
||||
if "local_file" in self.commands[command]["autocomplete"]:
|
||||
@@ -274,12 +283,11 @@ class CommandCompleter(object):
|
||||
matching_entries.append(entry_path)
|
||||
#
|
||||
for m in matching_entries:
|
||||
escaped = _shell_escape(m)
|
||||
if m.lower().startswith(
|
||||
remainder.lower()
|
||||
) or shlex.quote(m).lower().startswith(
|
||||
remainder.lower()
|
||||
):
|
||||
self.matches.append(command + " " + shlex.quote(m))
|
||||
) or escaped.lower().startswith(remainder.lower()):
|
||||
self.matches.append(command + " " + escaped)
|
||||
|
||||
else:
|
||||
# Generic case for subcommands
|
||||
|
||||
@@ -70,12 +70,15 @@ class LocalFileIO(object):
|
||||
"Openning local '%s' with mode '%s'" % (self.path, self.mode)
|
||||
)
|
||||
|
||||
local_path = self.dir + os.path.sep + os.path.basename(self.path)
|
||||
try:
|
||||
self.fd = open(
|
||||
self.dir + os.path.sep + os.path.basename(self.path), self.mode
|
||||
)
|
||||
except PermissionError:
|
||||
self.fd = open(local_path, self.mode)
|
||||
except (PermissionError, OSError) as err:
|
||||
self.fd = None
|
||||
self.logger.error(
|
||||
"Cannot open local file '%s' for writing: %s"
|
||||
% (local_path, err)
|
||||
)
|
||||
|
||||
# Write to remote (read local)
|
||||
elif self.mode in ["rb"]:
|
||||
@@ -88,15 +91,19 @@ class LocalFileIO(object):
|
||||
|
||||
try:
|
||||
self.fd = open(self.path, self.mode)
|
||||
except PermissionError:
|
||||
except (PermissionError, OSError) as err:
|
||||
self.fd = None
|
||||
self.logger.error(
|
||||
"Cannot open local file '%s' for reading: %s"
|
||||
% (self.path, err)
|
||||
)
|
||||
|
||||
if self.fd is not None:
|
||||
if self.expected_size is None:
|
||||
self.expected_size = os.path.getsize(filename=self.path)
|
||||
|
||||
# Create progress bar
|
||||
if self.expected_size is not None:
|
||||
# Create progress bar only if the file descriptor was opened successfully
|
||||
if self.expected_size is not None and self.fd is not None:
|
||||
self.__progress = Progress(
|
||||
TextColumn("[bold blue]{task.description}", justify="right"),
|
||||
BarColumn(bar_width=None),
|
||||
@@ -177,7 +184,7 @@ class LocalFileIO(object):
|
||||
except (PermissionError, FileNotFoundError):
|
||||
pass
|
||||
|
||||
if self.expected_size is not None:
|
||||
if self.expected_size is not None and self.fd is not None:
|
||||
self.__progress.stop()
|
||||
|
||||
del self
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import fnmatch
|
||||
import io
|
||||
import ntpath
|
||||
import os
|
||||
@@ -23,7 +24,7 @@ from impacket.smb import SMBFileStreamInformation
|
||||
from impacket.smb3structs import (DACL_SECURITY_INFORMATION,
|
||||
FILE_DIRECTORY_FILE, FILE_NON_DIRECTORY_FILE,
|
||||
FILE_OPEN, FILE_READ_ATTRIBUTES,
|
||||
GROUP_SECURITY_INFORMATION,
|
||||
FILE_TRAVERSE, GROUP_SECURITY_INFORMATION,
|
||||
OWNER_SECURITY_INFORMATION, READ_CONTROL,
|
||||
SMB2_0_INFO_FILE, SMB2_FILE_STREAM_INFO)
|
||||
from impacket.smbconnection import SessionError, SMBConnection
|
||||
@@ -409,7 +410,7 @@ class SMBSession(object):
|
||||
try:
|
||||
if entry.is_directory():
|
||||
if keepRemotePath:
|
||||
base_path = "./" # Use root as base
|
||||
base_path = ntpath.sep # Use remote share root as base
|
||||
relative_path = ntpath.relpath(fullpath, base_path)
|
||||
relative_path = relative_path.replace(
|
||||
ntpath.sep, os.path.sep
|
||||
@@ -424,7 +425,7 @@ class SMBSession(object):
|
||||
pass
|
||||
else:
|
||||
if keepRemotePath:
|
||||
base_path = "./" # Use root as base
|
||||
base_path = ntpath.sep # Use remote share root as base
|
||||
relative_path = ntpath.relpath(fullpath, base_path)
|
||||
relative_path = relative_path.replace(
|
||||
ntpath.sep, os.path.sep
|
||||
@@ -460,7 +461,7 @@ class SMBSession(object):
|
||||
)
|
||||
return
|
||||
if keepRemotePath:
|
||||
base_path = "./" # Use root as base
|
||||
base_path = ntpath.sep # Use remote share root as base
|
||||
relative_path = ntpath.relpath(path, base_path)
|
||||
relative_path = relative_path.replace(ntpath.sep, os.path.sep)
|
||||
output_filepath = os.path.normpath(
|
||||
@@ -498,6 +499,11 @@ class SMBSession(object):
|
||||
expected_size=entry.get_filesize(),
|
||||
keepRemotePath=keepRemotePath,
|
||||
)
|
||||
if f.fd is None:
|
||||
# The local file could not be opened (already logged by LocalFileIO).
|
||||
# Skip the remote transfer to avoid discarding data silently.
|
||||
f.close()
|
||||
return
|
||||
try:
|
||||
self.smbClient.getFile(
|
||||
shareName=self.smb_share, pathName=full_path, callback=f.write
|
||||
@@ -1276,9 +1282,8 @@ class SMBSession(object):
|
||||
"Executing '%s' with arguments: %s" % (executable, args)
|
||||
)
|
||||
process = subprocess.Popen(
|
||||
executable=executable,
|
||||
args=args,
|
||||
shell=True,
|
||||
args=[executable, *args],
|
||||
shell=False,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
)
|
||||
@@ -1332,8 +1337,8 @@ class SMBSession(object):
|
||||
"""
|
||||
Checks if the specified path is a directory on the SMB share.
|
||||
|
||||
This method determines if a given path corresponds to a directory on the SMB share. It does this by listing the
|
||||
contents of the path and filtering for entries that match the basename of the path and are marked as directories.
|
||||
This method determines if a given path corresponds to a directory on the SMB share. It first tries to resolve it
|
||||
through a directory listing, then falls back to opening the target path directly as a directory.
|
||||
|
||||
Args:
|
||||
path (str, optional): The path to check on the SMB share. Defaults to None.
|
||||
@@ -1366,9 +1371,9 @@ class SMBSession(object):
|
||||
if c.get_longname() == ntpath.basename(path)
|
||||
and c.is_directory()
|
||||
]
|
||||
return len(contents) != 0
|
||||
return len(contents) != 0 or self._can_open_directory(path)
|
||||
except Exception:
|
||||
return False
|
||||
return self._can_open_directory(path)
|
||||
else:
|
||||
return False
|
||||
|
||||
@@ -1435,7 +1440,7 @@ class SMBSession(object):
|
||||
if os.path.sep in localpath:
|
||||
if localpath.startswith(os.path.sep):
|
||||
# Absolute path
|
||||
tmp_search_path = os.path.normpath(localpath)
|
||||
tmp_search_path = os.path.normpath(os.path.dirname(localpath))
|
||||
else:
|
||||
# Relative path
|
||||
tmp_search_path = os.path.normpath(
|
||||
@@ -1454,9 +1459,8 @@ class SMBSession(object):
|
||||
for entry in matches:
|
||||
if entry == filename:
|
||||
matching_entries.append(entry)
|
||||
elif "*" in filename:
|
||||
regexp = filename.replace(".", "\\.").replace("*", ".*")
|
||||
if re.match(regexp, entry):
|
||||
elif "*" in filename or "?" in filename or "[" in filename:
|
||||
if fnmatch.fnmatchcase(entry, filename):
|
||||
matching_entries.append(entry)
|
||||
|
||||
matching_entries = sorted(list(set(matching_entries)))
|
||||
@@ -1668,9 +1672,8 @@ class SMBSession(object):
|
||||
continue
|
||||
if entry.get_longname() == filename:
|
||||
matching_entries.append(entry)
|
||||
elif "*" in filename:
|
||||
regexp = filename.replace(".", "\\.").replace("*", ".*")
|
||||
if re.match(regexp, entry.get_longname()):
|
||||
elif "*" in filename or "?" in filename or "[" in filename:
|
||||
if fnmatch.fnmatchcase(entry.get_longname(), filename):
|
||||
matching_entries.append(entry)
|
||||
|
||||
matching_entries = sorted(
|
||||
@@ -1974,9 +1977,37 @@ class SMBSession(object):
|
||||
if path in ["", ".", ".."]:
|
||||
self.smb_cwd = ""
|
||||
else:
|
||||
if self.path_isdir(pathFromRoot=path.strip(ntpath.sep)):
|
||||
# Path exists on the remote
|
||||
path_from_root = path.strip(ntpath.sep)
|
||||
if self.path_isdir(pathFromRoot=path_from_root):
|
||||
self.smb_cwd = ntpath.normpath(path)
|
||||
else:
|
||||
# Path does not exists or is not a directory on the remote
|
||||
self.logger.error("Remote directory '%s' does not exist." % path)
|
||||
|
||||
def _can_open_directory(self, pathFromRoot: str) -> bool:
|
||||
"""
|
||||
Returns True when the given remote path can be opened as a directory,
|
||||
without requiring the parent or target directory to be listable.
|
||||
"""
|
||||
path = pathFromRoot.replace("*", "").replace("/", ntpath.sep)
|
||||
path = ntpath.normpath(path).lstrip(ntpath.sep)
|
||||
if not path or path in [".", ".."]:
|
||||
return True
|
||||
file_id = None
|
||||
try:
|
||||
file_id = self.smbClient.createFile(
|
||||
treeId=self.smb_tree_id,
|
||||
pathName=path,
|
||||
desiredAccess=FILE_TRAVERSE,
|
||||
creationOption=FILE_DIRECTORY_FILE,
|
||||
creationDisposition=FILE_OPEN,
|
||||
)
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
finally:
|
||||
if file_id is not None:
|
||||
try:
|
||||
self.smbClient.closeFile(self.smb_tree_id, file_id)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@@ -35,17 +35,16 @@ class SessionsManager(object):
|
||||
sessions (dict): A dictionary of all active sessions, keyed by their session ID.
|
||||
"""
|
||||
|
||||
sessions = {}
|
||||
next_session_id: int = 1
|
||||
current_session: Optional[SMBSession] = None
|
||||
current_session_id: Optional[int]
|
||||
|
||||
config: Config
|
||||
logger: Logger
|
||||
|
||||
def __init__(self, config: Config, logger: Logger):
|
||||
self.config = config
|
||||
self.logger = logger
|
||||
self.sessions: dict = {}
|
||||
self.next_session_id: int = 1
|
||||
self.current_session: Optional[SMBSession] = None
|
||||
self.current_session_id: Optional[int] = None
|
||||
|
||||
def create_new_session(
|
||||
self,
|
||||
@@ -214,7 +213,7 @@ class SessionsManager(object):
|
||||
dest="auth_username",
|
||||
metavar="USER",
|
||||
action="store",
|
||||
help="User to authenticate with.",
|
||||
help=r"User to authenticate with. Supports DOMAIN\user.",
|
||||
)
|
||||
secret = mode_create.add_argument_group()
|
||||
cred = secret.add_mutually_exclusive_group()
|
||||
|
||||
@@ -157,7 +157,7 @@ class Find(Module):
|
||||
for item in exclude_dirs:
|
||||
parts = item.split(":")
|
||||
dirname = parts[0]
|
||||
depth = 0 # Default depth
|
||||
depth = 0 # Default depth
|
||||
case_sensitive = False # Default to case-insensitive
|
||||
|
||||
# Parse depth if provided
|
||||
@@ -165,7 +165,7 @@ class Find(Module):
|
||||
try:
|
||||
depth = int(parts[1])
|
||||
except ValueError:
|
||||
depth = 0 # Default if depth is invalid
|
||||
depth = 0 # Default if depth is invalid
|
||||
|
||||
# Parse case sensitivity if provided
|
||||
if len(parts) > 2 and parts[2]:
|
||||
@@ -200,14 +200,14 @@ class Find(Module):
|
||||
|
||||
if not case_sensitive:
|
||||
filename = filename.lower()
|
||||
|
||||
|
||||
for pattern in flat_patterns:
|
||||
pattern = pattern.strip()
|
||||
if not pattern:
|
||||
continue
|
||||
if not case_sensitive:
|
||||
pattern = pattern.lower()
|
||||
|
||||
|
||||
if fnmatch.fnmatch(filename, pattern):
|
||||
return True
|
||||
return False
|
||||
@@ -264,17 +264,17 @@ class Find(Module):
|
||||
# 1. Check Exclusions (--exclude)
|
||||
if self.options.exclude_name:
|
||||
if self._match_pattern(filename, self.options.exclude_name, case_sensitive=True):
|
||||
continue # Skip excluded file
|
||||
continue # Skip excluded file
|
||||
|
||||
# 2. Check Inclusions (-name)
|
||||
if self.options.name:
|
||||
if not self._match_pattern(filename, self.options.name, case_sensitive=True):
|
||||
continue # Skip if it doesn't match name pattern
|
||||
continue # Skip if it doesn't match name pattern
|
||||
|
||||
# 3. Check Case-Insensitive Inclusions (-iname)
|
||||
if self.options.iname:
|
||||
if not self._match_pattern(filename, self.options.iname, case_sensitive=False):
|
||||
continue # Skip if it doesn't match iname pattern
|
||||
continue # Skip if it doesn't match iname pattern
|
||||
|
||||
# Actions on matches
|
||||
if self.options.download:
|
||||
@@ -289,9 +289,9 @@ class Find(Module):
|
||||
rel_path = rel_path.lstrip(ntpath.sep).lstrip('/')
|
||||
if rel_path:
|
||||
path_to_download = rel_path
|
||||
|
||||
|
||||
self.smbSession.get_file(path=path_to_download, keepRemotePath=True)
|
||||
|
||||
|
||||
# Output formats
|
||||
output_str = ""
|
||||
if self.options.ls:
|
||||
|
||||
@@ -61,8 +61,9 @@ class Credentials(object):
|
||||
):
|
||||
super(Credentials, self).__init__()
|
||||
# Identity
|
||||
self.domain = domain
|
||||
self.username = username
|
||||
self.domain, self.username = self.parse_domain_username(
|
||||
domain=domain, username=username
|
||||
)
|
||||
self.password = password
|
||||
|
||||
# Hashes
|
||||
@@ -74,6 +75,21 @@ class Credentials(object):
|
||||
self.aesKey = aesKey
|
||||
self.ccacheFile = ccacheFile
|
||||
|
||||
@staticmethod
|
||||
def parse_domain_username(
|
||||
domain: str, username: Optional[str]
|
||||
) -> tuple[str, Optional[str]]:
|
||||
if username is None:
|
||||
return domain, username
|
||||
|
||||
for separator in ("\\", "/"):
|
||||
if separator in username:
|
||||
user_domain, user_name = username.split(separator, 1)
|
||||
if user_domain and user_name:
|
||||
return user_domain, user_name
|
||||
|
||||
return domain, username
|
||||
|
||||
def set_hashes(self, hashes: Optional[str]):
|
||||
"""
|
||||
Sets the LM and NT hashes for the credentials.
|
||||
|
||||
@@ -61,6 +61,10 @@ def parse_lm_nt_hashes(lm_nt_hashes_string: str) -> tuple[str, str]:
|
||||
elif m_lm_hash is not None and m_nt_hash is None:
|
||||
lm_hash_value = m_lm_hash
|
||||
nt_hash_value = "31d6cfe0d16ae931b73c59d7e0c089c0"
|
||||
else:
|
||||
lm_hash_value = m_lm_hash
|
||||
nt_hash_value = m_nt_hash
|
||||
|
||||
return lm_hash_value, nt_hash_value
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user