[原创]Hyper-V安全从0到1(3)
4. Hyper-V虚拟机与宿主机的数据传输
Hyper-V虚拟机和宿主机之间的数据传输是Hyper-V安全研究中一个最重要的部分,弄清楚数据传输的方法,流程,才能清楚地知道Hyper-V的攻击面,并且能根据数据的流向和处理分析出函数的功能和某些重要结构体的成员分布,为逆向工作减轻了工作量。同时,也为复现漏洞的过程提供了帮助。
下面,我们从虚拟机到宿主机的顺序,依次介绍数据的流向。这些顺序分别是:虚拟机内核层,Hypervisor层,Windows内核层,Windows应用层。其中,Hypervisor层我们不做过多的介绍,只介绍这层大致的功能。原因是Hypervisor层的代码多是有关VT指令的处理,要完全弄清楚内部的实现,需要大量时间做逆向工作,而且攻击面较窄,不容易出现问题,Hypervisor层代码也很少有解析从虚拟机传来数据的过程。就Windows内核层和Windows应用层的数据传输来说,他们有很多解析虚拟机传来数据的操作,拥有广泛的攻击面。所以,我们着重介绍其他3个方面的数据传输。不过,有兴趣的读者也可以自行逆向、调试Hypervisor层代码,一方面能增加对VT技术的理解,另一方面能了解虚拟化原理相关的知识,但是在本书中就不多赘述。
在介绍数据传输的过程中,希望读者能根据书中介绍自行调试一遍,以加深对数据传输流程的理解。
4.1. 数据传输流程概览
在介绍具体的数据传输流程之前,我们先对它进行一个大体的认识,这样在之后的分层介绍时有一个整体的概览,到时候回看本小节的内容可以帮助您理清思路。
如图1-31,描述了顺序为虚拟机到宿主机的数据传输过程,下面几个小节也是根据这个框架分层展开介绍的。

图1-31
图1-31描述了整个数据传输的大致流程,通过这幅图我们能了解Hyper-V的整体数据传输的原理。
如果有数据要从虚拟机发送,那么会将数据填入环形内存(buffer_ring),然后经由VMBus调用Hypercall通知Hypervisor层有数据到来;如果宿主机发来数据,则通过虚拟机中断,调用中断处理程序中对应的回调函数(callback),回调函数会从收到的环形内存(buffer_ring)中读取数据,并加以解析。
Hypervisor层用于处理VM-Exit事件,触发宿主机和虚拟机中断来通知宿主机和虚拟机有数据到来。
Windows内核层同样使用中断方法获取到Hypervisor层发来的数据,在通过Windows nt模块分发到vmbusr模块中,然后根据数据的类型分发到vmbkmclr模块中,vmbkmclr再分发到相应虚拟设备驱动中,或者通知用户态程序有数据来临;如果有数据从VMBus发到虚拟机的话,则会调用Hypercall产生一个VM-Exit事件,陷入Hypervisor层处理。
Windows用户态的某些Dll用于模拟显示,鼠标键盘,动态内存,集成服务等设备。用户态和内核的通信主要通过调用Writefile/Readfile读写不同设备对应的命名管道,如果用户态要发送数据到内核,调用Writefile直接将数据写入命名管道;如有数据从内核传到用户态,内核会通知用户态程序需要接收数据,用户态程序调用Readfile函数,从命名管道中读取数据。
在下面的介绍中,我们会详细介绍图中的内容,并且会着重介绍Windows内核部分的数据传输。
4.2. 虚拟机操作系统层
我们先从虚拟机内核部分说起,这里还是用Linux 内核代码作为介绍。笔者使用的Linux内核版本是4.7.2,可能不同版本的Linux内核代码会稍有不同。
Hyper-V设备在Linux内核中的位置如表1-6。

在介绍虚拟机内核部分的数据传输时,我们以hyperv_fb设备为例,分别介绍数据传出和传入虚拟机的流程。
这个hyperv_fb设备是虚拟显示设备,可以理解为它虚拟出来一个vga显示器,用于传输和控制虚拟机显示的画面。这个模块对应的Linux内核模块文件为./linux-4.7.2/drivers/video/fbdev/hyperv_fb.c,下面我们从这个文件开始,解读虚拟机内核部分的数据传输。
从宿主机接收数据
在Linux虚拟机中,每当宿主机发来数据都会以中断的方法通知虚拟机进行处理。图1-32中,显示了Linux虚拟机所有注册的中断。图中的倒数第五行就是Hyper-V在Linux内核中注册的中断,每当宿主机有数据发过来时,Hypervisor层会触发这个中断来通知内核需要接收数据,并且对数据进行处理。
下面我们以hyperv_fb设备进行整个数据接收过程的分析。

图1-32
先从hyperv_fb驱动中出发,当有数据传入虚拟机时,会调用回调函数synthvid_receive,那么我们看一下这个回调函数是如何知道数据到达了呢。在驱动初始化过程中,synthvid_connect_vsp函数调用vmbus_open函数来注册hyperv_fb驱动收到数据时的回调函数,如下代码所示。
static int synthvid_connect_vsp(struct hv_device *hdev)
{
struct fb_info *info = hv_get_drvdata(hdev);
struct hvfb_par *par = info->par;
int ret;
ret = vmbus_open(hdev->channel, RING_BUFSIZE, RING_BUFSIZE,
NULL, 0, synthvid_receive, hdev);
if (ret) {
pr_err("Unable to open vmbus channel\n");
return ret;
}
.....
return 0;
error:
vmbus_close(hdev->channel);
return ret;
}
注意:上传附件及图片大小不得大于30M。
⚠️ 版权声明:
本博客所有内容(含教程、源码、工具)仅供个人技术学习与研究交流使用,严禁商用、倒卖、二次分发及非法用途。
未经作者书面授权,任何组织或个人不得转载、复制或用于其他平台,违者将追究相关责任。
