前言
关于单核单线程INTEL CPU的VT技术初探.
感谢周壑老师的视频.
全都都是根据他的视频完成的.
这些都是一些简单的东西,我没有时间写在多核下面的代码。
相关的代码也有点多,我也不想直接贴出来,我就大概描述一下整个开启VT虚拟化技术的全过程,有这方面研究可以到github上面去阅读下载。
鼻炎有点加重不好意思,看情况分享了。
参考资料《intel白皮书》
开始
CPU需要支持并开启VT技术。
上图是周壑老师把开启VT技术的简化图。
首先我们要编写检测CPU是否支持并开启了VT代码。
给guest,host域分配在内核空间中的堆栈内存,这里一个分配4Kb的内存空间就足够使用,因为这是单核单线程的CPU环境。
然后设置vmcs
设置客户机入口,guest域,这里不清楚做什么的。就有些返回放在这里的。
设置虚拟机host域,这里可以接收各种异常。
每个域都有相关的寄存器的位设置,都可以在白皮书中找到
vmlaunch这个之后的代码就不会执行了。
如果要开启EPT的话,这里再guest域里面还需要设置很多东西,而且需要把你申请的内存空间地址指向这里,就可以在EPT中伪造内存页,伪造断点,各种各样的事情。
反正就是这个时候你的权限就很高。
这里开启EPT还需要注意的就是内存分页模式,在EPT伪造的页面当中,我们需要按照操作系统的分页模式对我们申请的内存进行处理。
一个绿色的页面是管理的是4k512=2m内存,它能管理2m的内存.我们现在的物理内存是256m,所以y一共需要分配128个绿色页面.
一个黄色页面是可以管理2m512=1g内存,所以我们整个需要分配一个黄色页面就够了.
红色的更多了512g,分配一个就够了,蓝色分配一个就够了
最朴素的方法是怎么做的呢?首先我们分配128个绿色页面,然后把每个绿色页面的8个字节8个字节一组,都填上4k对齐的物理地址,第一个地址填我实际物理内存的0偏移的地址,填到它的第一项里,然后它下一个加上4k地址填到第二个地址,一个一个填完.128个地址都填好了,这个就有256M的虚拟地址,物理内存就填到绿色这里面.然后这个绿色页面处理好,我在分配一个黄色的页面,在这个黄色页面里面填上一些什么东西?填上每一个绿色页面的起始物理地址,然后有128个绿色页面,我一个黄色页面里一共就有多少?512M的PTE,所以我只需要填前一半就够了.
然后黄色页面填好,我在分配一个红色页面,它的第一项填成这个黄色页面的起始物理地址就可以了.它的第二项往后都清零.然后在分配一个蓝色页面,它的第一项填上这个红色的起始物理地址.最后把这个蓝色页面分配的物理地址填到VMCS里要设的EPTP字段
然后我们虚拟机guest里,通过一些标志给它开启EPT,然后整个这套就可以正常运作了.
2是什么意思?就是这个红色的这个表,叫做PDTE.直接CR3指向的这个表.它只有4个项.
相比其它512项就少的多了.所以CPU当时在设计的时候,直接这4项j就在那个CPU内部做4个就缓存寄存器,然后你加载,你把一个东西mov to cr3的时候,它就直接把cr3指向那块内存的4个PDPTE给加载到对应的4个pdptr,就是pdpt寄存器里面.这4个把它加载进去了,这之后你用这个CR3保持不变,所以内存的时候它并不走,它并不在读cr3的物理地址,在查这个红色的表.它就直接从那4个它加载的高速缓存寄存器里面去读.
具体怎么测试呢?就是随便找个cr3读一下,这个已经是物理地址,所以dq前面4个就是有值的.
kd> r cr3
cr3=00b5d000
kd> !dq 00b5d000
# b5d000 00000000`00b5e001 00000000`00b5f001 这里可以看到就已经存在了
# b5d010 00000000`00b60001 00000000`00b61001 把一个东西写进cr3的时候,这4个寄存器写进CPU寄存器pdptr里.下回它就不再查这块内存了.减少一份索引,加快内存速度.
# b5d020 00000000`00000000 00000000`00000000
# b5d030 00000000`00000000 00000000`00000000
# b5d040 00000000`00000000 00000000`00000000
# b5d050 00000000`00000000 00000000`00000000
# b5d060 00000000`00000000 00000000`00000000
# b5d070 00000000`00000000 00000000`00000000
这个PAE这个东西,你如果说现在你要写这个64位的驱动的话,可能64位我感觉j就没有PAE这个东西了吧.因为64位地址空间物理地址已经支持到48位了,它这个东西(stuff,thing)已经不需要这种物理地址扩展,PAE它相当于是在软硬件历史发展的某一个j阶段它是这么一个阶段性的一个产物.它这个东西随着硬件发展就会消失.你这个PAE本质上就和当年那个Intel的8086它那个16位实模式下的段j寄存器寻址那个逻辑是一样的!!!你想当时Intel为什么要搞那些段寄存器寻址,因为它当时是16位CPU对吧,16位CPU它如果是16位的地址线,能索引到内存最大是4K,但是那个时候,硬件上发展家用机物理内存已经超过4k了,然后它就引入一个j叫做段或者段寻址,然后把那个东西实模式下.把那个段的那个地址什么左移4位,在加上段内偏移.就可以把地址空间变到20位.然后就是从64k一下就支持到2M了.这个PAE和这个基本上都是一回事.PAE是什么呢?大概就是0几年的时候,那个时候的主流计算机操作系统还是32位的,但是那个个人电脑这个高端的一些内存,硬件已经有那个4gb以上的要求了.然后Intel怎么办呢?就在CPU里搞出一个PAE的技术.才把这个支持的物理内存最大扩展到2的36次方?好像就是扩展了一些
kd> !db 00b5e000
# b5e000 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
# b5e010 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
# b5e020 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
# b5e030 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
# b5e040 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
# b5e050 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
# b5e060 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
# b5e070 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
kd> db c0600000 第一个cr3指向的物理地址,对应的是这个内存地址
c0600000 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
c0600010 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
c0600020 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
c0600030 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
c0600040 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
c0600050 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
c0600060 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
c0600070 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
kd> !db 00b60000
# b60000 63 a1 b6 00 00 00 00 00-63 b1 b6 00 00 00 00 00 c.......c.......
# b60010 e3 09 40 00 00 00 00 00-e3 09 60 00 00 00 00 00 ..@.......`.....
# b60020 63 e1 b6 00 00 00 00 00-63 f1 b6 00 00 00 00 00 c.......c.......
# b60030 00 00 00 00 00 00 00 00-63 01 b7 00 00 00 00 00 ........c.......
# b60040 e3 09 00 01 00 00 00 00-e3 09 20 01 00 00 00 00 .......... .....
# b60050 e3 09 40 01 00 00 00 00-e3 09 60 01 00 00 00 00 ..@.......`.....
# b60060 e3 09 80 01 00 00 00 00-e3 09 a0 01 00 00 00 00 ................
# b60070 63 69 c0 01 00 00 00 00-63 79 c0 01 00 00 00 00 ci......cy......
kd> db c0602000 红色表里面的第3个 每一个0x1000就是占了4kb的大小
c0602000 63 a1 b6 00 00 00 00 00-63 b1 b6 00 00 00 00 00 c.......c.......
c0602010 e3 09 40 00 00 00 00 00-e3 09 60 00 00 00 00 00 ..@.......`.....
c0602020 63 e1 b6 00 00 00 00 00-63 f1 b6 00 00 00 00 00 c.......c.......
c0602030 00 00 00 00 00 00 00 00-63 01 b7 00 00 00 00 00 ........c.......
c0602040 e3 09 00 01 00 00 00 00-e3 09 20 01 00 00 00 00 .......... .....
c0602050 e3 09 40 01 00 00 00 00-e3 09 60 01 00 00 00 00 ..@.......`.....
c0602060 e3 09 80 01 00 00 00 00-e3 09 a0 01 00 00 00 00 ................
c0602070 63 69 c0 01 00 00 00 00-63 79 c0 01 00 00 00 00 ci......cy......
所以我们就可以把c0602000转成物理地址,然后添到那个pdpte寄存器第3个不久完了????!!!!
有些进程保护自己内存,可执行代码它不希望被人下断点或者hook,因为如果是hook的话,你肯定是要改那个执行硬编码.如果你下断点,写入cc它也会变,整个把页面的小部分拿来做一个什么累加,做一个检验码,发现那个页面改变了.就知道你代码被人改了.
然后通过这种办法把读页面和执行页面给它分离开来.也可以做一般的过CRC检测保护.
关于EPT还有其它应用,你可以去查那个highpacephoeh或者是ddi,ddimn那个项目guthub上好的项目.可以做什么无限硬件断点之类的.基本上你拥有权限高了,你不管做什么东西都是很简单,你可以在这个层面ept.c建立一个内核调试框架.