-
Notifications
You must be signed in to change notification settings - Fork 680
Description
Describe the bug
Whenever I used "nxc rdp" to check a range of addresses i noticed that the results across the range were somewhat unreliable. Although the discovery phase worked quite well (e.g. "[*] Windows 10 or Windows Server 2016 Build 19041 (name:DESK02) (domain:example.local) (nla:False)" showing up every time), the part where credentials were checked against the rdp servers resulted in inconsistent behaviour such as missed/skipped login information.
To Reproduce
This can be reproduced in any network with some rdp servers in it. I'm not sure if the operating system matters, I have only executed tests against windows servers and clients.
The more hosts are present, the better the discrepancy can be shown. Execute nxc prefixed with "time" to check scan duration.
I did 3 runs of nxc rdp against the same small network with 5 hosts:
Run 1 (I hit CTRL+C after 21 minutes)
Command: time nxc rdp 192.168.0.0/24 -d <domain> -u <username> -p '<password>'
Result:
[*] Initializing RDP protocol database
RDP 192.168.0.1 3389 DC01 [*] Windows 10 or Windows Server 2016 Build 20348 (name:DC01) (domain:<domain>) (nla:False)
RDP 192.168.0.12 3389 SF [*] Windows 10 or Windows Server 2016 Build 20348 (name:SF) (domain:<domain>) (nla:True)
RDP 192.168.0.11 3389 CDC [*] Windows 10 or Windows Server 2016 Build 20348 (name:CDC) (domain:<domain>) (nla:True)
RDP 192.168.0.22 3389 DESK02 [*] Windows 10 or Windows Server 2016 Build 19041 (name:DESK02) (domain:<domain>) (nla:False)
RDP 192.168.0.13 3389 VDA01 [*] Windows 10 or Windows Server 2016 Build 20348 (name:VDA01) (domain:<domain>) (nla:True)
RDP 192.168.0.1 3389 DC01 [+] <domain>\<username>:[REDACTED]
RDP 192.168.0.12 3389 SF [+] <domain>\<username>:[REDACTED]
RDP 192.168.0.22 3389 DESK02 [+] <domain>\<username>:[REDACTED]
RDP 192.168.0.11 3389 CDC [+] <domain>\<username>:[REDACTED]
Running nxc against 256 targets ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╸ 100% 0:00:01
Running nxc against 256 targets ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╸ 100% 0:00:01
Running nxc against 256 targets ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╸ 100% 0:00:01
real 21m11.402s
user 1m0.628s
sys 0m2.356s
Run 2 (I hit CTRL+C after almost 6 minutes)
Command: time nxc rdp 192.168.0.0/24 -d <domain> -u <username> -p '<password>'
Result:
[*] Initializing RDP protocol database
RDP 192.168.0.1 3389 DC01 [*] Windows 10 or Windows Server 2016 Build 20348 (name:DC01) (domain:<domain>) (nla:False)
RDP 192.168.0.11 3389 CDC [*] Windows 10 or Windows Server 2016 Build 20348 (name:CDC) (domain:<domain>) (nla:True)
RDP 192.168.0.13 3389 VDA01 [*] Windows 10 or Windows Server 2016 Build 20348 (name:VDA01) (domain:<domain>) (nla:True)
RDP 192.168.0.12 3389 SF [*] Windows 10 or Windows Server 2016 Build 20348 (name:SF) (domain:<domain>) (nla:True)
RDP 192.168.0.22 3389 DESK02 [*] Windows 10 or Windows Server 2016 Build 19041 (name:DESK02) (domain:<domain>) (nla:False)
RDP 192.168.0.1 3389 DC01 [+] <domain>\<username>:[REDACTED]
RDP 192.168.0.11 3389 CDC [+] <domain>\<username>:[REDACTED]
Running nxc against 256 targets ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╸ 99% 0:00:01
Running nxc against 256 targets ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╸ 99% 0:00:01
^CException ignored in: <module 'threading' from '/usr/lib/python3.12/threading.py'>
Traceback (most recent call last):
File "/usr/lib/python3.12/threading.py", line 1592, in _shutdown
atexit_call()
File "/usr/lib/python3.12/concurrent/futures/thread.py", line 31, in _python_exit
t.join()
File "/usr/lib/python3.12/threading.py", line 1147, in join
self._wait_for_tstate_lock()
File "/usr/lib/python3.12/threading.py", line 1167, in _wait_for_tstate_lock
if lock.acquire(block, timeout):
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
KeyboardInterrupt:
real 5m44.807s
user 0m20.680s
sys 0m1.266s
Run 3 (I hit CTRL+C after 5.5 minutes)
Command: time nxc rdp 192.168.0.0/24 -d <domain> -u <username> -p '<password>'
Result:
time nxc rdp 192.168.0.0/24 -d <domain> -u <username> -p '<password>'
[*] Initializing RDP protocol database
RDP 192.168.0.13 3389 VDA01 [*] Windows 10 or Windows Server 2016 Build 20348 (name:VDA01) (domain:<domain>) (nla:True)
RDP 192.168.0.22 3389 DESK02 [*] Windows 10 or Windows Server 2016 Build 19041 (name:DESK02) (domain:<domain>) (nla:False)
RDP 192.168.0.1 3389 DC01 [*] Windows 10 or Windows Server 2016 Build 20348 (name:DC01) (domain:<domain>) (nla:False)
RDP 192.168.0.12 3389 SF [*] Windows 10 or Windows Server 2016 Build 20348 (name:SF) (domain:<domain>) (nla:True)
RDP 192.168.0.11 3389 CDC [*] Windows 10 or Windows Server 2016 Build 20348 (name:CDC) (domain:<domain>) (nla:True)
Running nxc against 256 targets ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╺ 98% 0:00:01
^CException ignored in: <module 'threading' from '/usr/lib/python3.12/threading.py'>
Traceback (most recent call last):
File "/usr/lib/python3.12/threading.py", line 1592, in _shutdown
atexit_call()
File "/usr/lib/python3.12/concurrent/futures/thread.py", line 31, in _python_exit
t.join()
File "/usr/lib/python3.12/threading.py", line 1147, in join
self._wait_for_tstate_lock()
File "/usr/lib/python3.12/threading.py", line 1167, in _wait_for_tstate_lock
if lock.acquire(block, timeout):
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
KeyboardInterrupt:
real 5m26.897s
user 0m17.428s
sys 0m1.002s
The three consecutive scans above show very different behaviour. Although the hosts show up in all three scans, the logon detection was inconsistent. In the first scan, credentials could be verified against 4 hosts. In the second scan logins were performed on two hosts and in the third scan no valid logon appeared at all.
Furthermore, I needed to terminate all three scans with CTRL+C since they did not finish (maybe they would have after a long time, but more than 20 minutes were enough to kill the processes)
Expected behavior
Based on my knowledge of the systems, this would be the correct behaviour:
Command: nxc rdp 192.168.0.0/24 -d <domain> -u <username> -p '<password>'
[*] Initializing RDP protocol database
RDP 192.168.0.1 3389 DC01 [*] Windows 10 or Windows Server 2016 Build 20348 (name:DC01) (domain:<domain>) (nla:False)
RDP 192.168.0.12 3389 SF [*] Windows 10 or Windows Server 2016 Build 20348 (name:SF) (domain:<domain>) (nla:True)
RDP 192.168.0.13 3389 VDA01 [*] Windows 10 or Windows Server 2016 Build 20348 (name:VDA01) (domain:<domain>) (nla:True)
RDP 192.168.0.11 3389 CDC [*] Windows 10 or Windows Server 2016 Build 20348 (name:CDC) (domain:<domain>) (nla:True)
RDP 192.168.0.22 3389 DESK02 [*] Windows 10 or Windows Server 2016 Build 19041 (name:DESK02) (domain:<domain>) (nla:False)
RDP 192.168.0.1 3389 DC01 [+] <domain>\<username>:[REDACTED]
RDP 192.168.0.12 3389 SF [+] <domain>\<username>:[REDACTED]
RDP 192.168.0.13 3389 VDA01 [+] <domain>\<username>:[REDACTED] (Pwn3d!)
RDP 192.168.0.11 3389 CDC [+] <domain>\<username>:[REDACTED]
RDP 192.168.0.22 3389 DESK02 [+] <domain>\<username>:[REDACTED]
Running nxc against 256 targets ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100% 0:00:00
Note that the host VDA01 shows 'Pwn3d!' because the user has RDP permissions on that host. In my three scans above, this info does not show up at all.
Debugging
Here's the thing - i did some debugging and found 2 locations were the scans hung.
Location 1: https://github.com/Pennyw0rth/NetExec/blob/main/nxc/protocols/rdp.py#L191
Here the scan seems to wait for the connection to terminate. If it never happens, the scan gets stuck on that line of code. Note, that even the RDP timeout value does not kick in in this case. This might be a dead lock, within the connection objects.
I noticed that when I add a manual termination after this block, the blocker at this location disappeared:
190: async def connect_rdp(self):
191: _, err = await asyncio.wait_for(self.conn.connect(), timeout=self.args.rdp_timeout)
192: if err is not None:
193: raise err
194: + self.conn._RDPConnection__terminate_called = True
Location 2: https://github.com/skelsec/aardwolf/blob/main/aardwolf/connection.py#L345
This is a tricky one, because it leaves the boundaries of "nxc". For some reason the second issue was that the following line never terminates:
_, err = await self.__handle_mandatory_capability_exchange()
This seems to fetch the capability PDU including encryption types etc.
I did a quick and dirty test, and commented out this part, just to see if there are more blockers:
345: #_, err = await self.__handle_mandatory_capability_exchange()
346: #if err is not None:
347: # raise err
348: #logger.debug('mandatory capability exchange OK')
After i ran the scan again (with changes in Location 1 and Location 2) it worked like a charm:
Command: time nxc rdp 192.168.0.0/24 -d <domain> -u <username> -p '<password>'
[*] Initializing RDP protocol database
RDP 192.168.0.12 3389 SF [*] Windows 10 or Windows Server 2016 Build 20348 (name:SF) (domain:<domain>) (nla:True)
RDP 192.168.0.13 3389 VDA01 [*] Windows 10 or Windows Server 2016 Build 20348 (name:VDA01) (domain:<domain>) (nla:True)
RDP 192.168.0.22 3389 DESK02 [*] Windows 10 or Windows Server 2016 Build 19041 (name:DESK02) (domain:<domain>) (nla:False)
RDP 192.168.0.11 3389 CDC [*] Windows 10 or Windows Server 2016 Build 20348 (name:CDC) (domain:<domain>) (nla:True)
RDP 192.168.0.1 3389 DC01 [*] Windows 10 or Windows Server 2016 Build 20348 (name:DC01) (domain:<domain>) (nla:False)
RDP 192.168.0.12 3389 SF [+] <domain>\<username>:[REDACTED]
RDP 192.168.0.13 3389 VDA01 [+] <domain>\<username>:[REDACTED] (Pwn3d!)
RDP 192.168.0.22 3389 DESK02 [+] <domain>\<username>:[REDACTED]
RDP 192.168.0.11 3389 CDC [+] <domain>\<username>:[REDACTED]
RDP 192.168.0.1 3389 DC01 [+] <domain>\<username>:[REDACTED]
Running nxc against 256 targets ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100% 0:00:00
real 0m11.786s
user 0m5.008s
sys 0m0.983s
It detected all logins perfectly, and reliably across multiple scans.
However commenting out the capability detection in aardwolf was only a quick check, and the proper detection of capabilities plays an important role when a graphical interaction is necessary (e.g. using the "screenshot" or the "command" feature). So it might need proper fixing, either on the end of aardwolf, or in this repo. I can't figure out a fix myself, because I'm not overly familiar with the RDP protocol, but maybe my insights might help you to find out what's going on. It appears to be some sort of dead lock situation, especially when a host stops responding (in case that is what happens in the capability detection step).
I also tried combinations of not modifying Location 1 and modifying Location 2, and vice versa, but that did not have an effect. It needed both modifications to stop the dead locks.
NetExec info
- OS: Ubuntu 24.04
- Version of nxc:
1.5.1 - Yippie-Ki-Yay - 8dbf09cc - 135 - Installed from: pipx