Skip to content

RDP Login checks are not reliable and connections are hanging #1169

@bka-dev

Description

@bka-dev

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    dependenciesPull requests that update a dependency file

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions