2021看雪SDC | 议题回顾《houdini编译机制探索》

发布者:Editor
发布于:2021-10-29 19:14

尽管houdini技术有着得天独厚的优越性,可国内对此的相关研究还处于启蒙阶段,尤其是其核心的实时指令转换的资料基本没有。


该议题通过对houdini机制的深入分析,从Houdini的前生今世,到其内部逻辑原理,指令篡改,代码校验......能够尝试理清其编译的诸多实现细节。为我们在模拟器领域对代码执行层面构建更为深入有效的安全方案提供技术基础。


下面就让我们来回顾2021看雪第五届安全开发者峰会《houdini编译机制探索》此议题的精彩内容。



演讲嘉宾

图片

张玉璞:腾讯IEG业务安全部手游对抗负责人,腾讯安全技术专家。


曾在盛大、淘宝等多家企业长期从事游戏开发及二进制安全相关工作。2012年加入腾讯业务安全中心,2013年开始负责手游客户端安全对抗的方案建设及团队管理工作,主导创建了手游客户端安全对抗团队及手游核心对抗方案。



演讲内容


以下为速记全文:


大家好,我是来自腾讯互动娱乐事业群的张玉璞,此次想和大家分享的主题就是《houdini编译机制的探索》。


01

个人及团队介绍


首先介绍一下我所在的团队和以及个人。

 

我们是腾讯互动娱乐事业群下面的业务安全部,我们主要的工作其实都是为了游戏的安全,通过我们建立的Anti Cheat Expert安全方案,为游戏在外挂对抗、内容安全、反打击、账号安全以及黑产管控等泛安全的领域实行全面的保驾护航。个人刚才我们的主持人也已经介绍了,所以我就不用再过多介绍了。


02

从一次模拟器上的外挂对抗谈起


回到我们的主题,不知道大家有多少接触过Houdini的转译系统,对于我们来说的话,其实我们也是大概从19年底20年初开始了解到这样的一个转译系统,并且开始对它做相关的一些研究的。


为什么会接触到这样的一个系统?因为我们是做游戏安全的,所以其实我们是从一个模拟器上面的外挂对抗开始接触到这样的一个系统的。在传统的模拟器的外挂上面,其实它的做法和移动端的外挂没有太大的区别,它还是修改的是安卓的游戏进程内的各种代码以及数据,但是在19年底我们发现了有一种新类型的外挂,这种外挂它修改的不是安卓进程空间内的,它修改的是PC进程的x86代码。

 

既然是改了PC进程,我们第一个想法就是说我们移动端的方案是不是搞不定了,所以我们会有这样的一个想法就是说我能不能够模拟器去使用PC端的方案呢?


如果我们只探讨,比如说我们可以接入方案之后的话,我们肯定是可以去这样去应用的,但是市面上的模拟器有很多,并不是所有的模拟器都会去接入你的 PC端的方案,我们怎么样去保证我们在各个模拟器上都能够对 x86代码进行校验?

03


Houdini的前世今生


结果我们后面发现,实际上经过Houdini的这套系统之后,x86的代码它同样会被映射进安卓的游戏进程内,所以我们其实是可以去由游戏进程类的移动端方案去感知PC进程生成之后的x86代码的一个正确性,这个就使得我们对于houdini的这套系统产生了一个比较浓厚的兴趣,因为这样我们可以用移动端的方案去校验PC进程内的x86代码,所以我们就对于 houdini的系统开始进行了一些初步的了解。

图片

Houdini这样的一个转译系统其实是英特尔在为了他在手机上面去运用x86架构的cpu,他为了实现这种x86架构的CPU可以去比较好的支持原生的基于arm指令集编译的apk,所以建立了这样的一套转译系统。

 

但是这一套的转译系统其实不止应用于x86的架构下的手机,由于它的一个性能要相对于传统模拟执行的方式要快很多,所以现在各个主流的模拟器全部使用了这套houdini的转译系统,所以hold it的一个技术特点就是快,它不但通过git的方式,而且的话它整个生成的我们在后面会介绍的一些很特殊的变化性很强的这些实现机制,都是为了尽可能的在一次转移译中只生成必要的内容来保证这样的一个效率。

 

而同时houdini是一个目前英特尔并没有正式公开的技术,所以在网上也基本上我们也尝试着找过,基本上是没有任何相关的一些资料的。


所以我们对于houdini的研究只能够通过逆向,目前houdini已经有到了七点几的一个版本,我们在最开始遇到的时候还是在四点几的一个版本,但是通过我们从四点几的版本到七点几的版本的各个适配来看的话,它的整个的核心机制是完全一模一样的,所以后面的介绍主要是依据于houdini4的一个版本来做。

3


逆向分析实例


我们现在来开始去分析houdini的实现机制。在分析合理的实现机制之前,我们先看一下这是同一个函数两次编译之后的一个汇编代码的内容。可以看到虽然是同一个函数,但是这两个汇编代码长的也不是很像啊。


首先第一个我们会发现这两个函数的地址就已经不一样了,差得很远。

 图片

第二个不知道大家能不能仔细看到这个内容,在这个代码的主体的两个代码块,红框标识的两个代码块里面,这个代码的内容也不一样,而且这两个代码块的内容也没有再出现在彼此的这两次编译之后的代码里面,为什么这两个代码在第二次编译之后就没了?

 

第三个,如果我们再去看这个代码块里面的细节,我们会发现这些计寄存器变化,第一次使用的是ebx,第二次使用的是eax另外原先的JNZ在这里变成了JZ这个可以看到只是同一个函数的两次编译,就已经有了这么多的变化。

4


猜想


让我们不得不对Houdini的生成机制有一些技术的猜想,来辅助我们去分析Houdini他大概会怎么样去做。

图片

首先就是这个地址是漂浮不定的,那么这个对于动态系统来说的话,我们觉得也是很好理解的,所以它很有可能是通过为了效率采用jit的一个生成机制,但是jit的生成机制,它就会要去解决一个问题,因为有的代码块先生成有的代码块后生成,那么如何去处理这些代码块之间的一个跳转的实现?

 

它是通过一个比如说都跳到一个公共的逻辑中,然后有公共的逻辑进行分发,还是说去进行彼此的一个直接跳转?如果是直接跳转的话,谁来把这个跳转去做一个修改?

 

第二,一个函数它编译的代码片段,有的时候有有的时候没有,那么就说明Houdini的这一套编译机制的话,它一定不是以函数为单位的,它应该是一个更小的呃逻辑区分来作为代码块的划分的,那么它划分的原则又是什么?我们必须要弄清楚。

 

第三,不断变化的代码细节,x86的寄存器器为什么会变化?代码块以什么样的形式去访问arm寄存器的内容,那么因为x86的寄存器肯定不会和arm的寄存器有映射了,因为它是在不断变换的,以及除了寄存器变化的信息,包括我们前面看到的跳转信息的变化之外,还有没有其他可能随机生成的一些内容?

 

最后除了前面这几个我们在前面的实例里面看到的内容之外,还有一个没看到的部分,那还没有生成的代码块去哪里了,没有生成的代码块,它又是以什么样的机制去进行生成的,以及生成之后,它是如何和这些已生成的代码块形成一个拼接的呢?这个就是我们对于Houdini的这些实现在最初产生的一些猜想。



分析目标与方法


而回到我们分析的目的来说,我们既然是做外挂对抗,那么我们的分析目的第一,我们要能够明确区分出,你的执行环境是在Houdini的执行环境之内,还是在手机或者说其他的一些执行环境之内。

 

第二,如果你是在houdini的执行环境之内,我们如何去校验这个代码的正确性?有了这样的一个分析目标,我们就有了一个实际的分析方向来对这个目标进行拆解。

图片

第一,我们既然要分析和定力完成这个目标,我们首先要知道 houdini中的内存布局大概是一个什么样的情况。然后的话前面的代码块,我们知道它不是以函数为单位,那么它的划分规则我们要弄清楚,在弄清楚了代码块的划分规则之后,那么它一定会有一个管理的机制,管理的机制是什么?

 

以及代码块中它会有各种各样的跳转,而如果外挂插入了一个hook,或者他修改了一个跳转,我们怎么样去区分这个跳转是正常的还是异常的。我们得弄清楚它控制流的一个机制,最后我们要知道代码块在什么时候开始,以及代码块在什么时候结束,然后我们最终去理清代码,快速的了解各个指令它翻译的一些大概机制以及它的一些细节,通过这些内容的了解,我们才能够去达到我们的目标。

6


逆向思路


但是前面我们也说了,houdini其实是没有任何的技术资料在对外公开的,所以要想去分析这各个点,我们只能够通过逆向的方式来做,那么如何逆向就是一个问题。


我个人的经验而言,我觉得逆向很像一个拼图游戏,我们大家不知道在玩拼图的时候是不是都是按照这个思路来做的,就我个人而言的话,在这个拼图里面我会首先找到一些单个的拼图,这些拼图的特点是什么呢?有它的图形里面是可以根据图形信息完全很容易的定位到它在整个拼图的一个相对位置。

 

然后基于这些固定的拼图信息,我们再向外进行扩展,找到它相关联的更多的拼图,然后逐步的通过往外延伸的延展的方式来完成整个评图。而逆向我觉得和这个思路非常的相似。在整个立项和定义中,我们首先要找到什么东西是可以固定的,并且和丁力一定不会对它进行转移更改的,然后我们又可以重复利用。

 

对我们来说主要是在arm端的里面的一些信息,比如说 arm中的各个函数的首地址,函数的块中的跳转地址,调用代码的一些地址,以及函数中一些对于成员变量访问时候的这些偏移信息,这些无论houdini在怎么样去变换,这些内容他基本上都得保持一致,因为他最终他要去和arm端去做一个比对的翻译过程,所以这些内容一定是会固定下来的,并且houdini会去做应用的。

 

所以依据这些固定的内容,我们就可以去建立一个快速逆向的思路。首先我们可以dump得出整个进程空间内的内存,而然后dump它会为我们带来两个分析的一个好处,第一个因为houdini本身是一个动态执行的系统,所以它有很多内容可能每次都会在不断的变。通过dump一次,我们可以保证在这一次的dump里面,所有代码块之间的信息,代码块和代码块之间的管理的信息它一定是固定的。

 

另外通过多次的dump进行比对,我们可以找到houdini在执行中的一些共性。然后有了这个dump之后,我们就可以前面提到的这些函数信息,跳转信息,调用信息,以及通过偏移的信息所定位出来的x86的代码块的首地址呢,来对整个内存进行搜索,从而定位到它的对于代码块跳转的管理机制的一些数据结构。

 

通过对这些数据结构再进行更加深入的分析,我们就可以剖析出houdini中的各个细节,从而完成我们的拼图。

 

那么下面我们来看我们如何对houdini层层剥茧式分析


1、houdini的内存布局


首先就是第一个目标我们要实现,要知道houdini的内存布局是怎么样的,我前面所说我们通过多次的内存比对,我们发现houdini的内存布局是一个很固定式的布局,在右边这个图里面可以看到houdini的布局分成4块,第一块叫做我们这个是我们所有的内容都是我们自己命名的,因为我们不知道他在英特尔那边的实际代码里面命名的是叫什么,所以这个都是我们自己命名的。

图片

第一块我们命名为houdinicode,这一块的内容是基本上完全固定的,因为他主要负责的就是为houdini的转译系统去进行使用的,所以这个代码是全部固定的,在houdinicode之下就codeblock, code block它所存放的内容就是转移之后的各个x86的代码块,而在code block下面就是数据段,数据段根据它的功能的使用,我们可以把它拆分为DataA和DataB,DataA里面存放的应该是它的代码块的管理的数据结构,而在DataB中存放的则是它在实际执行中所应用到的各种数据。


2、代码块的划分及生成规则


对于内存布局我们有了一个基本的了解,那么下面我们来看代码块的划分规则是什么?

 

从对于同一个函数进行多次比对,我们会发现houdini的一个处理原则基本上是这个样子的。我们知道在代码里面有很多的跳转啊,有条件的跳转,无条件的跳转等等。

 

那么houdini的一个划分规则,首先对于有条件的跳转,比如说 bne会变成jnz这种处理它所对有条件的调整,它会去生成对应的houdini的控制流。

 

而对于无条件的跳转,比如说像这个函数调用,它就会去生成一个新的代码块,所以这个是它对代码块的一个划分规则,而同时有条件的跳转,它生成的控制流,因为它后面的代码其实也是一个新的代码块,所以有条件的跳转最终会跳到下面的很短的无条件跳转的代码的部分,然后实现最终的调跳到新的一个代码块为他做准备。

 

那么什么时候去生成这个代码块?因为Houdini为了效率的一个原因,代码块只有在必须执行的时候才会去生成。那么什么时候怎么样去判断说Houdini的代码快在它就必须要生成的,就是通过这个jmp,他这样的jmp最终会跳到一个houdini的处理逻辑里面,我们会在后面讲到控制流的时候进行介绍。


3、代码块的管理机制 


所以通过这个分析,我们知道它无论是代码块的生成,还是像有条件调整之后的新的代码块的处理,他houdini中都必定存在着一个管理的数据结构,这个数据结构一定会做三件事情:

(1)它要管理已生成的代码块的信息。它所对应的arm地址,它所对应的x86地址的管理。

(2)它一定会管理这些代码块中的各种各样的这些跳转信息,他得知道下一次生成的时候生成的是哪一块。

(3)这些还没有生成的代码块,一定要有一个管理的机制来保证说当他要生成的时候,我知道他要生成哪一个,所以我们就需要找到这样的一个数据结构。

 

我们通过前面介绍的通过这些函数地址,跳转地址以及偏移信息所定位出来的x86代码块的首地址,在整个内存中进行搜索,通过多次的这种搜索,我们就可以逐步的理清这样的一个代码块的管理数据结构。

图片图片

这个地方我们首先把它命名叫做code block entry list array在这个数组中首先这个数组它所存放的位置是一个固定的位置,就是我们前面所提到的dataA的这一个内存段。其次 Array里面会保存了很多个叫做code block entry list的这样的一个数据结构,然后在这个数据结构里面就有我们所希望得到的一些关键信息。

 

首先,entry list里面有一个叫做我们命名为code block entry这样的一个结构,在这个结构里面可以看到,它就保留了每一个代码块的arm地址的信息和x86地址的信息。而除此之外,在 code entry struct里面,我们还发现有一个我们后面会用到的一个很重要的内容就是叫做jump table。在 jump table里面就有我们所存放的各种各样的跳转信息,所以我们来到了对于控制流的一个处理。


4、控制流的处理  

图片

在分析这些我们前面提到的这些所有的无条件的跳转,我们可以发现这些无条件的跳转主要分为4类,第一个处理未翻译的代码,也就是这些还没有生成的新的代码块。

 

第二个处理动态目标的一个跳转,比如说对于函数的调用,第三个就是立即数的调整,有些jmp他就直接这样子到了一个新的代码块。第4个就是一些块内的调整,这种调整一般类似于可能像循环之类的,它是直接往代码块的上面去跳。

 

而在这4个跳转中,我们发现前面的第一种和第二种它有一个共性,它都会跳到前面说的内存布局里面的第一个内存款块叫做houdinicode,那么说明这两种跳转一定是背后定义进行了特殊的处理,来实现它最终所要跳下目标的一个生成,或者说代码快的翻译,第一种它是我们把命名为Jump table call,这种跳转它通过我们前面提到的数据结构中的jumptable里面有,一个target的字段,在这个字段里面,它会存放这次跳转所要跳转的arm地址的信息。

 

如果这个代码块没有生成,那么它就会对这个代码进行生成,生成之后将 target的信息替换为新生成之后代码或者code block entry的信息,而同时它会将原先的那一条跳转更改为第三种,把它派去向直接的调整。那下一次Houdini在执行的时候,它就会直接跳转到已生成的代码块,而另一种我们把它命名为叫做ParseByPC,在PC里面的跳转,他在前面一定会去保存的PC寄存器,然后在实际的函数之中,它会去读取 PC计算器,并且将它调向到目标代码块,而如果没有找到的话,它也一样会对他去做一个翻译的工作。

 

所以整个houdini对于各个控制流的一个实际处理,那么通过我们对于这个Houdini控制流的一个分析,就能够为我们后面在做代码校验的时候,去发现一些异常的指令修改和就是说一些hook进行一个技术基础的准备。


5、指令翻译 

最后我们把前面的各个它的一个执行的逻辑分析清楚之后,我们就来到了对于一些细处细节的判断,在指定翻译这一块Houdini有很多的一些特殊的处理和一些特殊的变化,但是相对而言它也有一些很固定的内容。

图片

比如说我们前面看到的这些事例里面,如果我们看第一句话,它永远都是这样的一条指令,在这个指令里面 address,它实际上指向的就是当前生成代码块的code,block entry的信息,而同时所有的我们刚才提到的arm寄存器,它也都是通过这一次的目把这些寄存器以压栈的形式把它压到各个ebp可以访问的内容,就去完成这样的一个寄存器的映射。

 

而在指令的翻译中,Houdini对于各种不同类型的指令都有一些特殊的处理,以及在处理过程中也会产生一些特殊的变化,因为时间的原因,我们只能够大概每一类的指令去进行一个大概的介绍。

 

首先就是内存访问和算术指令,这些指令的处理和定义相对来说还是非常简单的,因为在arm的指令里面的话都可以找到对应的x86的指令,所以他尽量采用的都是一一对应的方式去实现这样的一个生成,但是在生成的过程中它有两点处理会比较特殊一点,第一个它实际上会对arm的执行会做一定的优化,现在上面的图里面就可以看到,实际的在中间的r0的情况是没有被回写的,而是直接参与到了下一条的指令。

 

而另外像arm里面所独有的像 ldrd,ldm类似的这种多值多计算器的一个操作,它会将它翻译成一组的x86的指令来做。

 

而对于条件跳转指令,我们前面也介绍了,条件跳转指令的选取,实际上它并不是根据这条指令来的,它根据的是什么?它根据的是当执行到这条指时候的逻辑判断,它会走哪一个分支,它是根据分支马上要执行哪一块来去决定说我让跳转它的一个跳转的选指令的选择会是什么样,而对于另一个没有被走到的逻辑的话,它就暂时不会去生成,而是通过前面所介绍的无条件的跳转,让它跳转到这样的一个地方,来为未来代码块的生成做一个准备。

 

再一个就是simd类型的指令,在Houdini中它是使用x86的叫做SSE指定集的一个方式来支持arm中的 simd的这一套的指令。并且为了考虑通用性,它只使用了xmm的寄存器。如果有的arm指定集里面的内容,x86没有对应的sse指定的时候怎么办呢?他就会去通过一组逻辑等价的指令来实现。像这里就可以看到原本一条指令在这个地方翻译成了很长的一段,而且还有一点比较特殊的,像这个图里面的 vcvt的指令,它甚至还会在里面产生一些跳转。

 

再一个比较特殊的就是x86的代码块回收。因为我们知道前面提到的 code block entry list array里面它的长度它被局限在了dataA里面,所以实际上并不是所有的代码生成不销毁,当x86代码去考虑是否需要重新翻译的时候,它会插入在头部插入这样的一个代码段。

 

根据这样的一个代码段,当它执行的时候一旦被触发,那么在下一次执行之前,它就会被Houdini进行一个实际的回收。

 

那么通过前面的这一系列的介绍,我们了解了x86的Houdini对于x86代码块的一个对于arm代码块的一个划分的机制,以及它的一个对于代码块的管理机制,以及对于跳转的一些处理和各个指令翻译的一些细节。



Houdini环境识别


那么回到我们前面提到的这个目标上面,第一个我们要识别哪个是houdini的执行环境,第二个我们想要对代码的正确性进行校验,那么我们来看如何去做。Houdini的环境识别,不知道大家在前面的介绍里面有没有一些自己的想法,我们是通过刚才提到的对于内存布局对吧?

 

这里面它所有的内容是固定的,所以只要我们去判断在固定的地址中有没有x86的 houdini的固定的代码块,我们就可以知道它是不是在houdini的环境中。

图片图片 

为什么采取这样的一个方式?我们知道因为Houdini本身它是一个非常复杂的系统,并且它所有的刚才看到的这些比如说跳转,它都得依赖于这个houdinicode内存段,所以的话如果说别人要想绕过我们的这一套检测或者说去进行篡改的话,那么它必须要篡改大量的houdini的实现,来使得它去避免在一个固定的位置去进行加载。

 

第二个显然这种我在固定地址去查找是不是有固定的代码的这种方式,非常的通用,而且也很简单,所以迁移起来也非常的方便。那么有了这一个,环境识别的一个方法之后,我们就来到了对于代码正确性的一个比对。在看代码正确性比对之前,我们首先要了解攻击者他们是怎么去做的,这个是一个通用的对于Houdini的搜索的方式和对于x86代码块的一个搜索的方式了。

 

那么它的一个定位的方式其实和我们前面所介绍的逆向的这样的一个大致思路是有点像的,他同样他得去根据一些固定的内容去定位到他想要的一些修改点,比如说通过偏移的信息,通过一些 x86转译之后,相对固定的指定格式进行修改进行定位,而定位完了之后,他所需能够修改的内容同样也得是一些相对固定的内容,比如说修改偏移信息,把加8变成加20,在指定的执行的话肯定就会出问题。

 

另外修改一些跳转指令,或者说通过hook的方式,像在下面这个图里面插入一个jump,让他去跳转到自己的shellcode。所以我们要对x86的这种篡改去进行一个有效的校验,我们至少要能够保证说我们有方法去校验这几种修改的方式。

8


Houdini的代码校验思考


那么如何去对待进行校验,可能我们最简单的校验方式就是什么?直接算个crc嘛,但是因为我们前面提到的Houdini的内容,它每次生成具有非常强的随机性,并且它所有的生成也都是动态的,要想简单算一个crc实在是不大可能,那么我们有没有一些退而求其次的方案?


第一个想到的就是说我们既然Houdini每次翻译都会不一样,我们至少能不能保证比如说在翻译这个时机点,我能够采集到正确的代码快块的信息,然后然后在以后我去不断的校验信息。

 

如果是前面说的这种PC端的方案的猜想的话,它可以对这个时机进行hook,那么这个是有可能的,但是如前面我们提到PC端的方案,它没有办法覆盖所有的模拟器,所以如果用移动端的方案,它是没有办法去进行实际的感知的,所以这种方案也是不大可行的。

 

如果在移动端方案要做,看起来我们可能只有选择一个最笨的方法,而且可能最后耗性能的方法,那就是我们去对所有的x86的代码块的指令去找到它对应的arm指令,然后一一去校验它的一个正确性,然后来识别比如说识别异常的hook,这种方法就同样还会有几个问题,第一个从前面的那么多的介绍里面,不知道大家有没有发现一点,我们只讲了每个代码块的开始,但是代码块是怎么结束的。

 

第二个,我们怎么样去区分是正常的跳转,还是创改之后的跳转,因为它里面就有很多的无条件的跳转。

 

第三个这种x86代码一条一条指令和arm代码一条一条指令互相比对,这个是不是性能消耗也太大了一点,我们进行过实际的一个测试,如果我们将所有的已执行的代码块的x86指令,一个去做一个反汇编的分析的话,那么大概就要需要20秒,这个时间我们应该也没有办法接受,对不对?



Houdini的动态代码校验


我们来看这几个问题怎么具体去解决。

 

首先是确定x86代码块的一个边界,从前面对于控制流的一个介绍里面,我们可以知道所有的jump它无非就是这几种方式,

图片

第一个他跳到Houdini code说明他什么?他的新的代码块还没有被生成。他直接跳到一个新的代码上,还有一种就是我向代码块的上面去跳一跳,所以一旦不是这几种类型的话,那么这个jump或者说其他的各种各样的这些跳转一定是有问题的。我们也同样可以通过这个机制,一旦走到这些信息的时候,我们就发现它应该是一个代码块的结束了。


第二个就是异常跳转指令的识别,就是我刚才说的它必须走跳到正常的跳转,必须在这几个范围内,一旦它不在这个范围内,那么它的跳转一定是有问题的。


第三个怎么样去优化反汇编呢?其实通过我们的目的里面,我们可以知道,其实我们关注的内容并不是所有的指令啊,我们关注的内容无非是某些指令涉及到偏移的,或者说涉及到跳转的这一类,所以我们对于绝大部分的指令,我们只需要关注说这个指令有多长,好让我们跳转到下一条指令就可以了。

 

所以我们的反汇编根本不需要去对每一类型的指令都去做一个完整的解析,我们只需要快速的跳转到我们感兴趣的指令就可以了。所以通过这种优化的方式或者说简化的方式,我们可以让所有的代码块的指令的解析只需要0.5秒。

 

而更进一步的我们通过将整个代码块的一个校验稍微做一下拆分,我们就可以保障在不对游戏的执行有任何性能影响的情况下面,去完整的把所有的代码块的校验全部做一遍。

 

那么从实际的对于校验方案的一个实现上面之后,我们也做了一些测试,我们可以对于所有Houdini转移之后的这些指令,都能够一一的进行一个校验,并且能够识别其中的各种异常跳转的修改,以及对于一些偏移正确性的验证。

 

这种方式对于X86代码块的一个覆盖率也基本上达到了98%,剩下的2%去哪里呢?我们前面讲到了有的代码块他会去插入一个回收的机制,这些被即将被回收的代码块,我们就不对他做相关的一些校验了。那么我们对于houdini整个的执行机制,我们做了一个通过逆向的方式的分析,那同时我们也结合我们业务的需求,给出了一个安全校验的方案,那么我们最终来对这一块做一下技术性的总结。

0


Houdini下的安全校验总结


第一个Houdini的这种执行的方式生成的方式,它以只生成必要内容为核心的这种实现,对于我们在jit在虚拟机的一些设计上面,都能够提供很多的技术参考的一个价值。第二个我们也同样可以看到要为Houdini这样的一个系统去实现这种安全的策略,需要非常强的一个定制化的能力。而安全对抗它是无时无刻不在激烈执行的,要想保证这样的一个安全对抗的时效性,就需要非常强大的动态能力。

图片

对于我们的方案来说,我们首先在移动端,无论是安卓还是iOS,我们都建立了一系列能够跨平台的,然后有非常强的一个灵活性的动态执行能力。

 

而且因为我们是游戏安全的方案,我们对于像游戏中的各个通用的引擎,我们也建立了非常定制的,能够更轻易的去获取各种数据,以满足我们开发的这样的一些数据获取的能力。


用这些能力我们才会有非常强的能够在不更新版本的情况下面去应对如Houdini这种非常定制化的校验的方案,而依托于这些动态能力,我们建立了一个非常完善的安全对抗的体系,正是依托这样的一个安全对抗的体系,我们才能够保证说我们在游戏对抗中有一个非常高的效率。

 

那么安全之道我觉得应用孙子兵法里面的一句话,以正合以齐奇特胜,通过对于Houdini的逻辑的分析,以及它的一个我们实现的校验方案,我觉得正应和了正合奇胜的一个思想。以上就是我的个人分享,谢谢大家。



声明:该文观点仅代表作者本人,转载请注明来自看雪