【标题】研读Reflector的保护原理心得(二) 【作者】henryouly 【目的】研究.NET的软件保护技术 【工具】Reflector, ildasm, OD, ultraedit 【研究软件】.NET Reflector 4.1.80.0 【软件性质】国外免费软件 【下载】http://www./roeder/dotnet 【声明】以单纯技术探讨为目的,转载时请保留原作信息 经过两天的研读,终于把Reflector的核心程序集的秘密完全揭开了,废话少说,我们直接来看破解过程。 【初步尝试】 上一篇文章说过,可以有两种解密思路,一种是用Reflector导出c#源代码,然后直接用c#编写程序。很快就发现这种方法是不可行的。由于导出的代码,存在大量函数名与变量名重名的情况,使得手动区分并改名的办法难以进行下去。特别是int各种类型存在隐式转换的问题,使得某些情况下根本无法区分。因此在源码层次上做逆向的思路行不通。 于是考虑第二种思路,直接加入IL代码。用ildasm打开,导出……非法操作了!!上网查找发现也有不少人碰到过这种问题,这是混淆器的另外一种功能,利用ildasm的bug可以使得ildasm crack掉,无法完整导出文件。可是没有人提出解决办法。 【进一步分析】 偶然发现ildasm是vc7编译的,于是我想起了老本行,打破这种僵局的唯一办法就是自己把ildasm的bug fix掉。用OD attach上ildasm线程(ildasm是启动后再自己创建一个线程运行的,不知道这样做的道理是什么,可能是想防止debug吧),运行,停在异常处,跟踪堆栈来到这里 00421892 |. FF91 D0000000 call dword ptr ds:[ecx+D0] 00421898 |. 0FB645 C4 movzx eax,byte ptr ss:[ebp-3C] 0042189C |. 8D48 FE lea ecx,dword ptr ds:[eax-2] ; Switch (cases 2..12),这里ecx变成0xffffffff 0042189F |. 83F9 10 cmp ecx,10 004218A2 |. 0F87 87010000 ja ildasm.00421A2F ;跳到Default case 004218A8 |. FF248D 8E1A42>jmp dword ptr ds:[ecx*4+421A8E] 004218AF |> 0FB645 CC movzx eax,byte ptr ss:[ebp-34] ; Cases 4,5 of switch 0042189C 004218B3 |. 50 push eax 004218B4 |. 68 3C6E4000 push ildasm.00406E3C ; ASCII " = int8(0x%02X)" 004218B9 |. E9 0C010000 jmp ildasm.004219CA 004218BE |> 0FB745 CC movzx eax,word ptr ss:[ebp-34] ; Cases 6,7 of switch 0042189C 004218C2 |. 50 push eax 004218C3 |. 68 286E4000 push ildasm.00406E28 ; ASCII " = int16(0x%04X)" 004218C8 |. E9 FD000000 jmp ildasm.004219CA ……省略…… 00421A2F |> 57 push edi ; Default case of switch 0042189C 00421A30 |. FF75 D4 push dword ptr ss:[ebp-2C] ; /<%d> 00421A33 |. 8B3D 24134000 mov edi,dword ptr ds:[<&MSVCR71.sprintf>>; |MSVCR71.sprintf 00421A39 |. 50 push eax ; |<%02X> 00421A3A |. 68 4C6D4000 push ildasm.00406D4C ; |format = " /* ILLEGAL CONSTANT type:0x%02X, size:%d bytes, blob: " 00421A3F |. 56 push esi ; |s 00421A40 |. FFD7 call edi ; \sprintf 00421A42 |. 83C4 10 add esp,10 00421A45 |. 03F0 add esi,eax 00421A47 |. 837D CC 00 cmp dword ptr ss:[ebp-34],0 ;buffer的长度,发现是个很大的数 00421A4B 74 1B je short ildasm.00421A68 ; 爆破,改成jmp 00421A4D |. 68 244D4000 push ildasm.00404D24 ; /format = "(" 00421A52 |. 56 push esi ; |s 00421A53 |. FFD7 call edi ; \sprintf 00421A55 |. 59 pop ecx 00421A56 |. 59 pop ecx 00421A57 |. FF75 10 push dword ptr ss:[ebp+10] ; /Arg4 00421A5A |. FF75 D4 push dword ptr ss:[ebp-2C] ; |Arg3 00421A5D |. FF75 CC push dword ptr ss:[ebp-34] ; |Arg2 00421A60 |. 53 push ebx ; |Arg1 00421A61 |. E8 99FCFFFF call ildasm.004216FF ; 输出buffer的内容 00421A66 |. EB 0A jmp short ildasm.00421A72 00421A68 |> 68 446D4000 push ildasm.00406D44 ; ASCII "NULL" 00421A6D |. 56 push esi 00421A6E |. FFD7 call edi 00421A70 |. 59 pop ecx 00421A71 |. 59 pop ecx 00421A72 |> 68 406D4000 push ildasm.00406D40 ; /src = " */" 00421A77 |. 53 push ebx ; |dest 00421A78 |. E8 F1AB0000 call <jmp.&MSVCR71.strcat> ; \strcat 00421A7D |. 59 pop ecx 00421A7E |. 59 pop ecx 00421A7F |. 5F pop edi 00421A80 |> 8B4D FC mov ecx,dword ptr ss:[ebp-4] 00421A83 |. 5E pop esi 00421A84 |. 5B pop ebx 00421A85 |. E8 97AB0000 call ildasm.0042C621 00421A8A |. C9 leave 00421A8B \. C2 0C00 retn 0C 00421A2F 这段是用于默认情况的处理,如果遇到ildasm不认识的标识位,就直接把缓冲的内容全部输出到il文件当中。正是这里使得ildasm发生越界访问错误。简单爆破一下,跳过缓冲区内容的输出。 00421A4B 74 1B je short ildasm.00421A68 改成 00421A4B EB 1B jmp short ildasm.00421A68 现在ildasm可以正常产生Reflector.il文件了 再把有问题的那段il code简单修复一下 .class auto ansi sealed nested private _1 extends [mscorlib]System.Enum { .field public specialname rtspecialname int32 value__ .field public static literal specialname rtspecialname valuetype _3/_1 _Deleted /* ILLEGAL CONSTANT type:0x01, size:415192 bytes, blob: NULL */ .field public static literal specialname rtspecialname valuetype _3/_1 _Deleted /* ILLEGAL CONSTANT type:0x01, size:415192 bytes, blob: NULL */ .field public static literal specialname rtspecialname valuetype _3/_1 _Deleted /* ILLEGAL CONSTANT type:0x01, size:415192 bytes, blob: NULL */ .field public static literal specialname rtspecialname valuetype _3/_1 _Deleted /* ILLEGAL CONSTANT type:0x01, size:415192 bytes, blob: NULL */ .field public static literal specialname rtspecialname valuetype _3/_1 _Deleted /* ILLEGAL CONSTANT type:0x01, size:415192 bytes, blob: NULL */ .field public static literal specialname rtspecialname valuetype _3/_1 _Deleted /* ILLEGAL CONSTANT type:0x01, size:415192 bytes, blob: NULL */ .field public static literal specialname rtspecialname valuetype _3/_1 _Deleted /* ILLEGAL CONSTANT type:0x01, size:415192 bytes, blob: NULL */ .field public static literal specialname rtspecialname valuetype _3/_1 _Deleted /* ILLEGAL CONSTANT type:0x01, size:415192 bytes, blob: NULL */ .field public static literal specialname rtspecialname valuetype _3/_1 _Deleted /* ILLEGAL CONSTANT type:0x01, size:415192 bytes, blob: NULL */ .field public static literal specialname rtspecialname valuetype _3/_1 _Deleted /* ILLEGAL CONSTANT type:0x01, size:415192 bytes, blob: NULL */ .field public static literal specialname rtspecialname valuetype _3/_1 _Deleted /* ILLEGAL CONSTANT type:0x01, size:415192 bytes, blob: NULL */ .field public static literal specialname rtspecialname valuetype _3/_1 _Deleted /* ILLEGAL CONSTANT type:0x01, size:415192 bytes, blob: NULL */ } // end of class _1 修改为: .class auto ansi sealed nested private _1 extends [mscorlib]System.Enum { .field public specialname rtspecialname int32 value__ .field public static literal valuetype _3/_1 enum0 = int32 (0x00000000) .field public static literal valuetype _3/_1 enum1 = int32 (0x00000001) .field public static literal valuetype _3/_1 enum2 = int32 (0x00000002) .field public static literal valuetype _3/_1 enum3 = int32 (0x00000003) .field public static literal valuetype _3/_1 enum4 = int32 (0x00000004) .field public static literal valuetype _3/_1 enum5 = int32 (0x00000005) .field public static literal valuetype _3/_1 enum6 = int32 (0x00000006) .field public static literal valuetype _3/_1 enum7 = int32 (0x00000007) .field public static literal valuetype _3/_1 enum8 = int32 (0x00000008) .field public static literal valuetype _3/_1 enum9 = int32 (0x00000009) .field public static literal valuetype _3/_1 enum10 = int32 (0x0000000a) } // end of class _1 去掉.publickey,重新编译成功,哈哈,马上试试能不能运行。 【有这么简单吗?】 没有!FileLoadException无情的把答案告诉你。是不是我做错了哪一步?于是我决定先确定是哪个函数抛出的FileLoadException 于是我修改了il文件,插入两句: newobj instance void [mscorlib]System.InvalidOperationException::.ctor() throw 这两句IL相当于throw new InvalidOperationException(),把它尝试插入到代码中,如果程序抛InvalidOperationException,说明抛FileLoadException的语句在插入语句之后.经过一些分析调试,确认了FileLoadException是在这里抛出的: public Application(IWindowManager windowManager) { this._1 = base.GetType().Assembly; Type type1 = this._1.GetType("Reflector.ApplicationManager"); if (type1 == null) { AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(this._1); using (Stream stream1 = this._1.GetManifestResourceStream("Reflector.resources")) { ……省略 this._1 = Assembly.Load(_2._1()); //没有抛出exception type1 = this._1.GetType("Reflector.ApplicationManager", true); } } object[] objArray1 = new object[1] { windowManager } ; this._1 = (IServiceProvider) Activator.CreateInstance(type1, objArray1);//抛出FileLoadException } 也就是说Assembly是可以正常加载的,解密过程没有错(否则执行Assembly.Load的时候有BadImageFormatException抛出),FileLoadException的原因基本确定为,从resource中加载的Assembly引用了Reflector.exe,.NET的机制检查出Reflector.exe的强名字已经被窜改,所以拒绝运行。 于是再修改Reflector.il,在Reflector.Application..ctor中增加一段 IL_00c4: callvirt instance unsigned int8[] _4::_1() //开始添加我们的代码 stloc.s temp ldstr "C:\\out.dll" ldc.i4.2 ldc.i4.2 newobj instance void [mscorlib]System.IO.FileStream::.ctor(string, valuetype [mscorlib]System.IO.FileMode, valuetype [mscorlib]System.IO.FileAccess) stloc.s fs ldloc.s fs ldloc.s temp ldc.i4.0 ldloc.s temp ldlen conv.i4 callvirt instance void [mscorlib]System.IO.Stream::Write(unsigned int8[], int32, int32) ldloc.s fs callvirt instance void [mscorlib]System.IO.Stream::Close() ldloc.s temp //添加结束 IL_00c9: call class [mscorlib]System.Reflection.Assembly [mscorlib]System.Reflection.Assembly::Load(unsigned int8[]) 同时增加上面那段代码用到的两个本地变量 .locals init (class [mscorlib]System.Type V_0, class [mscorlib]System.IO.Stream V_1, int32 V_2, unsigned int8[] V_3, unsigned int8 V_4, unsigned int8 V_5, int32 V_6, class _3 V_7, class _4 V_8, object[] V_9, unsigned int8[] temp,//增加 class [mscorlib]System.IO.FileStream fs)//增加 编译后用Reflector看到的代码为 _3 _1 = new _3(new MemoryStream(buffer1)); _1.MoveNext(); _4 _2 = (_4) _1.Current; byte[] buffer2 = _2._1(); //修改过的代码,原来是this._1 = Assembly.Load(_2._1()); FileStream stream2 = new FileStream(@"C:\out.dll", FileMode.Create, FileAccess.Write); //添加的代码 stream2.Write(buffer2, 0, buffer2.Length); //添加的代码 stream2.Close();//添加的代码 this._1 = Assembly.Load(buffer2);//修改过的代码 type1 = this._1.GetType("Reflector.ApplicationManager", true); 运行,还是FileLoadException,不过这正是我们期待的(如果是其他Exception就说明我加入的代码有问题),赶快用Reflector打开c:\out.dll,呵呵,核心代码提取出来了(虽然里面还是加了混淆),外壳已经成功脱掉。 【总结】 Reflector外壳自身的保护方法: 1、 加入了反ildasm,利用ildasm的溢出漏洞来防止ildasm导出 2、 用了强名字,文件中有publickey信息,签名用的private key只有作者才有,文件被修改后签名信息必然会改变。 3、 混淆,并且构造大量不同类型、重名的类和变量,并用同一个命名空间,使得无法通过修改命名空间的字符串来进行区分两者,导出的源码也无法直接编译。 Reflector外壳对核心部分的保护方法: 1、 把核心部分加密后作为resource保存,并利用Assembly.Load将解密后的核心部分动态加载 2、 利用.NET的机制,当核心部分检测到外壳的强名字无效时,拒绝运行 破解方法: 修复ildasm的bug后,添加代码把解密后的内容另存到文件中,获得完整的核心assembly |
|
来自: goodwangLib > 《Reflector》