Skip to content

Commit 4ffcc83

Browse files
committed
Make execution methods more efficient and wait powershell execution if detectable
1 parent 5655e5c commit 4ffcc83

6 files changed

Lines changed: 62 additions & 23 deletions

File tree

nxc/cli.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,10 @@ def gen_cli_args():
106106
print(f"{VERSION} - {CODENAME} - {COMMIT}")
107107
sys.exit(1)
108108

109+
# Multiply output_tries by 10 to enable more fine granural control, see exec methods
110+
if hasattr(args, "get_output_tries"):
111+
args.get_output_tries = args.get_output_tries * 10
112+
109113
return args
110114

111115

nxc/protocols/smb/atexec.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ def execute_handler(self, command, fileless=False):
131131

132132
xml = self.gen_xml(command, fileless)
133133

134-
self.logger.info(f"Task XML: {xml}")
134+
self.logger.debug(f"Task XML: {xml}")
135135
taskCreated = False
136136
self.logger.info(f"Creating task \\{tmpName}")
137137
try:
@@ -179,22 +179,32 @@ def execute_handler(self, command, fileless=False):
179179
else:
180180
":".join(map(str, self.__rpctransport.get_socket().getpeername()))
181181
smbConnection = self.__rpctransport.get_smb_connection()
182-
tries = 1
182+
183+
tries = 0
184+
# Give the command a bit of time to execute before we try to read the output, 0.4 seconds was good in testing
185+
sleep(0.4)
183186
while True:
184187
try:
185188
self.logger.info(f"Attempting to read {self.__share}\\{self.__output_filename}")
186189
smbConnection.getFile(self.__share, self.__output_filename, self.output_callback)
187190
break
188191
except Exception as e:
189-
if tries >= self.__tries:
192+
if tries > self.__tries:
190193
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")
191194
break
192195
if str(e).find("STATUS_BAD_NETWORK_NAME") > 0:
193196
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!)")
194197
break
195-
if str(e).find("SHARING") > 0 or str(e).find("STATUS_OBJECT_NAME_NOT_FOUND") >= 0:
196-
sleep(3)
198+
# When executing powershell and the command is still running, we get a sharing violation
199+
# We can use that information to wait longer than if the file is not found (probably av or something)
200+
if "STATUS_SHARING_VIOLATION" in str(e):
201+
self.logger.info(f"File {self.__share}\\{self.__output_filename} is still in use with {self.__tries - tries} left, retrying...")
197202
tries += 1
203+
sleep(1)
204+
elif "STATUS_OBJECT_NAME_NOT_FOUND" in str(e):
205+
self.logger.info(f"File {self.__share}\\{self.__output_filename} not found with {self.__tries - tries} left, deducting 10 tries and retrying...")
206+
tries += 10
207+
sleep(1)
198208
else:
199209
self.logger.debug(str(e))
200210

nxc/protocols/smb/mmcexec.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -242,23 +242,32 @@ def get_output_remote(self):
242242
if self.__retOutput is False:
243243
self.__outputBuffer = ""
244244
return
245-
tries = 1
245+
246+
tries = 0
247+
# Give the command a bit of time to execute before we try to read the output, 0.4 seconds was good in testing
248+
sleep(0.4)
246249
while True:
247250
try:
248251
self.logger.info(f"Attempting to read {self.__share}\\{self.__output}")
249252
self.__smbconnection.getFile(self.__share, self.__output, self.output_callback)
250253
break
251254
except Exception as e:
252-
if tries >= self.__tries:
255+
if tries > self.__tries:
253256
self.logger.fail("MMCEXEC: 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")
254257
break
255258
if str(e).find("STATUS_BAD_NETWORK_NAME") > 0:
256259
self.logger.fail(f"MMCEXEC: Getting the output file failed - target has blocked access to the share: {self.__share} (but the command may have executed!)")
257260
break
258-
if str(e).find("STATUS_SHARING_VIOLATION") >= 0 or str(e).find("STATUS_OBJECT_NAME_NOT_FOUND") >= 0:
259-
# Output not finished, let's wait
260-
sleep(2)
261+
# When executing powershell and the command is still running, we get a sharing violation
262+
# We can use that information to wait longer than if the file is not found (probably av or something)
263+
if "STATUS_SHARING_VIOLATION" in str(e):
264+
self.logger.info(f"File {self.__share}\\{self.__output} is still in use with {self.__tries - tries} left, retrying...")
261265
tries += 1
266+
sleep(1)
267+
elif "STATUS_OBJECT_NAME_NOT_FOUND" in str(e):
268+
self.logger.info(f"File {self.__share}\\{self.__output} not found with {self.__tries - tries} left, deducting 10 tries and retrying...")
269+
tries += 10
270+
sleep(1)
262271
else:
263272
self.logger.debug(str(e))
264273

nxc/protocols/smb/proto_args.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ def proto_args(parser, std_parser, module_parser):
4343
egroup.add_argument("--computers", nargs="?", const="", metavar="COMPUTER", help="enumerate computer users")
4444
egroup.add_argument("--local-groups", nargs="?", const="", metavar="GROUP", help="enumerate local groups, if a group is specified then its members are enumerated")
4545
egroup.add_argument("--pass-pol", action="store_true", help="dump password policy")
46-
egroup.add_argument("--rid-brute", nargs="?", type=int, default=4000, metavar="MAX_RID", help="enumerate users by bruteforcing RID's (default: %(default)s)")
46+
egroup.add_argument("--rid-brute", nargs="?", type=int, const=4000, metavar="MAX_RID", help="enumerate users by bruteforcing RID's (default: 4000)")
4747
egroup.add_argument("--wmi", metavar="QUERY", type=str, help="issues the specified WMI query")
4848
egroup.add_argument("--wmi-namespace", metavar="NAMESPACE", default="root\\cimv2", help="WMI Namespace (default: %(default)s)")
4949

@@ -66,7 +66,7 @@ def proto_args(parser, std_parser, module_parser):
6666
cgroup = smb_parser.add_argument_group("Command Execution", "Options for executing commands")
6767
cgroup.add_argument("--exec-method", choices={"wmiexec", "mmcexec", "smbexec", "atexec"}, default=None, help="method to execute the command. Ignored if in MSSQL mode (default: wmiexec)")
6868
cgroup.add_argument("--dcom-timeout", help="DCOM connection timeout, default is %(default)s secondes", type=int, default=5)
69-
cgroup.add_argument("--get-output-tries", help="Number of times atexec/smbexec/mmcexec tries to get results, default is %(default)s", type=int, default=5)
69+
cgroup.add_argument("--get-output-tries", help="Number of times atexec/smbexec/mmcexec tries to get results, default is %(default)s", type=int, default=10)
7070
cgroup.add_argument("--codec", default="utf-8", help="Set encoding used (codec) from the target's output (default: %(default)s). If errors are detected, run chcp.com at the target & map the result with https://docs.python.org/3/library/codecs.html#standard-encodings and then execute again with --codec and the corresponding codec")
7171
cgroup.add_argument("--force-ps32", action="store_true", help="force the PowerShell command to run in a 32-bit process")
7272
cgroup.add_argument("--no-output", action="store_true", help="do not retrieve command output")

nxc/protocols/smb/smbexec.py

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -98,9 +98,7 @@ def execute_remote(self, data):
9898
batch_file.write(command)
9999

100100
self.logger.debug("Hosting batch file with command: " + command)
101-
102101
self.logger.debug("Command to execute: " + command)
103-
104102
self.logger.debug(f"Remote service {self.__serviceName} created.")
105103

106104
try:
@@ -137,23 +135,32 @@ def get_output_remote(self):
137135
if self.__retOutput is False:
138136
self.__outputBuffer = ""
139137
return
140-
tries = 1
138+
139+
# TODO: It looks like the service is hanging anyway until the command is finished, so all this timeout logic is likely not needed
140+
# Still adding this for now to keep the structure similar until we can confirm the above
141+
tries = 0
141142
while True:
142143
try:
143144
self.logger.info(f"Attempting to read {self.__share}\\{self.__output}")
144145
self.__smbconnection.getFile(self.__share, self.__output, self.output_callback)
145146
break
146147
except Exception as e:
147-
if tries >= self.__tries:
148+
if tries > self.__tries:
148149
self.logger.fail("SMBEXEC: 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")
149150
break
150151
if str(e).find("STATUS_BAD_NETWORK_NAME") > 0:
151152
self.logger.fail(f"SMBEXEC: Getting the output file failed - target has blocked access to the share: {self.__share} (but the command may have executed!)")
152153
break
153-
if str(e).find("STATUS_SHARING_VIOLATION") >= 0 or str(e).find("STATUS_OBJECT_NAME_NOT_FOUND") >= 0:
154-
# Output not finished, let's wait
155-
sleep(2)
154+
# When executing powershell and the command is still running, we get a sharing violation
155+
# We can use that information to wait longer than if the file is not found (probably av or something)
156+
if "STATUS_SHARING_VIOLATION" in str(e):
157+
self.logger.info(f"File {self.__share}\\{self.__output} is still in use with {self.__tries - tries} left, retrying...")
156158
tries += 1
159+
sleep(1)
160+
elif "STATUS_OBJECT_NAME_NOT_FOUND" in str(e):
161+
self.logger.info(f"File {self.__share}\\{self.__output} not found with {self.__tries - tries} left, deducting 10 tries and retrying...")
162+
tries += 10
163+
sleep(1)
157164
else:
158165
self.logger.debug(str(e))
159166

nxc/protocols/smb/wmiexec.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -138,22 +138,31 @@ def get_output_remote(self):
138138
self.__outputBuffer = ""
139139
return
140140

141-
tries = 1
141+
tries = 0
142+
# Give the command a bit of time to execute before we try to read the output, 0.4 seconds was good in testing
143+
sleep(0.4)
142144
while True:
143145
try:
144146
self.logger.info(f"Attempting to read {self.__share}\\{self.__output}")
145147
self.__smbconnection.getFile(self.__share, self.__output, self.output_callback)
146148
break
147149
except Exception as e:
148-
if tries >= self.__tries:
150+
if tries > self.__tries:
149151
self.logger.fail("WMIEXEC: Could not retrieve output file, it may have been detected by AV. If it is still failing, try the 'wmi' protocol or another exec method")
150152
break
151153
if str(e).find("STATUS_BAD_NETWORK_NAME") > 0:
152154
self.logger.fail(f"SMB connection: target has blocked {self.__share} access (maybe command executed!)")
153155
break
154-
if str(e).find("STATUS_SHARING_VIOLATION") >= 0 or str(e).find("STATUS_OBJECT_NAME_NOT_FOUND") >= 0:
155-
sleep(2)
156+
# When executing powershell and the command is still running, we get a sharing violation
157+
# We can use that information to wait longer than if the file is not found (probably av or something)
158+
if "STATUS_SHARING_VIOLATION" in str(e):
159+
self.logger.info(f"File {self.__share}\\{self.__output} is still in use with {self.__tries - tries} left, retrying...")
156160
tries += 1
161+
sleep(1)
162+
elif "STATUS_OBJECT_NAME_NOT_FOUND" in str(e):
163+
self.logger.info(f"File {self.__share}\\{self.__output} not found with {self.__tries - tries} left, deducting 10 tries and retrying...")
164+
tries += 10
165+
sleep(1)
157166
else:
158167
self.logger.debug(str(e))
159168

0 commit comments

Comments
 (0)