Skip to content

Commit 0f62ca5

Browse files
committed
better catch and add no output option
1 parent f17f091 commit 0f62ca5

2 files changed

Lines changed: 90 additions & 159 deletions

File tree

nxc/protocols/rdp.py

Lines changed: 89 additions & 159 deletions
Original file line numberDiff line numberDiff line change
@@ -398,16 +398,18 @@ async def _send_win_r(self):
398398

399399
self.logger.debug("Win+R sent successfully")
400400
return True
401+
except (ConnectionResetError, ConnectionError, OSError) as e:
402+
self.logger.debug(f"Connection error while waiting for clipboard: {e!s}")
403+
self.logger.fail("Connection was reset by the remote host")
404+
return False
401405
except Exception as e:
402406
self.logger.debug(f"Error sending Win+R: {e!s}")
403-
404407
self.logger.debug("Using fallback approach for opening command prompt")
405408
return False
406409

407-
async def execute_cmd(self, payload, encoding=None, capture_screenshot=False):
408-
"""Execute a command using cmd.exe"""
410+
async def execute_shell(self, payload, get_output, shell_type):
409411
# Append | clip to send output to clipboard
410-
payload_with_clip = f"{payload} | clip & exit"
412+
payload_with_clip = f"{payload} | clip & exit" if shell_type == "cmd" else f"{payload} | clip; exit"
411413
self.logger.debug(f"Executing command: {payload_with_clip}")
412414

413415
# Create a connection
@@ -418,160 +420,98 @@ async def execute_cmd(self, payload, encoding=None, capture_screenshot=False):
418420
self.logger.debug(f"Error connecting to RDP: {e!s}")
419421
return None
420422

421-
self.logger.debug("Waiting for clipboard to be ready...")
422-
clipboard_ready = False
423-
await asyncio.sleep(self.args.cmd_delay)
424-
425-
timeout_counter = 0
426-
while not clipboard_ready and timeout_counter < 100: # 10 second timeout
427-
try:
428-
data = await asyncio.wait_for(self.conn.ext_out_queue.get(), timeout=0.1)
429-
if hasattr(data, "type") and data.type.name == "CLIPBOARD_READY":
430-
clipboard_ready = True
431-
self.logger.debug("Clipboard is ready!")
432-
break
433-
except asyncio.TimeoutError:
434-
timeout_counter += 1
435-
continue
436-
except Exception as e:
437-
self.logger.debug(f"Error waiting for clipboard: {e!s}")
438-
break
439-
440-
if not clipboard_ready:
441-
self.logger.fail("Warning: Clipboard may not be fully initialized, no output can be retrieved")
442-
443-
# Wait for desktop to be available
444-
await asyncio.sleep(self.args.cmd_delay)
445-
446423
try:
447-
# Try to open Run dialog using Windows+R
448-
self.logger.debug("Attempting to open Run dialog")
449-
win_r_success = await self._send_win_r()
450-
451-
if win_r_success:
452-
# Type cmd.exe in Run dialog
453-
self.logger.debug("Launching cmd.exe via Run dialog")
454-
await self._send_keystrokes("cmd.exe")
455-
await self._send_enter()
456-
await asyncio.sleep(self.args.cmd_delay) # Wait for cmd window to open
457-
else:
458-
# Fallback: Try direct command typing (assumes cmd may already be open)
459-
self.logger.debug("Sending cmd.exe command directly")
460-
await self._send_keystrokes("cmd.exe")
461-
await self._send_enter()
424+
if get_output:
425+
self.logger.debug("Waiting for clipboard to be ready...")
426+
clipboard_ready = False
462427
await asyncio.sleep(self.args.cmd_delay)
463-
464-
# Type the command with | clip
465-
self.logger.debug(f"Typing command: {payload_with_clip}")
466-
await self._send_keystrokes(payload_with_clip)
467-
await self._send_enter()
468-
469-
# Wait for command to execute
470-
await asyncio.sleep(self.args.cmd_delay)
471428

472-
# Get the current clipboard text
473-
self.logger.debug("Getting clipboard content...")
474-
clipboard_text = await self.conn.get_current_clipboard_text()
475-
476-
if clipboard_text:
477-
self.logger.debug("Command output retrieved from clipboard:")
478-
for line in clipboard_text.lstrip().strip("\n").splitlines():
479-
self.logger.highlight(line)
480-
else:
481-
self.logger.warning("Clipboard is empty or contains non-text data")
482-
483-
self.logger.debug("Command execution completed")
484-
return clipboard_text
429+
timeout_counter = 0
430+
while not clipboard_ready and timeout_counter < 100: # 10 second timeout
431+
try:
432+
data = await asyncio.wait_for(self.conn.ext_out_queue.get(), timeout=0.1)
433+
if hasattr(data, "type") and data.type.name == "CLIPBOARD_READY":
434+
clipboard_ready = True
435+
self.logger.debug("Clipboard is ready!")
436+
break
437+
except asyncio.TimeoutError:
438+
timeout_counter += 1
439+
continue
440+
except (ConnectionResetError, ConnectionError, OSError) as e:
441+
self.logger.debug(f"Connection error while waiting for clipboard: {e!s}")
442+
self.logger.fail("Connection was reset by the remote host")
443+
return ""
444+
except Exception as e:
445+
self.logger.debug(f"Error waiting for clipboard: {e!s}")
446+
self.logger.fail("Warning: Clipboard may not be fully initialized, no output can be retrieved")
447+
return ""
448+
449+
# Wait for desktop to be available
450+
await asyncio.sleep(self.args.cmd_delay)
485451

486-
finally:
487-
# Always clean up the connection
488-
if self.conn is not None:
489-
self.logger.debug("Terminating RDP connection")
490-
try:
491-
await self.conn.terminate()
492-
except Exception as e:
493-
self.logger.debug(f"Error terminating connection: {e!s}")
494-
495-
async def execute_ps(self, payload, capture_screenshot=False):
496-
"""Execute a command using PowerShell"""
497-
# Append | clip to send output to clipboard
498-
payload_with_clip = f"{payload} | clip & exit"
499-
self.logger.debug(f"Executing PowerShell command: {payload_with_clip}")
500-
501-
# Create a connection
502-
try:
503-
self.conn = RDPConnection(iosettings=self.iosettings, target=self.target, credentials=self.auth)
504-
await self.connect_rdp()
505-
except Exception as e:
506-
self.logger.debug(f"Error connecting to RDP: {e!s}")
507-
return None
508-
509-
self.logger.debug("Waiting for clipboard to be ready...")
510-
clipboard_ready = False
511-
await asyncio.sleep(self.args.cmd_delay)
512-
513-
timeout_counter = 0
514-
while not clipboard_ready and timeout_counter < 100: # 10 second timeout
515452
try:
516-
data = await asyncio.wait_for(self.conn.ext_out_queue.get(), timeout=0.1)
517-
if hasattr(data, "type") and data.type.name == "CLIPBOARD_READY":
518-
clipboard_ready = True
519-
self.logger.debug("Clipboard is ready!")
520-
break
521-
except asyncio.TimeoutError:
522-
timeout_counter += 1
523-
continue
524-
except Exception as e:
525-
self.logger.debug(f"Error waiting for clipboard: {e!s}")
526-
break
527-
528-
if not clipboard_ready:
529-
self.logger.fail("Warning: Clipboard may not be fully initialized, no output can be retrieved")
530-
531-
# Wait for desktop to be available
532-
await asyncio.sleep(self.args.cmd_delay)
533-
534-
try:
535-
# Try to open Run dialog using Windows+R
536-
self.logger.debug("Attempting to open Run dialog")
537-
win_r_success = await self._send_win_r()
453+
# Try to open Run dialog using Windows+R
454+
self.logger.debug("Attempting to open Run dialog")
455+
win_r_success = await self._send_win_r()
456+
457+
if win_r_success:
458+
self.logger.debug(f"Launching {shell_type} via Run dialog")
459+
await self._send_keystrokes(f"{shell_type}.exe")
460+
await self._send_enter()
461+
await asyncio.sleep(self.args.cmd_delay) # Wait for cmd window to open
462+
else:
463+
# Fallback: Try direct command typing (assumes cmd may already be open)
464+
self.logger.debug(f"Sending {shell_type} command directly")
465+
await self._send_keystrokes(f"{shell_type}.exe")
466+
await self._send_enter()
467+
await asyncio.sleep(self.args.cmd_delay)
538468

539-
if win_r_success:
540-
# Type powershell in Run dialog
541-
self.logger.debug("Launching PowerShell via Run dialog")
542-
await self._send_keystrokes("powershell")
543-
await self._send_enter()
544-
await asyncio.sleep(self.args.cmd_delay) # Wait for PowerShell window to open
545-
else:
546-
# Fallback: Try direct PowerShell typing (assumes we might be at a prompt)
547-
self.logger.debug("Sending powershell command directly")
548-
await self._send_keystrokes("powershell")
469+
# Type the command with | clip
470+
self.logger.debug(f"Typing command: {payload_with_clip}")
471+
await self._send_keystrokes(payload_with_clip)
549472
await self._send_enter()
473+
474+
# Wait for command to execute
550475
await asyncio.sleep(self.args.cmd_delay)
551-
552-
# Type the PowerShell command with | clip
553-
self.logger.debug(f"Typing PowerShell command: {payload_with_clip}")
554-
await self._send_keystrokes(payload_with_clip)
555-
await self._send_enter()
556-
557-
# Wait for command to execute
558-
await asyncio.sleep(self.args.cmd_delay)
559476

560-
# Get the current clipboard text
561-
self.logger.debug("Getting clipboard content...")
562-
clipboard_text = await self.conn.get_current_clipboard_text()
563-
564-
if clipboard_text:
565-
self.logger.debug("PowerShell command output retrieved from clipboard:")
566-
for line in clipboard_text.strip("\n"):
567-
self.logger.highlight(line)
568-
else:
569-
self.logger.warning("Clipboard is empty or contains non-text data")
477+
if get_output:
478+
# Get the current clipboard text
479+
self.logger.debug("Getting clipboard content...")
480+
clipboard_text = await self.conn.get_current_clipboard_text()
481+
482+
if clipboard_text:
483+
self.logger.debug("Command output retrieved from clipboard:")
484+
for line in clipboard_text.lstrip().strip("\n").splitlines():
485+
self.logger.highlight(line)
486+
else:
487+
self.logger.fail("Clipboard is empty or contains non-text data")
488+
return clipboard_text
570489

571-
self.logger.debug("Command execution completed")
572-
return clipboard_text
573-
490+
self.logger.debug("Command execution completed")
491+
return None
492+
493+
except (ConnectionResetError, ConnectionError, OSError) as e:
494+
self.logger.debug(f"Connection error during command execution: {e!s}")
495+
self.logger.fail("Connection was reset by the remote host during command execution")
496+
return None
497+
except Exception as e:
498+
self.logger.debug(f"Error during command execution: {e!s}")
499+
if "cannot unpack non-iterable NoneType object" in str(e):
500+
self.logger.fail("RDP connection was terminated unexpectedly")
501+
else:
502+
self.logger.fail(f"Command execution failed: {e!s}")
503+
return None
504+
505+
except (ConnectionResetError, ConnectionError, OSError) as e:
506+
self.logger.debug(f"Connection error: {e!s}")
507+
self.logger.fail("Connection was reset by the remote host")
508+
return None
509+
except Exception as e:
510+
self.logger.debug(f"Unexpected error: {e!s}")
511+
self.logger.fail(f"Command execution failed: {e!s}")
512+
return None
574513
finally:
514+
# Always clean up the connection
575515
if self.conn is not None:
576516
self.logger.debug("Terminating RDP connection")
577517
try:
@@ -584,22 +524,12 @@ def execute(self, payload=None, shell_type="cmd"):
584524
if not payload:
585525
payload = self.args.execute
586526

587-
588-
# Check if screenshot is requested
589-
capture_screenshot = hasattr(self.args, "screenshot") and self.args.screenshot
590-
591-
# Debug the args object to verify the screenshot flag
592-
self.logger.debug(f"Args object has screenshot attribute: {hasattr(self.args, 'screenshot')}")
593-
if hasattr(self.args, "screenshot"):
594-
self.logger.debug(f"Screenshot flag value: {self.args.screenshot}")
595-
596-
if capture_screenshot:
597-
self.logger.info("Will capture screenshot of command output")
527+
get_output = bool(not self.args.no_output)
598528

599529
self.logger.success(f"Executing command: {payload} with delay {self.args.cmd_delay} seconds")
600530

601531
try:
602-
result = asyncio.run(self.execute_cmd(payload, capture_screenshot=capture_screenshot)) if shell_type == "cmd" else asyncio.run(self.execute_ps(payload, capture_screenshot=capture_screenshot))
532+
result = asyncio.run(self.execute_shell(payload, get_output, shell_type))
603533

604534
if result:
605535
self.logger.debug("Command execution completed")

nxc/protocols/rdp/proto_args.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,6 @@ def proto_args(parser, parents):
2121
cgroup.add_argument("-x", metavar="COMMAND", dest="execute", help="execute the specified command")
2222
cgroup.add_argument("-X", metavar="PS_COMMAND", dest="ps_execute", help="execute the specified PowerShell command")
2323
cgroup.add_argument("--cmd-delay", type=int, default=3, help="Sleep time before executing command")
24+
cgroup.add_argument("--no-output", action="store_true", help="do not retrieve command output")
2425

2526
return parser

0 commit comments

Comments
 (0)