c#反射初探
字数 876 2025-08-06 20:12:38

C#反射技术详解

0x01 程序集加载方法

C#提供了三种主要的程序集加载方式,各有特点:

Assembly.Load()

  • 从当前应用程序域加载程序集
  • 需要把DLL放到程序当前路径
  • 也可以直接加载字节数组形式的程序集
  • 示例:
Assembly assembly1 = Assembly.Load("DllTest");  // 从名称加载
byte[] buffer = File.ReadAllBytes("testcalc.exe");
Assembly assembly = Assembly.Load(buffer);  // 从字节数组加载

Assembly.LoadFrom()

  • 需要提供完整路径
  • 会自动加载依赖项(如test1.dll引用了test2.dll,会同时加载)
  • 示例:
Assembly assembly3 = Assembly.LoadFrom(@"C:\path\DllTest.dll");
Assembly assembly4 = Assembly.LoadFrom("DllTest.dll");  // 当前目录

Assembly.LoadFile()

  • 需要提供完整路径
  • 不会自动加载依赖项
  • 示例:
Assembly assembly2 = Assembly.LoadFile(@"C:\path\DllTest.dll");

0x02 构造函数调用

基本构造函数调用

  1. 首先定义包含构造函数的DLL:
namespace DllTest {
    public class Class1 {
        public Class1() {
            Console.WriteLine("no params");
        }
        public Class1(string name) {
            Console.WriteLine($"have params value is {name}");
        }
    }
}
  1. 反射调用构造函数:
Assembly assembly = Assembly.LoadFrom("DllTest.dll");
Type type = assembly.GetType("DllTest.Class1");

// 调用无参构造
object obj = Activator.CreateInstance(type);

// 调用有参构造
object obj = Activator.CreateInstance(type, new object[] {"test"});

调用私有构造函数

  1. 定义包含私有构造函数的类:
public class Class1 {
    private Class1() {
        Console.WriteLine("no params");
    }
    public Class1(string name) {
        Console.WriteLine($"have params value is {name}");
    }
}
  1. 反射调用私有构造:
Assembly assembly = Assembly.LoadFrom("DllTest.dll");
Type type = assembly.GetType("DllTest.Class1");
object obj = Activator.CreateInstance(type, true);  // 第二个参数true表示允许非公开

0x03 类型和成员探查

获取所有类型

Assembly assembly = Assembly.LoadFrom("DllTest.dll");
foreach(var all_type in assembly.GetTypes()) {
    Console.WriteLine(all_type.Name);
}

获取所有构造方法

Type type = assembly.GetType("DllTest.Class1");
foreach(var all_cons in type.GetConstructors()) {
    Console.WriteLine(all_cons);
}

获取构造方法参数

foreach(var all_cons in type.GetConstructors()) {
    Console.WriteLine("构造方法:" + all_cons);
    foreach(var param in all_cons.GetParameters()) {
        Console.WriteLine("所有参数:" + param.Name);
    }
}

获取私有构造方法

foreach(var all_cons in type.GetConstructors(
    BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)) {
    Console.WriteLine("构造方法:" + all_cons);
}

0x04 方法调用

定义测试类

public class Class1 {
    public void TestMethod() {
        Console.WriteLine("TestMethod");
    }
    private void TestMethod2() {
        Console.WriteLine("TestMethod2");
    }
}

调用公共方法

var method = type.GetMethod("TestMethod");
method.Invoke(obj, new object[] {});  // 无参方法

调用私有方法

var method = type.GetMethod("TestMethod2", 
    BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
method.Invoke(obj, new object[] {});

0x05 泛型方法调用

定义泛型方法

public class Class1 {
    public void Test<T>() {
        Console.WriteLine("Test");
    }
    public void Test2<T>(string name) {
        Console.WriteLine($"name is:{name}");
    }
}

调用泛型方法

// 无参泛型方法
var method = type.GetMethod("Test");
var genericMethod = method.MakeGenericMethod(new Type[] {typeof(int)});
genericMethod.Invoke(obj, new object[] { });

// 有参泛型方法
var method = type.GetMethod("Test2");
var genericMethod = method.MakeGenericMethod(new Type[] {typeof(string)});
genericMethod.Invoke(obj, new object[] {"jjjj"});

0x06 属性操作

定义属性类

namespace DllTest {
    public class Pro {
        public string name { get; set; }
        public int num { get; set; }
    }
}

获取和设置属性

Assembly assembly = Assembly.LoadFrom("DllTest.dll");
Type type = assembly.GetType("DllTest.Pro");
object obj = Activator.CreateInstance(type);

// 遍历属性
foreach(var property in type.GetProperties()) {
    Console.WriteLine(property.Name);
    
    // 设置属性值
    if(property.Name.Equals("name")) {
        property.SetValue(obj, "zhangsan");
    } 
    else if(property.Name.Equals("num")) {
        property.SetValue(obj, 123123);
    }
    
    // 获取属性值
    Console.WriteLine(property.GetValue(obj));
}

0x07 实际利用案例

执行系统命令

  1. 定义执行类:
namespace DllTest {
    public class TestCalc {
        public static void Start() {
            Process p = new Process();
            p.StartInfo.FileName = "c:\\windows\\system32\\calc.exe";
            p.Start();
        }
    }
}
  1. 反射调用:
Assembly assembly = Assembly.LoadFrom("DllTest.dll");
Type type = assembly.GetType("DllTest.TestCalc");
var method = type.GetMethod("Start");
method.Invoke(type, null);  // 静态方法,第一个参数传类型

从字节数组加载程序集

  1. 将程序集转换为Base64字符串:
byte[] buffer = File.ReadAllBytes("testcalc.exe");
string base64str = Convert.ToBase64String(buffer);
Console.WriteLine(base64str);
  1. 从Base64字符串加载并执行:
string base64str = "TVqQAAMAA..."; // 省略的Base64字符串
byte[] buffer = Convert.FromBase64String(base64str);
Assembly assembly = Assembly.Load(buffer);
Type type = assembly.GetType("testcalc.Strat");
MethodInfo method = type.GetMethod("run");
Object obj = assembly.CreateInstance(method.Name);
method.Invoke(obj, new object[] {});

关键点总结

  1. 程序集加载方式选择

    • Load() - 适合加载已知名称或字节数组形式的程序集
    • LoadFrom() - 适合加载有依赖项的程序集
    • LoadFile() - 适合独立程序集加载
  2. 私有成员访问

    • 使用BindingFlags组合NonPublic标志
    • Activator.CreateInstance的第二个参数设为true
  3. 泛型方法处理

    • 先获取方法定义,再使用MakeGenericMethod指定类型参数
  4. 动态调用链

    • 加载程序集 → 获取类型 → 获取成员 → 创建实例 → 调用方法
  5. 隐蔽加载技术

    • 将程序集转换为Base64字符串形式加载
    • 可以避免文件落地,提高隐蔽性
  6. 安全考虑

    • 反射调用可能绕过访问限制
    • 可用于加载和执行未经验证的代码
    • 实际应用中需考虑安全防护措施
C#反射技术详解 0x01 程序集加载方法 C#提供了三种主要的程序集加载方式,各有特点: Assembly.Load() 从当前应用程序域加载程序集 需要把DLL放到程序当前路径 也可以直接加载字节数组形式的程序集 示例: Assembly.LoadFrom() 需要提供完整路径 会自动加载依赖项(如test1.dll引用了test2.dll,会同时加载) 示例: Assembly.LoadFile() 需要提供完整路径 不会自动加载依赖项 示例: 0x02 构造函数调用 基本构造函数调用 首先定义包含构造函数的DLL: 反射调用构造函数: 调用私有构造函数 定义包含私有构造函数的类: 反射调用私有构造: 0x03 类型和成员探查 获取所有类型 获取所有构造方法 获取构造方法参数 获取私有构造方法 0x04 方法调用 定义测试类 调用公共方法 调用私有方法 0x05 泛型方法调用 定义泛型方法 调用泛型方法 0x06 属性操作 定义属性类 获取和设置属性 0x07 实际利用案例 执行系统命令 定义执行类: 反射调用: 从字节数组加载程序集 将程序集转换为Base64字符串: 从Base64字符串加载并执行: 关键点总结 程序集加载方式选择 : Load() - 适合加载已知名称或字节数组形式的程序集 LoadFrom() - 适合加载有依赖项的程序集 LoadFile() - 适合独立程序集加载 私有成员访问 : 使用 BindingFlags 组合 NonPublic 标志 Activator.CreateInstance 的第二个参数设为 true 泛型方法处理 : 先获取方法定义,再使用 MakeGenericMethod 指定类型参数 动态调用链 : 加载程序集 → 获取类型 → 获取成员 → 创建实例 → 调用方法 隐蔽加载技术 : 将程序集转换为Base64字符串形式加载 可以避免文件落地,提高隐蔽性 安全考虑 : 反射调用可能绕过访问限制 可用于加载和执行未经验证的代码 实际应用中需考虑安全防护措施