.NET 基于开放委托实现任意命令执行

0x01 Select

Select 又称投影操作符,定义如下

public static IEnumerable<TResult> Select<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector)

IEnumerable<TSource>是原始数据集合,Func<TSource, TResult>代表从源序列的每个元素到目标序列元素的转换值,Select方法的返回类型是 IEnumerable<TResult>,表示一个新的序列,常用于对序列中的每个元素执行转换操作生成一个新的序列,比如从对象中选择特定的属性或进行计算等,如下demo


var numbers = new List<int> { 1, 2, 3, 4, 5 };var squares = numbers.Select(num => num * num);// 输出结果: 1 4 9 16 25

0x02 SelectMany

SelectMany操作符提供了将多个序列组合起来的功能,相当于数据库中的多表连接查询,它将每个对象的结果合并成单个序列

var students = new List<Student>{    new Student { Name = "John", Courses = new List<string> { "Math", "Science" } },    new Student { Name = "Alice", Courses = new List<string> { "History", "English" } }};var allCourses = students.SelectMany(student => student.Courses);// 输出结果: Math Science History English

需要说明的是Select和SelectMany操作符都属于 LINQ 的延迟加载,延迟加载是指查询操作在实际需要数据时才会执行,而不是立即执行,好处在于可以节省与数据库交互带来的计算资源损耗,它们只会创建一个查询表达式,而不会立即执行查询。在某些场景下,如果需要确保立即加载数据,可以使用像 ToList() 或 ToArray() 等方法来强制执行查询并将结果加载到内存中。

0x03 LINQ

LINQ-SelectMany操作符用于合并两个序列后产生一个新的序列结果,通过LINQ这个能力可以联合Aseembly.Load和Aseembly::GetTypes,再借用LINQ-Select操作符投影Activator.CreateInstance反射创建一个Aseembly对象,这样就可以实现命令执行

byte[] assemblyBytes = File.ReadAllBytes(Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "net-calc.dll"));List<byte[]> data = new List<byte[]>();data.Add(this.assemblyBytes);var e1 = data.Select(Assembly.Load);Func<Assembly, IEnumerable<Type>> map_type = (Func<Assembly, IEnumerable<Type>>)Delegate.CreateDelegate(typeof(Func<Assembly, IEnumerable<Type>>), typeof(Assembly).GetMethod("GetTypes"));var e2 = e1.SelectMany(map_type);var e3 = e2.Select(Activator.CreateInstance).ToList();

开放委托通过Delegate.CreateDelegate方法创建具有返回值的Func<Assembly, IEnumerable<Type>>委托,运行时通过typeof(Assembly).GetMethod("GetTypes"))反射创建实例成员GetTypes将其附加为程序集参数,这样便可获取Assembly程序集的类型,因为LINQ有延迟加载的机制,咱们需要它立即执行所以追加ToList() 就会立刻执行弹出计算器,如下图

.NET 基于开放委托实现任意命令执行