背景
从EternalBlue这个Exploit被影子经纪人公布到互联网上后,就成为了“明星”。在过去的五月中,这个Exploit被多款恶意软件利用。包括肆虐的WannaCryp0t,无文件的勒索软件UIWIX和SMB蠕虫EternalRocks。
EternalBlue(在微软的MS17-010中被修复)是在Windows的SMB服务处理SMB v1请求时发生的漏洞,这个漏洞导致攻击者在目标系统上可以执行任意代码。
漏洞分析
漏洞出现在Windows SMB v1中的内核态函数srv!SrvOs2FeaListToNt在处理FEA(File Extended Attributes)转换时,在大非分页池(内核的数据结构,Large Non-Paged Kernel Pool)上存在缓冲区溢出。函数srv!SrvOs2FeaListToNt在将FEA list转换成NTFEA(Windows NT FEA) list前会调用srv!SrvOs2FeaListSizeToNt去计算转换后的FEA lsit的大小。然后会进行如下操作:
1.srv!SrvOs2FeaListSizeToNt会计算FEA list的大小并更新待转换的FEA list的大小
2.因为错误的使用WORD强制类型转换,导致计算出来的待转换的FEA list的大小比真正的FEA list大
3.因为原先的总大小计算错误,导致当FEA list被转化为NTFEA list时,会在非分页池导致缓冲区溢出
溢出分析
我们分析的srv.sys文件版本是6.1.7601.17514_x86。漏洞代码是利用srv!SrvSmbOpen2函数被触发。函数的调用栈如下
1 2 3 4 5 6 7 8 |
|
为了分析溢出时的内存状态,我们可以通过如下方式来下断点:
1 |
|
当程序断下来后,我们得到如下十六进制的值(括号内是对应的十进制)
1 2 3 |
|
从这里我们可以看出IN-DATA的大小为65512,和NT Trans Request请求中Total Data Count的值一样且比FEA list的大小(65536)大。
IN-DATA会被转换为FEA list的结构,FEA list结构如下图所示
当IN-DATA转换完成后,我们得到FEA的大小为00010000(65536),这个值被存放在FEALIST->cbList中。接下来将会分配一个缓冲区用来存放将FEA list转换为NTFEA list后的数据。这就意味着需要通过函数srv!SrvOs2FeaListSizeToNt计算转换后NTFEA list的大小。
为了查看这个函数的返回值,我们可以这样下断点:
1 2 |
|
断下来后,我们可以得到
1 2 3 |
|
我们发现FEALIST->cbList从0x10000增长到了0x1ff5d。接下来,代码展示了错误是如何发生的。
在上面列出的代码中,40行后展示了一个计算错误的例子。因为原始的FEA list的大小被错误的更新,导致在拷贝数据到到NTFEA list中时拷贝的大小会超过v6中的NTFEA的大小(00010fe8)。注意到如果函数在28行或者21行返回,那么FEA list不会被更新。除了EternalBlue使用的方法外,如果在FEA list末尾不足以存放一个FEA结构也会更新v1的值。
我们也分析了当大非分页池发生溢出时候的状况。当SrvOs2FeaListSizeToNt函数返回,需要00010fe8大小的内存空间存放FEA list。这就需要srv.sys来分配一个大非分页池。使用以下断点我们可以观察FEA list是怎么转转换为NTFEA list。
1 2 3 |
|
当SrvOs2FeaListSizeToNt被调用且池分配完成后。SrvOs2FeaToNt函数会遍历FEA list并转换其中的元素。在SrvOs2FeaToNt中,有两处_memmove拷贝操作,这也是所有的缓冲区拷贝操作。利用上述断点,我们可以观察到FEA list转换期间的情况。
断点srv!SrvOs2FeaListToNt+0xd5断下来后,我们可以看到分析缓冲区溢出需要的所有数据。605号拷贝操作拷贝了0字节,这是因为在payload开始时FEA list含有0字节的数据对应605中的FEA结构体。下一个FEA的大小为F383(copy 606),最后的拷贝操作的结束地址应该为85915ff0。
在606号拷贝操作后,我们可以看到最后的缓冲区的地址为:85905008 + 10FE8 = 85915FF0。然而遍历到下一个FEA时,拷贝的大小会变成A8。这将会覆盖到下一个内存区域。我们注意到,溢出的数据覆盖了下一个不同的池,在这个例子中是SRVNET.sys分配的池。
在拷贝操作后,607是一个损坏的FEA结构,服务器会返回STATUS_INVALID_PARAMETER(0xC000000D)。与此同时NT Transaction中最后一个FEA会被发送到服务器。
EternalBlue 载荷功能
溢出发生在内存里的非分页池结构中的大非分页池。大非分页池没有池的头部。因此池和池之间的内存空间是紧密相联的,可以在上一个池后分配一个紧密相连的池,这个池属于驱动分配并含有驱动的数据。
因此,必须通过操纵池后被溢出的池。EternalBlue使用的技术就是控制SRVNET驱动的缓冲区结构。为了实现这一点,两个缓冲区在内存中必须是对齐的。为了实现非分页堆的对齐,可以使用内核池喷射技术。该技术细节如下:
创建多个SERNET缓冲区
释放一些缓冲区占位供SRV来拷贝
用SRV缓冲区溢出到SRVNET的缓冲区
利用机制
漏洞代码工作在内核的非分页内存中。也可以工作在大非分页池中。这些类型的池都没有在页的开始嵌入任何头部。因此需要特殊的技巧来利用这些漏洞。这些技巧需要逆向一些数据结构
创建多个SRVNET缓冲区。我们在在此故意省略了一些细节,以防这项技术被滥用。
EternalBlue 的利用链
EternalBlue经过一系列的过程完成最终的利用,我们将展示这些过程。
EternalBlue首先发送一个SRV buffer除了最后一个数据包。这是因为大非分页池将在会话中最后一个数据包被服务端接收的时候被建立。SMB服务器会把会话中接受到的数据读取并叠加起来放入输入缓冲区中。所有的数据会在TRANS包中被标明。当接收到所有的数据后SMB服务器将会处理这些数据。数据通过CIFS(Common Internet File System)会被分发到SrvOpen2函数中来读取。
EternalBlue发送的所有数据会被SMB服务器收到后,SMB服务器会发送SMB ECHO包。因为攻击可以在网速很慢的情况下实现,所以SMB ECHO是很重要的。
在我们的分析中,即使我们发送了初始数据,存在漏洞的缓冲区仍然没有被分配在内存中。
1.FreeHole_A: EternalBlue通过发送SMB v1数据包来完成占位
2.SMBv2_1n: 发送一组SMB v2数据包
3.FreeHole_B: 发送另一个占位数据包;必须确保第一个占位的FreeHole_A被释放之前,这块内存被分配
4.FreeHole_A_CLOSE: 关闭连接,使得第一个占位的内存空间被释放
5.SMBv2_2n: 发送一组SMB v2数据包
6.FreeHole_B_CLOSE: 关闭连接来释放缓冲区
7.FINAL_Vulnerable_Buffer: 发送最后的数据包,这个数据包将会被存储在有漏洞的缓冲区中
有漏洞的缓冲区(之前SRVNET创建的)被填入的数据将会覆盖和部分SRVNET的缓冲区。在FEA list转换到NTFEA list时会发生错误,因为FEA结构会在覆盖SRVNET缓冲区之后失效,所以服务器将以STATUS_INVALID_PARAMETER(0xC000000D)返回。