吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 3009|回复: 8
收起左侧

[会员申请] 申请会员ID: rickw【申请通过】

[复制链接]
吾爱游客  发表于 2016-3-30 11:40
1、申 请 I D :   rickw
2、个人邮箱:   bbsftp@gmail.com
3、原创技术文章:  

IDA,">标 题: 【原创】MaxtoCode对.Net程序加密的原理及解密探讨一
作 者: rick
时 间: 2006-09-14,20:49:38
链 接: http://bbs.pediy.com/showthread.php?t=31940

这里研究的对象是 MaxtoCode 3.1试用版.这里只探讨程序代码的加密.


对.Net程序代码的加密过程如下:

1. 运行 ildasm 将程序集反编译成 il代码文件.

2. 对IL代码文件进行处理.(*)

3. 运行 ilasm 将 IL代码文件编译成程序文件.

4. 直接对程序文件中的il字节码加密.(**)



粗体表示的 2 , 4 是关键步骤.

我们先来看看第四步.这一步就是加密的关键步骤,这里就是使用MaxtoCode的加密算法对程序代码进行加密。

显然,对于破解来说最直接直观的方法就是对其第四步的逆向解密。

如果从这个方向去破解解密加密过的程序,那就像MaxtoCode号称的那样MAXTOCODE的强度建立在加密算法之上。

理论上方法是可行的,但是工作量是非常大的。



那么我们还有其它的路可行呢?

现在来看看第二步MaxtoCode都做了什么。

用vs2003建一个最简单的winform程序,然后用MaxtoCode加密试试。我们将第三步之后,第四步之前的exe文件拿来研究。这个时候的exe程序代码是还没有被加密的。可以reflector。

看看 这个exe和我们直接的exe有什么区别:

1. 增加了一个类InFaceMaxtoCode .

2. 类都被增加了一个静态构造函数,在这个函数里面调用了InFaceMaxtoCode的一个静态函数Startup。

3. 类的原有构造函数里面也增加了调用InFaceMaxtoCode.Startup的语句。

从这些来看,MaxtoCode的目的是要确保InFaceMaxtoCode.Startup 在程序中能够最早的运行。

这个行为和win32程序加壳很像,一般壳都是加密程序代码,然后修改程序的启动入口,首先执行壳的代码,完成程序的解密,然后再执行程序。一般壳有一个特点:加密是对整个程序,启动时也是整个程序完全解密,然后再执行。(我也见到过一个很特别的壳,程序是部分解密的,软件注册算法的那一块, 是执行一部分解密一部分,然后之前解密的又被垃圾信息填充了。)

对于壳只要我们找对了时间和地点,就能从内存中得到我们需要的东西。

那么 MaxtoCode加密后的。Net程序呢?

先来看看 MaxtoCode的加密方式。用ildasm反编译 加密后的程序,会报很多错误,这是正常的,从生产的IL文件看,各个类,函数都还在,只是函数体里面是只有ildasm的错误信息。显然是加密后的代码无法反编译。MaxtoCode对。Net程序的加密不是对程序整体的,而只是对函数体加密,程序类结构不变。有一点我们是很清楚的,加密后的程序要能够正常运行,在运行时肯定是需要解密的。而解密的关键就在InFaceMaxtoCode.Startup 里面。

现在我们来看看InFaceMaxtoCode.Startup 里面究竟做了什么。InFaceMaxtoCode 类的代码如下:





using System;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;

public class InFaceMaxtoCode
{
    static InFaceMaxtoCode()
    {
        InFaceMaxtoCode.started = false;
    }

    [DllImport("MRuntime3.dll", EntryPoint="CheckRuntime", CharSet=CharSet.Unicode, SetLastError=true, ExactSpelling=true)]
    private static extern int A______();
    [DllImport("KERNEL32.DLL", EntryPoint="GetModuleHandleA", CharSet=CharSet.Ansi, SetLastError=true, ExactSpelling=true)]
    private static extern int B______(string x13d52f7d8e232e61);
    private static string ByteToString(byte[] x5fc6100148519126)
    {
        return Encoding.ASCII.GetString(x5fc6100148519126);
    }

    [DllImport("MRuntime3.dll", EntryPoint="MainDLL", CharSet=CharSet.Ansi, SetLastError=true, ExactSpelling=true)]
    private static extern bool C______(int x19218ffab70283ef, int xe7ebe10fa44d8d49);
    [DllImport("KERNEL32.DLL", EntryPoint="SetEnvironmentVariableA", CharSet=CharSet.Ansi, SetLastError=true, ExactSpelling=true)]
    private static extern bool D______(string x427bb0e14ed9e9b1, string x84ee6c5b88919f4c);
    public static void Startup()
    {
        if (!InFaceMaxtoCode.started)
        {
            string text1 = "";
            string text2 = "MRuntime3.dll";
            if (AppDomain.CurrentDomain.RelativeSearchPath != null)
            {
                if (AppDomain.CurrentDomain.RelativeSearchPath.IndexOf(@":\") != -1)
                {
                    text1 = AppDomain.CurrentDomain.RelativeSearchPath;
                }
                else
                {
                    text1 = AppDomain.CurrentDomain.BaseDirectory + AppDomain.CurrentDomain.RelativeSearchPath;
                }
            }
            else
            {
                text1 = AppDomain.CurrentDomain.BaseDirectory;
            }
            string text3 = Environment.GetEnvironmentVariable("path");
            if (text3.IndexOf(text1) == -1)
            {
                InFaceMaxtoCode.D______("path", text3 + ";" + text1.Replace("/", @"\"));
            }
            if (text1.Substring(text1.Length - 1, 1) == @"\")
            {
                text1 = text1;
            }
            else
            {
                text1 = text1 + @"\";
            }
            if (File.Exists(text1 + text2) && !File.Exists(Path.GetTempPath() + text2))
            {
                File.Copy(text1 + text2, Path.GetTempPath() + text2);
            }
            if (text3.IndexOf(Path.GetTempPath()) == -1)
            {
                InFaceMaxtoCode.D______("path", text3 + ";" + Path.GetTempPath().Replace("/", @"\"));
            }
            int num1 = 5;
            num1 = InFaceMaxtoCode.A______();
            if (num1 == 0)
            {
                int num2 = InFaceMaxtoCode.B______(text2);
                int num3 = InFaceMaxtoCode.B______(Assembly.GetExecutingAssembly().Location);
                InFaceMaxtoCode.started = InFaceMaxtoCode.C______(num2, num3);
            }
            else
            {
                //一堆垃圾代码,报告启动错误信息的。
            }
        }
    }


    private static bool started;
}


Startup精简后的代码如下:
public static void Startup()
        {
        if (!InFaceMaxtoCode.started)
        {
            //准备运行库;
            int num1 = 5;
            num1 = InFaceMaxtoCode.A______();
            if (num1 == 0)
            {
                int num2 = InFaceMaxtoCode.B______(text2);
                int num3 = InFaceMaxtoCode.B______(Assembly.GetExecutingAssembly().Location);
                InFaceMaxtoCode.started = InFaceMaxtoCode.C______(num2, num3);
            }
            else
            {
                //一堆垃圾代码,报告启动错误信息的。
            }
        }


从代码里面我们看得到InFaceMaxtoCode.Startup 正常启动后,在整个程序集中只会运行一次。

关键函数是 运行库的MainDLL,这个函数有两个参数,一个是运行库的句柄,一个是程序集的句柄。这个句柄实际上就是程序在内存中加载的位置。MaxtoCode加密后的程序都是对齐到0x1000的。   

MaxtoCode对.Net程序加密的原理及解密探讨二
自上次写第一篇文章到现在不知不觉两个月过去了,这篇文章我们将介绍怎么获取解密后的IL字节代码。
我们先回顾一下前文,在上一回我们提到“InFaceMaxtoCode.Startup 正常启动后,在整个程序集中只会运行一次。”。
当时这种说法是很武断的,如果 “InFaceMaxtoCode.C______(num2, num3)” 的返回值总是 false的话,该函数就会被执行多次,
不过根据后来动态调试的结果,我们证实了“InFaceMaxtoCode.C______(num2, num3)” 的返回值为 true,因此上次的说法是正确的。

现在言归正传,怎么取得解密后的代码呢?大概两个方向,
1.正面交锋,直接攻破maxtocode的运行库。
这就将问题直接回到了传统的win32层面,不过这个东西是业内人士写的在这方面的保护工作做得很好,像我这样的菜鸟就很难直接攻破了。
我曾有一个设想,就是通过分析运行库找到解密函数的入口,然后弄一个stub dll,hook这个地方,把解密后的il代码dump出来。
实际跟踪几次后我就放弃了。从跟踪到的信息来看,我猜测,运行库是通过 mscorwks.dll 挂接到 jit,在jit的前面实时解密代码。
理论上我们也可以挂一个到jit前面,在那里dump解密的il代码,不过这个实现的方式,还不清楚,如果弄明白了,也就能写一个同样原理的加密软件了。
这个难度比较大,所以我最终放弃了这个方案。

2.避开运行库,我们直接利用dotNet 2.0的特性获取IL代码。
如是我就试着用2.0写了一个winform程序,加密,运行,发现报错。
maxtocode3.1不支持2.0的winform程序,这就使我的这个方案实验夭折了。
两个月过去了,发现maxtocode升级到3.11了修正了这个bug,今天终于可以继续实验了。

我们来建一个简单的winform程序。一个窗体,然后一个按钮。
代码如下:




1 using System;
2 using System.Collections.Generic;
3 using System.ComponentModel;
4 using System.Data;
5 using System.Drawing;
6 using System.Text;
7 using System.Windows.Forms;
8 using System.Reflection;
9 using Spaces;
10 namespace Test5
11 {
12     public partial class Form1 : Form
13     {
14         public Form1()
15         {
16             InitializeComponent();
17         }
18   
19         private void TestMethod()
20         {
21             //  [7/17/2006]
22             int i = 0;
23             i = 1;
24             i++;
25             if(i>0)
26             {
27                 MessageBox.Show("OK");
28             }
29         }
30
31         private void button1_Click(object sender, EventArgs e)
32         {
33             Type tp = this.GetType();
34
35             MethodInfo mi = tp.GetMethod("TestMethod",
36                 BindingFlags.NonPublic|BindingFlags.DeclaredOnly|
37                 BindingFlags.Public|BindingFlags.Static
38                 |BindingFlags.Instance);
39             if(mi == null)
40             {
41                 MessageBox.Show("err");
42                 return;
43             }
44             MethodBody mb = mi.GetMethodBody();
45             byte[] bt= mb.GetILAsByteArray();
46             StringBuilder sb = new StringBuilder();
47             for (int i = 0; i < bt.Length; i++)
48             {
49                 sb.Append(bt.ToString("X2"));
50                 sb.Append(" ");
51             }
52             string stxt = sb.ToString();
53             MessageBox.Show(stxt);
54                        
55         }
56      
57     }
58 }

编译运行,我们点击按钮就能看到 TestMethod 的IL字节码。
然后用maxtocode加密在运行,同样能看到 TestMethod 的IL字节码。
两次看到的结果一样的,这个是当然了,如果不一样,maxtocode就破坏了程序的正确性了。

好了,我们的实验成功了。

看到这里大家应该知道怎么获取解密后的IL代码了吧。

这种方式比在内存里面找代码或者hook到maxtocode解密后dump代码的方式要优越很多,
因为内存dump的方式你还要担心运行时的函数覆盖率,没有运行到的就dump不到。

这种方式我们利用 DotNet的反射机制,可以枚举出程序集中的所有类型,以及每个类型的所有方法,成员,字段,构造函数等。


初步实验了一下,对于加了密的dll文件还是比较好弄的,2.0的、1.1的都能弄出IL代码来。
对于exe文件还有一关需要解决,那就是如何将我们的DotNet dll程序集插入到exe的运行空间中去。


发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

Hmily 发表于 2016-3-30 15:04
请在看雪论坛给hmilywen发一个短消息,确认是本人申请,谢谢。
吾爱游客  发表于 2016-3-30 17:29
Hmily 发表于 2016-4-1 10:31
ID:rickw
邮箱:bbsftp@gmail.com

申请通过,欢迎光临吾爱破解论坛,期待吾爱破解有你更加精彩,ID和密码自己通过邮件密码找回功能修改,请即时登陆并修改密码!
登陆后请在一周内在此帖报道,否则将删除ID信息。
rickw 发表于 2016-4-1 13:57
rickw 报道。
大家好,希望以后在吾爱破解论坛里同大家积极交流,共同学习进步。
aishiguangnian 发表于 2016-4-2 14:03
支持楼主!!!!!
214515047 发表于 2016-4-2 16:05 来自手机
Hmily 发表于 2016-4-1 10:31
ID:rickw
邮箱:


人家不是报道了吗?怎么标题写未报道呢?

点评

因为我还没注意到,所以没有修改。  详情 回复 发表于 2016-4-5 15:41
Hmily 发表于 2016-4-5 15:41
214515047 发表于 2016-4-2 16:05
人家不是报道了吗?怎么标题写未报道呢?

因为我还没注意到,所以没有修改。
吾爱游客  发表于 2016-4-9 03:21
很好,测试一下
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-15 22:56

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

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