forked from p34709852/monkey
Vastly improved communicate as new user PBA code structure, also not leaking any more process or thread handles.
This commit is contained in:
parent
e9cd20a345
commit
e618378c95
|
@ -0,0 +1,64 @@
|
||||||
|
import logging
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
from infection_monkey.post_breach.actions.add_user import BackdoorUser
|
||||||
|
from infection_monkey.telemetry.post_breach_telem import PostBreachTelem
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class NewUserError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class NewUser(object):
|
||||||
|
"""
|
||||||
|
RAII object to use for creating and using a new user in Windows. Use with `with`.
|
||||||
|
User will be created when the instance is instantiated.
|
||||||
|
User will log on start of `with` scope.
|
||||||
|
User will log off on end of `with` scope.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
# Created # Logged on
|
||||||
|
with NewUser("user", "pass") as new_user:
|
||||||
|
...
|
||||||
|
...
|
||||||
|
# Logged off
|
||||||
|
...
|
||||||
|
"""
|
||||||
|
def __init__(self, username, password):
|
||||||
|
"""
|
||||||
|
Creates a user with the username + password.
|
||||||
|
:raises: subprocess.CalledProcessError if failed to add the user.
|
||||||
|
"""
|
||||||
|
self.username = username
|
||||||
|
self.password = password
|
||||||
|
|
||||||
|
windows_cmds = BackdoorUser.get_windows_commands_to_add_user(self.username, self.password, True)
|
||||||
|
logger.debug("Trying these commands: {}".format(str(windows_cmds)))
|
||||||
|
_ = subprocess.check_output(windows_cmds, stderr=subprocess.STDOUT, shell=True)
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
# Importing these only on windows, as they won't exist on linux.
|
||||||
|
import win32security
|
||||||
|
import win32con
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Logon as new user: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-logonusera
|
||||||
|
self.logon_handle = win32security.LogonUser(
|
||||||
|
self.username,
|
||||||
|
".", # Use current domain.
|
||||||
|
self.password,
|
||||||
|
win32con.LOGON32_LOGON_INTERACTIVE, # Logon type - interactive (normal user).
|
||||||
|
win32con.LOGON32_PROVIDER_DEFAULT) # Which logon provider to use - whatever Windows offers.
|
||||||
|
except Exception as err:
|
||||||
|
raise NewUserError("Can't logon as {}. Error: {}".format(self.username, str(err)))
|
||||||
|
return self
|
||||||
|
|
||||||
|
def get_logon_handle(self):
|
||||||
|
return self.logon_handle
|
||||||
|
|
||||||
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||||
|
self.logon_handle.Close()
|
||||||
|
# TODO Delete user
|
|
@ -5,6 +5,7 @@ import string
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
from common.data.post_breach_consts import POST_BREACH_COMMUNICATE_AS_NEW_USER
|
from common.data.post_breach_consts import POST_BREACH_COMMUNICATE_AS_NEW_USER
|
||||||
|
from infection_monkey.monkey_utils.windows.new_user import NewUser, NewUserError
|
||||||
from infection_monkey.post_breach.actions.add_user import BackdoorUser
|
from infection_monkey.post_breach.actions.add_user import BackdoorUser
|
||||||
from infection_monkey.post_breach.pba import PBA
|
from infection_monkey.post_breach.pba import PBA
|
||||||
from infection_monkey.telemetry.post_breach_telem import PostBreachTelem
|
from infection_monkey.telemetry.post_breach_telem import PostBreachTelem
|
||||||
|
@ -31,88 +32,76 @@ class CommunicateAsNewUser(PBA):
|
||||||
def run(self):
|
def run(self):
|
||||||
username = USERNAME + ''.join(random.choice(string.ascii_lowercase) for _ in range(5))
|
username = USERNAME + ''.join(random.choice(string.ascii_lowercase) for _ in range(5))
|
||||||
if is_windows_os():
|
if is_windows_os():
|
||||||
# Importing these only on windows, as they won't exist on linux.
|
self.communicate_as_new_user_windows(username)
|
||||||
import win32con
|
|
||||||
import win32process
|
|
||||||
import win32security
|
|
||||||
|
|
||||||
if not self.try_to_create_user_windows(username, PASSWORD):
|
|
||||||
return # no point to continue if failed creating the user.
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Logon as new user: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-logonusera
|
|
||||||
new_user_logon_token_handle = win32security.LogonUser(
|
|
||||||
username,
|
|
||||||
".", # use current domain
|
|
||||||
PASSWORD,
|
|
||||||
win32con.LOGON32_LOGON_INTERACTIVE, # logon type - interactive (normal user)
|
|
||||||
win32con.LOGON32_PROVIDER_DEFAULT) # logon provider
|
|
||||||
except Exception as e:
|
|
||||||
PostBreachTelem(
|
|
||||||
self,
|
|
||||||
("Can't logon as {}. Error: {}".format(username, e.message), False)
|
|
||||||
).send()
|
|
||||||
return # no point to continue if can't log on.
|
|
||||||
|
|
||||||
# Using os.path is OK, as this is on windows for sure
|
|
||||||
ping_app_path = os.path.join(os.environ["WINDIR"], "system32", "PING.exe")
|
|
||||||
if not os.path.exists(ping_app_path):
|
|
||||||
PostBreachTelem(self, ("{} not found.".format(ping_app_path), False)).send()
|
|
||||||
return # Can't continue without ping.
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Open process as that user:
|
|
||||||
# https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessasusera
|
|
||||||
commandline = "{} {}".format(ping_app_path, "google.com")
|
|
||||||
_ = win32process.CreateProcessAsUser(
|
|
||||||
new_user_logon_token_handle, # A handle to the primary token that represents a user.
|
|
||||||
None, # The name of the module to be executed.
|
|
||||||
commandline, # The command line to be executed.
|
|
||||||
None, # Process attributes
|
|
||||||
None, # Thread attributes
|
|
||||||
True, # Should inherit handles
|
|
||||||
win32con.NORMAL_PRIORITY_CLASS, # The priority class and the creation of the process.
|
|
||||||
None, # An environment block for the new process. If this parameter is NULL, the new process
|
|
||||||
# uses the environment of the calling process.
|
|
||||||
None, # CWD. If this parameter is NULL, the new process will have the same current drive and
|
|
||||||
# directory as the calling process.
|
|
||||||
win32process.STARTUPINFO() # STARTUPINFO structure.
|
|
||||||
# https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/ns-processthreadsapi-startupinfoa
|
|
||||||
)
|
|
||||||
|
|
||||||
PostBreachTelem(self, (
|
|
||||||
CREATED_PROCESS_AS_USER_WINDOWS_FORMAT.format(commandline, username), True)).send()
|
|
||||||
return
|
|
||||||
except Exception as e:
|
|
||||||
# TODO: if failed on 1314, we can try to add elevate the rights of the current user with the "Replace a
|
|
||||||
# process level token" right, using Local Security Policy editing. Worked, but only after reboot. So:
|
|
||||||
# 1. need to decide if worth it, and then
|
|
||||||
# 2. need to find how to do this using python...
|
|
||||||
PostBreachTelem(self, (
|
|
||||||
"Failed to open process as user {}. Error: {}".format(username, str(e)), False)).send()
|
|
||||||
return
|
|
||||||
else:
|
else:
|
||||||
try:
|
self.communicate_as_new_user_linux(username)
|
||||||
linux_cmds = BackdoorUser.get_linux_commands_to_add_user(username)
|
|
||||||
commandline = "ping -c 2 google.com"
|
|
||||||
linux_cmds.extend([";", "sudo", "-u", username, commandline])
|
|
||||||
final_command = ' '.join(linux_cmds)
|
|
||||||
logger.debug("Trying to execute these commands: {}".format(final_command))
|
|
||||||
output = subprocess.check_output(final_command, stderr=subprocess.STDOUT, shell=True)
|
|
||||||
PostBreachTelem(self, (
|
|
||||||
CREATED_PROCESS_AS_USER_LINUX_FORMAT.format(commandline, username, output[:150]), True)).send()
|
|
||||||
return
|
|
||||||
except subprocess.CalledProcessError as e:
|
|
||||||
PostBreachTelem(self, (e.output, False)).send()
|
|
||||||
return
|
|
||||||
|
|
||||||
def try_to_create_user_windows(self, username, password):
|
def communicate_as_new_user_linux(self, username):
|
||||||
try:
|
try:
|
||||||
windows_cmds = BackdoorUser.get_windows_commands_to_add_user(username, password, True)
|
linux_cmds = BackdoorUser.get_linux_commands_to_add_user(username)
|
||||||
logger.debug("Trying these commands: {}".format(str(windows_cmds)))
|
commandline = "ping -c 2 google.com"
|
||||||
subprocess.check_output(windows_cmds, stderr=subprocess.STDOUT, shell=True)
|
linux_cmds.extend([";", "sudo", "-u", username, commandline])
|
||||||
return True
|
final_command = ' '.join(linux_cmds)
|
||||||
except subprocess.CalledProcessError as e:
|
logger.debug("Trying to execute these commands: {}".format(final_command))
|
||||||
|
output = subprocess.check_output(final_command, stderr=subprocess.STDOUT, shell=True)
|
||||||
PostBreachTelem(self, (
|
PostBreachTelem(self, (
|
||||||
"Couldn't create the user '{}'. Error output is: '{}'".format(username, e.output), False)).send()
|
CREATED_PROCESS_AS_USER_LINUX_FORMAT.format(commandline, username, output[:150]), True)).send()
|
||||||
return False
|
except subprocess.CalledProcessError as e:
|
||||||
|
PostBreachTelem(self, (e.output, False)).send()
|
||||||
|
|
||||||
|
def communicate_as_new_user_windows(self, username):
|
||||||
|
# Importing these only on windows, as they won't exist on linux.
|
||||||
|
import win32con
|
||||||
|
import win32process
|
||||||
|
import win32api
|
||||||
|
|
||||||
|
try:
|
||||||
|
with NewUser(username, PASSWORD) as new_user:
|
||||||
|
# Using os.path is OK, as this is on windows for sure
|
||||||
|
ping_app_path = os.path.join(os.environ["WINDIR"], "system32", "PING.exe")
|
||||||
|
if not os.path.exists(ping_app_path):
|
||||||
|
PostBreachTelem(self, ("{} not found.".format(ping_app_path), False)).send()
|
||||||
|
return # Can't continue without ping.
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Open process as that user:
|
||||||
|
# https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessasusera
|
||||||
|
commandline = "{} {} {} {}".format(ping_app_path, "google.com", "-n", "2")
|
||||||
|
process_handle = win32process.CreateProcessAsUser(
|
||||||
|
new_user.get_logon_handle(), # A handle to the primary token that represents a user.
|
||||||
|
None, # The name of the module to be executed.
|
||||||
|
commandline, # The command line to be executed.
|
||||||
|
None, # Process attributes
|
||||||
|
None, # Thread attributes
|
||||||
|
True, # Should inherit handles
|
||||||
|
win32con.NORMAL_PRIORITY_CLASS, # The priority class and the creation of the process.
|
||||||
|
None, # An environment block for the new process. If this parameter is NULL, the new process
|
||||||
|
# uses the environment of the calling process.
|
||||||
|
None, # CWD. If this parameter is NULL, the new process will have the same current drive and
|
||||||
|
# directory as the calling process.
|
||||||
|
win32process.STARTUPINFO() # STARTUPINFO structure.
|
||||||
|
# https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/ns-processthreadsapi-startupinfoa
|
||||||
|
)
|
||||||
|
|
||||||
|
PostBreachTelem(self,
|
||||||
|
(CREATED_PROCESS_AS_USER_WINDOWS_FORMAT.format(commandline, username), True)).send()
|
||||||
|
|
||||||
|
win32api.CloseHandle(process_handle[0]) # Process handle
|
||||||
|
win32api.CloseHandle(process_handle[1]) # Thread handle
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
# TODO: if failed on 1314, we can try to add elevate the rights of the current user with the
|
||||||
|
# "Replace a process level token" right, using Local Security Policy editing. Worked, but only
|
||||||
|
# after reboot. So:
|
||||||
|
# 1. need to decide if worth it, and then
|
||||||
|
# 2. need to find how to do this using python...
|
||||||
|
PostBreachTelem(self, (
|
||||||
|
"Failed to open process as user {}. Error: {}".format(username, str(e)), False)).send()
|
||||||
|
|
||||||
|
# Nothing more we can do. Leak the process handle.
|
||||||
|
except subprocess.CalledProcessError as err:
|
||||||
|
PostBreachTelem(self, (
|
||||||
|
"Couldn't create the user '{}'. Error output is: '{}'".format(username, str(err)),
|
||||||
|
False)).send()
|
||||||
|
except NewUserError as e:
|
||||||
|
PostBreachTelem(self, (str(e), False)).send()
|
||||||
|
|
Loading…
Reference in New Issue