前面我的几篇文章提到Reflector在反混淆技术中的应用,都是拿Reflector对其他软件开刀。Reflector的作者,Lutz Roeder,深知他写出来的Reflector的强大威力,那么Lutz又是怎样保护自己的软件作品不被Reflector分析个透彻哪?
带着这个疑问,我用Reflector打开了Reflector自身,里面除了一堆Interface定义以外(估计是方便其他coder做add-ins),其他功能在哪里实现,根本没有发现蛛丝马迹。疑点还是有的,就是那五个被简单混淆过的类,不过里面也没有主要功能的代码,只是一些转换,估计是用来做加密的。
最后找到这
<<Reflector.Application>>
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"))//秘密就是在这里
{
int num1 = (int) stream1.Length;
byte[] buffer1 = new byte[num1];
stream1.Read(buffer1, 0, num1);//读入字节流
byte num2 = 0x89;
byte num3 = 0x16;
for (int num4 = 0; num4 < num1; num4++)
{
buffer1[num4] = (byte) (buffer1[num4] ^ num2);//解密变换
num2 = (byte) (num2 + num3);
}
_3 _1 = new _3(new MemoryStream(buffer1));//下面几句是核心部分
_1.MoveNext();
_4 _2 = (_4) _1.Current;
this._1 = Assembly.Load(_2._1());//得到解密结果,加载Assembly到this._1
type1 = this._1.GetType("Reflector.ApplicationManager", true);
}
}
object[] objArray1 = new object[1] { windowManager } ;
this._1 = (IServiceProvider) Activator.CreateInstance(type1, objArray1);
}
public void Run()
{
if (this._1 != null)
{
this._1.GetType().GetMethod("Run").Invoke(this._1, null);//直接调用加载的Assembly的Run启动核心部分程序
}
}
[STAThread]
private static void _1()//嘿嘿,这个就是Main了,居然被改成这个名字
{
if (Application._1())
{
using (Application application1 = new Application(null))
{
application1.Run();
return;
}
}
MessageBox.Show("You cannot launch this application from a network share. Please try running it from a local directory.", typeof(Application).Assembly.Location, MessageBoxButtons.OK, MessageBoxIcon.Hand);
}
秘密就是在这里,原来把一个assembly藏在Reflector.resources文件中了。这里的工作就类似Win32 PE的加密壳技术,把代码段载入内存并解密,然后转移控制权。而_3, _4就是解密的部分,包含了一大堆眼花缭乱的变换,类当中还有子类,看来作者是以此阻止别人对解密原理进行静态分析。
和Win32PE不同的是,似乎动态调试技术在.NET中并没有发展到Win32 PE那么成熟,用过vs.net自带的调试器,如果加载的程序没有调试信息,就只能显示出机器码,甚至连按照机器码单步执行都不行。这层障碍使得解密者无法采用类似Win32 PE的停在OEP然后dump进程的办法破解。
解密的思路,可以从Reflector中把解密类导出,然后手动编写相关的输入输出接口。这种方法依赖于reflector代码的正确性(reflector输出的c#代码并不能100%编译成功)。更通用的办法类似Win32PE打补丁的办法,插入一个method到代码中,在解密完成时把Assembly的内容保存在硬盘上。有空我会尝试这两种方法的可行性。
带着这个疑问,我用Reflector打开了Reflector自身,里面除了一堆Interface定义以外(估计是方便其他coder做add-ins),其他功能在哪里实现,根本没有发现蛛丝马迹。疑点还是有的,就是那五个被简单混淆过的类,不过里面也没有主要功能的代码,只是一些转换,估计是用来做加密的。
最后找到这
<<Reflector.Application>>
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"))//秘密就是在这里
{
int num1 = (int) stream1.Length;
byte[] buffer1 = new byte[num1];
stream1.Read(buffer1, 0, num1);//读入字节流
byte num2 = 0x89;
byte num3 = 0x16;
for (int num4 = 0; num4 < num1; num4++)
{
buffer1[num4] = (byte) (buffer1[num4] ^ num2);//解密变换
num2 = (byte) (num2 + num3);
}
_3 _1 = new _3(new MemoryStream(buffer1));//下面几句是核心部分
_1.MoveNext();
_4 _2 = (_4) _1.Current;
this._1 = Assembly.Load(_2._1());//得到解密结果,加载Assembly到this._1
type1 = this._1.GetType("Reflector.ApplicationManager", true);
}
}
object[] objArray1 = new object[1] { windowManager } ;
this._1 = (IServiceProvider) Activator.CreateInstance(type1, objArray1);
}
public void Run()
{
if (this._1 != null)
{
this._1.GetType().GetMethod("Run").Invoke(this._1, null);//直接调用加载的Assembly的Run启动核心部分程序
}
}
[STAThread]
private static void _1()//嘿嘿,这个就是Main了,居然被改成这个名字
{
if (Application._1())
{
using (Application application1 = new Application(null))
{
application1.Run();
return;
}
}
MessageBox.Show("You cannot launch this application from a network share. Please try running it from a local directory.", typeof(Application).Assembly.Location, MessageBoxButtons.OK, MessageBoxIcon.Hand);
}
秘密就是在这里,原来把一个assembly藏在Reflector.resources文件中了。这里的工作就类似Win32 PE的加密壳技术,把代码段载入内存并解密,然后转移控制权。而_3, _4就是解密的部分,包含了一大堆眼花缭乱的变换,类当中还有子类,看来作者是以此阻止别人对解密原理进行静态分析。
和Win32PE不同的是,似乎动态调试技术在.NET中并没有发展到Win32 PE那么成熟,用过vs.net自带的调试器,如果加载的程序没有调试信息,就只能显示出机器码,甚至连按照机器码单步执行都不行。这层障碍使得解密者无法采用类似Win32 PE的停在OEP然后dump进程的办法破解。
解密的思路,可以从Reflector中把解密类导出,然后手动编写相关的输入输出接口。这种方法依赖于reflector代码的正确性(reflector输出的c#代码并不能100%编译成功)。更通用的办法类似Win32PE打补丁的办法,插入一个method到代码中,在解密完成时把Assembly的内容保存在硬盘上。有空我会尝试这两种方法的可行性。
【标题】研读Reflector的保护原理心得(二)
【作者】henryouly
【目的】研究.NET的软件保护技术
【工具】Reflector, ildasm, OD, ultraedit
【研究软件】.NET Reflector 4.1.80.0
【软件性质】国外免费软件
【下载】http://www.aisto.com/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
【作者】henryouly
【目的】研究.NET的软件保护技术
【工具】Reflector, ildasm, OD, ultraedit
【研究软件】.NET Reflector 4.1.80.0
【软件性质】国外免费软件
【下载】http://www.aisto.com/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