Eazfuscator.net 2020 虚拟化保护(Virtulization)机制分析
一、前言与目标
周末接触了一款游戏They are billons即亿万僵尸,想添加一些新的玩法元素比如新的兵种进去,
打开dnspy看了下,发现是Eazfuscator.net的Virtulization即虚拟化保护,并带了字段、方法混淆,字符串也加密了,
那就开始分析吧!
二、准备过程
1,先试试能不能插入自己的代码
打开dnspy,插入一段简单的探测用IL代码,可以编译,但打开程序报错:不能读取DAT文件(当然是英文的)。
这个提示很有意思,最开始我猜测是程序作了完整性/防篡改校验,但其实是也不是,后文揭晓吧。
2,正向阅读代码,建立对程序行为的整体性理解
这里用了比较久的时间,一个是虚拟化保护机制的单步跟踪十分耗时,另一个是游戏自己业务逻辑也算复杂。
3,抽象出虚拟化保护的逻辑框架
这里先列几个重点概念:
3.1 虚拟机
主要解释执行虚拟指令,姑且叫VLR;
3.2 虚拟IL代码
对比MSIL,MSIL算标准实现的话,这个算自定义实现,姑且叫VIL;在这套保护机制中,VIL的数据存储结构是字典即Dictionary<int32,Delegate>,其中Key就是VIL标识,Value就是此VIL对应的C#方法,这个方法“模拟”实现了MSIL的功能
3.3 指令指针
指向下条指令的"位置",这个"位置",就是第(2)条提到的Key即VIL标识,怎么来的呢?
3.4 计算堆栈,
自定义地实现了一个EvaluationStack(举个例子,在MSIL中的ldfld,stloc.s操作的就是这个栈了),其具体结构是:
(a)局部变量区:数组,
(b)方法参数区:数组,
(c)CallStack:自定义的LIFO的堆栈结构
3.5 跳转指令
控制程序流程,通过操作第(3)条的指令指针实现
3.6 单条指令执行的抽象形式
operate_instruction(EazDataType parameterData),
划重点:
3.6.1 指令执行的方法:其中Instruction_Operatate是一个封装了指令执行委托的结构,委托是关键:`private delegate void g(I #=zOSg$HgU=);`,
3.6.2 指令执行的参数:而EazDataType 是一个对所有基本类型,如int8,16,32,64,及其无符号类型,还有数组、object及IntPtr等类型的自定义封装
为什么要划重点?
3.6.3 理解了虚拟指令的行为及参数数值的含义,是理解被保护下的程序逻辑的基础,也是写出脱壳工具即DeVirtualizer的基础(github上有个15年后不再更新的,那时候的Eaz还很简单)
3.6.4 Eaz团队花这么大功夫做自定义类型,肯定不是把真实参数摆在类型结构内的一个字段就完事了,后面会知道,这跟序列化、出入EvaluationStack有关,没错,它就是极大地增加了我们阅读和还原程序的难度
3.7 序列化与反序列化
3.7.1 虚拟指令哪里来的?从程序的嵌入资源即EmbeddedResource来
3.7.2 Stream操作的Seek,Read,Write均被重写,同样混淆+反复跨多个类+高深度调用,增加难度
3.7.3 ReadInt4,8,16,32,64即无符号形式,均被自定义重写,其中还跨几个混淆后的类进行反复穿插调用,没错,也是为了增加我们阅读和还原程序的难度
3.8 Assembly Resolve
程序对DXVison.dll,DXPlatform_Desktop.dll等类库,采用了如下方式处理来增加难度:
3.8.1 将dll作为EmbeddedResource来构建,当然dll本身做了加密 and/or 压缩处理
3.8.2 在ResolveAssembly中进行Assembly.Load,具体的,当然会解密 and/or 解压缩
三、VIL执行过程分析
//----------------------------------------------------------------------------
// Eaz VIL执行逻辑 2020.8.23 6:22 A.M. Ben
//----------------------------------------------------------------------------
// Token: 0x06002490 RID: 9360 RVA: 0x0006F4F4 File Offset: 0x0006D6F4
private void #=zPq6qoiyuLMY82$aYQR3G2PDDewUYassYkHNyaic6mupX()
{
long num = this.#=z3Ey5Z$A=.a.d;
while (!this.#=zIohob_Q=)
{
//跳转标记,不空则顺序执行,否则跳转执行
if (this.#=z46nfKvA= != null)
{
//置指令指针
this.#=z3Ey5Z$A=.a.e = (long)((ulong)this.#=z46nfKvA=.Value);
//清跳转标记
this.#=z46nfKvA= = null;
}
//找出并执行指令
this.#=zSDEDP1kaZWXW$45uMxtJcKw=();
if (this.#=z3Ey5Z$A=.a.e >= num && this.#=z46nfKvA= == null)
{
break;
}
}
}
从上面的代码可以看出就是一个简单的while型结构,其中具体的“找出并执行指令”的方法,最终会来到这里:
// Token: 0x06002492 RID: 9362 RVA: 0x0006F60C File Offset: 0x0006D80C
private void #=zreNohAiSE8iKLx4yhAUiRL0=()
{
//指令指针
long num = this.#=z3Ey5Z$A=.a.e;
//指令标识
int key = this.#=z3Ey5Z$A=.#=zQ_ANng9RuwjiUHLMdDTa3uFQlfZa();
//执行指令的具体C#方法,这里即模拟MSIL执行的过程
Sa.h h;
if (!this.#=zY0bMZDI= /* <VIL,Delegate>型字典 */.TryGetValue(key, out h))
{
throw new InvalidOperationException(#=qgZ0DNC_7EOR3zfCQRQFvJ6zpp28vu_oHH5ALnGtD3WQ=.#=zAuKOdtM=(-105951893));
}
this.#=zmBYAt_U= = num;
//封装指令参数,并执行指令,这里的VIL一共203条,不是MSIL的226条
h.#=zBHOdjps=(this.#=zp5urEB_sgKnN5sPVX9mxjurqsdh7nWJ3ig==(this.#=z3Ey5Z$A=, h.#=zOSg$HgU=.b));
}
其中有几个点,
关于key即指令标识怎么来的,也就是如何取指令的,看这里:
//取指令
// Token: 0x0600241C RID: 9244 RVA: 0x0006CF2C File Offset: 0x0006B12C
internal int #=zKCiIwS5PZc7nU6m85A==()
{
if (!this.#=zzepStOk=)
{
throw new Exception();
}
//非跳转即顺序执行的情况下,下条指令在VIL Stream中的位置
int num = this.#=zTxm7_P0= += 4;
if (num > this.#=z1rdegSo=)
{
this.#=zTxm7_P0= = this.#=z1rdegSo=;
throw new Exception();
}
//this.#=zOSg$HgU=即VIL Stream的字节形式,这一句就是反序列化得出指令标识了
return (int)this.#=zOSg$HgU=[num - 3] << 8 | (int)this.#=zOSg$HgU=[num - 1] << 16 | (int)this.#=zOSg$HgU=[num - 4] << 24 | (int)this.#=zOSg$HgU=[num - 2];
}
五、字符串解密过程
太长,有兴趣的同学自己跟,我们列出这段的目的是寻找对我们理解被保护下的程序执行过程,为打开思路和诊断问题打下基础。
//字符串解密,此方法调用深度太深,略,通过看局部变量的变化,拿到返回的字符串,可以帮助理解程序执行流程
这里的字符串会出现:
a. "cctor"
b. "TheyAreBillions.exe"
c. "Log"
d. 类名
e. 方法名
// #=qgZ0DNC_7EOR3zfCQRQFvJ6zpp28vu_oHH5ALnGtD3WQ=
// Token: 0x0600008B RID: 139 RVA: 0x000045A0 File Offset: 0x000027A0
[MethodImpl(MethodImplOptions.NoInlining)]
internal static string #=zAuKOdtM=(int #=zJUrJqCXmqVi3rqXNtbpf4_w$1MCa)
{
#=qgZ0DNC_7EOR3zfCQRQFvJ6zpp28vu_oHH5ALnGtD3WQ=.l11ll11l111lll111 obj = #=qgZ0DNC_7EOR3zfCQRQFvJ6zpp28vu_oHH5ALnGtD3WQ=.#=zX2TmEXwAWH2uDIzJb_$ykHVBWImv;
string result;
lock (obj)
{
string text = #=qgZ0DNC_7EOR3zfCQRQFvJ6zpp28vu_oHH5ALnGtD3WQ=.#=zX2TmEXwAWH2uDIzJb_$ykHVBWImv.get_Item(#=zJUrJqCXmqVi3rqXNtbpf4_w$1MCa);
if (text != null)
{
result = text;
}
else
{
result = #=qgZ0DNC_7EOR3zfCQRQFvJ6zpp28vu_oHH5ALnGtD3WQ=.#=zAcaXirSWgb6OCMOieJ96pes=(#=zJUrJqCXmqVi3rqXNtbpf4_w$1MCa, true);
}
}
return result;
}
六、程序流程控制手段
主要手段如下:
1,从程序的嵌入资源中,得到VIL Stream及其字节表示形式,顺序执行 + 跳转执行;
2,反射执行:Stream -> Seek -> Read -> 得到 Type Name -> 字符串解密 -> 通过反射初始化此类型,方法同理
七、一些建议:如何更好地分析虚拟化保护下的程序?
到这里,我们可以愉快地调试并分析程序,但同样有几个细节需要注意,否则会迷失在看不懂的代码里:
1,关注dnspy中的方法调用堆栈,
2,关注EvaluationStack,局部变量和参数变化,尽快分析出操作局部变量、方法参数和EvaluationStack的VIL即对应的C#方法,及跳转指令如brfalse.s,brtrue,ceq及ret等
3,关注类型反射关注MethodBase及Constructor的调用
4,关注字符串解密的调用及重点字符串
以上建议,目的只有一个:全面、准确地理解程序是如何执行的
划重点:逆向工作和写业务代码不一样啊,在逆向工作中,搜索引擎能帮到你的有限,所以基本功夫做扎实不会错
八、最后
回到初心,做这个分析的目的是为了改改游戏,顺便练练技术内功。
游戏对部分资源文件(.dat)加了密,经过分析就是一个带密码的标准Zip协议压缩后的文件,
- 如何得到密码?
a. 修改其使用的zip.dll,打印出密码
or b. 调试得出
- 如何插入自己的代码?
前文提到过,插点代码程序就起不来了,但难得住我们吗?
a. 故布疑云,程序计算解压密码
读懂了程序后,发现其会根据exe本身的内容和大小,做一系列计算,得出zip包的解压缩密码
(还记得吗,当初我以为是Eaz保护后做了完备性/防篡改校验,其主要意义是保护资源,但是间接也防止了篡改,算一个一箭双雕的保护技巧吧 :|)
b. 张冠李戴,我们来代入正确密码
既然我们改了程序会导致“密码计算”出错,那直接写死个正确密码不就得了(实际上是多个文件,多套密码),
c. 偷梁换柱,载入我们修改后的dll
而使用这个密码并进行解压缩操作的dll恰好是通过上文提到的作为“EmbeddedResource”载入的,那么如法炮制,在dnspy中添加资源,并去掉程序对资源文件的解密/哈希过程,载入我们自己的dll即可
3,自由王国开启,但仍有雾霾笼罩
爽点:
可以Hook进我们自己的代码后,基本就是进入了自由王国,想干啥干啥,配个图,实现新兵种添加:
更爽点:
我们脱掉了虚拟机保护了吗?没有,我们只是十分熟悉了它并利用规律达到我们的目的。
技术男的终极目标必须得是:写出一个脱壳机DeVirtualizer!!!
那这个就交给大家吧 ^^
(完)
Eazfuscator.net 2020 虚拟化保护(Virtulization)机制分析的更多相关文章
- KVM基于X86硬件辅助的虚拟化技术实现机制【转】
内存虚拟化 Shadow Paging 作者 Shawn 在其中文博客中很详尽地介绍了 KVM 在只支持一级分页的 x86 平台上用 “Shadow Paging”进行 MMU 虚拟化的实现,由于目前 ...
- Java 动态代理机制分析及扩展
Java 动态代理机制分析及扩展,第 1 部分 王 忠平, 软件工程师, IBM 何 平, 软件工程师, IBM 简介: 本文通过分析 Java 动态代理的机制和特点,解读动态代理类的源代码,并且模拟 ...
- Linux内核态抢占机制分析(转)
Linux内核态抢占机制分析 http://blog.sina.com.cn/s/blog_502c8cc401012pxj.html 摘 要]本文首先介绍非抢占式内核(Non-Preemptive ...
- Keil C动态内存管理机制分析及改进(转)
源:Keil C动态内存管理机制分析及改进 Keil C是常用的嵌入式系统编程工具,它通过init_mempool.mallloe.free等函数,提供了动态存储管理等功能.本文通过对init_mem ...
- Java代理和动态代理机制分析和应用
本博文中项目代码已开源下载地址:GitHub Java代理和动态代理机制分析和应用 概述 代理是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个对象的访问.代理类负责为委托类预处理消息 ...
- linux RCU锁机制分析
openVswitch(OVS)源代码之linux RCU锁机制分析 分类: linux内核 | 标签: 云计算,openVswitch,linux内核,RCU锁机制 | 作者: yuzhih ...
- Linux 线程实现机制分析 Linux 线程模型的比较:LinuxThreads 和 NPTL
Linux 线程实现机制分析 Linux 线程实现机制分析 Linux 线程模型的比较:LinuxThreads 和 NPTL http://www.ibm.com/developerworks/c ...
- Linux内核抢占实现机制分析【转】
Linux内核抢占实现机制分析 转自:http://blog.chinaunix.net/uid-24227137-id-3050754.html [摘要]本文详解了Linux内核抢占实现机制.首先介 ...
- Java 动态代理机制分析及扩展,第 1 部分
Java 动态代理机制分析及扩展,第 1 部分 http://www.ibm.com/developerworks/cn/java/j-lo-proxy1/ 本文通过分析 Java 动态代理的机制和特 ...
随机推荐
- JavaScript 你真的了解this指向吗
JavaScript 你真的了解this指向吗 前言 终于开始写this指向了,相信这对很多JavaScript的学习者来说是一个非常恐怖的环节,个人认为也算是JavaScript中最难理解的一个知识 ...
- mybatis plus 更新值为null的字段
转载请注明出处: 由于mybatis plus调用默认的更新操作方法时,不更新值为空,null或默认值等得属性字段,只更新值为非null,非空非默认值的属性字段. 以下为mybatis plus sa ...
- 001_HyperLedger Fabric环境安装
HyperLedger Fabric的环境,有解决三大问题 第一,是系统环境,这里我们选择的是centos7 第二,是开发环境,这里我们选择的是Go语言 第三,是运行环境,这里我们选择的是Docker ...
- 薪资高,福利好,会Python的人就是这么豪横!
很多人可能会有这样的疑问,数据分析Excel挺强大的,会Excel就行,为什么还要去学python? 是的,Excel和python对于数据分析而言,这两者都只是不同的工具而已. 很多人学习pytho ...
- Vue 引用图片的三种方式
首先给图片地址绑定变量 <template> <img :src="imgUrl"> </template> 在script中设置变量 < ...
- 6、Java 运算符
Java运算符按功能可分为:算数运算符.关系运算符.逻辑运算符.位运算符.赋值运算符和条件运算符. 1.算数运算符 算术运算符包括通常的加(+).减(-).乘(*).除(/).取模(%),完成整数型和 ...
- Nginx配置SSL证书,提高网络安全性
首先区别Http与Https HTTP:是互联网上应用最为广泛的一种网络协议,是一个客户端和服务器端请求和应答的标准(TCP),用于从WWW服务器传输超文本到本地浏览器的传输协议,它可以使浏览器更加高 ...
- 2020-04-23:假设一个订单的编号规则是AAAAOrder2020-0000001,AAAAOrder2020-0000002....后面的数字是自增长,如果订单号码达到AAAAOrder2020-1000000(100万),数据库中应该有100万条数据,此时我随机删除2条数据(物理删除,且不考虑日志和备份),请问怎么找到删掉的数据的编号?给出解题思路即可,答案需要在1秒内运行得到。
福哥答案2020-04-23: 分批查询:分成500次count(),每次count()肯定小于等于2000条数据,经过测试,一次count()在.1ms左右,500次就是500ms.二分法(时间微超 ...
- 对象原型之__proto__
对象都会有一个__proto__指向构造函数的prototype原型对象,对象之所以能够使用构造函数的prototype原型对象的方法,就是因为有__proto__原型的存在. funct ...
- flask-migrate 处理sqlite数据库报错Constraint must have a name 的解决方案
环境:flask+python+sqlite,我想给某个表里某个字段加unique属性 执行 python manage.py db migrate 没报错,执行 python manage.py d ...