2014-10-11 70 views
17

有没有什么方法可以在C/C++中像shell代码一样在C#中执行IL代码数组?在C#中执行.NET IL代码#

我想创建一个方法,将其转换为IL代码,混淆它并存储在一个字节数组中,最后要执行它解密数组内容并执行IL代码。

例如,这是我的C#代码:

static int MyMethod(string A, int B) 
{ 
    B += 10; 
    if (A.Equals("A")) 
     B = 0; 
    return B; 
} 

现在我把它转换成IL代码:

private static int MyMethod(string A, int B) 
    { 
    locals: int local_0, 
      bool local_1 

    /* 0000025C 00    */ nop 
    /* 0000025D 03    */ ldarg_1 // int B 
    /* 0000025E 1F 0A   */ ldc_i4_s 10 
    /* 00000260 58    */ add 
    /* 00000261 10 01   */ starg_s arg_1 // int B 
    /* 00000263 02    */ ldarg_0 // string A 
    /* 00000264 72 01 00 00 70 */ ldstr "A" 
    /* 00000269 6F 11 00 00 0A */ callvirt System.String::Equals(string) // returns bool 
    /* 0000026E 16    */ ldc_i4_0 
    /* 0000026F FE 01   */ ceq 
    /* 00000271 0B    */ stloc_1 // bool local_1 
    /* 00000272 07    */ ldloc_1 // bool local_1 
    /* 00000273 2D 03   */ brtrue_s loc_28 
    /* 00000275 16    */ ldc_i4_0 
    /* 00000276 10 01   */ starg_s arg_1 // int B 
loc_28: 
    /* 00000278 03    */ ldarg_1 // int B 
    /* 00000279 0A    */ stloc_0 // int local_0 
    /* 0000027A 2B 00   */ br_s loc_32 
loc_32: 
    /* 0000027C 06    */ ldloc_0 // int local_0 
    /* 0000027D 2A    */ ret 
    } 

最后这是一个字节数组:

private byte[] ilcode = 
{ 
    0x00, 0x03, 0x1F, 0x0A, 0x58, 0x10, 0x01, 0x02, 0x72, 0x01, 0x00, 0x00, 0x70, 0x6F, 0x11, 0x00, 0x0, 0x0A, 0x16, 
    0xFE, 0x01, 0x0B, 0x07, 0x2D, 0x03, 0x16, 0x10, 0x01, 0x03, 0x0A, 0x2B, 0x00, 0x06, 0x2A 
}; 
+2

有没有办法按照您期望的方式执行IL,但是您可以使用反射来动态加载并调用其中的任何方法。 – Thangadurai 2014-10-11 07:07:17

+3

我认为你最大的问题是元数据令牌。例如,代码中'string.Equals()'('11 00 00 0A')的标记并不总是相同的。 – svick 2014-10-11 10:51:37

+1

@svick元数据令牌弄糟了这么大的时间,因为它们似乎存储在模块级别(至少这是我从我的快速测试中看到的)。字符串文字也是一样。 – 2014-10-12 10:06:32

回答

21

你将需要使用System.Reflection.Emit命名空间中的基础结构。具体来说,您应该查看的MethodBuilder.CreateMethodBody,它需要一个表示MSIL指令的字节数组。这里有一个完整的例子,但下面是它的使用的简短片段。在这里,我也将为动态方法创建一个委托。

我会注意到,这只是在非常有限的,这是在文档叫了支持:

这是目前不完全支持。用户不能提供令牌修复和异常处理程序的位置。

问题是在模块级别解决了IL中引用类型,方法,字符串等的元数据标记。因此,IL不是完全可移植的,因为您不能从一个模块中的某个方法获取任意的原始IL,而只是将其放入另一个模块中的另一个方法中。您需要在新模块中使用适当的元数据令牌。但是,如果您知道IL不包含元数据令牌,则可以执行此操作,但这会严重限制您可以对此执行的操作。 (HT:svick,西蒙·斯文森)

class Program 
{ 
    static void Main(string[] args) 
    { 
     // opcodes for pushing two arguments to the stack, adding, and returning the result. 
     byte[] ilcodes = { 0x02, 0x03, 0x58, 0x2A }; 
     var method = CreateFromILBytes(ilcodes); 
     Console.WriteLine(method(2, 3)); 
    } 

    private static Func<int, int, int> CreateFromILBytes(byte[] bytes) 
    { 
     var asmName = new AssemblyName("DynamicAssembly"); 
     var asmBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(asmName, AssemblyBuilderAccess.RunAndSave); 
     var module = asmBuilder.DefineDynamicModule("DynamicModule"); 
     var typeBuilder = module.DefineType("DynamicType"); 
     var method = typeBuilder.DefineMethod("DynamicMethod", 
      MethodAttributes.Public | MethodAttributes.Static, 
      typeof(int), 
      new[] { typeof(int), typeof(int) }); 
     method.CreateMethodBody(bytes, bytes.Length); 
     var type = typeBuilder.CreateType(); 
     return (Func<int, int, int>)type.GetMethod("DynamicMethod").CreateDelegate(typeof(Func<int, int, int>)); 
    } 
} 

注意使用RunAndSave选项。这会将动态程序集保存到临时位置的磁盘中。可能更希望使用RunAndCollect,它将仅在内存中生成程序集并允许稍后在所有对它的引用都死的时候收集它。但是,对于所谓的可收集组件有一些注意事项,详述如下:here

+0

很高兴认识这个。我现在应该开始阅读更多的书了! – Thangadurai 2014-10-11 07:52:21

+1

由于操作码('ldarg.0','ldarg.1','add','ret')不使用元数据令牌,因此此示例(使用字节数组)可以工作。 – sisve 2014-10-13 07:15:44

+0

@SimonSvensson这是正确的,我在回答中对此进行了扩展。 – 2014-10-13 16:27:09