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源代码。