|
1 | 1 | import os |
| 2 | +import random |
| 3 | +import re |
2 | 4 | from textwrap import dedent |
3 | 5 | from impacket.dcerpc.v5 import tsch, transport |
4 | 6 | from impacket.dcerpc.v5.dtypes import NULL |
@@ -80,52 +82,91 @@ def get_end_boundary(self): |
80 | 82 | return end_boundary.strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] |
81 | 83 |
|
82 | 84 | def gen_xml(self, command): |
| 85 | + global cmdstdout |
| 86 | + global cmd_path |
| 87 | + #Random setting order to help with detection |
| 88 | + settings = [ |
| 89 | + " <DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>", |
| 90 | + " <MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>", |
| 91 | + " <AllowHardTerminate>true</AllowHardTerminate>", |
| 92 | + " <StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>", |
| 93 | + " <RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable>" |
| 94 | + ] |
| 95 | + random.shuffle(settings) |
| 96 | + randomized_settings = "\n".join(settings) |
| 97 | + |
| 98 | + settings2 = [ |
| 99 | + " <AllowStartOnDemand>true</AllowStartOnDemand>", |
| 100 | + " <Hidden>true</Hidden>", |
| 101 | + " <Enabled>true</Enabled>", |
| 102 | + " <RunOnlyIfIdle>false</RunOnlyIfIdle>", |
| 103 | + " <WakeToRun>false</WakeToRun>", |
| 104 | + " <Priority>7</Priority>", |
| 105 | + " <ExecutionTimeLimit>P3D</ExecutionTimeLimit>" |
| 106 | + ] |
| 107 | + random.shuffle(settings2) |
| 108 | + randomized_settings2 = "\n".join(settings2) |
| 109 | + |
| 110 | + IdleSettings = [ |
| 111 | + " <StopOnIdleEnd>true</StopOnIdleEnd>", |
| 112 | + " <RestartOnIdle>false</RestartOnIdle>" |
| 113 | + ] |
| 114 | + random.shuffle(IdleSettings) |
| 115 | + randomized_IdleSettings = "\n".join(IdleSettings) |
| 116 | + random_digit = random.randint(2, 6) |
| 117 | + |
| 118 | + match = re.match(r'^(.+?\\[^\\ ]+)\s+(.*)', command) |
| 119 | + if match: |
| 120 | + cmd_path = match.group(1) |
| 121 | + cmd_args = match.group(2) |
| 122 | + else: |
| 123 | + print("Could not split the command properly.") |
| 124 | + |
83 | 125 | xml = f"""<?xml version="1.0" encoding="UTF-16"?> |
84 | | - <Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task"> |
| 126 | + <Task version="1.{random_digit}" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task"> |
85 | 127 | <Triggers> |
86 | | - <RegistrationTrigger> |
87 | | - <EndBoundary>{self.get_end_boundary()}</EndBoundary> |
88 | | - </RegistrationTrigger> |
| 128 | + <RegistrationTrigger> |
| 129 | + <EndBoundary>{self.get_end_boundary()}</EndBoundary> |
| 130 | + </RegistrationTrigger> |
89 | 131 | </Triggers> |
90 | 132 | <Principals> |
91 | | - <Principal id="LocalSystem"> |
92 | | - <UserId>{self.run_task_as}</UserId> |
93 | | - <RunLevel>HighestAvailable</RunLevel> |
94 | | - </Principal> |
| 133 | + <Principal id="LocalSystem"> |
| 134 | + <UserId>{self.run_task_as}</UserId> |
| 135 | + <RunLevel>HighestAvailable</RunLevel> |
| 136 | + </Principal> |
95 | 137 | </Principals> |
96 | 138 | <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> |
| 139 | + {randomized_settings} |
| 140 | + <IdleSettings> |
| 141 | + {randomized_IdleSettings} |
| 142 | + </IdleSettings> |
| 143 | + {randomized_settings2} |
113 | 144 | </Settings> |
114 | 145 | <Actions Context="LocalSystem"> |
115 | | - <Exec> |
116 | | - <Command>cmd.exe</Command> |
| 146 | + <Exec> |
| 147 | + <Command>{cmd_path}</Command> |
117 | 148 | """ |
118 | 149 | if self.__retOutput: |
119 | 150 | file_location = "\\Windows\\Temp\\" if self.output_file_location is None else self.output_file_location |
120 | 151 | if self.output_filename is None: |
121 | | - self.__output_filename = os.path.join(file_location, gen_random_string(6)) |
| 152 | + self.__output_filename = os.path.join(file_location, gen_random_string(8)) |
122 | 153 | else: |
123 | 154 | self.__output_filename = os.path.join(file_location, self.output_filename) |
124 | | - argument_xml = f" <Arguments>/C {command} > {self.__output_filename} 2>&1</Arguments>" |
| 155 | + |
| 156 | + if "cmd" in cmd_path.lower() or "powershell" in cmd_path.lower(): |
| 157 | + cmd_output = f"> {self.__output_filename} 2>&1" |
| 158 | + cmdstdout = 1 |
| 159 | + argument_xml = f" <Arguments>{cmd_args} {cmd_output}</Arguments>" |
| 160 | + else: |
| 161 | + cmd_output = "" |
| 162 | + cmdstdout = 0 |
| 163 | + argument_xml = f" <Arguments>{cmd_args} {cmd_output}</Arguments>" |
| 164 | + |
125 | 165 |
|
126 | 166 | elif self.__retOutput is False: |
127 | | - argument_xml = f" <Arguments>/C {command}</Arguments>" |
128 | | - |
| 167 | + argument_xml = f" <Arguments>{cmd_args}</Arguments>" |
| 168 | + |
| 169 | + |
129 | 170 | self.logger.debug("Generated argument XML: " + argument_xml) |
130 | 171 | xml += argument_xml |
131 | 172 |
|
@@ -182,40 +223,44 @@ def execute_handler(self, command): |
182 | 223 | tries = 1 |
183 | 224 | # Give the command a bit of time to execute before we try to read the output, 0.4 seconds was good in testing |
184 | 225 | sleep(0.4) |
185 | | - while True: |
186 | | - try: |
187 | | - self.logger.info(f"Attempting to read {self.__share}\\{self.__output_filename}") |
188 | | - smbConnection.getFile(self.__share, self.__output_filename, self.output_callback) |
189 | | - break |
190 | | - except Exception as e: |
191 | | - if tries >= self.__tries: |
192 | | - self.logger.fail("ATEXEC: Could not retrieve output file, it may have been detected by AV. Please increase the number of tries with the option '--get-output-tries'. If it is still failing, try the 'wmi' protocol or another exec method") |
193 | | - break |
194 | | - if "STATUS_BAD_NETWORK_NAME" in str(e): |
195 | | - self.logger.fail(f"ATEXEC: Getting the output file failed - target has blocked access to the share: {self.__share} (but the command may have executed!)") |
196 | | - break |
197 | | - elif "STATUS_VIRUS_INFECTED" in str(e): |
198 | | - self.logger.fail("Command did not run because a virus was detected") |
199 | | - break |
200 | | - # When executing PowerShell and the command is still running, we get a sharing violation |
201 | | - # We can use that information to wait longer than if the file is not found (probably av or something) |
202 | | - if "STATUS_SHARING_VIOLATION" in str(e): |
203 | | - self.logger.info(f"File {self.__share}\\{self.__output_filename} is still in use with {self.__tries - tries} tries left, retrying...") |
204 | | - tries += 1 |
205 | | - sleep(1) |
206 | | - elif "STATUS_OBJECT_NAME_NOT_FOUND" in str(e): |
207 | | - self.logger.info(f"File {self.__share}\\{self.__output_filename} not found with {self.__tries - tries} tries left, deducting 10 tries and retrying...") |
208 | | - tries += 10 |
209 | | - sleep(1) |
210 | | - else: |
211 | | - self.logger.debug(f"Exception when trying to read output file: {e!s}. {self.__tries - tries} tries left, retrying...") |
212 | | - tries += 1 |
213 | | - sleep(1) |
214 | | - |
215 | | - try: |
216 | | - self.logger.debug(f"Deleting file {self.__share}\\{self.__output_filename}") |
217 | | - smbConnection.deleteFile(self.__share, self.__output_filename) |
218 | | - except Exception: |
219 | | - pass |
| 226 | + if cmdstdout == 1: |
| 227 | + while True: |
| 228 | + try: |
| 229 | + self.logger.info(f"Attempting to read {self.__share}\\{self.__output_filename}") |
| 230 | + smbConnection.getFile(self.__share, self.__output_filename, self.output_callback) |
| 231 | + break |
| 232 | + except Exception as e: |
| 233 | + if tries >= self.__tries: |
| 234 | + self.logger.fail("ATEXEC: Could not retrieve output file, it may have been detected by AV. Please increase the number of tries with the option '--get-output-tries'. If it is still failing, try the 'wmi' protocol or another exec method") |
| 235 | + break |
| 236 | + if "STATUS_BAD_NETWORK_NAME" in str(e): |
| 237 | + self.logger.fail(f"ATEXEC: Getting the output file failed - target has blocked access to the share: {self.__share} (but the command may have executed!)") |
| 238 | + break |
| 239 | + elif "STATUS_VIRUS_INFECTED" in str(e): |
| 240 | + self.logger.fail("Command did not run because a virus was detected") |
| 241 | + break |
| 242 | + # When executing PowerShell and the command is still running, we get a sharing violation |
| 243 | + # We can use that information to wait longer than if the file is not found (probably av or something) |
| 244 | + if "STATUS_SHARING_VIOLATION" in str(e): |
| 245 | + self.logger.info(f"File {self.__share}\\{self.__output_filename} is still in use with {self.__tries - tries} tries left, retrying...") |
| 246 | + tries += 1 |
| 247 | + sleep(1) |
| 248 | + elif "STATUS_OBJECT_NAME_NOT_FOUND" in str(e): |
| 249 | + self.logger.info(f"File {self.__share}\\{self.__output_filename} not found with {self.__tries - tries} tries left, deducting 10 tries and retrying...") |
| 250 | + tries += 10 |
| 251 | + sleep(1) |
| 252 | + else: |
| 253 | + self.logger.debug(f"Exception when trying to read output file: {e!s}. {self.__tries - tries} tries left, retrying...") |
| 254 | + tries += 1 |
| 255 | + sleep(1) |
| 256 | + |
| 257 | + |
| 258 | + try: |
| 259 | + self.logger.debug(f"Deleting file {self.__share}\\{self.__output_filename}") |
| 260 | + smbConnection.deleteFile(self.__share, self.__output_filename) |
| 261 | + except Exception: |
| 262 | + pass |
220 | 263 |
|
| 264 | + else: |
| 265 | + self.logger.display("No output file was saved to be retrived") |
221 | 266 | dce.disconnect() |
0 commit comments