首页
社区
课程
招聘
[原创]cryxenet0.02unpackme完全分析
2009-11-5 14:02 8645

[原创]cryxenet0.02unpackme完全分析

2009-11-5 14:02
8645
【文章标题】: cryxenet0.02unpackme完全分析
【文章作者】: 峰回路转
【作者邮箱】: killbug2004@gmail.com
【软件名称】: cryxenet0.02unpackme
【下载地址】: http://www.crackmes.de/users/tfb/cryxenet_0.02_unpackme/
【操作平台】: .net
【作者声明】: 只是感兴趣,没有其他目的。失误之处敬请诸位大侠赐教! 如果我的代码你看着很眼熟,那绝对不是偶然^V^
--------------------------------------------------------------------------------
【详细过程】
  这个unpackme是作者的.net保护壳保护的,所以继续阅读之前需要对.net原理和.net PE文件结构有一定了解。程序由三个
  文件组成,只有unpackme.exe是可以运行的。
  一:unpackme.exe的分析
  PE工具察看unpackme.exe为.net PE,用.net Reflector载入unpackme进行分析。程序入口函数
  main无法以C#等高级语言查看,只能查看il代码,方法开始代码如下:
.method public static void main() cil managed
  {
      .custom instance void [mscorlib]System.STAThreadAttribute::.ctor()
      .entrypoint
      .maxstack 4
      .locals init (
          [0] int64 num,
          [1] class [mscorlib]System.IO.FileInfo info,
          [2] class [mscorlib]System.Reflection.Assembly 'assembly',
          [3] object[] objArray,
          [4] string[] strArray,
          [5] int64 num2,
          [6] int64 num3,
          [7] int64 num4,
          [8] int64 num5,
          [9] int64 num6,
          [10] class [mscorlib]System.IO.FileStream stream,
          [11] native int ptr,
          [12] uint8[] buffer,
          [13] class Project1.Program/obfuscation8 obfuscation,
          [14] uint8[] buffer2,
          [15] int64 num7,
          [16] int64 num8,
          [17] int64 num9)
      L_0000: nop 
      /*
      L_0001: ldc.i4.s 0x63  
      L_0003: conv.i8    
      L_0004: stloc.s num2    
      L_0006: ldc.i4.s 0x4b  
      L_0008: conv.i8    
      L_0009: stloc.s num3     
      L_000b: ldc.i4 0x38d  
      L_0010: conv.i8    
      L_0011: stloc.s num4     
      L_0013: call int64 Project1.Program::IsDebuggerPresent()
      L_0018: pop      
      L_0019: ldloc.s num3  
      L_001b: br.s L_003d    // 无条件跳转,所以reflector无法反编译为高级语言
      L_001d: add.ovf 
      L_001e: stloc.s num2
      L_0020: ldloc.s num4
      L_0022: ldc.i4.7 
      L_0023: conv.i8 
      L_0024: sub.ovf 
      L_0025: stloc.s num4
      L_0027: ldloc.s num4
      L_0029: ldloc.s num2
      L_002b: add.ovf 
      L_002c: stloc.s num3
      L_002e: ldloc.s num4
      L_0030: ldc.i4.7 
      L_0031: conv.i8 
      L_0032: add.ovf 
      L_0033: stloc.s num4
      L_0035: call int64 Project1.Program::IsDebuggerPresent()
      L_003a: pop 
      L_003b: ldloc.s num3
      L_003d: ldloc.s num4  
      L_003f: add.ovf    
      L_0040: stloc.s num2  
      L_0042: ldloc.s num4  
      L_0044: ldc.i4.7    
      L_0045: conv.i8    
      L_0046: sub.ovf    
      L_0047: stloc.s num4  
      L_0049: ldloc.s num4  
      L_004b: ldloc.s num2  
      L_004d: add.ovf    
      L_004e: stloc.s num3  
      L_0050: ldloc.s num4  
      L_0052: ldc.i4.7    
      L_0053: conv.i8    
      L_0054: add.ovf    
      L_0055: stloc.s num4  
      L_0057: call int64 Project1.Program::IsDebuggerPresent()
      L_005c: pop      
      L_005d: ldloc.s num3  
      L_005f: ldloc.s num4  
      L_0061: add.ovf    
      L_0062: stloc.s num2  
      L_0064: ldloc.s num4  
      L_0066: ldc.i4.7    
      L_0067: conv.i8    
      L_0068: sub.ovf    
      L_0069: stloc.s num4  
      L_006b: ldloc.s num4  
      L_006d: ldloc.s num2  
      L_006f: add.ovf    
      L_0070: stloc.s num3  
      L_0072: ldloc.s num4  
      L_0074: ldc.i4.7    
      L_0075: conv.i8    
      L_0076: add.ovf    
      L_0077: stloc.s num4  
      L_0079: ldloc.s num3  
      L_007b: ldloc.s num4  
      L_007d: add.ovf    
      L_007e: stloc.s num2  
      L_0080: ldloc.s num4  
      L_0082: ldc.i4.7    
      L_0083: conv.i8    
      L_0084: sub.ovf    
      L_0085: stloc.s num4  
      L_0087: ldloc.s num4  
      L_0089: ldloc.s num2  
      L_008b: add.ovf    
      L_008c: stloc.s num3  
      L_008e: ldloc.s num4  
      L_0090: ldc.i4.7    
      L_0091: conv.i8    
      L_0092: add.ovf    
      L_0093: stloc.s num4  
      */
      //创建Fileinfo对象,关联"native.dll"
      L_0095: ldstr "native.dll"  //将"native.dll"压入堆栈
      L_009a: newobj instance void [mscorlib]System.IO.FileInfo::.ctor(string)
          //创建一个FileInfo对象
      L_009f: stloc.1    //将堆栈顶的数据保存到索引为1的局部变量中
          //即将FileInfo对象保存到info中

对il代码进行简单分析可以知道,L_001b: 标签处的无条件跳转指令导致reflector无法反编译成高级语言,这里和汇编中的无条件
  跳转的花指令类似。这个函数里面有大量对num4,num3等变量的操作,代码都是重复,且这些变量没有参与别的操作,是垃圾代码。
  用ildasm将unpackme反编译成il,将垃圾代码注释掉,再用ilasm将修改后的il代码编译成exe,用reflector载入生成的程序,现在
  代码可以反编译成C#代码:
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Reflection;
namespace Project1
{
    class Program
    {
        private delegate int obfuscation8();
        public static void Main()
        {
            long num6;
            FileStream stream = new FileInfo("native.dll").OpenRead();
            long length = stream.Length;
            byte[] array = new byte[((int)length) + 1];
            stream.Read(array, 0, (int)length);
            stream.Close();
            long num7 = length;
            for (num6 = 0L; num6 <= num7; num6 += 1L)
            {
                array[(int)num6] = (byte)(array[(int)num6] ^ 0x37);
            }
            IntPtr destination = new IntPtr();
            destination = Marshal.AllocCoTaskMem((int)length);
            Marshal.Copy(array, 0, destination, (int)length);
            obfuscation8 delegateForFunctionPointer = (obfuscation8)Marshal.GetDelegateForFunctionPointer(destination, typeof(obfuscation8));
            File.WriteAllBytes("dump.bin", array);
            long num = new long();
            num = delegateForFunctionPointer();
            System.Console.WriteLine(" num = " + num);


            stream = new FileInfo("cryxed.dll").OpenRead();
            length = stream.Length;
            byte[] buffer2 = new byte[((int)length) + 1];
            stream.Read(buffer2, 0, (int)length);
            stream.Close();
            long num8 = length;
            for (num6 = 0L; num6 <= num8; num6 += 1L)
            {
                buffer2[(int)num6] = (byte)(buffer2[(int)num6] ^ 0x37);
            }
            File.WriteAllBytes("decrypted_assembly.exe", buffer2);
            Assembly assembly = Assembly.Load(buffer2);
            object[] parameters = new object[1];
            string[] strArray = new string[] { "" };
            parameters[0] = strArray;
            assembly.EntryPoint.Invoke(null, parameters);
        }


    }
}

 
  代码功能很明了:
  1、读取"native.dll"中的数据,异或0x37解密,然后用Marshal提供的方法以函数的形式直接运行解密的数据,可以猜想这个代码是需要自己重定位的。
  2、读入"cryxed.dll"文件,异或0x37解密,以Assembly的形式执行,可以判断解密后的数据为一个assembly。
  由于unpackme的功能简单,可以完全反编译成C#等高级代码,上面代码中的两行注释的代码是对反编译后C#代码的修改,将解密后的native.dll和cryxed.dll
  分别写入dump.bin和decrypted_assembly.exe,都是简单的异或加密,当然可以用工具或代码自己处理这两个原始文件,接下来就是对这两个解密后的文件分析。
  

二、dump.bin 壳loader的分析
    知道dump.bin是可以直接在内存中运行的代码,类似shellcode和普通壳的loader,所以用IDA以二进制形式载入,选择32位模式在文件开始处进行反汇编分析。
    代码中有一些花指令:
call    sub_10AC5
    
    seg000:00000AC5 JunkFun         proc near               ; CODE XREF: sub_10151+Ap
    seg000:00000AC5                                         ; sub_10151+22p
    seg000:00000AC5                 inc     dword ptr [esp]
    seg000:00000AC8                 retn
    seg000:00000AC8
    seg000:00000AC8 JunkFun         endp

  由于这个垃圾函数的调用比较多,所以利用IDC脚本处理一下便于分析:
 #include <idc.idc>
  
    static main(void)
    {
    auto EA,Counter;
    auto Address;
    EA = MinEA() + 3;
    Counter = 0;
    while(EA != BADADDR)
    {
      Address = FindBinary(EA, SEARCH_DOWN, "00 00 FF");
      if(Address == BADADDR)
        break;
      if((Byte(Address - 3) == 0xE8) && (Byte(Address + 2) == 0xFF))
      {
        PatchDword(Address - 3,0x90909090);
        PatchWord(Address + 1,0x9090);
        Counter++;
        EA = Address + 1;
      }
      EA ++;
    }
    Message("There are %d junk code blocks.\n",Counter);
    Message("done.\n");
    
    }

去花后的代码干净很多,代码不是很长,所以从头到尾分析,这是一个典型的loader,具体分析参考我的idb文件,执行流程如下:
    1、重定位代码以便正确访问变量
    2、利用PEB获取kernel32的基址,然后利用PE文件结构的导出表和函数名hash,得到LoadLibrayA、GetProcAddress函数地址
       再利用这两个函数获取代码要用到的一些API地址,这里利用模块mscorjit.dll的getJit得到.net中非常重要的函数compileMethod地址
    3、获取Strongnamesignatureverificationfromimage函数,通过inline hook挂钩该函数,在函数开始处跳转到代码Code_1函数内执行
  
    我们知道.net镜像启动后载入内存,系统通常都是调用Strongnamesignatureverificationfromimage对其进行强命名校验,loader挂钩该函数
    可以实现对后面载入的assembly执行前进行处理。
  
    接下来分析挂钩Strongnamesignatureverificationfromimage函数代码的功能:
    1、函数利用Strongnamesignatureverificationfromimage的参数获取待校验镜像的基址
    2、获取镜像基址后,利用PE文件结构获取镜像FileAlignment值
    3、利用0x00002050这个特殊的值,搜索要解密assembly的MethodDef方法描述表
    4、解密方法表的method header(分为tiny header和fat hader)
    5、挂钩compileMethod函数,将该函数的调用指向到Code_2
    6、解除对Strongnamesignatureverificationfromimage的hook,调用系统的Strongnamesignatureverificationfromimage继续运行
    
    我们看到对Strongnamesignatureverificationfromimage挂钩函数的功能是对后面执行的assembly的方法描述表的表头进行解密,这样运行时系统就可以
    获取要执行方法的正确信息,然后挂钩compileMethod实现运行时解密方法体的il代码,解密il是挂钩compileMethod的函数Code_2的主要功能,这里就不再讨论,
    主要注意一下tiny和fat的il解密有一点区别。
  
    现在已经完成这个unpackme的最关键部分的分析,作者的壳对pe文件进行处理,主要是加密文件的MethodDef方法描述表,处理后的程序,利用jit hook
    挂钩compileMethod实现动态解密il代码,这样就无法利用reflector等工具静态分析,上面生成的decrypted_assembly.exe文件用reflector察看,所有
    的方法都是无法处理的,应为方法表和il都是加密的。
  

三、脱壳机的编写
    有了对loader的分析可以知道decrypted_assembly.exe要正确运行的话,只需要解密方法表就可以,不再依赖别的模块。
    这里我就提取loader中简单循环移位的代码静态实现对cryxed.dll解密,脱壳的关键是解析.net PE文件定位到PE文件的MethodDef,加密算法很简单
    具体代码可能有点乱,可以参考附件中的DecryptAssembly程序的具体代码。对于动态脱壳机的编写也是可以实现的,注入程序inline hook compileMethod函数
    可以得到解密的il代码,重构PE文件的MethodDef是可以实现的,这里就不再讨论了。

四、注册机的编写
     有了脱壳机对cryxed.dll脱壳,生成的文件后缀名改为exe,可以直接运行,程序是vb.net的编写,用reflector直接查看源码,直接程序中的解密函数
     粘贴到自己的程序中就OK,调用系统加密模块,rc2加密,base64编码
public string TripleDESDecode(string value, string key)
          {
              TripleDESCryptoServiceProvider provider = new TripleDESCryptoServiceProvider();
              provider.IV = new byte[8];
              provider.Key = new PasswordDeriveBytes(key, new byte[0]).CryptDeriveKey("RC2", "MD5", 0x80, new byte[8]);
              byte[] buffer = Convert.FromBase64String(value);
              MemoryStream stream2 = new MemoryStream(value.Length);
              CryptoStream stream = new CryptoStream(stream2, provider.CreateDecryptor(), CryptoStreamMode.Write);
              stream.Write(buffer, 0, buffer.Length);
              stream.FlushFinalBlock();
              byte[] buffer2 = new byte[((int)(stream2.Length - 1L)) + 1];
              stream2.Position = 0L;
              stream2.Read(buffer2, 0, (int)stream2.Length);
              stream.Close();
              return Encoding.UTF8.GetString(buffer2);
          }

  注册机的代码参考附件的cryxenet002_keygen,至此基本完成的这个unpackme的全部分析,如果要很好的去理解,需要了解.net运行时的原理和.net PE文件的结构,
   其实和传统的壳有些方面是相通的。
    
  
      
  
--------------------------------------------------------------------------------
【经验总结】
  
  这个程序是jit hook实现的很简陋的.net保护壳,同当前流行的商业保护壳是有很大的差距,字符串加密、名称和流程混淆、复杂的分
  块加密、anti-debug、anti-dump分析起来是很伤身体的,不过透过这个unpackme也可以小窥一下当前.net保护的壳基本原
  理,大部分都是在compileMethod hook上下功夫。如果要写保护壳的话,还可以在许多许多方向上进行扩充的,传统的保
  护壳是有许多可借鉴的地方。.net方面的资料可以参考Daniel Pistell老大的网站www.ntcore.com
  
--------------------------------------------------------------------------------
【版权声明】: 本文原创于看雪技术论坛, 转载请注明作者并保持文章的完整, 谢谢!

[培训]内核驱动高级班,冲击BAT一流互联网大厂工 作,每周日13:00-18:00直播授课

上传的附件:
收藏
点赞7
打赏
分享
最新回复 (5)
雪    币: 431
活跃值: (1875)
能力值: ( LV17,RANK:1820 )
在线值:
发帖
回帖
粉丝
riusksk 41 2009-11-5 16:18
2
0
support!
.net+C#文盲飘过!!!
雪    币: 423
活跃值: (11)
能力值: ( LV9,RANK:230 )
在线值:
发帖
回帖
粉丝
nba2005 5 2009-11-5 19:09
3
0
不太懂。。。

学习中
雪    币: 291
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
leking 2009-11-5 22:51
4
0
果然精彩,顶!
雪    币: 6149
活跃值: (3077)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
chinarenjf 2009-11-28 13:16
5
0
支持作者,多出精贴.
雪    币: 6
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
AsmBrat 2009-12-12 15:18
6
0
csdn的大牛 支持一下
游客
登录 | 注册 方可回帖
返回