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 构造函数调用
基本构造函数调用
- 首先定义包含构造函数的DLL:
namespace DllTest {
public class Class1 {
public Class1() {
Console.WriteLine("no params");
}
public Class1(string name) {
Console.WriteLine($"have params value is {name}");
}
}
}
- 反射调用构造函数:
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"});
调用私有构造函数
- 定义包含私有构造函数的类:
public class Class1 {
private Class1() {
Console.WriteLine("no params");
}
public Class1(string name) {
Console.WriteLine($"have params value is {name}");
}
}
- 反射调用私有构造:
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 实际利用案例
执行系统命令
- 定义执行类:
namespace DllTest {
public class TestCalc {
public static void Start() {
Process p = new Process();
p.StartInfo.FileName = "c:\\windows\\system32\\calc.exe";
p.Start();
}
}
}
- 反射调用:
Assembly assembly = Assembly.LoadFrom("DllTest.dll");
Type type = assembly.GetType("DllTest.TestCalc");
var method = type.GetMethod("Start");
method.Invoke(type, null); // 静态方法,第一个参数传类型
从字节数组加载程序集
- 将程序集转换为Base64字符串:
byte[] buffer = File.ReadAllBytes("testcalc.exe");
string base64str = Convert.ToBase64String(buffer);
Console.WriteLine(base64str);
- 从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[] {});
关键点总结
-
程序集加载方式选择:
Load()- 适合加载已知名称或字节数组形式的程序集LoadFrom()- 适合加载有依赖项的程序集LoadFile()- 适合独立程序集加载
-
私有成员访问:
- 使用
BindingFlags组合NonPublic标志 Activator.CreateInstance的第二个参数设为true
- 使用
-
泛型方法处理:
- 先获取方法定义,再使用
MakeGenericMethod指定类型参数
- 先获取方法定义,再使用
-
动态调用链:
- 加载程序集 → 获取类型 → 获取成员 → 创建实例 → 调用方法
-
隐蔽加载技术:
- 将程序集转换为Base64字符串形式加载
- 可以避免文件落地,提高隐蔽性
-
安全考虑:
- 反射调用可能绕过访问限制
- 可用于加载和执行未经验证的代码
- 实际应用中需考虑安全防护措施