RdpThief: Extracting Clear-text Credentials from Remote Desktop Clients


Remote Desktop is one of the most widely used tools for managing Windows Servers. Admins love using RDP and so do attackers. Often the credentials that are used to login to RDP sessions are privileged, making them a perfect target during a red teaming operation. Although traditionally, many people focus on credential theft using LSASS, manipulation of lsass.exe is often monitored by both EDR and anti-virus so the natural progression is to research alternatives that may be less closely scrutinised. Additionally, manipulation of LSASS typically requires privileged access. In this blogpost I will describe the process I followed to write a tool that will extract clear-text credentials from the Microsoft RDP client using API hooking. Using this approach, if you are already operating under the privileges of the compromised user (e.g. as a result of a phish) and the user has an RDP session open, you are able to extract the clear-text credentials without privilege escalation.

API Hooking

In a nutshell, API hooking is the process of intercepting a function call in a program by redirecting it to another function. This is done by re-writing the in-memory code for the target function in order to be redirected to the other function, which later-on calls the original function. There are several API hooking methods and these techniques are complex enough to warrant a separate blog-post.

For the purpose of this tutorial, we will use the Microsoft Detours library which is open-source and supports both 32-bit and 64-bit processes. Other frameworks such as Frida provide similar capabilities, however Detours is extremely lightweight and as such this provides certain advantages when working over a pivot. To demonstrate how powerful this library is, we will use it to create a Hook for the MessageBox function.

Before hooking a function, we need two things; a target pointer containing the address of the original function and the hooked function. For the hooking to work properly, both the target and hooked function should have the same exact number of arguments, parameters type and calling convention.

On the example below we hook the MessageBox call and modify the parameters that are passed to the original function.

#include "pch.h"
#include <Windows.h>
#include <iostream>
#include <detours.h>

static int(WINAPI * TrueMessageBox)(HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType) = MessageBox;
int WINAPI _MessageBox(HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType) {
     return TrueMessageBox(NULL, L"Hooked", L"Hooked", 0);
int main()
// Hook MessageBox
DetourAttach(&(PVOID&)TrueMessageBox, _MessageBox); // Two Arguments DetourTransactionCommit();
MessageBox(NULL, L"We can't be hooked", L"Hello", 0); // Detach Hooked Function
DetourDetach(&(PVOID&)TrueMessageBox, _MessageBox); DetourTransactionCommit();

Running the program, the second message box was expected to say Unhooked but because we hooked it and modified the parameters the message box is different as can be seen from the image below:

Finding Targets to Hook

Before doing any hooking, we need to identify the functions that are a point of interest. These functions would ideally take as parameters the data in which we are interested; in this case, server hostname/IP, username and password. API monitor is a really powerful tool for this scenario. It allows you to attach to a process, log all the API calls and browse the results.

To do this we attach API monitor to mstsc.exe and initiate a sample connection:

Now we can search all the API calls for the string that was provided as the username. In this case, several API calls contained this string but the most interesting one was CredIsMarshaledCredentialW.

Using MSDN we can see it only takes one parameter of type long pointer to C Unicode string.

To make sure we get the correct data by hooking this function, we attach Windbg to mstsc.exe and set a breakpoint at the CredIsMarshaledCredentialW. When trying to login, we can see that the first parameter passed to the function is the address of the Unicode string.

Following the same approach, we search for the Password string and we see a call to CryptProtectMemory with a pointer to the password string.

According to API monitor, the CryptProtectMemory is located in Crypt32.dll which is not correct since the function was exported by dpapi.dll on the latest version of Win10. Just to make sure we have the correct data on this API call as well, we attach Windbg to the process and set a breakpoint to the CryptProtectMemory function.

From inspecting the memory, we can assume that the passed parameter is a pointer to a structure. Since the information we are after is in the beginning of the structure, we don’t have to parse it entirely. In contrary to the previous example, there are several calls to the function which don’t contain the information we are after. We can observe that the first 4 bytes contain the size of the password string. We can read the size from the memory and compare if it is bigger than 0x2 and if the condition is true it means that the structure contains the password and get around this issue.

The same procedure was followed for the SspiPrepareForCredRead which received the IP address as the second parameter.

RdpThief Demonstration

We now have a clear map of what functions needs to be hooked to extract the information which can be used as a foundation to implement something similar to RdpThief.

RdpThief by itself is a standalone DLL that when injected to the mstsc.exe process, will perform the API hooking, extract the clear-text credentials and save them to a file. An aggressor script accompanies it, which is responsible for managing the state, monitoring for new processes and injecting the shellcode in mstsc.exe. The DLL has been converted to shellcode using the sRDI project. When enabled, RdpThief will get the process list every 5 seconds, search for mstsc.exe, and inject to it.

When the aggressor script is loaded on Cobalt Strike, three new commands will be available:

  • rdpthief_enable: Enables the heartbeat check of new mstsc.exe processes and injects into them.
  • rdpthief_disable: Disables the hearbeat check of new mstsc.exe but will not unload the already loaded DLL.
  • rdpthief_dump: Prints the extracted credentials if any

Below you can find a simple demonstration of the tool:

The RdpThief source code is available for download.

This blog post was written by Rio Sherri.

written by

MDSec Research

Ready to engage
with MDSec?

Copyright 2021 MDSec