10.3.1 文件校验
1.移动端
(1)检查签名
每一个软件在发布时都需要开发人员对其进行签名,而签名使用的密钥文件是开发人员所独有的,破解者通常不可能拥有相同的密钥文件(密钥文件被盗除外),因此,签名成了Android软件一种有效的身份标识,如果软件运行时的签名与自己发布时的不同,说明软件被篡改过,这个时候我们就可以让软件中止运行。
Android SDK中提供了检测软件签名的方法,可以调用Package Manager类的getPackageInfo()方法,为第二个参数传入PackageManager.GET_SIGNATURES,返回的PackagInfo对象的signatures字段就是软件发布时的签名,但这个签名的内容比较长,不适合在代码中做比较,可以使用签名对象的hashCode()方法来获取一个Hash值,在代码中比较它的值即可,获取签名Hash值的代码如图10.44所示。
图10.44 获取签名Hash值的代码
作者使用Eclipse自带的调试版密钥文件生成的.apk文件的Hash值为2071749217,在软件启动时,判断其签名Hash是否为这个值,来检查软件是否被篡改过,相应的代码如图10.45所示。
图10.45 检查软件是否被篡改过代码
(2)校验保护
重编译Android软件的实质是重新编译classes.dex文件,代码经过重新编译后,生成的classes.dex文件的Hash值已经改变。我们可以检查程序安装后classes.dex文件的Hash值,来判断软件是否被重打包过。至于Hash算法,MD5或者CRC都可以,.apk文件本身是zip压缩包,而且Android SDK中有专门处理zip压缩包及获取CRC检验的方法,为了不徒增代码量,作者采用CRC作为classes.dex的校验算法。另外,每一次编译代码后,软件的CRC都会改变,因为,无法在代码中保存它的值来进行判断,文件的CRC校验值可以保存到Assert目录下的文件或字符串资源中,也可以保存到网络上,软件运行时再联网读取,作者为了方便,选择了前一种方法,相应的代码如图10.46所示。
图10.46 校验保护代码
2.PC端
很多程序破解都需要对程序某个部分的数据进行修改,数据校验就是通过技术手段侦测出这些修改,以达到保护软件的目的。在这里我们介绍两种数据校验的方法,分别是文件校验和内存校验。
文件校验是指在程序启动时计算程序文件的校验值,然后与事先计算好的校验值进行比较,从而判断文件是否被篡改。要想理解这种方式,我们可以用现成的例子。在PE程序文件中,PE文件头的Optional Header中有一个CheckSum成员,就是用于存放PE程序文件的校验值的,但是在一般的Win32程序中,这个值并不强制要求正确,Windows在启动PE时也不校验这个值,而Windows驱动程序加载时却需要校验这个值。下面我们就用这个值进行演示。
首先来看这个值的计算方法,如图10.47所示。可以看出,PE程序文件的校验值计算相当简单,只是文件大小与所有PE数据的16位字相加的值。
图10.47 PE校验值代码
构建如图10.48所示的代码进行校验。这样,当一个PE程序设定校验值以后,如果再修改文件内容,就会被侦测到。
图10.48 PE校验应用代码
这种将校验算法复杂化并将校验代码加密的方式会起到很好的保护效果。但是,这种校验只能侦测到对程序文件的修改。现在的很多破解技术往往不直接修改程序文件,而是在程序运行以后直接修改程序内存中的数据。如果采取这种破解方式,以上方法就失效了。内存校验就是一种防止破解者通过程序运行以后修改程序内存数据而达到破解目的的反破解方式。
当程序运行后,其内存数据是随时变化的,无论是系统修改还是程序自身代码都有可能修改内存中的数据,所以,内存校验无法校验所有的内存数据。那么,选取哪些数据进行校验最合适呢?先观察一下PE程序被映射到内存后数据存放位置内存空间的特点,如图10.49所示。
图10.49 PE内存数据存放示意图
我们看到图10.49下半部分是我们通过工具查看PE程序的区段描述,上半部分是该PE程序映射到内存空间以后的区段。可以看到,程序对.text、.rsrc、.reloc区段的访问类型都是只读,而对.data区段可以读、写、访问,这就说明在程序运行时,大多数情况下,.text、.rsrc、.reloc区段的内容都不会发生变化,我们可以校验这些空间中的数据;而.data区段的内容随时都有可能发生变化,我们将不能进行校验。
有两种方式校验这些区段。一种方式是事先从PE程序文件中计算出区段的校验值,并在程序启动后与相应内存中的区段校验值进行对比。但是这里要注意,程序映射到内存空间与文件中的数据并非是任何情况下都相同的,当程序存在重定位并发生了重定位的情况下,区段会不一样,因此这种情况下需要进行特殊处理。另外一种方式是事先不计算区段的校验值,而是在程序启动时计算,然后定时再次计算并比较两次的计算结果,如果不同就说明内存数据被修改了。