天天看點

從reflector實作看.net的混淆與反混淆技術【原創】

标 題: 從reflector實作看.net的混淆與反混淆技術【原創】

作 者: dreaman

時 間: 2006-12-31,13:07

鍊 接: http://bbs.pediy.com/showthread.php?t=37217

【文章标題】: 從reflector實作看.net的混淆與反混淆技術

【文章作者】: dreaman

【作者郵箱】: [email protected]

【作者首頁】: http://dreaman.haolinju.net

【軟體名稱】: reflector 4.2.50.0

【軟體大小】: 850KB

【下載下傳位址】: http://www.aisto.com/roeder/dotnet/

【加殼方式】: 

【保護方式】: 多種混淆+加密壓縮主功能檔案内嵌到架構程式資源段

【編寫語言】: 未知

【使用工具】: reflector,vs.net 2005,自編反名稱混淆工具與反編譯工具

【操作平台】: winxp , .net 2.0

【軟體介紹】: .net 程式反編譯工具,差不多無人不知了吧。

【作者聲明】: 主要是出于學習目的,别無他意:)

一直想利用C#或C++這類語言的編譯優化功能來反.net程式的流程混淆,做過一些簡單的試驗,這個思路是可行的

,但是目前的反編譯工具reflector,dis#,spices.net對做了流程混淆的程式都不能正常反編譯,不得已我隻好嘗

試自己寫一個将IL反編譯成進階語言文法的工具,我的目标與前面的反編譯工具不太一樣,主要在于将混淆過的IL

代碼反成等價的僅僅文法上可編譯的進階語言結構(不使用if,while,do-while,for,for-each等進階語言構造,而

是使用goto語句,再經由進階語言編譯器優化編譯後,再用前述工具才可以看到使用這些構造的便于人看的代碼)

,我主要針對reflector采用的混淆方式來進行開發,是以本文主要講述的混淆方式都來自reflector。

一、reflector的保護

1、名稱混淆

reflector使用不可見名作名稱混淆,用它自己看的話都是框框。

2、主功能檔案隐藏,位元組碼直接裝入記憶體使用

reflector能夠十分容易的查找多個檔案間的交叉引用,但當我們用reflector來看reflector.exe的交叉引用時卻發

現主要接口都沒有對應實作,後來反編譯後調試時才發現,reflector.exe其實隻是個架構,主要是定義了reflector

的接口,另外是啟動代碼部分,接口的實作差不多都在另一個檔案裡。

在編譯後調試還發現另一個保護,就是reflector.exe的關鍵函數都加了屬性[DebuggerHidden],這樣在使用vs.net

調試跟蹤時會直接跳過這些函數,無法跟蹤到函數内,當然這種保護很弱,注釋掉就可以了。

下面我們來看一下reflector啟動的過程,首先是程式入口點函數:

[STAThread, DebuggerHidden]  <--這裡加了屬性,如果不去掉,程式就沒法調試,直接運作起來了,呵呵。

public static void Main()

{

      if (!T_x2.M_x1())

      {

            MessageBox.Show(a("/ucf95/uf797/uef99/ubc9b/ufd9d/uc19f/ucca1/ucaa3/uc9a5/udca7/u8aa9/uc0ab/ucfad/uc5af/udcb1/ud7b3/udeb5/u98b7/uceb9/ud4bb/ud7bd/ub3bf/ue2c1/ua5c3/ub6c5/ub8c7/ua6c9/ua5cb/uadcd/ub1cf/ua6d1/ubdd3/ub9d5/ub6d7/ufad9/ubadb/uacdd/u8fdf/u8fe1/uc4e3/u87e5/uc8e7/u84e9/u89eb/u9aed/u87ef/u9df1/u86f3/u9df5/ud8f7/u89f9/u94fb/u9ffd/u72ff/u6701/u2a03/u2605/u5807/u6609/u690b/u6f0d/u630f/u7711/u3413/u6215/u6a17/u6319/u3c1b/u6c1d/u551f/u4c21/u4a23/u4f25/u4627/u4d29/u0c2b/u472d/u442f/u1231/u5233/u4435/u5737/u5739/u1c3b/u5f3d/u603f/u2e41/u2b43/u2545/u2947/u2649/u6c4b/u2a4d/u394f/u2051/u3153/u3555/u2c57/u3559/u2e5b/u275d/u4e5f", 9), typeof(T_x2).Assembly.Location, MessageBoxButtons.OK, MessageBoxIcon.Hand);

      }

      else

      {

            T_x2 _x1 = new T_x2(null);

            try

            {

                  _x1.M_x6();

            }

            finally

            {

            }

      }

}

Main中的if語句調用的T_x2::M_x1函數的内容:

[return: MarshalAs(UnmanagedType.U1)]

private static bool M_x1()

{

      try

      {

            new PermissionSet(PermissionState.Unrestricted).Demand();

      }

      catch (SecurityException)

      {

            return false;

      }

      return true;

}

這個是檢查權限的,如果通不過就提示說不要從網絡共享裡啟動程式。彈出消息使用全局函數a解密,這個函數我們後面再說。

安全檢查通過後會構造T_x2類執行個體,構造内容:

[DebuggerHidden]  <--又是不讓調試!

public T_x2(IWindowManager A_0)

{

      this.M_x1(A_0);

}

調了M_x1(IWindowManager):

[DebuggerHidden]  <--又是不讓調試!

private void M_x1(IWindowManager A_0)

{

      string text1 = T_x4.M_x2();

      object obj1 = Activator.CreateInstance(T_x3.F_x1.GetType(text1, true), new object[] { A_0 });

      this.F_x1 = (IServiceProvider) obj1;

}

這個函數時建立了一個對象,比較關鍵了,它的第一行使用了T_x4.M_x2():

internal static string M_x2()

{

      return a("/uc390/uf692/uf394/ufb96/ufc98/uf89a/ue99c/uf09e/ud3a0/u8da2/ue4a4/ud7a6/ud9a8/uc7aa/uc4ac/uccae/ud0b0/uc7b2/udcb4/ud8b6/ud7b8/u95ba/ufcbc/ucfbe/ub1c0/uafc2/uacc4/ua4c6/ua8c8/ubfca/ua4cc/ua0ce/ubfd0/u9ed2/ub4d4/ub9d6/ub8d8/ubcda/ub8dc/uadde", 4);

}

原來隻是取一個字元串,跟到解密函數裡看一下,是"Reflector.Application.ApplicationManager",這個名字有點意思了,reflector.exe

裡并不包含Reflector.Application名空間,也沒有ApplicationManager這個類。

M_x1(IWindowManager)在取出上面的字元串後就用T_x3.F_x1.GetType來取對應此名字的類型了,T_x3.F_x1這個字段的類型是

internal static Assembly F_x1;

它的初化化在靜态構造裡:

static T_x3()

{

      T_x3.F_x1 = typeof(T_x2).Assembly;

}

用的Assembly是T_x2所屬的Assembly,其它隻是用标準.net的功能而已,也就是說到這裡T_x2的Assembly裡已經有Reflector.Application.ApplicationManager

這個Type了!回頭再看一下從入口點過來的代碼,好象沒有别的代碼了,有點怪?呵呵,reflector利用了類的靜态構造來做實際工作了,前面的過程中一共

涉及3個類T_x2,T_x3,T_x4,T_x2沒有靜态構造,T_x3的上面剛看了,就剩下T_x4了,我們來瞧一下:

[DebuggerHidden]  <--汗,又來了!!!

static T_x4()

{

      byte[] buffer1 = T_x4.M_x1();

      SymmetricAlgorithm algorithm1 = new TripleDESCryptoServiceProvider();

      string text2 = a("/udc94/ub096/uf498/ubb9a/uee9c/uf09e/ud3a0/ud1a2/udca4/u87a6/udda8/uc4aa/u8dac/udcae/ud4b0/ud6b2/u95b4/uceb6/ud6b8/uceba/u9dbc/udebe/ub3c0/ua6c2/ue5c4/ua3c6/ua6c8/ua2ca/ua3cc/ua8ce/uf1d0/ua7d2/ubdd4/ubed6/uaad8/ufbda/uaedc/ubede/u85e0/uc3e2/u86e4/u95e6/u88e8/u88ea/u86ec/u86ee/u9ff0/u94f2/ud5f4/u92f6/u81f8/u9efa/u8ffc/u9cfe/u6800/u7002/u6004/u2906/u2908/u5b0a/u610c/u6a0e/u7010/u6012/u7014/u3716/u6a18/u7e1a/u731c/u7b1e/u0120/u4e22/u4024/u0726/u4828/u452a/u0d2c/u6a2e/u5c30/u5232/u5c34/u5b36/u1938/u5a3a/u493c/u1f3e/u6640/u3142/u2a44/u2246/u2d48/u2e4a/u3f4c/u0f4e/u3050/u3a52/u2654/u2356/u3658/u755a/u3e5c/u305e/u0c60/u4462/u4564/u1466/u0668/u4b6a/u1a6c/u0a6e/u5170/u1072/u1474/u1976/u5978/u187a/u157c/u1e7e/uf580/ua382/ue484/ue586/ue688/ufe8a/uf98c/uaf8e/uf090/ub392/uf894/uf896/ueb98/ufe9a/ubd9c/uf29e/uc4a0/uc2a2/ucba4/ucea6/uc7a8/uccaa/ucbac/udaae/uddb0/u93b2/uc6b4/ud8b6/ud5b8/uceba/uc9bc/ud6be/uaec0/uadc2/uebc4/ue7c6/u80c8/uecca/ua0cc/uefce/ub2d0/ua6d2/ua7d4/ubed6/ub6d8/uaeda/uaedc/uffde/u96e0/u8be2/u9ce4/uc7e6/u90e8/u84ea/u98ec/ucfee/u90f0/u81f2/u90f4/ud7f6/u9df8/u94fa/u94fc/u91fe/u6600/u2302/u7104/u6f06/u6008/u780a/u230c/u2f0e/u4510/u7b12/u7014/u6516/u7c18/u3b1a/u701c/u6a1e/u5220/u5722/u0524/u4526/u4c28/u0b2a/u4c2c/u0f2e/u5c30/u5c32/u4734/u5236/u1938/u4b3a/u4f3c/u503e/u2540/u3642/u2644/u3346/u2048/u3d4a/u284c/u6f4e/u2650/u3252/u2c54/u7756/u2d58/u345a/u7d5c/u325e`/u0862d/u4766/u1068/u046a/u186c/u4f6e/u1970/u1272/u0574/u0776x/u557a", 8);

      byte[] buffer3 = Encoding.ASCII.GetBytes(text2);

      byte[] buffer2 = new MD5CryptoServiceProvider().ComputeHash(buffer3);

      algorithm1.Key = buffer2;

      algorithm1.Mode = CipherMode.ECB;

      int num1 = buffer1.Length;

      buffer1 = algorithm1.CreateDecryptor().TransformFinalBlock(buffer1, 0, num1);

      T_x1 _x1 = new T_x1(new MemoryStream(buffer1));

      _x1.M_x8();

      T_x7 _x2 = (T_x7) _x1.M_x6();

      ResolveEventHandler handler1 = new ResolveEventHandler(T_x4.M_x1);

      AppDomain.CurrentDomain.AssemblyResolve += handler1;

      string text1 = a("/ud994/uf896/uf898/uff9a", 8);  //這個字元串解出來是Load

      Type[] typeArray1 = new Type[1];

      Type type1 = typeof(byte[]);

      typeArray1[0] = type1;

      object[] objArray1 = new object[] { _x2.M_x1() };

      object obj1 = typeof(T_x3).GetFields(BindingFlags.NonPublic | BindingFlags.Static)[0].FieldType.GetMethod(text1, typeArray1).Invoke(null, objArray1);

      typeof(T_x3).GetFields(BindingFlags.NonPublic | BindingFlags.Static)[0].SetValue(null, obj1);

}

呵呵,看到有關加密的東東了,TripleDES、MD5!在解密之前調了T_x4.M_x1:

private static byte[] M_x1()

{

      byte[] buffer2 = null;

      string text2 = typeof(T_x2).Module.FullyQualifiedName;

      FileStream stream1 = File.OpenRead(text2);

      try

      {

            BinaryReader reader1 = new BinaryReader(stream1);

            BinaryReader reader2 = reader1;

            try

            {

                  T_x4.T_x2 _x1 = new T_x4.T_x2(reader1);

                  T_x4.T_x2 _x6 = _x1;

                  ushort num5 = _x1.M_x20();

                  T_x4.T_x5 _x3 = new T_x4.T_x5(reader1, num5);

                  T_x4.T_x5 _x5 = _x3;

                  string text1 = a("/ub895/uea97/ue999/uee9b/ufd9d", 9); //這個字元串解出來是.rsrc

                  T_x4.T_x6 _x2 = _x3.M_x1(text1);

                  T_x4.T_x6 _x4 = _x2;

                  uint num4 = _x2.M_x2();

                  int num3 = _x1.M_x7().M_x5();

                  reader1.BaseStream.Position = num3 + num4;

                  int num2 = reader1.ReadInt32();

                  int num6 = num2;

                  byte[] buffer1 = reader1.ReadBytes(num2);

                  byte[] buffer3 = buffer1;

                  return buffer1;

            }

            finally

            {

                  int num1 = 2;

                  if (reader2 != null)

                  {

                        num1 = 1;

                        num1 = 0;

                  }

            }

      }

      finally

      {

      }

      return buffer2;

}

可以看到是讀reflector.exe的.rsrc段,原來reflector.exe把它的關鍵代碼放在架構程式裡了。接着看T_x4的靜态構造,

先解密了一個字元串:

"I'm sorry to see you are doing this sad cracking exercise. Please send me an Email at '[email protected]' 

so we can chat about a more meaningful solution. I'm curious why you are doing this. There must be a more 

productive way to make you happy." 

汗了,原來是讓偶不要研究他的程式了。

reflector用了這段話的md5 hash值作TripleDES的密鑰,有點搞笑了。解密完之後是一段可能是驗證與解壓的過程(沒細看),

完了開始裝入,最後兩句話的本意是

T_x3.F_x1=Assembly.Load(byte[]);

故意用了一段基于reflection api的寫法,迷惑人!到這時也才發現原來T_x3的靜态構造裡對T_x3.F_x1的初始化居然也

是用來迷惑人的,reflector的作者真的是别具匠心了。

現在回到M_x1(IWindowManager):

[DebuggerHidden]  

private void M_x1(IWindowManager A_0)

{

      string text1 = T_x4.M_x2();

      object obj1 = Activator.CreateInstance(T_x3.F_x1.GetType(text1, true), new object[] { A_0 });

      this.F_x1 = (IServiceProvider) obj1;

}

本意是:

object obj=Activator.CreateInstance(assembly.GetType("Reflector.Application.ApplicationManager"),new object[]{null}); 

this.F_x1 = (IServiceProvider)obj;

到這裡,真正的功能代碼已經裝入了,并且已經執行個體化了一個ApplicationManager對象,現在回到reflector.exe的入口

函數了:

[STAThread, DebuggerHidden]

public static void Main()

{

      if (!T_x2.M_x1())

      {

            MessageBox.Show(a("/ucf95/uf797/uef99/ubc9b/ufd9d/uc19f/ucca1/ucaa3/uc9a5/udca7/u8aa9/uc0ab/ucfad/uc5af/udcb1/ud7b3/udeb5/u98b7/uceb9/ud4bb/ud7bd/ub3bf/ue2c1/ua5c3/ub6c5/ub8c7/ua6c9/ua5cb/uadcd/ub1cf/ua6d1/ubdd3/ub9d5/ub6d7/ufad9/ubadb/uacdd/u8fdf/u8fe1/uc4e3/u87e5/uc8e7/u84e9/u89eb/u9aed/u87ef/u9df1/u86f3/u9df5/ud8f7/u89f9/u94fb/u9ffd/u72ff/u6701/u2a03/u2605/u5807/u6609/u690b/u6f0d/u630f/u7711/u3413/u6215/u6a17/u6319/u3c1b/u6c1d/u551f/u4c21/u4a23/u4f25/u4627/u4d29/u0c2b/u472d/u442f/u1231/u5233/u4435/u5737/u5739/u1c3b/u5f3d/u603f/u2e41/u2b43/u2545/u2947/u2649/u6c4b/u2a4d/u394f/u2051/u3153/u3555/u2c57/u3559/u2e5b/u275d/u4e5f", 9), typeof(T_x2).Assembly.Location, MessageBoxButtons.OK, MessageBoxIcon.Hand);

      }

      else

      {

            T_x2 _x1 = new T_x2(null);

            try

            {

                  _x1.M_x6();

            }

            finally

            {

            }

      }

}

在try塊内調_x1.M_x6():

public void M_x6()

{

      IServiceProvider provider1 = this.F_x1;

      if (provider1 != null)

      {

            string text1 = a("/uc899/ue99b/uf09d", 13); //這個字元串解出來是Run

            provider1.GetType().GetMethod(text1).Invoke(this.F_x1, null);

      }

}

又是一段reflection api的寫法,本意是:

((IServiceProvider)(this.F_x1)).Run();

其實這才是程式真正的入口!

3、流程混淆及結構類混淆技術

reflector在混淆上用的花樣實在太多,這個在下一部分說了。

4、擷取真正功能實作檔案

主要是改兩個地方,一個是讀檔案的地方,一個是解密後:

1)将T_x4.M_x1()裡的FileStream stream1 = File.OpenRead(text2);改為

FileStream stream1 = File.OpenRead("d://reflector.exe");//假設原始的reflector.exe放在這個地方

2)在T_x4的靜态構造裡object[] objArray1 = new object[] { _x2.M_x1() };後面加上

byte[] data=objArray1[0] as byte[];

FileStream fs = new FileStream("d://reflector.application.dll", FileMode.Create, FileAccess.Write);

fs.Write(data,0,data.Length);

fs.Close();

之後執行到fs.Close();後,我們就得到reflector.application.dll,這個檔案大小是1756KB,在它的托管資源部分還嵌了

一個可執行檔案reflector.install.exe

二、reflector用到的混淆技術

我覺得可以将reflector用到的混淆技術除名稱混淆外分為兩類,一類是防止反編譯的混淆,另一類是防止重編譯的混淆

(一)防止反編譯的混淆(資料流或控制流混淆)

1、無用條件跳轉或永真條件跳轉

      L_000f: ldc.i4.0 

      L_0010: dup 

      L_0011: ldc.i4.1 

      L_0012: blt.s L_0047

上面這段代碼的條件跳轉是if(0<1)goto L_0047;這是永遠成立的跳轉,等價于一條goto語句,與之對應,還有一類條件跳

轉是永遠不成立的跳轉,就是可以删除的跳轉。

2、塊間來回跳轉

這應該就是流程混淆的最初形态了,簡單說就是将順序執行的指令序列分為若幹塊,然後打亂順序,再用goto将這些塊連接配接

為原來的執行順序。

--附帶說一下,1與2這兩種情形都是可以經過編譯優化處理的。

3、用switch作跳轉

用switch作跳轉是比較有意思的,這裡用一段C#說明,比用IL應該更清楚:

  int v=0;

  goto IL_01;

IL_01:

  switch(v)

  {

    case 0:goto IL_02;

    case 1:goto IL_03;

    case 2:goto IL_04;

    case 3:goto IL_05;

    default:goto IL_06;

  }

IL_02:

  ...做實際工作

  v=1;

  goto IL_01;

IL_03:

  ...做實際工作

  v=2;

  goto IL_01;

IL_04:

  ...做實際工作

  v=3;

  goto IL_01;

IL_05:

  ...做實際工作

  v=4;

  goto IL_01;

IL_06:

  ...做實際工作

  return

上面這段代碼實際上是順序執行的,這段代碼如果用C#編譯,IL_02到IL_06的代碼會嵌到switch裡面各個标簽處,也就是說

編譯器将這些goto連接配接起來了,但這種優化卻不能實作反混淆,這個switch的所有跳轉其實都是編譯時能計算的

  v=1;

  goto IL_01;

這樣模式的句子實際上就是無條件跳轉goto IL_03,真正原始的代碼就是去掉上面的v變量定義,switch語句、所有對v的指派

以及所有goto語句後剩下的代碼。

4、catch塊跳往try塊

  try

  {

IL_0001:

  ...

  }

  catch(Exception)

  {

  ...

  leave.s IL_0001

  }

這樣的可能算不上混淆,不過直接反編譯為C#或C++時這樣的跳轉是不合法的,在進階語言中goto隻能跳轉同一塊或者是外層塊

,而不能跳到嵌入塊或其它塊中。此外,有多層try-catch時在塊間跳轉在IL級也是合法的,這種混淆會成為反混淆的難題。(

手工反應該不難,應該主要難在自動處理上,特别是使用了filter與fault塊的情況)

5、用堆棧儲存值

這種方式對付目前的反編譯軟體是最有效的,這裡我們看一下reflector用來解密字元串的函數,函數IL指令後面的//注釋開始的

代碼是按IL指令序列反出來的C#代碼,()包括的内容是執行完IL指令後的堆棧内容,右邊是棧頂,我們用這種方式模拟反編譯的

過程,因為IL指令是基于堆棧計算的,函數的資料流主要反映在堆棧中,而進階語言是基于變量與表達式的,将堆棧計算還原到

到變量與表達式就是反編譯了。

.method privatescope hidebysig static string a(string A_0_1, int32 A_1_2) cil managed

{

      .maxstack 8

      .locals init (

            [0] char[] loc0,

            [1] int32 loc1,

            [2] int32 loc2,

            [3] unsigned int8 loc3,

            [4] unsigned int8 loc4)

      L_0000: ldarg.0 

      L_0001: callvirt instance char[] string::ToCharArray()

      L_0006: stloc.0         //loc0=A_0_1.ToCharArray();

      L_0007: ldc.i4 832044172

      L_000c: ldarg.1 

      L_000d: add 

      L_000e: stloc.1        //loc1=832044172+A_1_2;

      L_000f: ldc.i4.0         (0)

      L_0010: br.s L_0045

-----------------------------------------------------------------------------------------------------------------------      

      L_0012: dup 

      L_0013: stloc.2        //loc2=stack_top;      

      L_0014: ldloc.0         

      L_0015: ldloc.2 

      L_0016: ldloc.0      

      L_0017: ldloc.2 

      L_0018: ldelem.i2       (0,loc0,loc2,loc0[loc2])

      L_0019: dup         (0,loc0,loc2,loc0[loc2],loc0[loc2])

      L_001a: ldc.i4 255  

      L_001f: and             (0,loc0,loc2,loc0[loc2],loc0[loc2] & 255)

      L_0020: ldloc.1     

      L_0021: dup         (0,loc0,loc2,loc0[loc2],loc0[loc2] & 255,loc1,loc1)

      L_0022: ldc.i4.1     

      L_0023: add             (0,loc0,loc2,loc0[loc2],loc0[loc2] & 255,loc1,loc1+1)

      L_0024: stloc.1         (0,loc0,loc2,loc0[loc2],loc0[loc2] & 255,loc1)    

            //loc1=loc1+1

      L_0025: xor         

      L_0026: conv.u1 

      L_0027: stloc.3         (0,loc0,loc2,loc0[loc2])

            //loc3=(loc0[loc2] & 255) ^ loc1  

            //這裡的loc1不是上面+1後的loc1,而應該是+1操作前的loc1

      L_0028: dup 

      L_0029: ldc.i4.8     

      L_002a: shr         (0,loc0,loc2,loc0[loc2],loc0[loc2] >> 8)

      L_002b: ldloc.1     

      L_002c: dup         (0,loc0,loc2,loc0[loc2],loc0[loc2] >> 8,loc1,loc1)

      L_002d: ldc.i4.1     

      L_002e: add         (0,loc0,loc2,loc0[loc2],loc0[loc2] >> 8,loc1,loc1+1)

      L_002f: stloc.1         (0,loc0,loc2,loc0[loc2],loc0[loc2] >> 8,loc1)

            //loc1=loc1+1

      L_0030: xor             

      L_0031: conv.u1 

      L_0032: stloc.s num4      (0,loc0,loc2,loc0[loc2])   

            //loc4=(loc0[loc2] >> 8) ^ loc1     

            //這裡的loc1不是上面+1後的loc1,而應該是+1操作前的loc1

      L_0034: pop         (0,loc0,loc2)

      L_0035: ldloc.s num4      (0,loc0,loc2,loc4)

      L_0037: ldloc.3 

      L_0038: stloc.s num4      (0,loc0,loc2,loc4)    

            //loc4=loc3      

      L_003a: stloc.3         (0,loc0,loc2)

            //loc3=loc4 

            //這裡的loc4是棧中儲存的原來的loc4,并不是上一語句改變過其值的loc4

      L_003b: ldloc.s num4    

      L_003d: ldc.i4.8 

      L_003e: shl         (0,loc0,loc2,loc4 << 8)

      L_003f: ldloc.3         (0,loc0,loc2,loc4 << 8,loc3)

      L_0040: or         (0,loc0,loc2,((loc4 << 8) | loc3))

      L_0041: conv.u2         

      L_0042: stelem.i2       (0)        

            //loc0[loc2]=((loc4 << 8) | loc3)

      L_0043: ldc.i4.1         

----------------------------------------------------------------------------------------------------------------------------------------------

      L_0044: add                                                 (0 + 1)

      L_0045: dup         (0,0)        第二次執行的堆棧内容:(1,1)

      L_0046: ldloc.0         (0,0,loc0)      第二次執行的堆棧内容:(1,1,loc0)

      L_0047: ldlen         (0,0,len(loc0))      第二次執行的堆棧内容:(1,1,len(loc0))

      L_0048: conv.i4         

      L_0049: blt.s L_0012      (0)        第二次執行的堆棧内容:(1)

            //if(0<len(loc0)) goto L_0012;            //if(1<len(loc0)) goto L_0012

----------------------------------------------------------------------------------------------------------------------------------------------

      L_004b: pop             

      L_004c: ldloc.0                                             

      L_004d: newobj instance void string::.ctor(char[])          

      L_0052: call string string::Intern(string)                  

      L_0057: ret         //return String.Intern(new string(loc0))                                        return string.Intern(new String(loc0))

}

上面代碼中有二類導緻反編譯失效的情形:

1)、觀察我們用括号列出的堆棧分析,可以看到在L_0012到L_0042指令,堆棧一直不為空,有一個值在整個塊中從未出過堆棧,此值在L_0044時執行+1操作,仍然不

出棧,直到L_0049跳轉回L_0012,可以看到這是一個循環,從函數入口跳到L_0044開始執行,之後每次循環棧底的值被加1,此值同時用作循環條件。如果我們簡單

按照注釋部分生成C#代碼,則此循環被忘記了(因為循環變量是用棧作的)。

2)、L_0023、L_002e兩處對棧頂值執行+1操作,此時棧中儲存着對應變量的兩份值,稍後棧頂值再次賦給原變量,原變量值改變,但棧中仍儲存了一份此變量被必變

前的值;這種情況還有一處出現,L_0038處對變量loc4指派,指派後棧中仍儲存着原來的loc4值,緊接着對loc3指派時使用了。也就是說,棧在操作中起到了臨時變量

的作用。我們來看與L_0023處堆棧操作相關的反編譯:

  loc1=loc1+1;

  loc4=(loc0[loc2] >> 8) ^ loc1

這樣結果就不正确了,考慮到棧起到臨時變量的作用,實際上可以寫成:

  temp1=loc1;

  loc1=temp1+1;

  loc4=(loc0[loc2] >> 8) ^ temp1;

這樣三句就與原IL程式等價了。我們再看一下L_0038處的反編譯:

  loc4=loc3;

  loc3=loc4;

這也是不正确的,考慮棧的臨時變量作用,寫成:

  temp2=loc4;

  loc4=loc3;

  loc3=temp2;

這樣就正确了。

大家可以用dis#,spices.net分别反一下reflector的這個函數,看看他們的反編譯能力。(reflector比較狡猾,它隻要發現堆棧有問題就說可以是混淆代碼,不給反

了,呵呵)

(二)防止重編譯的混淆(結構混淆)

這種混淆我把它稱為結構混淆,就是通過使用進階語言沒有對應實作的文法結構來混淆,使得反編譯後的程式無法通過編譯,一般的反編譯程式是不會考慮這個問題

的,因為它們通常不是為了讓大家重新編譯的,呵呵。

1、使用filter,fault的異常塊,在C#中沒有對應構造,C++中對應也不太明确。

2、引用與非引用參數在C++中是不區分的,不過C#與.net裡是區分的,如

ref class A

{

  void Test(int i);

  void Test(int% i);

};

這樣的定義雖然可以通過編譯,但是卻不能被調用

A^ a=gcnew A();

a->Test(1);

編譯時會說無法決定使用哪個重載。

3、外層類與嵌入類同名,這在C#與C++中都是不允許的,如

class A

{

  Class A

  {

  }

}

這樣的定義是不合法的。

4、類的完全限定名不能恰好是另一個類的完全限定名的字尾,這在定義時不會出錯,但在引用時往往引起錯誤,如

Class A

{

}

Class B

{

  Class A

  {

  }

}

這裡的A成為了B.A的字尾,如果在類B中定義一個A的執行個體變量,不使用完全限定名的情況下有時會引起混淆,這種情

況隻出現在沒有名空間包含的全局類與這樣類的嵌入類之間,是以有一個簡單辦法解決,就是為所有全局類指定一個

名空間,比如Root(dis#就是這麼幹的,相信它是考慮到這一點了),這樣上面的類變成Root.A與Root.B.A,不再是

字尾了。

5、引用assembly與被引用assembly類型重名,reflector.exe與reflector.application.dll在名稱混淆後就有這種情

況,因為它們這此類實際上互不相幹,在運作時不會倒緻問題,但是反編譯時如果我們對這些混淆成相同名稱的類還

是采用相同的名稱,那麼編譯reflector.exe能過去,因為它不引用reflector.application.dll(它使用動态裝入),

但是編譯reflector.application.dll則不行,因為它要引用前者,要使用裡面定義的接口,雖然那些接口定義與它不

沖突,但因為引用的不相幹的類與它有沖突,會倒緻編譯失敗,因為這種沖突隻出現互不相幹的類之間,是以可以為

某一方指定一個不同的名空間。

6、改變通路許可,通路許可看起來像是編譯時檢查的東西,混淆時如果将某些方法改為私有,似乎并不會影響調用它

的方法的執行,但是編譯時這是錯誤的。(事實上我們用reflection api調用方法時就不受通路許可的限制,呵呵)

7、重載換名,就是方法重載使用.override訓示一個不同的名稱,C#對此沒有支援,mc++對此也不支援,隻在較新的

c++/cli中有支援。

8、父類或實作接口名與特性名稱相同,這在C++使用類外函數實作文法時會無法編譯(因為C++不像C#會自動解決交叉

引用,而.net程式的交叉引用泛濫,類外函數實作是反編譯大工程時的必然先擇),C#中倒無此問題。

三、針對這些混淆的反編譯

1、目标進階語言的選擇

目智語言的選擇主要考慮前面提到的結構混淆的問題,因為重載換名C#完全不支援,而其它情形的混淆均可以通過修改

讓C++通過編譯,C++在文法結構的豐富性勝過C#,另一方面,比較了一下C#與C++的編譯優化,雖然二者在處理流程混淆

的無用跳轉與來回跳轉都很有效,但C#在處理變量使用優化時明顯不如C++,優化選項也不C++豐富。綜合這兩點,C++

是較好的選擇,不過C++語言不夠動态,vs.net的調試與源代碼編輯方面都不如C#,隻可惜魚與熊掌不能兼得。

2、反混淆的關鍵點

在前面介紹混淆方式時大多已經提到了反混淆的方法,這裡着重提一下針對switch混淆的反混淆與利用堆棧混淆的反混淆

,其它混淆一般不影響反編譯結果的正确性,可以利用C++的優化編譯處理,利用異常的混淆在reflector中用得較簡單,

是以我也沒有深入探索。

1)反編譯與反混淆的基礎

這裡說的基礎是在編譯原理之類書藉在講到編譯優化時要說的東東,就是要建立程式流圖,因為将基于棧的IL反編譯成基于

變量與表達式的進階語言關鍵在于保證資料流的正确性,而資料流是在控制流的架構内維持的,是以我們首先需要為每個

函數建立其程式流圖,流圖的每個結點就是一個基本塊,基本塊内我們隻要按前面說堆棧混淆時的那種堆棧分析方法來生成

表達式即可(相當于解釋執行IL),在基本塊與基本塊之間則需要考慮塊間暫存在堆棧中的資料的一緻性問題。

事實上,程式流圖的建立還用于像來回跳轉這種優化以及識别分支與循環結構這種進階語言結構上,不過這些都有現成工具

來做,我就沒有重複做這些事了。

2)用switch作跳轉的反混淆

還是拿前面講混淆時的例子,反混淆的辦法就是分析每個switch語句的判斷表達式與switch塊的入口點,然後在每個跳往

switch塊入口的跳轉時計算switch判斷表達式的值,将跳轉目标直接用對應switch分支目标代替,這樣C++編譯器就能優化

掉switch了(因為這樣處理後switch塊就是一個空架子了),前面的例子這樣處理的結果如下:

  int v=0;

  goto IL_01;  改為switch(0)的分支目标-->goto IL_02;

IL_01:

  switch(v)

  {

    case 0:goto IL_02;

    case 1:goto IL_03;

    case 2:goto IL_04;

    case 3:goto IL_05;

    default:goto IL_06;

  }

IL_02:

  ...做實際工作

  v=1;

  goto IL_01;  改為switch(1)的分支目标-->goto IL_03;

IL_03:

  ...做實際工作

  v=2;

  goto IL_01;  改為switch(2)的分支目标-->goto IL_04;

IL_04:

  ...做實際工作

  v=3;

  goto IL_01;  改為switch(3)的分支目标-->goto IL_05;

IL_05:

  ...做實際工作

  v=4;

  goto IL_01;  改為switch(default)的分支目标-->goto IL_06;

IL_06:

  ...做實際工作

  return

3)用堆棧儲存值的混淆的反混淆

前面說的時候已經提到了基本辦法,就是引入臨時變量,相當于為堆棧中暫存的值命名。這裡說一下引入臨時變量的幾種情形:

1、變量值更新倒緻原變量值需要臨時變量記錄

在編譯優化中處理基本塊時一般使用一種稱為DAG的結構來描述,在構造DAG的過程中如果一個結點被指定給某一個變量時,此變

量需要與先前關聯的結點斷開,我們這裡所處理的情形與此類似,隻需要跟蹤變量的指派,也就是stloc,starg語句即可,當執

行指派語句時,我們檢查目前堆棧中是否有引用該變量的值存在,如果有,則對原引用賦一個昨時變量。還是以先前堆棧混淆的

代碼為例:

L_0020: ldloc.1     

L_0021: dup         (0,loc0,loc2,loc0[loc2],loc0[loc2] & 255,loc1,loc1)

L_0022: ldc.i4.1   

L_0023: add             (0,loc0,loc2,loc0[loc2],loc0[loc2] & 255,loc1,loc1+1)

L_0024: stloc.1        (0,loc0,loc2,loc0[loc2],loc0[loc2] & 255,loc1)    

          //loc1=loc1+1

在L_0024時,我們要對loc1指派,此時棧頂值引用了原loc1,是以我們需要對原loc1引入一個臨時變量temp1,不過temp1這個變

量生成C#的地方要放到該值進棧的那條IL語句也就是L_0020上,也就是說記錄變為:

L_0020: ldloc.1       //temp1=loc1

L_0021: dup         (0,loc0,loc2,loc0[loc2],loc0[loc2] & 255,temp1,temp1)

L_0022: ldc.i4.1   

L_0023: add             (0,loc0,loc2,loc0[loc2],loc0[loc2] & 255,temp1,temp1+1)

L_0024: stloc.1        (0,loc0,loc2,loc0[loc2],loc0[loc2] & 255,temp1)    

          //loc1=temp1+1

這樣生成的C#代碼序列為:

temp1=loc1;

loc1=temp1+1;

因為棧中記錄的是temp1,對loc1的指派不會影響棧中值的使用。

2、過程調用傳回值需要使用臨時變量暫存

過程調用有可能改變目前函數的上下文,也就是說過程調用語句的順序可能是不能改變的,不過我們用堆棧分析IL指令時,棧中的

值是用産生該值的表達式表示的(除非該表達式值賦給了臨時變量),對于過程調用的傳回值,在棧中是用過程調用表示的,僅當

值出棧時才會生成C#語句,由于棧的後進先出特性,将過程調用表達式放在棧中就有可能改變語句的順序。

如果能夠進行全程式優化,過程調用的影響是可以确切知道的,但不做這種優化的話,我們需要保證調用語句的執行順序,此時就

需要為傳回值的調用引入臨時變量。

3、數組通路、指針需要引入臨時變量

如果數組元素與指針指向的值進棧後再被修改,則與1中所說變量被修改情形是一樣的,需要對棧中記錄的原值引入臨時變量。

4、塊間利用堆棧傳值需要為棧中變量引入臨時變量名

前面說的1、2、3都是基本塊内的處理辦法,除了塊内有用堆棧混淆外,塊間也會有這樣的混淆:

IL_0041:            ldstr        L"/xDA8E/xE290/xF692/xF194/xB796/xDB98/xE29A"

IL_0046:            ldloc        V_1

IL_004a:            call        System::String^ undefined_type::a(System::String^,System::Int32)

IL_004f:            br.s        IL_007f

-----------------------------------------------------------------------------------------------------------------------------------------------------------

IL_0051:            ldstr        L"/xDA8E/xE290/xF692/xF194/xB796/xD098/xF59A/xBD9C/xB89E"

IL_0056:            ldloc        V_1

IL_005a:            call        System::String^ undefined_type::a(System::String^,System::Int32)

IL_005f:            ldarg.0

IL_0060:            ldfld        Reflector::CodeModel::IAssembly^ Root::T_x32::T_x1 F_x2

IL_0065:            callvirt        System::String^ Reflector::CodeModel::IAssemblyReference::get_Name()

IL_006a:            ldstr        L"/xA88E/xB190/xD192/xEC94"

IL_006f:            ldloc        V_1

IL_0073:            call        System::String^ undefined_type::a(System::String^,System::Int32)

IL_0078:            call        System::String^ System::String::Concat(System::String^,System::String^,System::String^)

IL_007d:            br.s        IL_007f

-----------------------------------------------------------------------------------------------------------------------------------------------------------

IL_007f:            call        void System::Windows::Forms::TreeNode::set_Text(System::String^)

IL_0084:            ldarg.0

IL_0085:            call        System::Windows::Forms::TreeNodeCollection^ System::Windows::Forms::TreeNode::get_Nodes()

IL_008a:            newobj        void Root::T_x32::T_x14::.ctor()

IL_008f:            callvirt        System::Int32 System::Windows::Forms::TreeNodeCollection::Add(System::Windows::Forms::TreeNode^)

IL_0094:            pop

IL_0095:            ret

上面這段代碼由三個基本塊組成,第一塊與第二塊為第三塊的前導,我們看到第一塊與第二塊執行結束時最後一次函數調用的傳回值在堆棧中并未出棧,在第三塊

開始的調用直接使用了棧中傳入的參數(進階語言基于變量與表達式的文法是不會産生這種情況的)。

當我們反編譯這段代碼時,反編譯到第三塊時,函數調用的參數用哪個呢?它有兩個前導塊,各自通過堆棧傳入一個值,程式的原意是根據條件不同使用不同的參數

,如果我們用進階語言這樣的功能,我們會寫成:

string s;

if(...)

{

  s=xxCall1();

}

else

{

  s=xxCall2();

}

xxCall3(s);

也就是說,我們使用一個局部變量來統一兩個分支的傳回值,然後用這個變量作參數調用xxCall3。這樣一類比就比較清楚了,仍然是需要引入臨時變量,

隻不過這次不隻是引入臨時變量,還需要兩個分支塊的臨時變量是同一個變量。

上面的是一個分支結構的例子,其實塊間傳值還有一種情形發生在循環時(從這個角度看,循環其實是可以歸入分支結構的),這裡的一個例子是我們

前面用來說明堆棧存值混淆的refelctor的字元串解密函數,在循環的全過程中棧中有個值一直未出棧,L_0049->L_0012,前者是一個基本塊的結束,後者是

一個基本塊的開始,是以棧中的值也是在基本塊間傳遞的,按照前面的方法,我們也需要為它引入一個臨時變量,這個變量便是循環變量了。

最後我貼出我寫的反混淆反編譯程式對字元串解密函數反出來的代碼(相對原始的代碼,呵呵)與經過C++優化編譯後再用refelctor看到的代碼,大家有興趣可以仔細看

一下,這裡面有關于堆棧混淆與反混淆的主要資訊。

反混淆反編譯的結果:

System::String^ a(System::String^ A_0,System::Int32 A_1)

{

  //temp variables , should be optimized by C++/cli compiler.

  array<System::Char>^ Temp_0 = nullptr;

  System::Int32 Temp_1 = 0;

  System::Int32 Temp_2 = 0;

  System::Int32 Temp_3 = 0;

  System::Byte Temp_4 = 0;

  System::String^ Temp_5 = nullptr;

  System::String^ Temp_6 = nullptr;

  System::Int32 Temp_7 = 0;

  System::Int32 Temp_8 = 0;

  System::Int32 Temp_9 = 0;

  //local variables.

  array<System::Char>^ V_0 = nullptr;

  System::Int32 V_1 = 0;

  System::Int32 V_2 = 0;

  System::Byte V_3 = 0;

  System::Byte V_4 = 0;

  //method body ------- 

  IL_0000:                                                                        //ldarg.0

  IL_0001:            Temp_0=A_0->ToCharArray();                                  //callvirt        array<System::Char>^ System::String::ToCharArray()

  IL_0006:            V_0=Temp_0;                                                 //stloc.0

  IL_0007:                                                                        //ldc.i4        0x3197fc8c

  IL_000c:                                                                        //ldarg.1

  IL_000d:                                                                        //add

  IL_000e:            V_1=(832044172 + A_1);                                      //stloc.1

  IL_000f:                                                                        //ldc.i4.0

  IL_0010:                                                                        //dup

  IL_0011:                                                                        //ldc.i4.1

  IL_0012:            Temp_8=0;goto IL_0047;                                      //blt.s        IL_0047

  IL_0014:                                                                        //dup

  IL_0015:            Temp_9=Temp_8;                                              //stloc.2

  IL_0016:                                                                        //ldloc.0

  IL_0017:                                                                        //ldloc.2

  IL_0018:                                                                        //ldloc.0

  IL_0019:                                                                        //ldloc.2

  IL_001a:                                                                        //ldelem.i2

  IL_001b:                                                                        //dup

  IL_001c:                                                                        //ldc.i4        0xff

  IL_0021:                                                                        //and

  IL_0022:            Temp_2=V_1;                                                 //ldloc.1

  IL_0023:                                                                        //dup

  IL_0024:                                                                        //ldc.i4.1

  IL_0025:                                                                        //add

  IL_0026:            Temp_3=(Temp_2 + 1);                                        //stloc.1

  IL_0027:                                                                        //xor

  IL_0028:                                                                        //conv.u1

  IL_0029:            V_3=safe_cast<System::Byte>(((V_0[Temp_9] & (System::Char)255) ^ Temp_2));//stloc.3

  IL_002a:                                                                        //dup

  IL_002b:                                                                        //ldc.i4.8

  IL_002c:                                                                        //shr

  IL_002d:                                                                        //ldloc.1

  IL_002e:                                                                        //dup

  IL_002f:                                                                        //ldc.i4.1

  IL_0030:                                                                        //add

  IL_0031:            V_1=(Temp_3 + 1);                                           //stloc.1

  IL_0032:                                                                        //xor

  IL_0033:                                                                        //conv.u1

  IL_0034:            Temp_4=safe_cast<System::Byte>(((V_0[Temp_9] >> 8) ^ safe_cast<System::Char>(Temp_3)));//stloc.s        V_4

  IL_0036:                                                                        //pop

  IL_0037:                                                                        //ldloc.s        V_4

  IL_0039:                                                                        //ldloc.3

  IL_003a:            V_4=V_3;                                                    //stloc.s        V_4

  IL_003c:            V_3=Temp_4;                                                 //stloc.3

  IL_003d:                                                                        //ldloc.s        V_4

  IL_003f:                                                                        //ldc.i4.8

  IL_0040:                                                                        //shl

  IL_0041:                                                                        //ldloc.3

  IL_0042:                                                                        //or

  IL_0043:                                                                        //conv.u2

  IL_0044:            V_0[Temp_9]=safe_cast<System::Char>(safe_cast<System::UInt16>(((V_4 << 8) | V_3)));//stelem.i2

  IL_0045:                                                                        //ldc.i4.1

  IL_0046:            Temp_8=(Temp_9 + 1);                                        //add

  IL_0047:                  //dup

  IL_0048:                                                                        //ldloc.0

  IL_0049:            Temp_1=V_0->Length;                                         //ldlen

  IL_004a:                                                                        //conv.i4

  IL_004b:            if(Temp_8<Temp_1)goto IL_0014;                              //blt.s        IL_0014

  IL_004d:                                                                        //pop

  IL_004e:                                                                        //ldloc.0

  IL_004f:            Temp_5=gcnew System::String(V_0);                           //newobj        void System::String::.ctor(array<System::Char>^)

  IL_0054:            Temp_6=System::String::Intern(Temp_5);                      //call        System::String^ System::String::Intern(System::String^)

  IL_0059:            return Temp_6;                                              //ret

}

經過C++優化編譯後再用reflector看到的C++/cli代碼:

String^  a(String^ A_0, int A_1)

{

      array<wchar_t>^ chArray1 = A_0->ToCharArray();

      int num2 = (A_1 + 832044172);

      int num1 = 0;

      if ((0 < chArray1->Length))

      {

            do

            {

                  int num4 = num2;

                  int num3 = (num2 + 1);

                  num2 = (num3 + 1);

                  wchar_t ch1 = chArray1[num1];

                  chArray1[num1] = ((wchar_t) (((unsigned short) ((((unsigned char) ch1) << 8) ^ (((unsigned char) num4) << 8))) | ((unsigned short) (((unsigned char) (ch1 >> 8)) ^ ((unsigned char) num3)))));

                  num1++;

            }

            while((num1 < chArray1->Length));

      }

      return String::Intern(gcnew String(chArray1) );

}

C#代碼:

internal static string a(string A_0, int A_1)

{

      char[] chArray1 = A_0.ToCharArray();

      int num2 = A_1 + 0x3197fc8c;

      int num1 = 0;

      if (0 < chArray1.Length)

      {

            do

            {

                  int num4 = num2;

                  int num3 = num2 + 1;

                  num2 = num3 + 1;

                  char ch1 = chArray1[num1];

                  chArray1[num1] = (char) (((ushort) ((((byte) ch1) << 8) ^ (((byte) num4) << 8))) | ((ushort) (((byte) (ch1 >> 8)) ^ ((byte) num3))));

                  num1++;

            }

            while (num1 < chArray1.Length);

      }

      return string.Intern(new string(chArray1));

}

四、反編譯成進階語言後...

反編譯成可編譯的進階語言的一個用處是利用進階語言編譯器的優化功能處理混淆,另外還有一個便是調試,對于.net程式,還是在有源碼的情況下用vs.net調試來得

直覺,我寫的反編譯工具輸出的源碼是進階語言與IL對應的,主要是考慮到反編譯如果出錯,便于人工修改。

現在的程式還隻是個初步的程式,對于COM,generic的支援還不行,當混淆程式使用了結構混淆時生成的代碼會出現編譯錯誤,類型轉換與推導有時也會出錯,還需要

人工作一些調整,就reflector的反編譯來說,reflector.exe反編譯後隻有兩處類型轉換錯誤了,但reflector.application.dll則有數十處錯誤(其中主要的錯誤都源

于引用參數與非引用參數的重載混淆所緻)。

最終我重新編譯通過了reflector.exe,reflector.application.dll,但是運作時剛顯示主界面就會異常退出,跟蹤調試時發現是使用IOleObject::SetClientSite時出

錯了,現在原因還沒搞清楚,汗。

附件是我寫的反名稱混淆程式與反編譯程式(均是未加殼未混淆的,用reflector可直接閱讀代碼的)。

反編譯程式的用法如下:

1、用ildasm反彙編.net程式并儲存成IL檔案;

2、在指令行執行反編譯程式test.exe:

  test 目标檔案.il

此時在test.exe所在目錄生成一個子目錄il2cpp,其下是反編譯出來的c++源檔案;

3、用vs.net 2005建立立一個C++/cli項目(視目标程式是console還是winform選不同類型),若是winform生成後删除主form檔案,将2中生成的檔案拷到項目源檔案目錄,

再用“添加現有項”将這些檔案添加項目中,并将所有*.cpp檔案的預編譯頭設為“不使用預編譯頭”;

4、在項目主程式cpp檔案添加包含頭檔案

  #include "global_xref.h"

在main函數裡調用原程式對應的入口函數調用;

5、編譯、改錯、再編譯直到通過,呵呵;

6、發現問題請發資訊至[email protected],此程式目前還相當初級,需要完善。

--------------------------------------------------------------------------------------------------

謝謝閱讀,呵呵                

                  2006.12.31

上傳的附件
從reflector實作看.net的混淆與反混淆技術【原創】
deobfu.rar (2006-12-31 13:07, 0, 341 次下載下傳)

<script type="text/javascript"> </script> <script type="text/javascript" src="http://bbs.pediy.com/images/show_ads.js"> </script> name="google_ads_frame" src="http://pagead2.googlesyndication.com/pagead/ads?client=ca-pub-1585618761901261&dt=1231470854083&lmt=1231470853&alt_color=F7F7F7&format=468x60_as&output=html&channel=0724307974&url=http%3A%2F%2Fbbs.pediy.com%2Fshowthread.php%3Ft%3D37217&color_bg=F7F7F7&color_text=000000&color_link=003399&color_url=008000&color_ marginwidth="0" marginheight="0" vspace="0" hspace="0" allowtransparency="true" scrolling="no" width="468" frame height="60">

此帖于 2007-01-03 15:43 被 dreaman 最後編輯.

從reflector實作看.net的混淆與反混淆技術【原創】
dreaman
檢視公開資訊
通路 dreaman 的個人網站
查找 dreaman 發表的更多文章
檢視 dreaman 發表的精華帖
dreaman <script type="text/javascript"> vbmenu_register("postmenu_260131", true); </script>
從reflector實作看.net的混淆與反混淆技術【原創】
從reflector實作看.net的混淆與反混淆技術【原創】

『.net逆向小組』

資 料: 注冊日期: Mar 2006 文章: 191

從reflector實作看.net的混淆與反混淆技術【原創】

精華: 9

看雪勳章

從reflector實作看.net的混淆與反混淆技術【原創】
2
從reflector實作看.net的混淆與反混淆技術【原創】
2006-12-31, 13:13
從reflector實作看.net的混淆與反混淆技術【原創】
從reflector實作看.net的混淆與反混淆技術【原創】

反編譯後又重編譯出的reflector:

1、reflector_org.exe,這個是包括原來解密代碼的,我将其中讀自身的代碼改為讀D:/data.dll了,可以看到T_x4的靜态構造裡的解密代碼;

2、reflector.exe,這個是跳過原來的解密代碼直接裝入reflector.application.dll的版本;

3、reflector.application.dll,這個是真正的reflector功能實作,它的資源部分還嵌了一個reflector.install.exe,那個是版本更新用的,我沒有反它。

上傳的附件
從reflector實作看.net的混淆與反混淆技術【原創】
de_reflector.rar (2006-12-31 13:13, 0, 236 次下載下傳)

繼續閱讀