中国IT动力,最新最全的IT技术教程
最新100篇 | 推荐100篇 | 专题100篇 | 排行榜 | 搜索 | 在线API文档
首 页 | 程序开发 | 操作系统 | 软件应用 | 图形图象 | 网络应用 | 精文荟萃 | 教育认证 | 硬件维护 | 未整理篇 | 站长教程
ASP JS PHP工程 ASP.NET 网站建设 UML J2EESUN .NET VC VB VFP 网络维护 数据库 DB2 SQL2000 Oracle Mysql
服务器 Win2000 Office C DreamWeaver FireWorks Flash PhotoShop 上网宝典 CorelDraw 协议大全 网络安全 微软认证
硬件维护  CPU  主板  硬盘  内存  显卡  显示器  键盘鼠标  声卡音箱  打印机  机箱电源  BIOS  网卡  C#  Java  Delphi  vs.net2005
  当前位置:> 看雪学院专区 > 加壳与脱壳
Themida虚拟机简单介绍
作者:softworm 时间:2006-12-13 17:47 出处:pediy.com 责编:月夜寒箫
              摘要:Themida虚拟机简单介绍
Themida虚拟机简单介绍


之所以写这篇东西,是我打算放弃跟v1.5.0.0的VM代码了:-(,另外,有些东西已经忘了,
所以下面的描述可能有错,也不完整。

1. Themida v1.1.1.0虚拟机代码的简单介绍

   v1.1.1.0的VM代码由多个动态分配的内存块组成,除代码外,有一个全局的
   数据结构,可称之为VM_CONTEXT,所有的虚拟机代码都要使用这个结构,v1.5.0.0
   的数据结构没有改变.

   结构中最重要的是+34处的dword,代表VM的register。

   代码通过push/jmp指令进入VM,格式为:

   push xxxxxxx
   jmp  xxxxxxx
   push xxxxxxx
   jmp  xxxxxxx
   ...
   push xxxxxxx
   jmp  xxxxxxx
   dd   xxxxxxx
   dd   xxxxxxx
   ...
   dd   FFFFFFFF

   最后的jmp下面为VM相关数据,第1个dword加delta offset为pcode数据起始地址,
   第2个dword为数据size。下面每3个dword为1组,对应一块独立的pcode数据,也对应
   上面的1个push/jmp指令对。以FFFFFFFF结束。

   原始代码按call分块,即原来代码内的call指令为 push/jmp指令对数 - 1。估计是
   因为call需要脱离VM,执行完后重新进入VM。

   
   每句PCODE包含如下数据(解码用到4个key):

   flag_zero_key;  // 是否清零4个key
   flag_to_init_key1;  // 取值0 - 3,决定如何初始变换key1
   num_of_rotate;  // vm_context内保存有进入VM时的寄存器(执行过程中会改变),
         这些寄存器视为环状,会随机顺序滚动,这样同样地址的数据
         可能代表不同的reg32。这个值决定滚动多少字节

   opcode;    // opcode类型,用key1解码
   opcode_flag;    // opcode的附加信息,用key1解码
   argument;    // pcode的dword参数,用key1解码
   next_pcode;    // 下一条pcode数据地址,用key3解码
   index1;    // 用key4解码
   index2;    // 用key2解码,这2个index合起来用于查表,搜索下一条pcode
         解释函数地址

   解释函数的动作包括:初始化各key,解码上述数据,如果需要滚动context内寄存器,解释
   执行pcode。计算下一条pcode地址(保存在全局数据结构+0处)及其handler地址,然后
   jmp esi执行下一句pcode。

   以类似下面的指令退出VM:

   push dword ptr ds:[edi+94]
   push dword ptr ds:[edi+9C]
   mov dword ptr ds:[edi+28],0
   popad
   popfd
   retn

   用解码程序可以得到类似下面的pcode,其中第1列为pcode地址,第2列为解释函数地址,
   register为vm_context内的register,即VM自己的register。VM使用自己的栈,push都
   是压入vm栈。

   00A186CE:011D80CA  mov  register,addr_of_context.ebp  
   00A186EA:011D0C5D  push  dword ptr [register]   
   00A18706:011DA3DB  mov  register,context.esp   
   00A18722:011F0000  sub  register,00000004   
   00A186F8:011D3F6E  rep  movsb [register],[esp] (04 bytes)   
   00A186DC:011D18AF  push  register   
   00A18714:011D744B  rep  movsb context.esp,[esp] (04 bytes)   
   00A18730:0123157F  mov  context.ebp,context.esp   
   00A1873E:01240B8E  mov  register,0D70178C       ; nop
   00A1874C:012352F6  add  context.esp,FFFFFFE0

   等价的真实代码:
   push ebp
   mov  ebp,esp
   add  esp,FFFFFFE0

   当然,也可以考虑自己定义一套pcode,把得到的vm pcode转换为自己的pcode(或直接跟
   Themida自己的编码),然后把上面的结果转换为真正的汇编码。我是自己读出来的;-)

   加壳时,对VM代码做了随机化处理,pcode数据的各个field摆放的顺序是变化的,对应的
   handler执行的操作顺序也在变化,虽然干的事情相同,但处理先后顺序可变。

   这样,修复VM保护代码时,难以写出通用的解码程序,即使是用同一版本加壳。必须逐个
   分析handler。从跟的结果看,很多handler是相同的,区别只是处理顺序。


2. Themida v1.5.0.0虚拟机代码的变化

   首先一个变化是混淆代码增强了,也许更早的版本就已经这样了。这是分析VM的主要障
   碍。对1110,可以把清理混淆代码后的VM贴回dumped_.exe直接运行,现在就难以做到了。
   基本的指令序列是类似的,但指令序列不连续,分隔指令序列的垃圾代码及其数量也在
   变化,难以识别或用NOP替代。

   我最后是这样做的: 跳着判断指令序列,如果匹配上,将中间的分隔指令全部作为junk
   清除。理由是,既然符合混淆指令序列,即由原来的正常指令expand而来,除匹配指令外,
   其余的应该是插入的垃圾。这样做的结果似乎还凑合,主要的问题是模式之间存在嵌套,
   对1个模式的处理破坏了别的代码。估计最好能判断出嵌套的模式,然后从内向外处理。
   总之费了不少劲,但结果不好。

   VM这次在1个连续的内存块内。本身的结构并无多大变化,pcode数据各field的含义,各
   key的使用都和原来一样。主要的区别有2处:

   pcode数据各field的offset固定了,这个不是很肯定,但看了4个handler都相同。各field
   在pcode数据内的偏移为:

   0x0,    // 是否清零keys,这个byte还参与key1的初始化
   0xC,    // 初始化key1
   0xC,    // 本轮rotate bytes,与上面使用1 byte不同位
   0x1,    // opcode
   0x4,    // opcode附加信息
   0x5,    // dword参数
   0xB,    // 下1条pcode地址
   0x9,    // index1
   0xA,    // index2

   这是好事。坏消息则"相当"肯定。每个handler在解码时使用的常量都不同。如下面一段
   代码,是解码v1.1.1.0使用的:

   void InitKeys(DWORD curr_pcode_data,int nIndex)
   {
  // 初始化4个解码key

  BYTE zero_key  = 0;
  BYTE init_key1  = 0;
  
  ReadProcessData(curr_pcode_data + data[nIndex].offset_zero_key,  &zero_key, 1);
  if(zero_key & 0x80)
  {
    key_1 = 0;  
    key_2 = 0;
    key_3 = 0;
    key_4 = 0;
  }

  ReadProcessData(curr_pcode_data + data[nIndex].offset_how_to_init_key1, &init_key1, 1);

  switch(init_key1 & 0x3)  // 对v1.5.0.0,使用的常数不同
  {
  case 0:
    
    key_1 ^= 0xD7;    
    break;

  case 1:
    key_1 += 0x89;    
    break;

  case 2:
    key_1 ^= zero_key;
    break;

  default:
    key_1 += zero_key;
  }
   }
 
   对于v1.1.1.0,这个函数是固定的,对1.5.0.0,下面case 0,case 1内的0xD7,0x89是变化的,
   每个handler都不同。不只是key的初始化,opcode,opcode_flag,dword参数,下句pcode地址
   及handler地址,2个index,所有数据的解码,不同handler都使用了不同的常数。看到这个我
   彻底失去了耐心。

   v1.5.0.0主程序用vm保护的代码有40余处。留给有坚强意志的朋友完成吧。



关闭本页
 
首页 | 投资与合作 | 服务条款 | 隐私政策 | 收藏本站 | 设为首页 | 新用户注册 | 免责声明 | 使用帮助
Copyright ©2005-2008 chinaitpower.com All rights reserved. www.chinaitpower.com 版权所有