From c8a742674f1109058e2512f4c652dfaf0224577d Mon Sep 17 00:00:00 2001
From: Shay Nehmad <shay.nehmad@guardicore.com>
Date: Mon, 23 Dec 2019 17:41:42 +0200
Subject: [PATCH 1/3] Replaced ping with curl/wget and Invoke-WebRequest

---
 .../actions/communicate_as_new_user.py        | 39 +++++++++++--------
 .../infection_monkey/utils/windows/users.py   |  2 +-
 2 files changed, 24 insertions(+), 17 deletions(-)

diff --git a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py
index 2a1a79b91..1e4ce4e66 100644
--- a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py
+++ b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py
@@ -10,10 +10,10 @@ from infection_monkey.post_breach.pba import PBA
 from infection_monkey.telemetry.post_breach_telem import PostBreachTelem
 from infection_monkey.utils.environment import is_windows_os
 
-PING_TEST_DOMAIN = "google.com"
+INFECTION_MONKEY_WEBSITE_URL = "https://infectionmonkey.com"
 
-CREATED_PROCESS_AS_USER_PING_SUCCESS_FORMAT = "Created process '{}' as user '{}', and successfully pinged."
-CREATED_PROCESS_AS_USER_PING_FAILED_FORMAT = "Created process '{}' as user '{}', but failed to ping (exit status {})."
+CREATED_PROCESS_AS_USER_SUCCESS_FORMAT = "Created process '{}' as user '{}' and the process succeeded."
+CREATED_PROCESS_AS_USER_FAILED_FORMAT = "Created process '{}' as user '{}', but the process failed (exit status {})."
 
 USERNAME_PREFIX = "somenewuser"
 PASSWORD = "N3WPa55W0rD!1"
@@ -23,8 +23,8 @@ logger = logging.getLogger(__name__)
 
 class CommunicateAsNewUser(PBA):
     """
-    This PBA creates a new user, and then pings google as that user. This is used for a Zero Trust test of the People
-    pillar. See the relevant telemetry processing to see what findings are created.
+    This PBA creates a new user, and then creates HTTP/S requests as that user. This is used for a Zero Trust test of the
+    People pillar. See the relevant telemetry processing to see what findings are created.
     """
 
     def __init__(self):
@@ -34,9 +34,9 @@ class CommunicateAsNewUser(PBA):
         username = CommunicateAsNewUser.get_random_new_user_name()
         try:
             with create_auto_new_user(username, PASSWORD) as new_user:
-                ping_commandline = CommunicateAsNewUser.get_commandline_for_ping()
-                exit_status = new_user.run_as(ping_commandline)
-                self.send_ping_result_telemetry(exit_status, ping_commandline, username)
+                http_request_commandline = CommunicateAsNewUser.get_commandline_for_http_request(INFECTION_MONKEY_WEBSITE_URL)
+                exit_status = new_user.run_as(http_request_commandline)
+                self.send_result_telemetry(exit_status, http_request_commandline, username)
         except subprocess.CalledProcessError as e:
             PostBreachTelem(self, (e.output.decode(), False)).send()
         except NewUserError as e:
@@ -47,21 +47,28 @@ class CommunicateAsNewUser(PBA):
         return USERNAME_PREFIX + ''.join(random.choice(string.ascii_lowercase) for _ in range(5))
 
     @staticmethod
-    def get_commandline_for_ping(domain=PING_TEST_DOMAIN, is_windows=is_windows_os()):
-        format_string = "PING.exe {domain} -n 1" if is_windows else "ping -c 1 {domain}"
-        return format_string.format(domain=domain)
+    def get_commandline_for_http_request(url, is_windows=is_windows_os()):
+        if is_windows:
+            format_string = "powershell -command \"Invoke-WebRequest {url}\""
+        else:
+            # true || false -> 0.  false || true -> 0.  false || false -> 1. So:
+            # if curl works, we're good.
+            # If curl doesn't exist or fails and wget work, we're good.
+            # And if both don't exist: we'll call it a win.
+            format_string = "curl {url} || wget -O/dev/null -q {url}"
+        return format_string.format(url=url)
 
-    def send_ping_result_telemetry(self, exit_status, commandline, username):
+    def send_result_telemetry(self, exit_status, commandline, username):
         """
-        Parses the result of ping and sends telemetry accordingly.
+        Parses the result of the command and sends telemetry accordingly.
 
-        :param exit_status: In both Windows and Linux, 0 exit code from Ping indicates success.
+        :param exit_status: In both Windows and Linux, 0 exit code indicates success.
         :param commandline: Exact commandline which was executed, for reporting back.
         :param username: Username from which the command was executed, for reporting back.
         """
         if exit_status == 0:
             PostBreachTelem(self, (
-                CREATED_PROCESS_AS_USER_PING_SUCCESS_FORMAT.format(commandline, username), True)).send()
+                CREATED_PROCESS_AS_USER_SUCCESS_FORMAT.format(commandline, username), True)).send()
         else:
             PostBreachTelem(self, (
-                CREATED_PROCESS_AS_USER_PING_FAILED_FORMAT.format(commandline, username, exit_status), False)).send()
+                CREATED_PROCESS_AS_USER_FAILED_FORMAT.format(commandline, username, exit_status), False)).send()
diff --git a/monkey/infection_monkey/utils/windows/users.py b/monkey/infection_monkey/utils/windows/users.py
index cf6eb73c4..c6e46a690 100644
--- a/monkey/infection_monkey/utils/windows/users.py
+++ b/monkey/infection_monkey/utils/windows/users.py
@@ -65,7 +65,7 @@ class AutoNewWindowsUser(AutoNewUser):
                 self.username,
                 ".",  # Use current domain.
                 self.password,
-                win32con.LOGON32_LOGON_INTERACTIVE,  # Logon type - interactive (normal user). Need this to open ping
+                win32con.LOGON32_LOGON_INTERACTIVE,  # Logon type - interactive (normal user).
                 # using a shell.
                 win32con.LOGON32_PROVIDER_DEFAULT)  # Which logon provider to use - whatever Windows offers.
         except Exception as err:

From ab57b5895e40b0c763555517c2c12c6a56b80c1a Mon Sep 17 00:00:00 2001
From: Shay Nehmad <shay.nehmad@guardicore.com>
Date: Tue, 24 Dec 2019 19:08:52 +0200
Subject: [PATCH 2/3] Replaced CreateProcessAsUser with CreateProcessWithLogonW
 to get over the "0xc0000142" error code and added -UseBasicParsing to get
 over the IE Engine inconfigured error

This will be heavily documented in a tweet sometime
---
 .../actions/communicate_as_new_user.py        | 13 +++++---
 monkey/infection_monkey/requirements.txt      |  1 +
 .../infection_monkey/utils/windows/users.py   | 32 ++++++++-----------
 3 files changed, 23 insertions(+), 23 deletions(-)

diff --git a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py
index 1e4ce4e66..4a10abdc2 100644
--- a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py
+++ b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py
@@ -10,10 +10,10 @@ from infection_monkey.post_breach.pba import PBA
 from infection_monkey.telemetry.post_breach_telem import PostBreachTelem
 from infection_monkey.utils.environment import is_windows_os
 
-INFECTION_MONKEY_WEBSITE_URL = "https://infectionmonkey.com"
+INFECTION_MONKEY_WEBSITE_URL = "https://infectionmonkey.com/"
 
 CREATED_PROCESS_AS_USER_SUCCESS_FORMAT = "Created process '{}' as user '{}' and the process succeeded."
-CREATED_PROCESS_AS_USER_FAILED_FORMAT = "Created process '{}' as user '{}', but the process failed (exit status {})."
+CREATED_PROCESS_AS_USER_FAILED_FORMAT = "Created process '{}' as user '{}', but the process failed (exit status {}:{})."
 
 USERNAME_PREFIX = "somenewuser"
 PASSWORD = "N3WPa55W0rD!1"
@@ -49,7 +49,7 @@ class CommunicateAsNewUser(PBA):
     @staticmethod
     def get_commandline_for_http_request(url, is_windows=is_windows_os()):
         if is_windows:
-            format_string = "powershell -command \"Invoke-WebRequest {url}\""
+            format_string = 'powershell.exe -command "Invoke-WebRequest {url}" -UseBasicParsing'
         else:
             # true || false -> 0.  false || true -> 0.  false || false -> 1. So:
             # if curl works, we're good.
@@ -71,4 +71,9 @@ class CommunicateAsNewUser(PBA):
                 CREATED_PROCESS_AS_USER_SUCCESS_FORMAT.format(commandline, username), True)).send()
         else:
             PostBreachTelem(self, (
-                CREATED_PROCESS_AS_USER_FAILED_FORMAT.format(commandline, username, exit_status), False)).send()
+                CREATED_PROCESS_AS_USER_FAILED_FORMAT.format(
+                    commandline, username, exit_status, twos_complement(exit_status)), False)).send()
+
+
+def twos_complement(exit_status):
+    return hex(exit_status & (2 ** 32 - 1))
diff --git a/monkey/infection_monkey/requirements.txt b/monkey/infection_monkey/requirements.txt
index 93b3b4ca3..7a75c1b7e 100644
--- a/monkey/infection_monkey/requirements.txt
+++ b/monkey/infection_monkey/requirements.txt
@@ -13,3 +13,4 @@ wmi
 pywin32 ; sys_platform == 'win32'
 pymssql<3.0
 pyftpdlib
+WinSys-3.x
diff --git a/monkey/infection_monkey/utils/windows/users.py b/monkey/infection_monkey/utils/windows/users.py
index c6e46a690..fcd364032 100644
--- a/monkey/infection_monkey/utils/windows/users.py
+++ b/monkey/infection_monkey/utils/windows/users.py
@@ -5,7 +5,7 @@ from infection_monkey.utils.auto_new_user import AutoNewUser
 from infection_monkey.utils.new_user_error import NewUserError
 
 ACTIVE_NO_NET_USER = '/ACTIVE:NO'
-WAIT_TIMEOUT_IN_MILLISECONDS = 20 * 1000
+WAIT_TIMEOUT_IN_MILLISECONDS = 60 * 1000
 
 logger = logging.getLogger(__name__)
 
@@ -65,8 +65,7 @@ class AutoNewWindowsUser(AutoNewUser):
                 self.username,
                 ".",  # Use current domain.
                 self.password,
-                win32con.LOGON32_LOGON_INTERACTIVE,  # Logon type - interactive (normal user).
-                # using a shell.
+                win32con.LOGON32_LOGON_INTERACTIVE,  # Logon type - interactive (normal user), since we're using a shell.
                 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)))
@@ -78,33 +77,28 @@ class AutoNewWindowsUser(AutoNewUser):
         import win32process
         import win32api
         import win32event
+        from winsys import _advapi32
 
         exit_code = -1
         process_handle = None
         thread_handle = None
 
         try:
-            # Open process as that user:
-            # https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessasusera
-            process_handle, thread_handle, _, _ = win32process.CreateProcessAsUser(
-                self.get_logon_handle(),  # A handle to the primary token that represents a user.
-                None,  # The name of the module to be executed.
-                command,  # 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
+            # Open process as that user
+            # https://github.com/tjguk/winsys/blob/master/winsys/_advapi32.py
+            proc_info = _advapi32.CreateProcessWithLogonW(
+                username=self.username,
+                domain=".",
+                password=self.password,
+                command_line=command
             )
+            process_handle = proc_info.hProcess
+            thread_handle = proc_info.hThread
 
             logger.debug(
                 "Waiting for process to finish. Timeout: {}ms".format(WAIT_TIMEOUT_IN_MILLISECONDS))
 
+            # https://social.msdn.microsoft.com/Forums/vstudio/en-US/b6d6a7ae-71e9-4edb-ac8f-408d2a41750d/what-events-on-a-process-handle-signal-satisify-waitforsingleobject?forum=vcgeneral
             # Ignoring return code, as we'll use `GetExitCode` to determine the state of the process later.
             _ = win32event.WaitForSingleObject(  # Waits until the specified object is signaled, or time-out.
                 process_handle,  # Ping process handle

From 6e9ee4158d356c0c136a8c74480efaf94e521d5c Mon Sep 17 00:00:00 2001
From: Shay Nehmad <shay.nehmad@guardicore.com>
Date: Wed, 25 Dec 2019 10:57:17 +0200
Subject: [PATCH 3/3] We chose HTTPS

---
 .../post_breach/actions/communicate_as_new_user.py              | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py
index 4a10abdc2..42162fb55 100644
--- a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py
+++ b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py
@@ -23,7 +23,7 @@ logger = logging.getLogger(__name__)
 
 class CommunicateAsNewUser(PBA):
     """
-    This PBA creates a new user, and then creates HTTP/S requests as that user. This is used for a Zero Trust test of the
+    This PBA creates a new user, and then creates HTTPS requests as that user. This is used for a Zero Trust test of the
     People pillar. See the relevant telemetry processing to see what findings are created.
     """