本帖最后由 whshizy 于 2016-1-31 10:56 编辑
在我上一篇文章http://www.52pojie.cn/thread-459242-1-1.html中,我们使用DNSPY自身的一些功能来恢复被狗加密过的方法体,经过这几天的再研究,发现上次那种方法有两个缺陷: 1、加密后的方法体基本上都是如下面的的结构 [C#] 纯文本查看 复制代码 private static void Main(string[] args)
{
try
{
DynamicMethod dynamicMethod = Class0.smethod_7(null, MethodBase.GetCurrentMethod(), true);
if (dynamicMethod != null)
{
dynamicMethod.Invoke(null, new object[]
{
args
});
}
Class0.smethod_7(null, MethodBase.GetCurrentMethod(), false);
}
catch (Exception arg_49_0)
{
Exception expr_4E = arg_49_0.InnerException;
if (expr_4E == null)
{
throw;
}
throw expr_4E;
}
} 方法体长度一般在85-100 bytes之间,如果原方法体长度小于这个加密后的方法体长度,采用复制的方法没有问题,如果长度大于这个范围,则会产生不可预料的问题,以前看过一篇文章,采用CFF工具在原EXE文件中增加一个区段,将取得的方法体附加进这个段,再修改元数据的RAV重新指向这个区段来解决,这样虽然能解决长度问题,但需要修改的地方太多,如果加密的方法体太多,工作量较大。本文采用另一种方法,可以较快、准确、较快的还原方法体,后面会有详述。 1、如果原有方法体中有异常处理模块(TRY CATCH等)处理模块,采用上一次我发的方法是无法还原的,这也是我们今天要解决的问题。 老办法,我们写一个测试程序,里面有异常处理程序。 [C#] 纯文本查看 复制代码 using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using myFirstDll;
namespace rundll
{
class Program
{
static void Main(string[] args)
{
int i = 123;
try
{
myFirstDll.Show a = new myFirstDll.Show();
string test = "这是SUPERDOG加密狗的测试程序";
test = test + "主要用于测试字符串加密";
Console.WriteLine(test);
Console.WriteLine(a.Messages() + i.ToString());
Console.WriteLine(a.myadd(2, 3).ToString());
string testasc = "this is a test programe";
Console.WriteLine(testasc);
}
catch
{
i = 1000;
Console.WriteLine("出错了");
}
finally
{
Console.WriteLine("最后执行测试");
}
}
}
} 我们使用DNSPY打开编译好的EXE程序,选中MAIN方法,选择Edit Method Body,查看方法体异常结构,这个数据我们记录下来,后面要用到。 这里简单普及一下.NET异常处理数据结构。(有兴趣的朋友可以参考一下:http://www.ecma-international.org/publications/standards/Ecma-335.htm ECMA spec partiton II 25.4.6),上图表示的异常结构在PE文件里面是按如下格式存储的:标志/Try结构偏移/Try结构长度/Handler偏移/Handler长度/Handler类型meta data token/filter-base exception hander,这个结构分为small和fat两种类型,这两种类型结构一样的,只是存储长度不同而已,small异常结构适用于try与handle代码块长度都小于256字节并且偏移量小于65536的方法,反之则使用fat异常处理结构。我们来看一下上图第一行异常处理是如何存储的:00/0400/5f/6300/16/01000001,这是一个small异常处理模块。 编译,加密,使用de4dot去混淆,这些步骤在上一篇文章已经说过了,这里就不再啰嗦。以下开始我们的代码还原之旅。 1、打开DNSPY调试去混淆后的加密程序rundll-cleaned.exe,在glass0.smethod_8的这223行下断点 2、在内存中取得原方法体代码为:(如何取得,请参照我的上一篇文章,这里也不再啰嗦) 001F7B0A00733B0100060B725307007028730000060C0872850700702873000006281F00000A0C08282C01000A00076F39010006120028AB00000A281F00000A282C01000A000718196F3A0100061304120428AB00000A282C01000A0072A707007028730000060D09282C01000A0000DE16260020E80300000A72D9070070282C01000A0000DE0000DE0E0072E1070070282C01000A0000DC002A 3、使用“IL字节码解码工具”将取得的二进制码翻译成IL码: 大家可能看到这解码后的IL码有一些乱码,这是由于SUPERDOG加密狗加密常量字符串造成,我们后面将研究如何处理这些加密字符串。 4、打开ILDASM,将rundll-cleaned反编译成IL码,文件名我们暂定为crack.il。 5、使用ULTRAEDIT打开crack.il,找到main方法体如下: [C#] 纯文本查看 复制代码 .method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
// 代码大小 89 (0x59)
.maxstack 4
.locals init (class [mscorlib]System.Object[] V_0,
class [mscorlib]System.Reflection.Emit.DynamicMethod V_1)
.try
{
IL_0000: ldnull
IL_0001: call class [mscorlib]System.Reflection.MethodBase [mscorlib]System.Reflection.MethodBase::GetCurrentMethod()
IL_0006: ldc.i4.1
IL_0007: call class [mscorlib]System.Reflection.Emit.DynamicMethod Class0::smethod_7(object,
class [mscorlib]System.Reflection.MethodBase,
bool)
IL_000c: stloc.1
IL_000d: ldloc.1
IL_000e: ldnull
IL_000f: ceq
IL_0011: brfalse.s IL_0016
IL_0013: ldnull
IL_0014: br.s IL_0039
IL_0016: ldc.i4 0x1
IL_001b: newarr [mscorlib]System.Object
IL_0020: stloc.0
IL_0021: ldloc.0
IL_0022: ldc.i4 0x0
IL_0027: ldarg args
IL_002b: box string[]
IL_0030: stelem.ref
IL_0031: ldloc.1
IL_0032: ldnull
IL_0033: ldloc.0
IL_0034: callvirt instance object [mscorlib]System.Reflection.MethodBase::Invoke(object,
object[])
IL_0039: ldnull
IL_003a: call class [mscorlib]System.Reflection.MethodBase [mscorlib]System.Reflection.MethodBase::GetCurrentMethod()
IL_003f: ldc.i4.0
IL_0040: call class [mscorlib]System.Reflection.Emit.DynamicMethod Class0::smethod_7(object,
class [mscorlib]System.Reflection.MethodBase,
bool)
IL_0045: pop
IL_0046: pop
IL_0047: leave.s IL_0058
} // end .try
catch [mscorlib]System.Exception
{
IL_0049: callvirt instance class [mscorlib]System.Exception [mscorlib]System.Exception::get_InnerException()
IL_004e: dup
IL_004f: ldnull
IL_0050: ceq
IL_0052: brfalse.s IL_0057
IL_0054: pop
IL_0055: rethrow
IL_0057: throw
} // end handler
IL_0058: ret
} // end of method Program::Main 将方法体替换成上面我们使用工具解码出来的IL码,现在MAIN方法体如下: [C#] 纯文本查看 复制代码 .method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
// 代码大小 89 (0x59)
.maxstack 4
.locals init (class [mscorlib]System.Object[] V_0,
class [mscorlib]System.Reflection.Emit.DynamicMethod V_1)
IL_0000: nop
IL_0001: ldc.i4.s 0x7B
IL_0003: stloc.0
IL_0004: nop
IL_0005: newobj instance void myFirstDll.Show::.ctor()
IL_000A: stloc.1
IL_000B: ldstr "䂪哖㬎垸몤婛ᰲ復间πւꍹ阝᮳럡떗䰄隝逩"
IL_0010: call string GClass3/GClass6::smethod_4(string )
IL_0015: stloc.2
IL_0016: ldloc.2
IL_0017: ldstr "궋ʎ♻업෭洇饑뽴偠黸ꏮﲣ䖽瞒᧩ẽ"
IL_001C: call string GClass3/GClass6::smethod_4(string )
IL_0021: call string [mscorlib]System.String::Concat(string , string )
IL_0026: stloc.2
IL_0027: ldloc.2
IL_0028: call void [mscorlib]System.Console::WriteLine(string )
IL_002D: nop
IL_002E: ldloc.1
IL_002F: callvirt instance string myFirstDll.Show::Messages()
IL_0034: ldloca.s 0x0
IL_0036: call instance string [mscorlib]System.Int32::ToString()
IL_003B: call string [mscorlib]System.String::Concat(string , string )
IL_0040: call void [mscorlib]System.Console::WriteLine(string )
IL_0045: nop
IL_0046: ldloc.1
IL_0047: ldc.i4.2
IL_0048: ldc.i4.3
IL_0049: callvirt instance int32 myFirstDll.Show::myadd(int32 , int32 )
IL_004E: stloc.s 0x4
IL_0050: ldloca.s 0x4
IL_0052: call instance string [mscorlib]System.Int32::ToString()
IL_0057: call void [mscorlib]System.Console::WriteLine(string )
IL_005C: nop
IL_005D: ldstr "ʡ麅覌륉஠ᬖⵇ㜸k⁋老ꅵ摣ρ憾嚳滺쿪ᷔ諚鋙ᬫ"
IL_0062: call string GClass3/GClass6::smethod_4(string )
IL_0067: stloc.3
IL_0068: ldloc.3
IL_0069: call void [mscorlib]System.Console::WriteLine(string )
IL_006E: nop
IL_006F: nop
IL_0070: leave.s IL_0088
IL_0072: pop
IL_0073: nop
IL_0074: ldc.i4 0x3E8
IL_0079: stloc.0
IL_007A: ldstr "出错了"
IL_007F: call void [mscorlib]System.Console::WriteLine(string )
IL_0084: nop
IL_0085: nop
IL_0086: leave.s IL_0088
IL_0088: nop
IL_0089: leave.s IL_0099
IL_008B: nop
IL_008C: ldstr "最后执行测试"
IL_0091: call void [mscorlib]System.Console::WriteLine(string )
IL_0096: nop
IL_0097: nop
IL_0098: endfinally
IL_0099: nop
IL_009A: ret
} // end of method Program::Main 再在在glass0.smethod_6的这里下断开,找到局部变量定义 看NUM2值为5,说明局部变量有5个,执行5次,得到局部变量类型如下:[0] int 32,[1] {myFirstDll.Show},[2] string ,[3]string,[4] int32 修改main方法体如下代码 修改main方法体如下代码 .locals init (class [mscorlib]System.Object[] V_0, class [mscorlib]System.Reflection.Emit.DynamicMethod V_1) 为 .locals init ([0] int32 V_0, [1] class myFirstDll.Show V_1, [2] string V_2, [3] string V_3, [4] int32 V_4) 6、进入命令行,使用ILASM重新编译CRACK.IL文件C:\Windows\Microsoft.NET\Framework\v4.0.30319\ilasm crack.il 这时会生成CRACK.EXE文件 7、现在还没有完,我们还得处理异常模块,在glass0.smethod_8里下断点 这时ARRAY5数组内的数据就是异常处理数据,这里解释一下这个数组的含义,[0]0x01表示这是一个small异常处理结构,[1]0x1c,表示数组长度,后面[4]-[15]为异常结构处理体00/0400/6e/7200/16/01000001,前面我们分析过,前两位为00表示这是一个有异常处理类型的结构,类型TOKEN为后4位0x01000001,我们打开METADATA看看这是什么类型,原来是System.Object,后面[16]-[27]是另一个异常处理结构finally,和前面分析相同,这里不多说,大家可以自行分析下。 8、下面我们来为方法体添加异常处理模块,用DNSPY打开CRACK.EXE选中MAIN方法,选择Edit Method Body-Exception Handlers,我们添加两个异常处理,如下图 第一条是根据前身得到的异常处理模块数据00/0400/6e/7200/16/01000001得来的,为什么是这样,前面已经解释过,第二条根据后面的数据得来,点OK,我们看看这时MAIN方法是什么样子。 [C#] 纯文本查看 复制代码 // rundll.Program
// Token: 0x0600013C RID: 316 RVA: 0x000093D4 File Offset: 0x000075D4
private static void Main(string[] args)
{
int num = 123;
try
{
Show show = new Show();
string text = GClass3.GClass6.smethod_4("䂪哖㬎垸몤婛ᰲ復间πւꍹ阝᮳럡떗䰄隝逩");
text += GClass3.GClass6.smethod_4("궋ʎ♻업෭洇饑뽴偠黸ꏮﲣ䖽瞒᧩ẽ");
Console.WriteLine(text);
Console.WriteLine(show.Messages() + num.ToString());
Console.WriteLine(show.myadd(2, 3).ToString());
string value = GClass3.GClass6.smethod_4("ʡ麅覌륉஠ᬖⵇ㜸k⁋老ꅵ摣ρ憾嚳滺쿪ᷔ諚鋙ᬫ");
Console.WriteLine(value);
}
catch
{
Console.WriteLine("出错了");
}
finally
{
Console.WriteLine("最后执行测试");
}
} 和前面对比,发现程序除了字符串常量不正确外,全部已经正确了,这时找到被加密的字符串常量就可以收工了,这个很简单,程序提示已经非常明显了,在Gclass3.Gclass6.method_4方法,最后一条语句下断,循环执行,就能看到所有解密字符串了,当然有兴趣的朋友可以研究一下狗的字符串加密原理,自己写一个解密程序,也行,看兴趣,我这里就不啰嗦了,找到解密后的字符串,替换一下,原来的代码,至此,去狗操作全部完成,这种方法可以完美去除狗。 解密完成后,可以进一步优化程序,将狗壳附加的代码全部删除,这样就和原来的代码一模一样了,大家可以去尝试下。 |