以前我在文章《异步函数带来的另一好处—不用混淆代码了?》里提到过:对于新的语法糖async,当前反编译器都无法还原,而async语法糖生成的函数有一定复杂度,相当于有一定的自混淆的功能。
今天看到了Telerik这个出控件的公司出了一款支持async语法糖的反编译器JustDecompile,便下载试用了一下,确实能反编译async语法,非常强大。
首先以一个最简单的异步函数为例:
static async Task AsyncTest()
{
await Task.Delay(100);
Console.WriteLine("hello world");
}
对于这个函数,Reflector的反编译结果如下(其它不支持async的反编译器基本上也是这个结果):
[AsyncStateMachine(typeof(<AsyncTest>d__0)), DebuggerStepThrough]
private static Task AsyncTest()
{
<AsyncTest>d__0 d__;
d__.<>t__builder = AsyncTaskMethodBuilder.Create();
d__.<>1__state = -1;
d__.<>t__builder.Start<<AsyncTest>d__0>(ref d__);
return d__.<>t__builder.Task;
}
但对于JustDecompile来说,其反编译结果如下:
private static async Task AsyncTest()
{
await Task.Delay(100);
Console.WriteLine("hello world");
}
基本上完美的还原了async的语法糖,效果非常好。
那么,对于JustDecompile这类反编译工具,我们该如何保护自己的代码呢?还是得靠混淆,我用VisualStudio自带的混淆工具重命名了一下程序集,这次JustDecompile的反编译结果如下:
private static async Task a()
{
a.a variable.b = AsyncTaskMethodBuilder.Create();
variable.a = -1;
AsyncTaskMethodBuilder asyncTaskMethodBuilder = variable.b;
asyncTaskMethodBuilder.Start<a.a>(ref variable);
Task task = variable.b.Task;
return task;
}
程序集重命名后JustDecompile就失去了还原async语法糖的功能,和Reflector反编译的结果基本一致。
为什么仅重命名程序集能阻止async的还原呢?原理也很简单:混淆后async语法糖产生的函数被重命名了,和C#编译器生成的规则不一样了,JustDecompile按照C#编译器的规则无法还原这个语法糖,只好输出IL码的直接反编译的效果了。
当然,反编译器也能针对混淆工具的混淆算法进行分析,从而还原混淆后的async语法糖,不过感觉这样的难度和工作量都较大,并且意义不大,应该不会去做这种事的。