首页
社区
课程
招聘
[原创]也说X86虚拟机(CPU仿真)
发表于: 2007-8-27 13:12 22540

[原创]也说X86虚拟机(CPU仿真)

2007-8-27 13:12
22540

也说X86虚拟机(CPU仿真)
作 者: wht0395

    注:本人不善言辞,而且写此文也仅算是对本人近期编码和思考的一个总结,不会太注意用词的。
    前面linxer前辈有写过《ring 3级32位x86 cpu仿真》,对于x86CPU的仿真的做过一些构想和阐述。可惜,我是在完成了X86的基本指令集的解释引擎后才注意到此文章。拜读之后,觉得自己与前辈的实现思想有些许差别,所以就随便写下。

目标优先级:
    1)可维护性。
    2)执行效率。
    3)可移植性。本引擎并非设计为在桌面平台使用

一、CPU仿真

1、CPU CONTEXT。
    因为一些原因,在CPU相关的结构上,有借鉴linxer前辈的思想。

    此处要提到的是,本人对于所有寄存器和寄存器索引,是按照IA32手册上的标准定义的。linxer前辈在EFLAG寄存器的标识位索引定义时并未按照标准,并且注释为了“效率”,本人到现在仍然没有理解这个“效率”问题。

2、内存寻址和访问接口。
    因为模块化编程的原因,此处对于内存的操作是单独提取出来的接口。当前版本是使用一个映射接口来完成,其声明如下:

DWORD32        MmMapAddress(pVM,                     /*虚拟机结构*/
                     WORD16  Seg,             /*段地址/段选择子*/
                     DWORD32 Address,         /*线性地址。在16位寻址模式下仅仅使用低16位*/
                     DWORD32 Size,            /*需要映射的区域大小*/
                     DWORD32 Privilege);      /*映射的权限(读、写、执行等)*/

    每当VCPU(我们仿真的虚拟CPU)要访问访问内存时,调用此接口,映射得到一个本地指针,可以对其进行相应读写操作。
    在内存映射模块的内部,基本的思想是跟linxer前辈一样,区别如下
    a)效率问题,对地址部分采用了HASH处理。
    b)根据Privilege检查内存块属性,不符合时会触发VCPU GP类异常

   
    备注:1)我们并未对VCPU的读写进行函数级封装,效率问题
          2)内存映射部分并非本人所作,这里说的仅仅是我们的原始设计
         
    本人其实会更倾向于对于读写进行函数级封装,虽然效率低,但可以对页属性进行较为完善的仿真。

3、指令解析部分
    这个部分本人和linxer前辈区别较大。
    事实上,如果看下INTEL IA32手册的(OPCODE MAP)部分,你会发现指令解析的方法和方式相当清晰:建表。
    IA32体系结构下的机器码其实就是由几张表来完成的“One-byte Opcode Map”、“Two-byte Opcode Map”、“Opcode Extensions for ..."以及浮点协处理器的指令解析表格。
    所以,指令解析框架很简单:建立如下的“One-byte Opcode Map”(256项)
     ONE_OPCODE_TABLE_ENTRY One_byte_Opcode_Map[]={
/*0x00*/  Function1_ADD_00                  ,  /*ADD      */
/*0x01*/  Function1_ADD_01                  ,  /*ADD      */
/*0x02*/  Function1_ADD_02                  ,  /*ADD      */
/*0x03*/  Function1_ADD_03                  ,  /*ADD      */

    其中FuncOne_ADD_00等都是对应的解析函数。
    解析过程如下:
    通过指令第一个字节查询"One-byte Opcode Map",直接CALL对应的解析函数
    a)如果这条指令是单字节指令(如0x00 add),则这个解析函数其实就是这条指令的实现函数
    b)否则,这个解析函数其实是另外一个分派函数(将会查询另外一个表格),此解析函数根据指令第二个字节查询“Two-byte Opcode Map” “Opcode Extensions for ..." 或者 浮点协处理指令解析表,调用下一级指令解析函数
    以此类推,到最低层解析函数时,指令所对应的参数和格式已经最大程度地明确了,实现起来很简单。

    选择这种解析方式的理由:
    a)结构简单,利于维护。添加和修改指令都只需写/更改相应指令解析函数,然后填入对应位置即可。
    b)解析速度会比switch快些。虽然switch case结构在编译后其实也可能用表格来实现(也可能用DEC/JZ之类跳转实现),但其更加依赖于编译器优化。
    c)假如对这些解析表和相应的X86指令序列进行随机置乱的话,可以成为一个简单的保护代码执行引擎。

    这种结构的弊端也很简单:前期工程量会稍大点,因为实现基本指令就要几百个解析函数,另外要建立好几个表格,比较累(还好可以写一些辅助工具完成)
   
4、中断仿真
    对于中断的仿真可以以如下方式完成:
     a)在指令产生异常/中断(GP/DE/DB等等)时,指令会填充CPU CONTEXT的一个INTERRUPT_INFOMATION结构,把相应的异常/中断信息保存,然后逐层返回到最高层的解析函数(查询
"One-byte Opcode Map"的那个函数)。
     b)根据中断号调用相应中断处理程序。中断处理程序会根据INTERRUPT_INFOMATION结构的内容执行相应的操作。SEH之类的模拟,可以在这里完成的。

5、指令仿真
    因为是采用标准C来实现指令仿真,所以有些标志位处理起来比较麻烦。
    用内联汇编的方法实现指令很简单,没必要说什么。这里主要说下用C来实现实现指令模拟时会遇到的问题
   a)AF位的处理。这个标志主要影响调整指令,比较麻烦,我的当前版本暂时回避了此标志,下个版本中将使用使用算法来解决此标志。
   b)CF/OF位。由于暂时没有找到统一的处理办法,当前版本中,我是通过做有符号/无符号两种运算来分别设置的。
   c)PF位。使用计算海明码码重的算法(LINUX源码中可以找到),起码比逐位遍历要高效不少
   d) ModR/M SIB位。本部分一样是使用查表(参见IA32手册INSTRUCTION FORMAT部分)来完成解析的,解析结果是地址,保存到如下结构中
struct xxxxxxxxxxxxxxxxxxxxxx
{
        /*
        The total length of ModR/M、SIB(if exist) and Displacement(if exist).
        Filled by GetInstructionArgs()
        */
        UCHAR Length;

        /*The reg field of the ModR/M byte*/
        UCHAR Reg_Opcode;

        /*The r/m field of the ModR/M byte*/
        /*
        Is EffectiveAddress a memory address or general register ?
        1 -> Register. the RegIndex field is the index of the general register.
        0 -> Memory address.
        */
        UCHAR bIsEAReg;
        union
        {
                UCHAR RegIndex;
                ULONG Address;
        }EffectiveAddress;

}

6、用于提高解析效率的指令CACHE
   当前版本并未实现CACHE,但为了解析效率计划加上。实现CACHE的麻烦在于自修改指令的处理,不过因为对内存访问使用单独的接口,所以,只要映射内存时将Address、Size、Privilege与指令Cache对比即可准确判断是否要更新CACHE

7、指令模拟的正确性测试
   利用微软的DEBUG API接口写测试工具,单步,内存/寄存器 对比。

二、PE运行环境的模拟
   这部分我并未实际编码,所以说的估计都是错的

1、内存管理
   没什么好说的,链表/数组什么的,就是要高效点。
   另外,要把常用的DLL文件的地址空间、TEB/PEB也仿真了

2、API仿真
   对于截获API调用并不需要在在CALL/JMP/RET等里面进行单独处理,我的思路是对于被仿真的API的虚拟地址维护一张表,在解释一条指令之前,对比EIP与API地址表,如果是API调用,则执行仿真API,否则正常解析

胡言乱语:
指令/编码层次对抗仿真的手段
1)将浮点运算/BCD码运算的结果融入到正常指令流中
2)使用一些特殊的指令参数。例如:ENTER/CPUID等虽然是常用指令,但个别类型的入口参数仿真起来很繁琐。起码不使用内联X86汇编的情况下,我是觉得很烦。
3)未公开指令。这个思路其实有点扯淡,你能得到的东西,仿真者也能得到。
3)调用不常用的API,这个是最容易想到的。
4)API的仿真是不可能把KERNEL32真的加载进去的,所以,如果读取这些地址,比如API的前N个字节,应该会导致仿真失败。


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

收藏
免费 7
支持
分享
最新回复 (15)
雪    币: 1746
活跃值: (287)
能力值: (RANK:450 )
在线值:
发帖
回帖
粉丝
2
楼主 不错,好强大~~~

EFLAG寄存器  分开写的原因是 当初没有用sahf  lahf来操作仿真标志位 如果用这两条指令的话 分开写的方法是低效的

指令识别和指令解析部分 要高效的话,还的确是用opcode作哈希,在发x86机器码识别一文的时候  笨笨雄大哥就说过设个问题, 不过这个还的确比较麻烦,又是体力活

关于cache的引入,还真要注意用smc技术的一些程序,否则cache一旦失效,虚拟CPU没有觉察到,后果严重,如果CPU要效率比较高 是应该加入cache功能,甚至可以引入2级cache

对抗虚拟机:
其实这里说的虚拟机是很脆弱的,不堪一击,只要你乐意攻击
1. 没有OS支持,要穿透很easy
2. 对未公开指令,关键是要善于发掘这些指令,目前发现这种指令也就那么几条,仿真上就OK了
3. 对故意引入的错误指令,虚拟CPU支持捕获这类异常就可以了
2007-8-27 14:57
0
雪    币: 1946
活跃值: (263)
能力值: (RANK:330 )
在线值:
发帖
回帖
粉丝
3
很好,很强大。
2007-8-27 18:59
0
雪    币: 233
活跃值: (10)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
4
感谢兄弟分享~

学习  如果能配合多一点代码就完美了  
2007-8-27 21:34
0
雪    币: 242
活跃值: (14)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
5
写的时候是有代码的,不过删掉了。公司的,不太方便。
不过倒是有想法维护一个开放源码的解释引擎,包括64位指令,不过又感觉没必要
2007-8-28 09:07
0
雪    币: 846
活跃值: (221)
能力值: (RANK:570 )
在线值:
发帖
回帖
粉丝
6
这种结构的弊端也很简单:前期工程量会稍大点,因为实现基本指令就要几百个解析函数,另外要建立好几个表格,比较累(还好可以写一些辅助工具完成)

半年前建好了这张表,半年来看到一大堆函数要写,一直没动手
2007-8-29 08:37
0
雪    币: 846
活跃值: (221)
能力值: (RANK:570 )
在线值:
发帖
回帖
粉丝
7
不知道你们说的cache是怎么实现的

有没有试过用多线程虚拟CPU执行指令的流水线?

最近看过一篇文章有提到,真实CPU执行的时候,流水线的各部分是并行执行的。其中提到两个影响CPU并行执行的问题,相关和转移。

相关即后一条指令必须依赖前一条指令的计算结果。为此CPU在指令解释部分有一个乱序执行的单元,就是为了去除指令相关性,方便并行执行的。

转移即JCC了。CPU提供了一个分支预测的功能,一旦分支预测出错,CPU就必须重置整个流水线,重新执行指令。

这个分支预测不知道是怎么实现,看那篇文章的时候,只提到了一个简单的例子,即在循环内那个跳出循环的条件跳转只会被执行一次。
2007-8-29 08:49
0
雪    币: 398
活跃值: (343)
能力值: (RANK:650 )
在线值:
发帖
回帖
粉丝
8
我也来学习一下
2007-8-29 08:58
0
雪    币: 1746
活跃值: (287)
能力值: (RANK:450 )
在线值:
发帖
回帖
粉丝
9
多线程虚拟CPU执行指令的流水线  的问题

我认为是不必要的 CPU的指令流水线用软件去模拟的话,势必要多线程,多线程之间的协作切换就是消耗系统性能,对一些不存在IO之类,需要挂起当前虚拟CPU的线程的,就更不能用多线程了,加速虚拟CPU运行,可以适当提高线程优先级别
2007-8-30 13:53
0
雪    币: 267
活跃值: (16)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
10
最近找到的很优秀的文章,分享一下
上传的附件:
2007-8-31 20:59
0
雪    币: 226
活跃值: (15)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
11
好东西啊,楼上很不错,很可爱。
2007-8-31 21:19
0
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
12
朋友,那公司的东西说事,小心我告你泄密哦!^-^!
2007-9-5 10:53
0
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
13
来看热闹的, 建议版主先搞点资源链接,相关的知识比较少,好多术语都不懂,一个术语对一个门外汉来说就是一条河,你在那头,我在这头......
2007-9-5 11:11
0
雪    币: 242
活跃值: (14)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
14
呵呵,又没源码,我个人的思想,何来泄密?事实上,公司的的框架是别人设计的,所以跟我想的略有不同。俺只是苦力

况且,都是很SIMPLE的东西,实在无秘密可言
2007-9-5 12:49
0
雪    币: 209
活跃值: (10)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
15
别怕,让刀客来保护你。。。
2007-9-12 21:48
0
雪    币: 267
活跃值: (16)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
16
2007-9-13 22:49
0
游客
登录 | 注册 方可回帖
返回
//