信息来源:FreeBuf
近日,Nettitude安全人员在Avast Virtualization内核模式驱动(aswSnx.sys)中发现一个安全漏洞,使用普通账户登录的本地攻击者可利用该漏洞提升权限,以系统权限执行任意代码,进而完全控制受影响主机。
AvastVirtualization(aswSnx.sys)驱动用于处理所有Avast Windows产品的“Sandbox”和“DeepScreen”功能。
受影响产品及版本
Avast InternetSecurity v11.1.2245
Avast ProAntivirus v11.1.2245
Avast Premierv11.1.2245
Avast FreeAntivirus v11.1.2245
上述产品中的早期版本也可能受到影响。
技术细节
AvastVirtualization内核模式驱动(aswSnx.sys)没有验证用户空间的IOCTL请求中的Unicode文件绝对路径的长度,而该路径长度会被复制到固定大小的分页池内存中,攻击者可以借助特制的代码导致内核分页池分配的数据块溢出,并损坏其相邻的攻击者控制的内核对象,如图一。
图一 攻击者控制的内核对象
图二显示了aswSnx.sys驱动对nt!memmove函数的调用,但是这一过程没有验证数据是否按可用大小被复制到分页池的数据块中。这些信息取自10.x版本,同样适用于11.x版本。
图二 漏洞原因
堆缓冲区溢出的利用
在处理基于缓冲区的动态内存分配,也就是基于堆的缓冲区溢出时,首先需要预测缓冲区溢出发生的位置,以便于控制该漏洞的执行。这在处理内核地址空间的代码运行时是十分重要的,因为如果利用失败,系统可能会出现故障。损坏一个不可控制的随机内核对象实在不是一个明智的选择。
为了达到这个目的,面临的另一个挑战就是,根据数据块的大小创建一个合理的动态内存分配布局,用于溢出的产生。如果已知数据块的大小,那么我们就可以没有更多限制的实现这个目的。然而,当我们处理固定掉的数据块(在该实例中为0×418字节),很难找到一个合适大小的对象导致堆溢出。想要克服该问题的人可以参考此处。
溢出内核分页池
私有命名空间是创建可控大小的分页池对象的有效方法。通过创建多个带有边界描述符名称的、特制长度的私有命名空间,我们可以获得以下内存布局:
图三 堆溢出#1
因此,在这种情况下,我们并不能控制分页池数据块的大小,但是,我们可以控制分页池对象的大小,如图三所示,指定一个内存页(4KB)来显示分页池的开始。
也就是说,我们可以创建一个控制内存页开始的对象,然后就可以拥有一个0x3b8字节大小的可用空间,和两个一直到内存页结束都可以控制的相邻对象。由于边界描述符名称的长度可变,甚至可以使用可控对象占据整个内存页。
图四 堆溢出#2
但是,由于可溢出的缓冲区是固定大小(0×418字节)的,并且可以以内存页中最后分配的对象为目标,因此我们完全不用考虑page_allocation_base + 0×418中的空间内容。也就是说,我们仍然可以允许内核使用该空间。
通过使用ProcessExplorer,我们可以更清楚地看到堆溢出后的分页池内存分配情况,如图五。
图五 分页池的内存分配布局
图六为堆溢出后的内存页的布局情况。我们利用该漏洞导致了SnxN缓冲区的溢出,并损坏了相邻对象。
图六 堆溢出#3
私有命名空间不仅可以使攻击者控制分页池大小,而且可以通过重写NAMESPACE_DESCRIPTOR结构体的LIST_ENTRY字段中的指针来控制执行情况。该字段将NAMESPACE_DESCRIPTOR结构体链接到系统中所有可用私有命名空间的列表。
假定我们已经成功损坏了特定私有命名空间的NAMESPACE_DESCRIPTOR结构体的LIST_ENTRY字段。
图七 不太安全的Unlinking功能(Win 7 SP1)
但是在Windows 8及更高版本中,由于安全的LIST_ENTRY结构体的使用,该方法并不能奏效。
图八 安全的Unlinking功能(Win 8.1)
图九为私有命名空间的目录对象的损坏前后对比。可以看到,NAMESPACE_DESCRIPTOR结构体的LIST_ENTRY字段已经使用用户空间地址(0×41414141)成功重写。
图九 目录对象的损坏前后对比
控制EIP
成功损坏目录对象后,接下来就是控制漏洞的执行了。使用write-what-where条件重写HalDispatchTable的函数指针,尤其是存储在HalDispatchTable+sizeof(ULONG_PTR)这的hal!HaliQuerySystemInformation函数,然后通过调用ntdll!NtQueryIntervalProfile将漏洞执行重定向到我们自己的负载。
函数调用序列为ntdll!NtQueryIntervalProfileà nt!NtQueryIntervalProfile à nt!KeQueryIntervalProfileà call [nt!HalDispatchTable+sizeof(ULONG_PTR)](0×41414141)。
目前我们可以做到的是重写内核地址空间中的任意指针,并通过劫持攻击控制EIP。但是这是远远不够的。
write-what-where条件在断开私有命名空间和系统中可用私有命名空间的连接时发生,也就是我们在关闭处理器或者使用特定私有命名空间中的目录对象时。可以通过调用ClosePrivateNamespace或CloseHandle来实现该过程。
需要注意的是,为了堆溢出的发生,分页池的数据块头已经损坏,一旦释放数据块头中的任意对象,都会触发SoD,因为内核会对比之前对象的实际大小和释放的数据大小。
我们可以通过隔离这两个阶段来避开这个问题。我们是通过调用ZwDeletePrivateNameSpace而触发的what-where条件,这会导致连接断开,但是并不是释放对象内存。我们可以在post-exploitation清理阶段重新保存LIST_ENTRY字段。
最后,我们可以安全地修复损坏的目录对象,和NAMESPACE_DESCRIPTOR结构体,以保证系统不会报错。
厂商修复
图十 存在漏洞的函数
图十一 修复的函数
由图可以发现,修复的函数中添加了一个调用子程序的call sub_C161C指令,它的目的是验证复制到固定大小的内存中的数据大小,如果大小不合适,程序会调用ExAllocatePoolWithTag函数重新分配一个合适的空间,可以安全地保存数据。
*原文地址:nettitude,FB小编vul_wish编译,转载请注明来自FreeBuf黑客与极客(FreeBuf.COM)