Unity3D 的DLL 加密,保护代码

 

//

SmartAssembly,.NET 混淆器

可轻易反组译是采用中介语言(.NET, Java)平台的共有特性,也是实务应用的资安隐忧,面对这个问题,最有效的解决方案是-- 混淆器( Obfuscator )。

混淆器的运作原因,是解析编译好的DLL或EXE档,将其转换成执行结果相同的组件,差别在于私有类别、属性、方法、栏位、参数名称都已改到面目全非,难以阅读理解;更进一步还可以打乱程式码的排列流程(执行顺序不变)、加密程式码中的字串常数,让反组译的程式码乱如咒语天书,令有意破解者却步,至少要让对方追程式追到流涕痛哭。天下没有破解不了的程式,混淆器的目标在于逼迫绝大部分的破解者尽早放弃,即使被破解也要对方付出极其可观(甚至难以想像)的高昂代价。

印象中,专业等级的.NET混淆器价格不斐,甚至如PreEmptive限定由销售团队洽谈应用状况再决定报价(嗯,有没有人跟我一样觉得毛毛的?),最近在估评其他软体时看到Red Gate的SmartAssembly,标准版约1,000 USD、专业版约1,500 USD,价格贴近公司采购的其他开发元件,便决定实测看看。注: SmartAssembly有个有趣的开发者版本,不到200 USD,限制混淆后的程式只能在开发者自己的机器上执行,且程式会在七天后爆炸失效,目的是让开发者测试混淆后的程式是否执行正常(某些混淆技巧可能导致程式不正常,记得要实测及适度调整混淆参数),故实务上全公司可共用一套标准版/专业版,再视需要采购开发版,若不用开发版,每次上测试台前再统一混淆亦是可行策略。

SmartAssembly的操作还算简单,新增一个专案,选取DLL或EXE,设定混淆参数,建置就可以得到混淆版。

SmartAssembly可混淆对象包含: Windows Form, WPF, Console, Silverlight XAP, 程式库(DLL), .NET Web Service, ASP.NET Web bin下的DLL… 等,原则上只要是.NET编译出的组件(Assembly,DLL或EXE)都可以处理。除了混淆功能外,SmartAssembly还有一些"额外"功能,例如:

  1. Strong Name Siging
    为混淆后的元件加上强式签名防止变造
  2. Automated Error Reporting
    程式当掉时搜集错误资讯建立当机报告(背后会送到Red Gate的Web Service,并可透过SmartAssembly UI浏览,见前图左侧选单的Reporting项目) [参考]


    一旦程式出错,会弹出以下对话框(透过SDK,对话框可以客制化)

  3. Feature Usage Reporting
    可以统计程式执行环境、各功能被使用的次数(也是透过Red Gate的Web Service搜集,第一次执行前征求使用者同意可传送统计资讯较不会有争议)
  4. Dependencies Merging
    将参照的DLL也合并起来混淆(可提高混淆程度),并非所有DLL都适用,3rd Party的元件可考虑改用嵌入(Embedding)做法
  5. Dependencies Embedding
    将参照DLL嵌入程式中(预设会加密、压缩)但不进行混淆,第一次执行时解密、解压缩还原,能减少部署档案数目。
  6. Pruning
    移除没用到的Metadata,例如事件名称、属性、方法参数等,让程式码更难解读,也有助于减少程式档体积。[参考]
  7. Resource Compression and Encryption
    资源压缩及加密,第一次执行时还原,能缩小档案体积。
  8. Other Optimization
    减少保留但未使用的记忆体、自动seal所有类别禁止继承

回到重点-- 混淆,SmartAssembly透过以下技巧让反组译的程式码难以解读,包含:

  1. 将类别、方法名称改成看不懂的怪字
  2. 把类别的栏位名称乱改一通,甚至让不同类别用相同的栏位名称
  3. 将类别A的方法搬到类别B底下,但风险较高,有时会出错,宜斟酌使用
  4. Control Flow Obfuscation,把程式码的先后顺序调乱,但执行顺序保持不变
    题外话: 以上四招我曾见过有人能徒手运用,直接写出没人看得懂的程式码,堪称"人体混淆"大绝orz
  5. 动态Proxy: 一律透过执行期间产生的Proxy呼叫参照DLL,如此Assembly一旦被篡改,程式就会坏掉无法执行
  6. 字串加密: 预设.NET组件中的字串被直接储存,检视档案二进位内容时就看得到,SmartAssembly支援将字串内容加密混淆
  7. 加上注记禁止MSIL Disassembler(ildasm)对档案进行反组译
  8. 如果有pdb的话,混淆StackTrace中出现的原始码档案路径

接着来实测一番,以下是简单的Console程式:

排版显示纯文字
<span style="background-color:#ffffff"><span style="color:#000000"><span style="color:#000000"><span style="background-color:#f8f8f8"><span style="background-color:#f0f0f0"><span style="color:#0000ff">using</span> System;</span></span></span></span></span>
using System.Reflection;
<span style="background-color:#ffffff"><span style="color:#000000"><span style="color:#000000"><span style="background-color:#f8f8f8"><span style="background-color:#f0f0f0"><span style="color:#0000ff">using</span> Boo;</span></span></span></span></span>
using Foo;
<span style="background-color:#ffffff"><span style="color:#000000"><span style="color:#000000"><span style="background-color:#f8f8f8"><span style="background-color:#f0f0f0"> </span></span></span></span></span>
namespace ObfuscationTest
<span style="background-color:#ffffff"><span style="color:#000000"><span style="color:#000000"><span style="background-color:#f8f8f8"><span style="background-color:#f0f0f0">{</span></span></span></span></span>
    class Program
<span style="background-color:#ffffff"><span style="color:#000000"><span style="color:#000000"><span style="background-color:#f8f8f8"><span style="background-color:#f0f0f0">    {</span></span></span></span></span>
        static  string dllPath = @"d:\Newtonsoft.Json.dll" ;
<span style="background-color:#ffffff"><span style="color:#000000"><span style="color:#000000"><span style="background-color:#f8f8f8"><span style="background-color:#f0f0f0">        <span style="color:#0000ff">static </span> <span style="color:#0000ff">void</span> Main( <span style="color:#0000ff">string</span> [] args)</span></span></span></span></span>
        {
<span style="background-color:#ffffff"><span style="color:#000000"><span style="color:#000000"><span style="background-color:#f8f8f8"><span style="background-color:#f0f0f0">            Assembly asm = Assembly.LoadFile(dllPath);</span></span></span></span></span>
            Type t = asm.GetType( "Newtonsoft.Json.JsonConverter" , true );
<span style="background-color:#ffffff"><span style="color:#000000"><span style="color:#000000"><span style="background-color:#f8f8f8"><span style="background-color:#f0f0f0">            <span style="color:#0000ff">foreach</span> (var pi <span style="color:#0000ff">in</span> t.GetProperties())</span></span></span></span></span>
            {
<span style="background-color:#ffffff"><span style="color:#000000"><span style="color:#000000"><span style="background-color:#f8f8f8"><span style="background-color:#f0f0f0">                Console.WriteLine(pi.Name);</span></span></span></span></span>
            }
<span style="background-color:#ffffff"><span style="color:#000000"><span style="color:#000000"><span style="background-color:#f8f8f8"><span style="background-color:#f0f0f0">            BooClass boo = <span style="color:#0000ff">new</span> BooClass() { Name = <span style="color:#006080">"Jeffrey"</span> };</span></span></span></span></span>
            FooClass foo = new FooClass() { Name = "Darkthread" };
<span style="background-color:#ffffff"><span style="color:#000000"><span style="color:#000000"><span style="background-color:#f8f8f8"><span style="background-color:#f0f0f0">            Console.WriteLine( <span style="color:#006080">"{0} / {1}"</span> , boo.Name, foo.Name);</span></span></span></span></span>
            string res = Console.ReadLine();
<span style="background-color:#ffffff"><span style="color:#000000"><span style="color:#000000"><span style="background-color:#f8f8f8"><span style="background-color:#f0f0f0">            <span style="color:#0000ff">if</span> (! <span style="color:#0000ff">string</span> .IsNullOrEmpty(res))</span></span></span></span></span>
                throw  new ApplicationException( "My Bad! XD" );
<span style="background-color:#ffffff"><span style="color:#000000"><span style="color:#000000"><span style="background-color:#f8f8f8"><span style="background-color:#f0f0f0">        }</span></span></span></span></span>
    }
<span style="background-color:#ffffff"><span style="color:#000000"><span style="color:#000000"><span style="background-color:#f8f8f8"><span style="background-color:#f0f0f0">}</span></span></span></span></span>

使用反组译工具解析ObfuscationTest.exe档,可以看到几乎原汁原味的原始程式码:

接着我们透过SmartAssembly恶搞一番,另外Build成ObfuscationTestSA.exe,再用反组译工具试着打开。嗯,很好,程式码已经被整到连他娘都不认得!


注: 我是选择不加密字串常数,好不容易才透过程式码中"Jeffrey"、"Darkthread"找到main()的所在位置,试过连字串都加密,我选择直接投降~

评估之后,SmartAssembly看来是值得考虑的.NET混淆器解决方案。如果担心专案中的程式经混淆会出问题,倒是可以先用试用版(功能完整,但编译结果只能在有安装程式的机器执行,七天后失效)实测评估后再考量。

.NET obfuscation of a DLL: how can I protect my code?​​​​​​​.

I don't know if this is an option for you, but you might consider providing the algorithm as a web service using WCF or Xml-rpc or REST or something.

With HTTPS, of course.

That way they can use the algorithm, but they can never get at your code.

Should work as long as the algorithm doesn't require too many round trips or huge amounts of data being transfered. Which is to say, as long as the algorithm doesn't need to be used in real time with a responsiveness of < 100ms or so.

Option 1: Dotfuscator or a similar product

Dotfuscator for .NET | PreEmptive

Option 2: Use C or C++ for the sections covered by trade secrets.

Obfuscation will give you more sense of IP protection. Check this free tool out, it may be a good starting point Phoenix Protector – NTCore

I think what will really protect your IP is contract and law. Make sure you have it clearly define at the start of the engagement and have an agreement draw up that will protect your interest.

混淆器会将私有和/或内部成员重命名为其他名称,从而使代码更难阅读。
然而,有一些混淆器可以额外改变控制流,用功能等效的指令替换你编译的 CIL,这可能会导致一些反编译器中断。
 
我以前使用过 Eazfuscator.NET:
https://www.gapotchenko.com/eazfuscator.net
 
它的旧版本是免费的(我认为最后一个是 3.3)。
但是,这些旧版本不适用于 .NET Core 和 .NET Standard。
​​​​​​​

/

//

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值