点击上方蓝字 江湖评谈设为关注
区别
一般的简单点来说,托管就是C#语言写的代码,非托管就是C++语言写的代码。离了C++,C#完全无法运行。本质上来说,C#和C++是不分家的。那么更确切一点以最常用的win平台为例,C#语言生成的PE文件托管DLL和C++非托管DLL,区别在于有无.NET头结构体IMAGE_COR20_HEADER。
托管加固
一些加密软件,为了对托管DLL的逆向难度进行增强。来回的在托管和非托管中切换,理论上来说托管的函数都遵循CLR/JIT的规则。一个托管函数的编译过程本身就是极其复杂的工程,即使是一个最简单的托管函数,比如以下简单的C#代码:
static void ABC()
{
Console.WriteLine("Call ABC");
}
ABC函数需要经过Roslyn构建MSIL,经过CLR加载,经过IR变形,经过JIT优化以及生成机器码。这个中间的过程,辗转了整个runtime的几百万行代码的亲密接触,经历了二进制,汇编,C/C++,以及最上层的C#的各种骚操,形成了最后的那简单的几行汇编代码,存放在内存里,用即时编译器运行出来。
中间的过程是难点,最后的结果更是难点。即时编译的结果是存放在内存里的,内存里的东西运行的时候可以更改,但是程序结束了,它又恢复了原样,且地址亦不固定。所以这里需要从非托管作为切入口对它进行hook以及一些其它工作。
以上面的ABC函数为例,它里面只是调用了System.Console.dll库里面的函数Console.WriteLine打印出字符串Call ABC。这个看似简单的过程,可以通过加密软件把这个ABC函数的MSIL进行重新构建。它原有的MSIL如下:
IL_0000 00 nop
IL_0001 28 06 00 00 06 call 0x6000006
IL_0006 00 nop
IL_0007 28 07 00 00 06 call 0x6000007
IL_000c 00 nop
IL_000d 72 25 00 00 70 ldstr 0x70000025
IL_0012 28 10 00 00 0a call 0xA000010
IL_0017 00 nop
IL_0018 28 11 00 00 0a call 0xA000011
IL_001d 26 pop
IL_001e 2a ret
被加密之后的MSIL如下:
IL to import:
IL_0000 28 c7 00 00 0a call 0xA0000C7
IL_0005 6f c8 00 00 0a callvirt 0xA0000C8
IL_000a 2b 18 br.s 24 (IL_0024)
IL_000c 06 ldloc.0
IL_000d 04 ldarg.2
IL_000e 16 ldc.i4.0
IL_000f 0b stloc.1
IL_0010 38 5e 00 00 00 br 94 (IL_0073)
IL_0015 06 ldloc.0
IL_0016 07 ldloc.1
IL_0017 9a ldelem.ref
IL_0018 2b 13 br.s 19 (IL_002d)
IL_001a 17 ldc.i4.1
IL_001b 2d 02 brtrue.s 2 (IL_001f)
IL_001d 18 ldc.i4.2
IL_001e 26 pop
IL_001f 0a stloc.0
IL_0020 2b ec br.s -20 (IL_000e)
IL_0022 2b 09 br.s 9 (IL_002d)
IL_0024 17 ldc.i4.1
IL_0025 2d 02 brtrue.s 2 (IL_0029)
IL_0027 1b ldc.i4.5
IL_0028 26 pop
IL_0029 2b ef br.s -17 (IL_001a)
IL_002b 2b df br.s -33 (IL_000c)
IL_002d 0c stloc.2
IL_002e 08 ldloc.2
IL_002f 2b 08 br.s 8 (IL_0039)
IL_0031 6f 5e 00 00 0a callvirt 0xA00005E
IL_0036 0d stloc.3
IL_0037 de 44 leave.s 68 (IL_007d)
IL_0039 16 ldc.i4.0
IL_003a 2c 02 brfalse.s 2 (IL_003e)
IL_003c 1e ldc.i4.8
IL_003d 26 pop
IL_003e 2b 05 br.s 5 (IL_0045)
IL_0040 18 ldc.i4.2
IL_0041 2b 0b br.s 11 (IL_004e)
IL_0043 4e ldind.r4
IL_0044 56 stind.r4
IL_0045 16 ldc.i4.0
IL_0046 2c 02 brfalse.s 2 (IL_004a)
IL_0048 1c ldc.i4.6
IL_0049 26 pop
IL_004a 2b 0b br.s 11 (IL_0057)
IL_004c 2b f3 br.s -13 (IL_0041)
IL_004e 17 ldc.i4.1
IL_004f 2d 02 brtrue.s 2 (IL_0053)
IL_0051 1b ldc.i4.5
IL_0052 26 pop
IL_0053 2b dc br.s -36 (IL_0031)
IL_0055 2b ee br.s -18 (IL_0045)
IL_0057 17 ldc.i4.1
IL_0058 2d 02 brtrue.s 2 (IL_005c)
IL_005a 1c ldc.i4.6
IL_005b 26 pop
IL_005c 02 ldarg.0
IL_005d 2b ed br.s -19 (IL_004c)
IL_005f 2b 04 br.s 4 (IL_0065)
IL_0061 02 ldarg.0
IL_0062 03 ldarg.1
IL_0063 de 08 leave.s 8 (IL_006d)
IL_0065 17 ldc.i4.1
IL_0066 2d 02 brtrue.s 2 (IL_006a)
IL_0068 17 ldc.i4.1
IL_0069 26 pop
IL_006a 26 pop
IL_006b 2b f6 br.s -10 (IL_0063)
IL_006d 07 ldloc.1
IL_006e 17 ldc.i4.1
IL_006f 58 add
IL_0070 2b 0d br.s 13 (IL_007f)
IL_0072 0c stloc.2
IL_0073 07 ldloc.1
IL_0074 06 ldloc.0
IL_0075 8e ldlen
IL_0076 69 conv.i4
IL_0077 2b 0e br.s 14 (IL_0087)
IL_0079 4e ldind.r4
IL_007a 53 stind.i2
IL_007b 14 ldnull
IL_007c 2a ret
IL_007d 09 ldloc.3
IL_007e 2a ret
IL_007f 17 ldc.i4.1
IL_0080 2d 02 brtrue.s 2 (IL_0084)
IL_0082 18 ldc.i4.2
IL_0083 26 pop
IL_0084 0b stloc.1
IL_0085 2b ec br.s -20 (IL_0073)
IL_0087 17 ldc.i4.1
IL_0088 2d 02 brtrue.s 2 (IL_008c)
IL_008a 15 ldc.i4.m1
IL_008b 26 pop
IL_008c 3f 84 ff ff ff blt -124 (IL_0015)
IL_0091 2b e8 br.s -24 (IL_007b)
以上是一个逆向的实例,这里面包含了大量的跳转br.s,brtrue.s这种东西。不说它在JIT里面的IR变形和优化,也不说变成机器码之后的程序结果。只看当前就非常艰涩。它某些跳转里面包含了一些函数的调用,这些函数的调用里面又包含了十几个跳转。这些十几个跳转里面又包含了几个函数,每个函数里面再包函十几个跳转,这些跳转从托管到非托管,然后跳转回来,来来回回往复循环。耗尽耐心之后,防护加固就成功了。这是托管层面的,下面看下非托管层面的加固模式。为了解决这个困惑,可以在JIT的PreStubworker上入手,查看其汇编结果,逐步推导。
非托管加固
非托管里面的实质是,可以通过加密软件加密的托管代码,调用一些非托管库函数,然后运行这些非托管库函数,比如zlibc这种压缩库。一般的来说,在.NET里面压缩它是有intel特别定制的库文件,比如System.IO.Compression.Native.dll,它一共导出了如下函数
CompressionNative_Crc32
CompressionNative_Deflate
CompressionNative_DeflateEnd
CompressionNative_DeflateInit2_
CompressionNative_DeflateReset
CompressionNative_Inflate
CompressionNative_InflateEnd
CompressionNative_InflateInit2
CompressionNative_InflateReset
等函数,还有一些没写出来
它里面进行了代码的压缩以及解压,这个过程非常复杂。基本上在耗尽耐心。虽然它看似足够牛逼,为了解决这个非托管困惑。这里依然有足够宽松的切入点,那就上面所说的非托管DLL。
今天的分享大致就这么多。关于更多的知识点,可以加入知识星球,里面的硬核知识,骚操技术,你难以想象,持续性分享。
往期精彩回顾