JemmyloveJenny 发表于 2020-8-9 03:10

逆向分析 不是人玩的CM系列(一)

本帖最后由 JemmyloveJenny 于 2020-8-9 11:37 编辑

# 前言
这个CM是 @wwh1004编写的,第一个做出来的人奖励500CB
题目在这里:https://www.52pojie.cn/thread-1241463-1-1.html
CM取名叫“不是人玩的CM系列”,然而我却在这里发帖讲我怎么玩的?这就是“我不是人系列”吗?
感觉怪怪的233333

诶,这个CM是真的好难啊啊啊啊,我花了六个多小时才做出来……
@Hmily 版主让我单独开贴写一下,那我就把全部的分析思路写出来,包括成功的和失败的……
失败的思路不想看就跳过,左边目录直接跳转

# 分析思路
## 先运行一下看看
随便输一个 00112233445566778899

嗯…我失败了
OK,知道错误提示了,准备开干!

## 上dnSpy直接干它(失败)
出题的@wwh1004 大佬直接告诉你了,这是个.Net程序,无壳无混淆
哦?那还等什么?无壳无混淆.Net不就相当于裸奔吗!直接拿dnSpy上去干啊!
当然,是我太天真了……
开局一看,unsafe,指针什么的都出来了

再仔细一看,额……Stack是啥?Tuple是啥?GClass0.Gclass1又是啥?怎么这么多变量啊啊啊

光是`smethod_6`这一个函数就占了800+行

大佬就是大佬,无壳无混淆的CM都能这么花里胡哨

## 真·逆向分析(失败)
浏览一下`Main`函数,发现提示失败的只有一种情况,出现Exception会失败

原本想着能爆破一下什么的,结果发现连if都么有,是靠Exception判断的!这就是大佬出的题啊……
但是至少我们GET到了一个条件
**条件1:运行流程不能出现Exception**

看到提示成功的地方

刚一看到这个ECB模式,还以为这边会有什么密码学的漏洞呢,但事实证明我想多了……
细看了一下,程序的运行流程会生成AES的Key,而程序里给出的是密文。
大佬出题的那个帖子里面,可以看到一部分提示语,我原本想猜测一下明文,反推Key的,也就是真·逆向分析
然而我仔细想了一下,AES是抗 已知明文攻击 的,也就是说无法利用明文和密文反推密钥,只能放弃。

## 大致了解程序运行流程
好吧,歪门邪道什么的面对大佬的CM完全无效,那就认真分析了!
我连Eazfuscator.Net都分析过,难道还会被一个没有混淆的CM难住?

先从Main入手,了解一下运行流程:
因为出题大佬对这个CM进行了内联,所以能看到有很多重复的代码

### 划分内联代码
比如说一下这一段,内容和`smethod_0`完全一致

结合`smethod_0`以及被调用的`smethod_1`,`smethod_2`可以大概知道,这段代码的作用是把String转换为GClass1,因此我们可以给这段代码起名叫做`String2GClass`

还有这一段,一连串的类型判断

这段代码就是对上面得到的object进行类型判断,最终转换为一个Double类型,因此可以给这段代码取名`Object2Double`

区分开内联代码之后,脑子里就应该对Main的执行流程有一个大致了解了
我用伪代码写一下(实际上我没有写,只是为了让大家都清楚)
### 清理过的 Main.cs
```
private unsafe static void Main()
{
      Console.Title = "CrackMe by wwh1004 2020/08/07";
      Console.WriteLine("输入你的答案:");
      try
      {
                String input = Console.WriteLine();
                GClass1 gclass = String2GClass(input);
                double num = 0.0;
               
                /* 以 GClass0.smethod_6 为主的一串代码 */
                object obj = gclass;
                object obj2 = 1;
                object obj3 = obj2;
                object obj4 = obj;
                GClass0.GClass1 gclass2 = obj4 as GClass0.GClass1;
                object obj5 = (gclass2 != null) ? GClass0.smethod_6(gclass2, new object[]
                {
                        num
                }) : obj4;
               
                double num7 = Object2Double(obj5);
               
                /* 同上,以 GClass0.smethod_6 为主的一串代码 */
                GClass0.GClass1 gclass3 = obj3 as GClass0.GClass1;
                obj5 = ((gclass3 != null) ? GClass0.smethod_6(gclass3, new object[]
                {
                        num
                }) : obj3);
               
                double num8 = Object2Double(obj5);
               
                /* 判断语句,条件是莫名其妙的1E-05 */
                GClass0.GClass1 gclass4;
                if (Math.Abs((num8 - num7) / num7) >= 1E-05)
                {
                        gclass4 = null;
                }
                else
                {
                        /* 构造GClass1 */
                        GClass0.GClass1 gclass5 = new GClass0.GClass1
                        {
                              byte_0 = 22,
                              object_0 = gclass, //把object_0修改为了input得到的gclass
                              object_1 = 0
                        };
               
                        text2 = "100412041404070000000000041004120002000000040700000000000003000000";
                        GClass0.GClass1 gclass6 = String2GClass(text2);
                        gclass6.object_1 = gclass; //把object_1修改为了input得到的gclass
                        
                        for (double num9 = 0.0; num9 < 100.0; num9 += 0.1)
                        {
                              /* 以 GClass0.smethod_6 为主的一串代码 */
                              object obj6 = gclass5;
                              object obj7 = gclass6;
                              num = num9;
                              obj3 = obj7;
                              obj4 = obj6;
                              gclass2 = (obj4 as GClass0.GClass1);
                              obj5 = ((gclass2 != null) ? GClass0.smethod_6(gclass2, new object[]
                              {
                                        num
                              }) : obj4);
                              
                              double num7 = Object2Double(obj5);
                              
                              /* 以 GClass0.smethod_6 为主的一串代码 */
                              gclass3 = (obj3 as GClass0.GClass1);
                              obj5 = ((gclass3 != null) ? GClass0.smethod_6(gclass3, new object[]
                              {
                                        num
                              }) : obj3);
                              
                              double num11 = Object2Double(obj5)
                              
                              /* 判断语句,条件也是莫名其妙的1E-05 */
                              if (Math.Abs((num11 - num7) / num7) >= 1E-05)
                              {
                                        gclass4 = null;
                                        goto IL_3CC;
                              }
                        }
                        gclass4 = gclass;
                }
                IL_3CC:
                GClass0.GClass1 gclass1_ = gclass4;
                byte[] bytes = BitConverter.GetBytes((double)GClass0.smethod_6(gclass1_, new object[]
                {
                        20.0
                }));
                byte[] bytes2 = BitConverter.GetBytes((double)GClass0.smethod_6(gclass1_, new object[]
                {
                        20.0
                }));
                byte[] bytes3 = BitConverter.GetBytes((double)GClass0.smethod_6(gclass1_, new object[]
                {
                        8.0
                }));
                Array bytes4 = BitConverter.GetBytes((double)GClass0.smethod_6(gclass1_, new object[]
                {
                        7.0
                }));
                byte[] array3 = new byte;
                Buffer.BlockCopy(bytes, 0, array3, 0, 8);
                Buffer.BlockCopy(bytes2, 0, array3, 8, 8);
                Buffer.BlockCopy(bytes3, 0, array3, 16, 8);
                Buffer.BlockCopy(bytes4, 0, array3, 24, 8);
                byte[] bytes5;
                using (Aes aes = Aes.Create())
                {
                        aes.Mode = CipherMode.ECB;
                        aes.Key = array3;
                        byte[] array4 = new byte[]
                        {
                              ······
                        };
                        using (ICryptoTransform cryptoTransform = aes.CreateDecryptor())
                        {
                              bytes5 = cryptoTransform.TransformFinalBlock(array4, 0, array4.Length);
                        }
                        goto IL_51B;
                }
                goto IL_503;
                IL_51B:
                Console.WriteLine("你成功了:" + Encoding.Unicode.GetString(bytes5));
                goto IL_545;
                IL_503:
                throw new NotSupportedException();
      }
      catch
      {
                Console.WriteLine("你失败了。");
      }
      IL_545:
      Console.ReadKey(true);
}
```
至此,`Main`中大部分的代码都被我们给分类了,可以发现`smethod_6`为主的代码段非常重要,因此我们需要分析一下`smethod_6`的具体作用!

## 仔细分析函数的作用
这个`smethod_6`我刚看到直接就被闪瞎了眼……乱七八糟的根本就不想看
但是既然知道`smethod_6`非常重要,我们就不得不面对它了

### 处理goto语句
这个`smethod_6`有很多的goto语句到处跳转,让人看的心烦。因此,我们首先吧goto处理掉。
先把整个`smethod_6`的代码复制出来,找一个好用的文本编辑器贴进去(我用的是Notepad++)

然后找一个有goto需要处理的地方

看到这个`goto IL_AB4`了吧?我们来找`IL_AB4`对应的代码

找到对应代码之后,直接复制,粘贴到goto语句的位置

贴过去之后又找到有`goto IL_A76`,如法炮制,继续复制粘贴,直到你能看懂程序到底在做什么为止……
处理过程中要注意 复制的代码中可能还有goto语句 if之后的goto语句就有可能是else
总之,就是脑子清楚一点,把逻辑弄正确就行了……

### 继续清理内联代码
清理完goto语句之后,把对付`Main`的方法再用一次,把重复的代码段用函数代替。
之后就可以得到一段 可读性比较高的 代码了!

### 解释一下smethod_6的作用
清理完成后,我们就可以知道这个函数的具体作用了。
这个`smethod_6`有点复杂,可能得多看几次才能明白……

`smethod_6`的作用就像是一个计算器,而输入的算式使用类似于 波兰表达式(前置表达式) 的形式表达的,而这个表达式在程序中,是被`GClass1`存储的。

先举个栗子吧:
随便写一个运算式 `5*6`,然后我们把它改写成先运算符号,再两个操作数的形式
比如 `5*6`改写成`{*;5;6}`,`*`,`5`,`6`分别叫做`运算类型`,`第一操作数`,`第二操作数`,

表达式也可以成为操作数,比如说`((2+3)*(x^4))/e^x`可以写成`{/;(2+3)*(x^4);e^x}`
虽然写可以这样写,但是实际运算要符合数学运算顺序,不能先算外部再算内部,因此,`操作数`中含有`非数字表达式`的波兰表达式还需要再次展开。

继续算刚才的表达式`((2+3)*(x^4))/e^x`
实际运算是要先算`value1={+;2;3}`,再算`value2={^;x;4}`,然后计算`value1={*;value1;value2}`,`value2={^;e;x}`,最后计算`result={/;value1;value2}`

我们看一下GClass1的结构
```
public class GClass1

{
      public byte byte_0;/* 运算类型,具体运算见smethod_6 */
      public object object_0;/* 第一操作数,可以是数字,也可以是另一个GClass1(表达式) */
      public object object_1;/* 第二操作数,可以是数字,也可以是另一个GClass1(表达式) */
}
```
我们可以把`GClass1`理解为例子中的大括号,`{类型;第一操作数;第二操作数}`
注意,`操作数`可以是数字,也可以是另外一个`GClass1`(表达式)

这个CM的主要内容,便是用GClass的嵌套实现进行运算的目的

### 清理之后的 smethod_6
我对`smethod_6`清理了goto,清理了内联代码,然后对代码进行了一些注释。
建议是先读懂上面说的`波兰表达式`,再来看`smethod_6`的代码,这样比较好理解……
不想看的直接左边目录跳到下一节!
```
private static object smethod_6(GClass0.GClass1 gclass1_0, params object[] object_0)
{
      Stack<Tuple<GClass0.GClass1, object[], object, object, bool, int>> stack = new Stack<Tuple<GClass0.GClass1, object[], object, object, bool, int>>();
      stack.Push(new Tuple<GClass0.GClass1, object[], object, object, bool, int>(gclass1_0, object_0, null, null, false, 0));
      object obj = null;
      for (;;)
      {
                Tuple<GClass0.GClass1, object[], object, object, bool, int> tuple = stack.Pop();
                gclass1_0 = tuple.Item1;
                object_0 = tuple.Item2;
                object obj2 = tuple.Item3;
                object obj3 = tuple.Item4;
                bool flag = tuple.Item5;
                int item = tuple.Item6;
                switch (item)
                {
                case 0:
                        /* item=0时,GClass1中的操作数分别放到第一操作数(obj2),第二操作数(obj3),前一步骤的运算结果(obj)不处理 */
                        obj2 = gclass1_0.object_0;
                        obj3 = gclass1_0.object_1;
                        flag = (obj2 is GClass0.GClass1);
                        /* 判断第一个操作数(obj2)是数字,还是表达式 */
                        if (flag)
                        {
                              /* 如果obj2是表达式,先Push自身item=1(结果放入第一操作数的位置),并把obj2的表达式展开Push到stack上 */
                              stack.Push(new Tuple<GClass0.GClass1, object[], object, object, bool, int>(gclass1_0, object_0, obj2, obj3, flag, 1));
                              stack.Push(new Tuple<GClass0.GClass1, object[], object, object, bool, int>((GClass0.GClass1)obj2, object_0, obj2, obj3, flag, 0));
                              goto IL_A66;/* 直接开始循环 */
                        }
                        flag = (obj3 is GClass0.GClass1);
                        /* 如果obj2是表达式,会在`goto IL_A66`句直接跳转,因此执行到这里,obj2不是表达式 */
                        /* 第一个操作数(obj2)不是表达式,接下来判断第二个操作数(obj3)是数字,还是表达式 */
                        if (flag)
                        {
                              /* 如果obj3是表达式,先Push自身item=2(结果放入第二操作数的位置),并把obj3的表达式展开Push到stack上 */
                              stack.Push(new Tuple<GClass0.GClass1, object[], object, object, bool, int>(gclass1_0, object_0, obj2, obj3, flag, 2));
                              stack.Push(new Tuple<GClass0.GClass1, object[], object, object, bool, int>((GClass0.GClass1)obj3, object_0, obj2, obj3, flag, 0));
                              goto IL_A66;/* 直接开始循环 */
                        }
                        goto IL_1E;/* 如果都不是表达式,开始运算 */
                case 1:
                        if (flag)
                        {
                              obj2 = obj;/* 如果上一步骤运算的东西是个表达式,把结果从obj放入obj2(因为item=1) */
                        }
                        flag = (obj3 is GClass0.GClass1);
                        if (flag)
                        {
                              /* 同上,把obj3表达式展开Push到stack上 */
                              stack.Push(new Tuple<GClass0.GClass1, object[], object, object, bool, int>(gclass1_0, object_0, obj2, obj3, flag, 2));
                              stack.Push(new Tuple<GClass0.GClass1, object[], object, object, bool, int>((GClass0.GClass1)obj3, object_0, obj2, obj3, flag, 0));
                              goto IL_A66;
                        }
                        goto IL_1E;/* 如果都不是表达式,开始运算 */
                case 2:
                        if (flag)
                        {
                              obj3 = obj;/* 如果上一步骤运算的东西是个表达式,把结果从obj放入obj3(因为item=2) */
                        }
                        goto IL_1E;
                case 3:
                        goto IL_1E;/* 不改变obj2,obj3,还保留了上一步的结果obj,仅有case22用到 */
                }
                goto Block_101;
                IL_A66:
                if (stack.Count == 0)/* 如果stack上的东西都算完了 */
                {
                        return obj;/* 返回运算结果 */
                }
                continue;
                IL_1E:
                switch (gclass1_0.byte_0)/* switch 运算类型 */
                {
                /* 类型判断被我精简过了,含义应该自己就能看懂 */
                /* 一元运算 */
                case 0:
                {
                        obj = Object2Int(obj2);/* 把第一操作数(Tuple.Item3)转为Int放到结果(obj)内 */
                        goto IL_A66;
                }
                case 1:
                {
                        obj = Object2Long(obj2);
                        goto IL_A66;
                }
                case 2:
                {
                        obj = Object2Float(obj2);
                        goto IL_A66;
                }
                case 3:
                {
                        obj = Object2Double(obj2);
                        goto IL_A66;
                }
                /* 有关传入自变量(x)的运算(主要就是取出自变量的值) */
                case 4:
                {
                        obj = (int)object_0;/* 理论上,obj2可以是任何整数,但是由于传入的object_0都是只含有一个元素,所以第一操作数(obj2)必须为0 */
                        goto IL_A66;
                }
                case 5:
                {
                        obj = (long)object_0;
                        goto IL_A66;
                }
                case 6:
                {
                        obj = (float)object_0;
                        goto IL_A66;
                }
                case 7:
                {
                        obj = (double)object_0;
                        goto IL_A66;
                }
                /* 接下来是二元的加减乘除 */
                case 16:
                {
                        obj = Object2Double(obj2) + Object2Double(obj3);
                        goto IL_A66;
                }
                case 17:
                {
                        obj = Object2Double(obj2) - Object2Double(obj3);
                        goto IL_A66;
                }
                case 18:
                {
                        obj = Object2Double(obj2) * Object2Double(obj3);
                        goto IL_A66;
                }
                case 19:
                {
                        obj = Object2Double(obj2) / Object2Double(obj3);
                        goto IL_A66;
                }
                /* 乘方类运算 */
                case 20:
                {
                        /* 计算e^obj2,e为自然底数 */
                        obj = Math.Exp(Object2Double(obj2));
                        goto IL_A66;
                }
                case 21:
                {
                        /* 计算乘方 obj2^obj3 */
                        obj = Math.Pow(Object2Double(obj2), Object2Double(obj3));
                        goto IL_A66;
                }
                /* case22相当于计算了(f(x+10^-8)-f(x))/(10^-8),也就是计算f(x)的导数 */
                case 22:/* 对于case22来说,第一操作数必须是表达式,Main里传递过来的是表达式f(x) */
                {
                        object obj7;
                        if (item != 3)/* 判断是否展开过求导运算 */
                        {
                              /* item!=3表明没有展开过,开始展开运算 */
                              object[] object_1 = Copy(object_0); /* Copy是伪代码,复制一下传入参数数组object_0,复制到object_1 */
                              obj7 = object_0; /* 把传入参数中的第(obj3)个取出(obj3只能为0) */
                              object_1 = Object2Double(obj7) + 1E-08;/*把取出的值加上1E-08, 即0.00000001,一个足够小的值*/
                              stack.Push(new Tuple<GClass0.GClass1, object[], object, object, bool, int>(gclass1_0, object_0, obj2, obj3, flag, 3));/* 此时压入f(x)表达式,item=3指示已经展开过求导运算 */
                              stack.Push(new Tuple<GClass0.GClass1, object[], object, object, bool, int>((GClass0.GClass1)gclass1_0.object_0, object_1, obj2, obj3, flag, 0));
                              /* 把f(x)展开后压入,由于object_1中的自变量被加上了1E-08,因此实际运算的是f(x+1E-08) */
                              goto IL_A66;
                        }
                        obj = (Object2Double(obj) - Object2Double(obj2)) / 1E-08;/* obj是 后Push的f(x+1E-08)的值;obj2是 先Push的f(x)展开式的值 */
                        /* 最终求得导数f'(x) */
                        goto IL_A66;
                }
                }
                break;
      }
}
```

## 回头继续分析Main
分析完了十分重要的`smethod_6`,我们就是回头来继续看Main
我们知道,输入的字符串会被`String2GClass`(实际为`smethod_0`)转换为`GClass1`
而`GClass1`是波兰表达式形式的表达式(突然觉得我没学过语文XD)
所以我们输入的东西,实际上是一个函数,我们可以把它记作`f(x)`

刚才分析过了`smethod_6`,知道它的作用是把波兰表达式完全展开,求出结果。`Main`当中调用`smethod_6`的地方就可以理解为计算表达式的值了!

我们来看第一次调用`smethod_6`的地方
```
                object obj5 = (gclass2 != null) ? GClass0.smethod_6(gclass2, new object[]
                {
                        num /* 此时num=0.0 */
                }) : obj4
```
这个gclass2是我们输入的`f(x)`GClass1
传入的num是自变量`x`,这就相当于`obj5 = f(0)`
接下来是一串类型转换`Object2Double`

后面再次出现了熟悉的`smethod_6`和`Object2Double`,不过这次参与运算的表达式不是`f(x)`,而是null(因为尝试把int 1转换为GClass1,转换失败得到了null)

这次`smethod_6`运算的结果是1

来到后面的判断语句
```
                /* num8 = `1`, num7 = `f(0)` */
                if (Math.Abs((num8 - num7) / num7) >= 1E-05)
                {
                        gclass4 = null;
                }
                else
                {
                        ······
                }
```
如果if成立,那么就会执行gclass4=null,会导致后面出现NullPointerException
根据`条件1`,出现Exception会导致失败,所以if不可以成立

这个语句看起来是在要求`|(1-f(0))/f(0)|<0.00001`,但是后来想想1E-05貌似也没什么意义,应该是判断等于0的意思
后来出题大佬也解释了,由于double精度不够,所以采取了这种判断方式。

所以利用数学知识可以解得一个函数需要满足的条件`f(0)=1`

构造一个符合`f(0)=1`的表达式之后,就可以进入else分支
看到程序内构造了一个GClass1
```
                        GClass0.GClass1 gclass5 = new GClass0.GClass1
                        {
                              byte_0 = 22,
                              object_0 = gclass,/* gclass就是输入的f(x) */
                              object_1 = 0/* 必须为0 */
                        };
```
这个byte_0=22是求导的意思,具体展开方式都在`smethod_6`当中,还算是比较清楚的!
所以得到了此处的表达式`d/dx(f(x))`

接下来又出现了一串字符串,这个字符串会被处理成GClass(就和我们输入的内容一样)
注意,这里把gclass1中的object_1(第二操作数)替换成了我们输入的`f(x)`
根据 `波兰表达式` 解读GClass便可以得到第二个表达式 `e^x*(2x+3)+f(x)`

之后就进入了一个for循环,x的取值遍历了0-100之间每个0.1,共1000个值
`d/dx(f(x))`和`e^x*(2x+3)+f(x)`会被赋予自变量x的值,并且求出这两个表达式的具体结果
对这两个表达式的具体结果进行判断
```
                              /* num11 = `e^x*(2x+3)+f(x)` */
                              /* num7 = `d/dx(f(x))` */
                              if (Math.Abs((num11 - num7) / num7) >= 1E-05)
                              {
                                        gclass4 = null;
                                        goto IL_3CC;
                              }
```
这里的if同样不可以成立,并且由于if处于for循环内部,也就是说对于任意满足0<x<100的x都要有 `((e^x*(2x+3)+f(x))-(d/dx(f(x))))/(d/dx(f(x))) < 0.00001` 成立
这个1E-05也是等于0的意思,因此条件可以化简为 `e^x*(2x+3)+f(x) = d/dx(f(x))`


## 做数学题
整理一下,我们目前有两个条件:
`f(0) = 1`
`e^x*(2x+3)+f(x) = d/dx(f(x))`
接下来需要求出`f(x)`的表达式
虽然我和出题大佬同样都是今年高三毕业,但是我们高中并没有教导数23333333
然后…这个东西我就不会做了

### 一个错误的想法
我最开始在做这道题的时候,并没想到`(f(x+10^-8)-f(x))/(10^-8)`的意思是`d/dx(f(x))`
因此,我的做法就变成了`f(x+10^-8)=e^x*(2x+3)*10^-8+(1+10^-8)*f(x)`,把这个式子理解为递推公式,加上已知的`f(0)=1`,尝试推算出通项公式(类似于数列的思想)
然而通项我自己求不出来,去WolframAlpha上算了一下,如果带上`f(0)=1`,它告诉我无解
不带`f(0)=1`的话,解出来的结果直接把我吓傻了……

所以有没有人能帮我解答一下,我这个想法错在哪里?
(高三结束的暑假,老师和同学都无心学习,问不到学霸了……)

### 偷懒的做法
由于`d/dx(f(x))`可以写成`f'(x)`,因此到网上搜一下`e^x*(2x+3)+f(x)=f'(x)`
从百度知道的问题当中获得题目图片一张

但是解答里面并没有`f(x)`的表达式
看不懂导数的我只能小猿搜题拍一下

在一串不太能看懂的解答里找到一行救命稻草:
`f(x)=(x^2+3x+1)*e^x`
好了,数学题的部分就结束了(无知地跳过了……)

### 构造答案表达式
所以我们只要构造出这个表达式的GClass,然后调用CM里的smethod_3转化为字符串就行了
演示一下:
```
                        new GClass1() {
                              byte_0 = 18,/* 乘法 {*;x^2+3x+1;e^x} */
                              object_0 = new GClass1() {
                                        byte_0=16,/* 加法 {+;x^2;3x+1} */
                                        object_0= new GClass1()
                                        {
                                                byte_0 = 21,/* 乘方 {^;x;2} */
                                                object_0 = new GClass1()
                                                {
                                                      byte_0 = 7,/* 取值 {获取x的值} */
                                                      object_0 = 0,
                                                      object_1 = null
                                                },
                                                object_1 = 2
                                        },
                                        object_1=new GClass1() {
                                                byte_0=16,/* 加法 {+;1;3x} */
                                                object_0=1,
                                                object_1=new GClass1() {
                                                      byte_0=18,/* 乘法 {*;3;x} */
                                                      object_0=3,
                                                      object_1=new GClass1()
                                                      {
                                                                byte_0 = 7,/* 取值 {获取x的值} */
                                                                object_0 = 0
                                                      }
                                                }
                                        }
                              },
                              object_1= new GClass1()
                              {
                                        byte_0 = 20,/* e乘方 {e^;x} */
                                        object_0 = new GClass1()
                                        {
                                                byte_0 = 7,/* 取值 {获取x的值} */
                                                object_0 = 0,
                                                object_1 = null
                                        }
                              }
                        }
```
这个GClass转换成字符串的结果就是
`1204100415040700000000000002000000041000010000000412000300000004070000000000041404070000000000`
输入到程序里面验证一下

OK,可以收工了!

wu0o0pj 发表于 2020-8-10 11:49


嗯,终于发现和大佬们之间的差别了,大佬们是刚经历过残酷的高考,而我还只是个400个月不到的宝宝{:301_974:}

大佬逻辑很清晰,本宝宝先mark下,等有空再好好学习

.Net_破解 发表于 2020-8-10 12:06

那个数学公式是微分方程,非齐次一阶线性常系数微分方程。好解的很

三栖德鲁伊 发表于 2020-8-9 03:53

一个外行看完了
虽然不懂代码内容 但是楼主解释归纳的很到位
逻辑清晰 很钦佩 貌似是沙发

sitiger 发表于 2020-8-9 06:36

赞一个,随便看看。。。

luhaok 发表于 2020-8-9 06:59

小白看的有点乱 谢谢楼主分享!

5loveme 发表于 2020-8-9 07:20

我以为是GM,就进来看看,结果被楼主福尔摩斯般的推理所吸引,从头到尾看完了。

kongbai666 发表于 2020-8-9 08:00

谢谢楼主分享!

杨U 发表于 2020-8-9 08:32

真的佩服楼主,虽然我不懂。

Jeraxx 发表于 2020-8-9 08:47

进来学习一下

sanchodon 发表于 2020-8-9 08:51

真心佩服楼主,厉害!

6f7a8d 发表于 2020-8-9 08:53

学习了,支持~~
页: [1] 2 3 4 5 6 7 8 9 10
查看完整版本: 逆向分析 不是人玩的CM系列(一)