首页
社区
课程
招聘
[旧帖] [原创]菜鸟也谈虚拟机开发,希望给个邀请码^^ 0.00雪花
发表于: 2009-6-23 13:38 1786

[旧帖] [原创]菜鸟也谈虚拟机开发,希望给个邀请码^^ 0.00雪花

2009-6-23 13:38
1786
最近突然对反汇编来了兴致。懒的学习OD那些工具,主要是懒的去理解那些汇编指令。于是想开发一款小工具来自己玩,如果大家有兴趣的话,开发完毕后我会发布出来让大家拍砖。
由于好久没有接触汇编和WINDOWS了,在开发的过程中遇到很多的问题,又找不到相关的资料,急需和这方面的人士交流,所以将我最近的工作总结下,发个文,希望能混到一个邀请码^^

(说明一下,这个虚拟机的作用是用来分析、跟踪PE的,不是用来保护PE的)

首先需要解决的几个问题

A、虚拟机如何获取完全控制权
B、虚拟机的堆栈和原始代码的堆栈如何隔离
C、虚拟机如何识别、跟踪并执行指令 (x86)
D、执行非正常流转的指令、API等时如何维持虚拟机的控制权
E、虚拟机如何躲避检测
F、虚拟机如何获取多线程的控制权
G、如何识别调用的API
  
A、虚拟机如何获取完全控制权:
* 实现完全控制权的方法比较多,例如内存HOOK注入等
* 我这里采用的办法是:将虚拟机编译成DLL,然后修改磁盘上的原始PE文件,注意是磁盘上的,在PE中寻找一个合适的空间,将启动虚拟机DLL的代码(当然这部分代码需要包含保存现场环境的部分)写进去。
* 当然,在写进去之前我们需要将PE中原始的数据备份出来。我们做这件事的时候必需,不能破坏原始PE中的导入表、重定位等信息。
* 这样一来我们将无法使用原始PE的导入表和数据段,所以这里的启动代码使用ZCode是不二选择。
* 做完这些后,将PE的入口地址修改为我们注入的代码的入口点,并记住PE原始的入口地址。
* 如果这些都比较顺利的话,我们将成功启动我们的虚拟机DLL,此时原始PE中的指令没有被执行一个,而我们的虚拟机首先获取了控制权。
* 当然虚拟机启动后一下几件事情,必不可少:将保存的启动环境还原到虚拟机的Context,修复我们注入的代码部分,修复入口地址(防止内存校验),将虚拟机的EIP指向PE的原始入口。
*
B、 虚拟机的堆栈和原始代码的堆栈如何隔离:
* 这个问题比较容易解决,虚拟机启动后开辟一块足够大的内存空间,做为虚拟机的执行堆栈,我看过很多的资料都是跟原始代码公用一个堆栈,包括《加解密3》中虚拟机的部分
* 感觉这种方式会给虚拟机带来比较大的麻烦,如原蓄意破坏堆栈等,没有具体分析过。我是比较懒的^^,所以干脆把他们彻底隔离。
* 现在我们有两个堆栈,一个是系统分配的,还给原始的代码使用,一个是虚拟机分配的,做为虚拟机的专用执行堆栈。我们需要在两种代码执行切换的时候同时切换堆栈,这个问题也不难解决。
* 好了我们现在具备在虚拟机中执行原始代码的环境和一些权利了,从入口地址开始,可是我们如何执行呢?执行的是什么样的指令呢?
*
*
C1、虚拟机如何识别指令 (x86):
* 现在虚拟机面对的是x86指令的0和1,所以我们还需要一个反汇编引擎,从网上找了不少DASM的例子,可大部分的代码都晦涩难懂,我是一个比较懒的人,所以基本都没有看完。
* 总结了一下,大部分的DASM都是直接返回一条指令的字符串形式,因为这个虚拟机高度依赖DASM,所以我决定学习下x86的指令系统,并在这基础上实现一个DASM,
* 我最初的目标是这个DASM要有高度的可配置性,因为我对指令系统了解的实在太肤浅,系统的掌握又需要太长的时间,所以我必需总结指令的一般特征。
* 查阅了大量资料做了无数次实验后,基本上找出了这个特征,当然还有一些例外就需要代码来控制了。
*   贴一下使用XML描述的基本特征,这个是32位指令系统的,其实16,64没多大区别。
* <OpCode Name ="ADC" ShiftCodeName="操作数宽度切换后的名称"
        Opers="2 操作数只表示可变操作数数量 对于特殊操作数 例如 立即数 固定寄存器不包括在内"
        W="w" Prefix="True 前缀指令 例如rep,repnz等">
  <Code MASK="16进制的MASK码 Code中没有标记为0或者1的记为0" Value="37">0001001woorrrmmm</Code>
  <w>8</w><!--W位在指令中的Bit位-->
  <d>7</d><!--操作数是否取反标志位 D为为0表示操作方向取反-->
  <Mode sss="1 sss=1表示rrr位置的寄存器指示的是段寄存器" rrr="-1 -1表示未定义">2</Mode>
  <!--Mode在指令中的Byte位-->
  <rrr>6</rrr> <!--不包括Mode节的一个寄存器寻址rrr的bit位置-->
  <sss>3</sss><!--段寄存器的bit位置-->
  <cccc>13</cccc><!--cccc标志位的bit位置-->
  <Jmp>Offset/Absolute</Jmp><!--跳转指令系列 InnerText指示跳转的计算方式-->
  <Call>Offset/Absolute</Call><!--调用指令系列 InnerText指示调用地址的计算方式-->
  <Ret>True</Ret>
  <Oper1>Reg</Oper1>
  <Oper2>Reg,Mem</Oper2><!--同时为Reg和Mem时使用Mode+SIB寻址 这也是代码中的判断依据-->
    <!--Acc指的是eax累加器-->
  <Seg W="32">Acc</Seg><!-- 固定寄存器-->
  <Imm W="32">true</Imm><!-- 操作码后面的立即数-->
</OpCode>
*
C2、虚拟机如何跟踪并执行指令:
*
* 有了以上的DASM支持后,我们就可以开始执行原始的x86指令了,好了我们从虚拟机的EIP开始,调用DASM获取一个指令,我们欲执行这个指令,可是我们在那里执行呢?
* 选择1:在这个指令所在的原始位置执行,好了还原原始环境->JMP Vm.EIP ->程序直接跑飞了,郁闷了半天才搞清楚,原来我忘记拿回控制权了^^
* 于是在 Vm.Eip指令后面下个钩子 JMP Vm.Back ,再次执行JMP Vm.EIP ,执行完后,控制权拿回来了。
* 选择2:我们不是把所有的指令通过DASM解析出来了嘛(别拍砖,打个假设),我们就写一系列的Handler来模拟CPU执行我们的指令,开始动笔写几个,比如ADD,MOV之类的,发现好麻烦啊,我比较懒,直接放弃
* 选择3:开辟一个单独的内存空间,将我们要执行的指令复制进去,然后追加拿回控制权的指令,JMP到这个内存地址去执行。
*
* 毫无疑问的,我选择了3。我选择这个的主要原因是我比较的懒^^,选择2实在是太麻烦了,也许你对选择3存有一定的疑问,比如重定位啊什么的,你的担心是对的,慢慢看下文。其实一开始我也担心,不过用记事本试过以后,我就不怎么担心了。
* 为什么不选择1,显然的,1会破环原始PE的用户空间的数据,不到万不得已的迫不得已我是不会向原始的用户空间去写数据的,关键问题是麻烦啊,人懒啊。
*
* 废话不多说,选择3后,我继续跟踪,发现好几个指令执行下来都是对的,激动之余直接F5(VS2008),pia~,给我出来一个异常对话框,直接看不懂的那种(E文的),郁闷之余,慢慢的F11,发现就是在不远出的一个JMP指令出错了。
* N天想不通啊,后来才知道,自己对指令系统的理解几乎到了无知的地步了。原来这个JMP不是一般的JMP ,他是一个相对跳转JMP 我FT%&^*%$%^!
* 没有办法只能写一个额外的Handler来处理这个特殊的指令了,后来才发现这一个一个额外的Handeler还不止一个,主要有Call,JMP ,JCC,RET等
* 没办法只能一个个写了,他们的主要思路就是Vm.EIP的指令如果是类似的指令的时候,直接修改堆栈和和VM.EIP
* 例如执行一个CALL时
* 先将 (VM.EIP+VM.EIP处指令的长度)丫栈,别丫错了啊,我们有两个堆栈,这个是要丫到原始PE的堆栈中的数据,然后JMP XXXXXXXX,这里的XX就是CALL指令后面的XX,当然这个XX有可能不是立即数,这里就体现出我们DASM的作用了,通过解析出的指令直接去VM中获取操作数的值
* 再例如执行一个RET
* 有的人坏的很,在写代码时 该CALL时不用CALL他用RET,一个不小心你就迷失在系统的代码空间中出不来。
* 以上这句是有感而发,略过。
* 执行RET的过程和执行CALL的过程,差不多
* 只是多了个一个RET后面的 XXXX 也就是 RET XXXX指令,我们做完类似CALL的处理后,直接将虚拟机的ESP加上这个值,这里应该是加把,不是减,记不清了。
* 有一些特使的调用我们是不用跟踪的,比如认为没有必要跟踪或者一些系统的API我们可以直接让虚拟机跳进去执行,然后等他的返回就完事了,注意这个要在原始堆栈上执行即可。
*
D、 执行非正常流转的指令、API等时如何维持虚拟机的控制权
* 首当其冲的就是SEH,我目前使用的解决办法是下钩子,我研究了很久实在找不到好的解决办法,虽然这有违虚拟机设计的原则,也是没有办法的事,我们就快速的钩,钩完就跑,嘿嘿。
* 跳板函数,例如NTContinue之类,直接修改EIP搞定
* API,这里指的是有回调函数的API,直接修改参数搞定,为了做的比较漂亮,API返回后(如果返回的话)或者在回调函数被执行时,还原堆栈中的参数为人家原来的值
*
*
E、 虚拟机如何躲避检测:
* 这个我想了很久,不知道出于何种原因我要躲避人家的检测,可能是看调试、反调试、反反调试、反反反调试....看的太多了。
* 其实就虚拟机而言,我感觉不存在反调试的问题。为什么呢,因为到目前为止,我们的虚拟机已经成功运行了,并能跟踪你的指令,却连调试2字都没有出现过,更别说启动调试器了。
* 总结如上的大篇废话,我觉得,虚拟要避开的无外乎是
* 1、磁盘上的文件被修改过
* 2、用户的进程空间中多了一个DLL模块,而且这个模块看起来好像什么都不想做
* 3、如果代码能感觉到自己的话他会觉得自己年老体衰了,以前1秒就搞定的事现在要10秒甚至更多。
* 1、2都比较容易解决。3比较麻烦,太麻烦,我还没有想出比较好的对策,如果遇到的话,只能一行一行郁闷的去看如天书般的指令了
*
F、虚拟机如何获取多线程的控制权:
* 多线程从何处来?当然要调用系统的API(?我不知道我说这句话对不对啊,不知道还有没有其他的启动方式),调用API要经过哪里?要经过我们现有的虚拟机
* 对嘛,我们可以在虚拟机执行线程API时首先知道它要干啥,篡改下参数,当然如果遇到比较变态的,启动了线程后不去执行,而是修改下CONTEXT再去执行,那偶就没办法了,只好也变态,慢慢的看着他修改,他改完了偶再改回来。

G、如何识别调用的API:
   其实这点我不想多说的,有点画蛇添足了,知道了API的地址,去识别API应该是一件轻松愉快的事。如何获取API地址? CALL JMP RET等指令后面的操作数如果跑到了系统空间,那基本上就是一个API地址了。

说了这么多废话,欢迎大家来拍砖,目前我的工作还没有做最后的两步 也就是躲避检测和多线程的创建、同步等。

如果有需要代码的,留下MAIL,我会考虑提供部分代码,其实,我在上面的文字中已经把整个思路都提供了。

[课程]Linux pwn 探索篇!

收藏
免费 0
支持
分享
最新回复 (7)
雪    币: 50
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
支持,希望给新手一个机会给我一个邀请码
2009-6-23 23:12
0
雪    币: 1450
活跃值: (35)
能力值: (RANK:680 )
在线值:
发帖
回帖
粉丝
3
文章看了一半~

严格来说, LZ所说的虚拟机称不上是虚拟机.

你所说的只是把每条指令执行一下然后跳到VMDispatcher, 再执行下一条指令(执行的还是X86指令).

你注入dll有什么用?  没什么用.  如果你直接将代码段指令变为你所说的"执行--jmp back"的话, 也只能说是

代码变形. 也就抵抗一下初级静态分析.

真正的虚拟机是将代码段的X86指令变换成虚拟机可以识别并执行的指令(如字节码). 然后由虚拟机VM一个一个字节的执行字节码. 这样才能到达强力抵抗静态分析的效果.
2009-6-23 23:47
0
雪    币: 72
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
不好意思 忘了说了 我这个虚拟机不是用来防破解的 是用来破解的^^
2009-6-24 09:28
0
雪    币: 1450
活跃值: (35)
能力值: (RANK:680 )
在线值:
发帖
回帖
粉丝
5
不管怎样你这个称不上是虚拟机. 只能叫代码变形.
2009-6-24 09:34
0
雪    币: 72
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
这么说吧 我的目标是开发一个不启动调试器的调试工具,让反调试的技术失效,因为看雪的牛人实在太多,不敢说出来。
2009-6-24 09:40
0
雪    币: 72
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
我解释一下吧 可能是我的思路太乱了

DLL注入,也就是修改磁盘上PE的目的是为了在PE启动之初就拿到代码的控制权。虚拟机启动后,将PE被修改的部分在内存中修复,防止被调试的程序有内存自校验。

VMDispatcher的作用是解析每条指令并跟踪他们执行,这里跟踪执行的是纯X86指令,被调试目标的每个指令在被执行之前都需要经过VMDispatcher,这样就达到了跟踪和调试的目的。

不管是反调试 还是加壳保护 基本上都是无效的,至少反调试是无效的,因为这个调试工具根本就没有启动调试器。
2009-6-24 09:51
0
雪    币: 122
活跃值: (40)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
8
LZ能分享知识,值得赞一个!!!
2009-6-25 09:20
0
游客
登录 | 注册 方可回帖
返回
//