API Monitoring and Hooking for Offensive Tooling

Rio recently posted about his tool RdpThief which I thought was plain genius. It allows for offensive operators to steal RDP credentials by injecting RdpThief's DLL into the RDP client mstsc.exe.

Under the hood, RdpThief does the following:

  • hooks mstsc.exe functions responsible for dealing with user supplied credentials

  • intercepts the user supplied username, password, hostname during authentication

  • writes out intercepted credentials and hostname to a file

These are some notes of me tinkering with API Monitor, WinDBG and Detours (Microsoft's library for hooking Windows APIs) and reproducing some of the steps Rio took during his research and development of RdpThief.

These notes will serve me as a reference for future on how to identify and hook interesting functions that can be useful when writing offensive tooling.


If we launch mstsc.exe and attempt connecting to a remote host WS01:

..we are prompted to enter credentials:

If API monitor was attached to mstsc.exe when we tried to authenticate to the remote host WS01, we should now have a huge list of API calls invoked by mstsc.exe and its module logged.

Intercepting Username

If we search for a string spotless, we will find some functions that take spotless as a string argument and one of those functions is CredIsMarshaledCredentialW as shown below:

In WinDBG, if we put a breakpoint on ADVAPI32!CredIsMarshaledCredentialW and print out its first and only argument (stored in RCX register per x64 calling convention), we will see DESKTOP-NU8QCIB\spotless printed out:

bp ADVAPI32!CredIsMarshaledCredentialW "du @rcx"

Intercepting Hostname

To find the hostname of the RDP connection, we find API calls that took ws01 (our hostname) as a string argument. Although RdpThief hooks SSPICLI!SspiPrepareForCredRead (hostname supplied as a second argument), another function that could be considered for hooking is CredReadW (hostname a the first argument) as seen below:

If we jump back to WinDBG and set another breakpoint for CredReadW and attempt to RDP to our host ws01, we get a hit:

bp ADVAPI32!CredReadW "du @rcx"

Out of curiosity, let's also put a breakpoint on SSPICLI!SspiPrepareForCredRead and once it's hit, print out the second argument supplied to the function, which is stored in the RDX register:

bp SSPICLI!SspiPrepareForCredRead
du @rdx

Intercepting Password

We now know the functions required to hook for intercepting the username and the hostname. What's left is hooking the function that deals in one way or another with the password and from Rio's article, we know it's the DPAPI CryptProtectMemory.

Weirdly, searching for my password in API Monitor resulted in no results although I could see it in plain text in CryptUnprotectMemory:

Reviewing CryptProtectMemory calls manually in API Monitor showed no plaintext password either, although there were multiple calls to the function and I would see the password already encrypted:

From the above screenshot, note the size of the encrypted blob is 32 bytes - we will come back to this in WinDBG

While having issues with API Monitor, let's put a breakpoint on CryptProtectMemory in WinDBG and print out a unicode string (this should be the plaintext password passed to the function for encryption) starting 4 bytes into the address (first 4 bytes indicate the size of the encrypted data) pointed by the RCX register:

bp dpapi!cryptprotectmemory "du @rcx+4"

Below shows the plain text password on a second break:

Earlier, I noted the 32 bytes encrypted blob seen in CryptProtectMemory function call (in API Monitor) and also mentioned the 4 byte offset into RCX that holds the size of the encrypted blob - below shows that - first 4 bytes found at RCX (during the CryptProtectMemory break) are 0x20 or 32 in decimal:

RdpThief in Action

Compiling RdpThief provides us with 2 DLLs for 32 and 64 bit architectures. Let's inject the 64 bit DLL into mstsc.exe and attempt to RDP into ws01 - we see the credentials getting intercepted and written to a file:

Intercepting Hostname via CredReadW

I wanted to confirm if my previous hypothesis about hooking CredReadW for intercepting the hostname was possible, so I made some quick changes to the RdpThief's project to test it.

I commented out the _SspiPrepareForCredRead signature and hooked CreadReadW with a new function called HookedCredReadW which will pop a message box each time CredReadW is called and print its first argument as the message box text.

Also, it will update the lpServer variable which is later written to the file creds.txt together with the username and password.

Below screenshot shows the code changes:

Of course, we need to register the new hook HookedCredReadW and unregister the old hook _SspiPrepareForCredRead:

Compiling and injecting the new RdpThief DLL confirms that the CredReadW can be used to intercept the the hostname:


Last updated