对Xenocode混淆加密代码的反编译及解码研究(1)——技术探讨

Xenocode解密 Xenocode反编译 Xenocode反混淆 .net反编译

Xenocode是国外的一款针对.net代码进行加密混淆保护的软件,其功能强大,使用率比较高。它采用的是自有的字符串加密逻辑,通过一个插入的类对程序集中的字符串进行加密保护,同时还会对程序执行流程进行混淆,并且还会在程序中随机加入一些无效代码,使得要读懂程序逻辑和反编译代码的难度大大增加。一个典型的Xenocode混淆加密后的dll在reflector中的显示图片如下:

Xenocode反编译

可见其中有一个特征性的类,即Xen0code,该类可能保存了加密的类的动态映射表(推测),其详细的生成规则及作用后面再做探讨。这里要说明的是,在较新版本的reflector中可以查看到经Xenocode加密混淆后的代码,但是这些代码经过了字符串的加密和流程的混淆后很难读懂,也基本上不能使用,我们希望通过技术手段尽可能的恢复这些代码的原貌。目前国外的研究者通过还原经Xenocode加密代码的IL文件来达到破译的效果,效率很高,但是兼容性不好(典型的问题是,使用该工具处理加密文件时,很容易出现异常(Exception)而退出,而作者并没有公开破解思路或破解工具的代码)。我们这里是在reflector反编译处理得到C#后再进行处理,这样处理的好处是代码直观,容易理解,缺点是效率较低,也不排除异常情况。一段经reflector反编译后的Xenocode示例代码如下(由于篇幅的关系,仅截取部分代码进行演示):

private void x85601834555fb7d5()
    {
        ComponentResourceManager manager = new ComponentResourceManager(typeof(x4dc9cb69c65ee315));
        if (-2 != 0)
        {
            this.x14b9c2a9b57534b8 = new Label();
            this.x0049a6285a44bbd6 = new TextBox();
            this.x792ada3ad34d61e0 = new TextBox();
            this.xf8ffb0d954b2c2f6 = new PictureBox();
            this.xb115ebd03641a474 = new Button();
            if (0 != 0)
            {
                goto Label_0558;
            }
            goto Label_05BB;
        }
        goto Label_031E;
    Label_0056:
        base.StartPosition = FormStartPosition.CenterParent;
        this.Text = "错误";
        ((ISupportInitialize) this.xf8ffb0d954b2c2f6).EndInit();
        base.ResumeLayout(false);
        base.PerformLayout();
        return;
    Label_00AE:
        base.Controls.Add(this.xb115ebd03641a474);
        base.Controls.Add(this.xd15b9cf5592f8785);
        base.Controls.Add(this.xf6d68814b444670e);
        if (15 == 0)
        {
            goto Label_0435;
        }
        base.Icon = (Icon) manager.GetObject("$this.Icon");
        base.MaximizeBox = false;
        base.MinimizeBox = false;
        this.MinimumSize = new Size(0x138, 0x10d);
        if (3 != 0)
        {
            if (0 == 0)
            {
                if (1 == 0)
                {
                    return;
                }
                base.Name = "MessageForm";
                base.SizeGripStyle = SizeGripStyle.Show;
                goto Label_0056;
            }
            goto Label_0360;
        }
        return;
    Label_010D:
        base.CancelButton = this.xb115ebd03641a474;
        base.ClientSize = new Size(0x16a, 0xf6);
        base.Controls.Add(this.x14b9c2a9b57534b8);
    Label_013F:
        base.Controls.Add(this.x0049a6285a44bbd6);
        base.Controls.Add(this.x792ada3ad34d61e0);
        base.Controls.Add(this.xf8ffb0d954b2c2f6);
        if (0 == 0)
        {
            goto Label_00AE;
        }
    Label_0219:
        this.xf6d68814b444670e.Font = new Font("Microsoft Sans Serif", 8.25f, FontStyle.Bold, GraphicsUnit.Point, 0);
        this.xf6d68814b444670e.Location = new Point(0x38, 14);
        this.xf6d68814b444670e.Name = "lblMsg";
        this.xf6d68814b444670e.Size = new Size(0x126, 40);
        this.xf6d68814b444670e.TabIndex = 10;
        if (-2147483648 != 0)
        {
            this.xf6d68814b444670e.Text = "An error occurred";
            base.AutoScaleMode = AutoScaleMode.Inherit;
            if (0 != 0)
            {
                goto Label_05D1;
            }
            if (-2147483648 == 0)
            {
                goto Label_0254;
            }
            goto Label_010D;
        }
        goto Label_031E;
    Label_0254:
        this.xd15b9cf5592f8785.Size = new Size(0x56, 0x11);
        this.xd15b9cf5592f8785.TabIndex = 12;
        this.xd15b9cf5592f8785.Text = "错误类型:";
        this.xf6d68814b444670e.Anchor = AnchorStyles.Right | AnchorStyles.Left | AnchorStyles.Top;
        this.xf6d68814b444670e.FlatStyle = FlatStyle.System;
        goto Label_0219;
    Label_031E:
        this.xb115ebd03641a474.DialogResult = DialogResult.Cancel;
        if (15 == 0)
        {
            goto Label_00AE;
        }
        this.xb115ebd03641a474.FlatStyle = FlatStyle.Popup;
        while (true)
        {
            this.xb115ebd03641a474.Location = new Point(0x11b, 220);
            this.xb115ebd03641a474.Name = "buttonOK";
            this.xb115ebd03641a474.Size = new Size(0x43, 0x18);
            this.xb115ebd03641a474.TabIndex = 8;
            this.xb115ebd03641a474.Text = "OK";
            this.xb115ebd03641a474.Click += new EventHandler(this.x38e768d357a8f002);
            this.xd15b9cf5592f8785.FlatStyle = FlatStyle.System;
            this.xd15b9cf5592f8785.Location = new Point(12, 0x3b);
            if (-2147483648 != 0)
            {
                this.xd15b9cf5592f8785.Name = "labelType";
                if (0 != 0)
                {
                    goto Label_05A7;
                }
                if (0 != 0)
                {
                    goto Label_04D4;
                }
                goto Label_0254;
            }
        }
    Label_0360:
        this.xf8ffb0d954b2c2f6.Paint += new PaintEventHandler(this.x952d2a572666dc32);
        this.xb115ebd03641a474.Anchor = AnchorStyles.Right | AnchorStyles.Bottom;
        if (4 != 0)
        {
            goto Label_031E;
        }
        if (3 != 0)
        {
            goto Label_03EE;
        }
    Label_0395:
        this.xf8ffb0d954b2c2f6.Location = new Point(12, 12);
        this.xf8ffb0d954b2c2f6.Name = "pictIcon";
        this.xf8ffb0d954b2c2f6.Size = new Size(0x26, 0x22);
        this.xf8ffb0d954b2c2f6.TabIndex = 9;
        if (0 != 0)
        {
            goto Label_0593;
        }
        this.xf8ffb0d954b2c2f6.TabStop = false;
        goto Label_0360;
    Label_03EE:
        this.x792ada3ad34d61e0.Size = new Size(0x152, 0x15);
        this.x792ada3ad34d61e0.TabIndex = 13;
        goto Label_0395;
    Label_0435:
        this.x792ada3ad34d61e0.Name = "textType";
        if (0 != 0)
        {
            goto Label_0523;
        }
        this.x792ada3ad34d61e0.ReadOnly = true;
        if (-2147483648 == 0)
        {
            goto Label_0056;
        }
        goto Label_03EE;
    Label_04D4:
        this.x0049a6285a44bbd6.Location = new Point(12, 0x83);
        if (0 != 0)
        {
            goto Label_013F;
        }
        this.x0049a6285a44bbd6.Multiline = true;
        this.x0049a6285a44bbd6.Name = "textDetails";
        this.x0049a6285a44bbd6.ReadOnly = true;
        this.x0049a6285a44bbd6.ScrollBars = ScrollBars.Vertical;
        this.x0049a6285a44bbd6.Size = new Size(0x152, 0x53);
        this.x0049a6285a44bbd6.TabIndex = 15;
        this.x792ada3ad34d61e0.Anchor = AnchorStyles.Right | AnchorStyles.Left | AnchorStyles.Top;
        this.x792ada3ad34d61e0.BackColor = SystemColors.Info;
        this.x792ada3ad34d61e0.BorderStyle = BorderStyle.FixedSingle;
        this.x792ada3ad34d61e0.Location = new Point(12, 0x4c);
        goto Label_0435;
    Label_0523:
        this.x14b9c2a9b57534b8.Text = "错误明细:";
        this.x0049a6285a44bbd6.Anchor = AnchorStyles.Right | AnchorStyles.Left | AnchorStyles.Bottom | AnchorStyles.Top;
        this.x0049a6285a44bbd6.BackColor = SystemColors.Info;
        if (0 == 0)
        {
            this.x0049a6285a44bbd6.BorderStyle = BorderStyle.FixedSingle;
            goto Label_04D4;
        }
        goto Label_05BB;
    Label_0558:
        this.x14b9c2a9b57534b8.Size = new Size(0x43, 0x11);
        this.x14b9c2a9b57534b8.TabIndex = 14;
        goto Label_0523;
    Label_0593:
        this.x14b9c2a9b57534b8.Location = new Point(12, 0x6f);
    Label_05A7:
        this.x14b9c2a9b57534b8.Name = "labelDetails";
        goto Label_0558;
    Label_05BB:
        this.xd15b9cf5592f8785 = new Label();
        this.xf6d68814b444670e = new Label();
    Label_05D1:
        ((ISupportInitialize) this.xf8ffb0d954b2c2f6).BeginInit();
        if (0 == 0)
        {
            base.SuspendLayout();
            this.x14b9c2a9b57534b8.FlatStyle = FlatStyle.System;
            if (0 != 0)
            {
                goto Label_010D;
            }
            goto Label_0593;
        }
        goto Label_0523;
    }

这是一段Xenocode加密过的.net窗口应用程序经reflector反编译后得到的代码。从中可以看到,类名、函数名称及控件名称等均被加密成无意义的字符串。代码中有多处判断和跳转,由于流程的混淆导致reflector不得不在代码中加入很多的标签(Label_****)来实现跳转。程序中也存在很多诸如if (-2147483648 == 0)等无效代码。我们处理的第一步是进行代码清理,即去除无效代码和精简正常代码。Xenocode加密后的代码中,无效代码有很多种类型:比如if (-2147483648 == 0)、if(0!=0)、if(uint.MinValue>13842)、while(!true)等,还有一些判断中涉及到逻辑计算的,比如if((uint.MaxValue+4434)<0)之类,甚至还有采用插入参数判断的,比如int a=5;if(a<0)等,可谓五花八门。另外就是冗余代码,与无效代码的判断类似:比如if(0=0)、if(4!=0)等,还有假的死循环语句如while(true)之类,这类语句中一般都存在return语句,因此也可当作冗余代码处理。之所以会产生这样多种类并且复杂结构的语句,其中很大程度上与reflector的语言解析器有关,如果不是采用reflector反编译生成代码的话,仅从IL代码来看跳转虽然很多,但语句还是相对简洁的。

通过以上分析,可以看出,无论是去除无效代码还是精简冗余代码都需要从语句的判断上入手。其判断语句虽然是随机多样的,但类型有限并且是可操作的,多是数字比较判断,或是数字运算后的比较及恒真恒假条件等。我们采取的方式是正则匹配再解析运算的方法,比如针对纯数字(类似于if(2<10))的比较判断语句可以采用的正则语句是if \((-?\d+ [!>=<]{1,2} -?\d+)\)\n[ ]*{([^{}]*)},这里面针对正负数的大于、等于、小于、不等于的代码块进行匹配。匹配成功后对if语句中的条件判断进行计算,其中的条件语句在程序处理中只是一个字符串而已,而要对其进行计算就需要单独构建一个语言编译器,一个采用C#反射技术实现的语言编译器的核心代码如下:

private void ConstructEvaluator(EvaluatorItem[] items)
{
    ICodeCompiler compiler = new CSharpCodeProvider().CreateCompiler();
    CompilerParameters options = new CompilerParameters();
    options.ReferencedAssemblies.Add("system.dll");
    options.ReferencedAssemblies.Add("system.data.dll");
    options.ReferencedAssemblies.Add("system.xml.dll");
    options.GenerateExecutable = false;
    options.GenerateInMemory = true;
    StringBuilder builder = new StringBuilder();
    builder.Append("using System; \n");
    builder.Append("using System.Data; \n");
    builder.Append("using System.Data.SqlClient; \n");
    builder.Append("using System.Data.OleDb; \n");
    builder.Append("using System.Xml; \n");
    builder.Append("namespace EvalGuy { \n");
    builder.Append(" public class _Evaluator { \n");
    foreach (EvaluatorItem item in items)
    {
        builder.AppendFormat("    public {0} {1}() ", item.ReturnType.Name, item.Name);
        builder.Append("{ ");
        builder.AppendFormat("return ({0});", item.Expression);
        builder.Append("}\n");
    }
    builder.Append("} }");
    CompilerResults results = compiler.CompileAssemblyFromSource(options, builder.ToString());
    if (results.Errors.HasErrors)
    {
        StringBuilder builder2 = new StringBuilder();
        builder2.Append("编译有错误的表达式: ");
        foreach (CompilerError error in results.Errors)
        {
            builder2.AppendFormat("{0}\n", error.ErrorText);
        }
        throw new Exception("编译错误: " + builder2.ToString());
    }
    this._Compiled = results.CompiledAssembly.CreateInstance("EvalGuy._Evaluator");
}

(注:本文为 [风影网络工作室] 原创文章,未经书面许可,严禁转载和复制本站的任何信息,违者必究)