如何通过RDP客户端窃取明文密码

 

0x00 前言

远程桌面是管理Windows服务器最常用的一种工具,管理员和黑客都喜欢使用RDP。通常情况下,用来登录RDP会话的凭据权限都比较高,因此自然会成为渗透过程中的绝佳目标。现在许多人主要通过LSASS来窃取凭据,但lsass.exe通常受EDR及反病毒软件的重点保护,因此我们需要研究可以替代的、比较新颖的方法。除此之外,针对LSASS的修改操作通常需要较高的访问权限。在本文中,我介绍了如何开发针对性工具,通过API hook方法从微软RDP客户端中提取明文凭据。通过这种方法,如果攻击者掌握目标用户的权限(比如通过钓鱼攻击),并且用户打开了RDP会话,那么就可以提取明文密码,无需提升权限。

 

0x01 API Hook

简而言之,API hook是拦截程序中的函数调用、将其重定向到另一个函数的一个过程。该过程需要重写目标函数在内存中的代码,以便重定向到另一个函数,后者将再次调用原始函数。目前关于API hook已经有多种实现方式,这些技术都比较复杂,可以单独拎出来介绍。

在本文中,我们将使用微软的Detours开源库,该库支持32位及64位进程。其他框架(如Frida)也能提供类似功能,但Detours非常轻量级,因此在使用时具有一定优势。为了演示该库的强大功能,我们接下来使用这个库来hook MessageBox函数。

在hook函数之前,我们需要找到两个信息:包含原始函数地址的目标指针以及hook函数。为了保证hook顺利,目标及hook函数都应当遵循相同数量的参数、参数类型以及调用约定。

如下所示,我们可以hook MessageBox,修改传递给原始函数的参数。

#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
    DetourRestoreAfterWith();
    DetourTransactionBegin();
    DetourUpdateThread(GetCurrentThread());
    DetourAttach(&(PVOID&)TrueMessageBox, _MessageBox); // Two Arguments
    DetourTransactionCommit();
    MessageBox(NULL, L"We can't be hooked", L"Hello", 0); // Detach Hooked Function
    DetourTransactionBegin();
    DetourUpdateThread(GetCurrentThread());
    DetourDetach(&(PVOID&)TrueMessageBox, _MessageBox);
      DetourTransactionCommit();
}

运行该程序后,本来应弹框提示无法被hook,然而由于我们hook该函数并修改了参数,因此结果如下图所示:

 

0x02 寻找目标

在hook之前,我们需要定位感兴趣的目标函数,这些函数接受的参数中通常包含我们感兴趣的数据。在RDP场景中,我们需要获取服务端主机名/IP地址、用户名及密码。API Monitor是一款非常强大的工具,可以帮我们attach进程、记录下所有的API调用、查看相关结果。

为了完成任务,我们在mstsc.exe上执行API监控操作,然后初始化一个RDP示例连接。

现在我们可以搜索所有API调用,寻找包含用户名的字符串。经过搜索后发现,有多个API调用中包含该字符串,但需要重点关注的是CredIsMarshaledCredentialW

根据MSDN文档,我们发现该函数只接受1个参数,该参数为指向C Unicode字符串的long指针:

为了确保hook该函数能获取到所需的数据,我们使用Windbg attach到mstsc.exe进程,在CredIsMarshaledCredentialW上设置断点。当用户尝试登录时,可以看到传递给该函数的第一个参数为Unicode字符串的地址:

采用相同的方法,我们搜索密码字符串,然后找到了CryptProtectMemory API以及指向密码字符串的一个指针。

根据API Monitor分析结果,CryptProtectMemory位于Crypt32.dll中,但这个结论并不准确。在最新版的Windows 10中,该函数是dpapi.dll的一个导出函数。为了确保该API上包含我们所需的数据,我们使用Windbg attach到目标进程,在CryptProtectMemory函数上设置断点。

检查内存数据,我们可以猜测传入的参数为指向某个结构的指针。由于我们寻找的信息位于该结构起始位置,因此我们无需完整解析这个结构。与前面的分析过程相反,这里能找到对该函数的多处调用,但很多都没有包含我们寻找的信息。根据观察,我们可知前4个字节包含密码字符串的大小。因此为了解决该问题,我们可以从内存中读取大小值,判断该值是否大于0x2,如果满足条件,则意味着该结构中包含密码信息。

采用同样的思路,我们可以定位到SspiPrepareForCredRead,该API的第2个参数即为我们需要提取的IP地址。

 

0x03 RdpThief

现在我们已经知道该hook哪些函数,才能提取所需信息。我们可以根据这些知识来开发RdpThief工具,方便窃取凭据信息。

RdpThief是一个独立的DLL,可以注入到mstsc.exe进程中,执行API hook操作,提取明文凭据并保存到文件中。该工具包含一个守护脚本,负责管理状态、监控新进程并将shellcode注入mstsc.exe中。我们使用sRDI工具将DLL转换成shellcode。启用该DLL后,RdpThief会每隔5秒钟获取进程列表,搜索mstsc.exe然后执行注入操作。

当在Cobalt Strike中加载攻击脚本后,我们就可以使用如下3条新命令:

  • rdpthief_enable:启动心跳检测,检查新的mstsc.exe进程,执行注入操作。
  • rdpthief_disable:禁用针对mstsc.exe的心跳检测,但没有卸载已加载的DLL。
  • rdpthief_dump:打印已提取的明文凭据。

简单的攻击过程如下图所示:

大家可以访问此处下载RdpThief源代码。

(完)