找回密码
 立即注册
查看: 297|回复: 0

【Unity代码加密浅析】速通成绩验证与代码加密

[复制链接]
发表于 2022-12-29 10:26 | 显示全部楼层 |阅读模式
最近我做的Unity版《马戏团》得到大家的好评,于是做了一个速通竞速版《马戏团》,让更多人体验挑战速通的乐趣。
而为了防伪速通成绩截图,我做了一个MD5校验码的功能。本来是一个很小的功能,但又引申出关于代码加密的问题。所以我把这些天的研究成果总结成文章,以飨读者。
一、引子——游戏速通成绩验证

在制作速通版马戏团时,因为玩家的成绩最终是以界面方式展现的,大家在交流成绩时截图即可,像这样:


可以看到,界面下方是一个校验码。它是做什么用的呢?
设计校验码,是因为截图成绩太容易PS篡改,如果没有某种简单的防伪机制,伪造成绩就很影响速通交流的兴趣。
这个码实际就是个MD5码,用C#生成非常简单,只需要用内置的MD5 API即可:
using System.Security.Cryptography;  // 加密算法的命名空间

// 函数输入:任意字符串
// 函数输出:以16进制表示的MD5码字符串,例如 FF-EE-AA-BB-80-81-82... 这样的
    string CalcMD5(string source)
    {
        MD5 md5 = MD5.Create();
        byte[] bytes_src = Encoding.UTF8.GetBytes(source);
        byte[] bytes_md5 = md5.ComputeHash(bytes_src);

        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < bytes_md5.Length; i++)
        {
            sb.Append(bytes_md5.ToString("X2"));
            if (i != bytes_md5.Length - 1)
            {
                sb.Append("-");
            }
        }
        return sb.ToString();
    }
那我用哪些东西作为原始数据进行加密呢?我把用户昵称、开始时间、结束时间、总时间,外加一些特殊数值作为加密的原始字符串,就可以获得一个对每个人、每次挑战都不一样的MD5数值。
在验证时,我写了一个小工具,只要输入以上信息,就能算出MD5值,只要MD5值相同,就是真实的成绩了。
二、破解方法——C#反编译

仔细想想,其实只要知道我是把哪些原始数据、以什么格式、什么顺序拼在一起的,就能计算出同样的MD5码。具体比如:
          原始字符串 = 开始时间 日/月/年 + 结束时间 年/月/日 + 总时间 时:分:秒.毫秒 + 昵称
如果我像上面这样,直接告诉你加密的格式,你只要把任意信息按上面的格式拼成字符串,再送到MD5函数中计算,自然就能得到和我一样的MD5码了。
所以,唯一要保密的就是原始数据的格式,一旦被别人知道了格式和顺序,就没有任何秘密可言了。
本来,作为一个小小的防作弊手段,能防止玩家PS截图已经足够了。但是如果较真一些,高手会尝试采用C#反编译的方法获取源码。
在Unity2020之前的版本中,默认脚本后端采用Mono编译器,将C#代码编译为CIL代码。编译好的CIL看起来是一个dll文件,而把CIL的dll还原为C#有很多办法。
比如开源小工具dnSpy,github地址:https://github.com/dnSpy/dnSpy/releases
只要把脚本的dll拖进去,就可以看到反编译结果。
例如马戏团Unity工程,开发时的dll位于:工程目录/Library/ScriptAssemblies/Assembly-CSharp.dll
将它拖到dnSpy窗口里:



把代码的底裤扒光

可以看到,不管什么MonoBehaviour、Start、Update、Awake……通通给你把底裤扒掉。
同理,小小的加密函数,根本逃不出dnSpy的魔爪……反编译之后,一眼就看到格式和顺序了,毫无秘密可言。
针对初级的黑客技术手段,咱们也要考虑反击手段,有两种:

  • 对C#代码进行混淆。
  • 使用IL2CPP,将CIL代码转为C++再编译。
其中,第二种IL2CPP为推荐做法。
三、代码混淆与IL2CPP浅析

代码混淆的百度解释:
代码混淆(Obfuscated code)亦称花指令,是将计算机程序的代码,转换成一种功能上等价,但是难于阅读和理解的形式的行为。
如果现在大家搜索“Unity代码混淆”,会看到很多5年前的老回答、老博客。因为在IL2CPP普及以后,其实Unity做代码混淆的意义已经不大了。
在IL2CPP成熟之前,有一些常用的Unity混淆插件,例如开源小插件Unity-Obfuscator。https://github.com/DrFlower/Unity-Obfuscator
它的工作原理为:其实C#代码混淆在其它领域需求更多,有一个专门的库叫做Mono.Cecil,用于修改和混淆CIL格式的dll文件。而Unity-Obfuscator就是借用了Cecil库进行代码混淆的。
经我测试,Unity-Obfuscator插件最多只能用在Unity 2017版本中,到2018版本后就需要升级了。
虽然在Unity混淆方面用处不大,但从纯技术角度看:如果能够方便地读取和修改CIL代码,就能实现更多黑客技术,例如代码注入、插入检测指令等功能。Cecil自有它的用武之地。我也研究了两天Cecil,就当是给自己科普了  :)

接下来是IL2CPP。



Project Settings -> Player -> Other Settings -> Configuration,在这里修改脚本引擎

解释一下IL2CPP——IL就是CIL(通用中间语言),2就是to,CPP就是C++。
IL2CPP,就是把初步编译的CIL代码,转成C++源码,再用C++编译器转成dll。虽然看起来也是dll,但标准已经是天差地别。
经IL2CPP转换后的代码运行效率更高,而且……加密效果超级好,至少比Unity-Obfuscator好很多。具体说起来我也不是专家,看看其它人的研究成果:
四、编译后的代码所在的路径

我们在使用Unity时,其实所有的脚本都要编译为DLL才能运行。这里简单介绍一下各个DLL的存储路径。
首先,如果用Mono脚本后端,咱们的所有脚本都统一编译到Assembly-CSharp.dll之中。
在编辑器做开发时,它位于:
        工程目录\Library\ScriptAssemblies\Assembly-CSharp.dll    (含pdb)
打成windows运行的发布包后,它位于:
        发布包\Circus_Data\Managed\Assembly-CSharp.dll  (不含pdb)
        如果要动态修改dll,还需要找到pdb。pdb位于:
        Temp\ManagedSymbols\Assembly-CSharp.pdb
而如果用IL2CPP打包后,很多相关的代码会统一打包到GameAssembly.dll中,它就位于打包目录的第一级目录下。如图:


这里容易搞错的是,有一个目录叫XXX_BackUpThisFolder_ButDontShipItWithYourGame,意思就是不要把这个目录发布出去【捂脸】,因为里面存放的是很多CIL文件,如果让人拿到,就没有加密可言了。话说Unity为什么把这么重要的东西放在这里【捂脸】

总结——矛与盾

加密与破解是一对永恒的矛盾。
不存在无坚不摧的矛,也不存在坚不可摧的盾。它们永远在激烈地交锋,而交锋的焦点只有一个——成本与收益。
盾的使命是在尽量少增加加密成本的前提下,不断增加矛的成本;
而矛的使命是用更低的成本去攻破盾,从而得到正向的收益。
二者此消彼长,循环往复,推动网络与软件安全的进步。
例如,Unity-Obfuscator的混淆效果不足,更容易被攻破;而IL2CPP相对更难被攻破。但难攻破不代表攻不破。
在互联网时代,破解技术也在飞速普及,随便一搜,就有一个精彩的破解案例:U3D游戏包il2cpp逆向解包,apk加密资源解密_爱码使的博客-CSDN博客_il2cppinspector
总之,软件安全是一个庞大的话题,不是专业人士很难精通。
我们能做的,就是尽量提高自己的安全意识,了解一些基本的知识。最起码的,做到不要给安全专家拖后腿 【笑】
<hr/>欢迎加入游戏开发群欢乐搅基:1082025059
对学习游戏开发、游戏制作感兴趣的童鞋,欢迎访问咱们的主页:皮皮关

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-11-16 00:22 , Processed in 0.226704 second(s), 26 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表