仅供演示目的。完整利用程序适用于易受攻击的 Windows Server 系统。
查看分析文章 CVE-2022-30136 “Windows 网络文件系统漏洞”分析。
我撰写本文旨在展示在开发滥用 CVE-2022-30136 漏洞的 Core Impact 利用程序“Windows 网络文件系统远程”时进行的分析过程。
Windows 网络文件系统远程代码执行漏洞是一个大小计算错误,该错误发生在使用 NFS 4.1 版本处理复合请求(COMPOUND REQUEST)并创建服务器响应时。
服务器计算出的池分配大小小于实际所需,随后在复制数据以生成响应时,导致缓冲区溢出。
nfssvr.sys 中的 Nfs4SvrXdrpGetEncodeOperationResultByteCount 函数为每个操作被调用,并返回一个比实际需要小(每个操作少 4 字节)的大小。
对 Nfs4SvrXdrpGetEncodeOperationResultByteCount 进行了修补。
该函数在每个复合请求(COMPOSE REQUEST)的操作(OPERATION)中被调用,根据操作码(OPCODE)返回每个操作所需的字节数。然后这些字节数被加到头部和响应的其他部分。接着计算整个响应的最终大小以进行分配,然后复制数据并回复。
在每种情况下,我们可以看到,易受攻击版本中每个操作返回的大小值比修补后的版本小四个字节。
我为 Windows Server 2019 构建了 PoC。
下面是用于此 PoC 的易受攻击版本 nfssvr.sys,以及 Windows Server 2019 的修补版本:

下一张图片显示了差异中的 CASE 26:

在 CASE 26 的例子中,我们可以看到添加到计算值中的常量在易受攻击版本中是 0x2c,而在修补版本中是 0x30。
每个对应操作码的 CASE 中都可以看到相同的情况。易受攻击版本总是返回比修补版本小四个字节的大小。
我们不会展示所有情况,因为补丁对所有操作码都类似。
Nfs4SvrXdrpGetEncodeOperationResultByteCount 的父函数是 Nfs4SvrXdrEncodeCompoundResults。它读取复合请求中发送的操作数量。
在此 PoC 中,该值为 0x34(52d)。当我的 PoC 连接到服务器的 2049 端口(NFS 默认端口)时,我需要设置一个条件断点以便停止。


在此实例中,当 number_of_operations=0x34 时停止。

这里分配了带有标签 ARGS 的池。

然后我将创建一个名为 TAG_ARGS_0x10e0 的结构体来逆向字段。

它将 number_of_operations 复制到 r13,并为每个操作循环进入易受攻击函数,直到计数器达到 r13 的值。

这表明第一个 package_OPCODE= 0x35,对应复合请求中第一个必需操作的 SEQUENCE。在下图中,箭头指向我的包中的这个操作码。

这里我们可以看到易受攻击函数的参数。


在易受攻击函数内部,它读取操作码并跳转到相应的 CASE。

从原始操作码值 (53) 中减去三。

然后跳转到 CASE 50,返回 0x28 作为此操作所需的大小。


在差异中我们可以看到修补版本返回 0x2c。

此返回值被添加到响应中其他字段的先前值,以计算操作的大小。在本例中,该值为 0x40c。

下面我们可以看到正在添加的值:

当退出循环时,计算出总大小。在本例中,总大小为 0x1310。

我们可以通过公式 number_of_operations * 4 来估算易受攻击版本和修补版本之间的差异。
在这种情况下,修补版本中的分配将比易受攻击版本大 0x34 * 4 = 0x68。

之后它添加了 0x24。这个值在易受攻击版本和修补版本中的计算方式类似。

然后在两种情况下都添加了常量 0xf。

到目前为止,这个例子中的大小是 0x1340。

接下来它到达了 rpcxdr_OncRpcBufMgrpAllocate。

然后移动到 r15。


它减一然后加四。然后与 0x800 进行比较。
这个错误计算的大小仅当大于 0x800 时才会被使用。因此,只有复合请求才会触发此漏洞。

首先,它分配一个大小为 0x80 且标签为 XdBD 的池。

最后,它在这里为回复分配池,大小为 0x1398,添加了一些常量值。

然后分配 0x13a0(包括标签 XdBP 和头部)。

从那里,它将新分配池的地址存储在字段 tag_XdBD_0x80.p_TAG_XDBP_0x13a0 中。

这指向它始终复制的回复地址。

然后它将开始构建回复头部。

下面是一个示例,展示它如何将数据保存到临时指针的内容中,然后加四。


下面我们可以看到它如何复制到回复地址的内容。

这写入第一个双字,并将指针增加四。

接下来它写入第二个双字并加四。


退出函数后,整个头部已写入。

之后它返回到 nfssvr.sys 继续写入回复。