如何将.NET托管DLL注入其他.NET进程并外部调用其方法
这个需求完全可实现,全C#技术栈即可完成,不需要依赖C++等原生语言开发组件。
注意和传统原生DLL注入的核心差异:C#编译生成的是托管DLL,依赖.NET CLR运行,不能直接套用原生LoadLibrary远程注入的逻辑加载,需要结合CLR的托管加载规则实现。
前置准备:开发待注入的C# DLL
把需要在目标进程执行的逻辑封装在类库项目中,将需要被外部触发的方法定义为public static类型,比如预留统一入口:public class InjectMain { public static void Entry(string param) { // 这里写注入后要执行的初始化逻辑,比如启动IPC监听 } }编译时注意两点:一是平台目标和要注入的目标进程保持一致(同为x86或x64);二是目标框架版本和目标进程的.NET版本完全匹配(比如目标进程跑.NET Framework 4.8就不要把DLL编译成.NET 6,反之亦然),否则会加载失败。
核心注入逻辑开发(注入器部分)
注入器是独立的C#控制台/WinForm程序,核心通过调用Win32 API完成操作,流程如下:- 通过
OpenProcess拿到目标进程的操作句柄,申请权限至少包含PROCESS_CREATE_THREAD、PROCESS_VM_OPERATION、PROCESS_VM_WRITE、PROCESS_QUERY_INFORMATION,如果目标进程是管理员/系统权限运行,注入器也需要以对应权限启动,否则会拿不到句柄。 - 在目标进程的虚拟内存空间中申请一块可读写的内存,将待注入DLL的完整本地路径写入这块内存。
- 不要直接远程调用
kernel32.dll的LoadLibrary函数加载托管DLL——这只会把DLL当做原生PE映射到进程内存,不会进入CLR加载流程,也无法执行托管代码。正确的做法是针对目标进程的CLR版本,调用CLR导出的托管加载接口(.NET Framework场景可以借助clr.dll/mscorwks.dll的CLR托管接口,.NET Core/.NET 5+场景可以借助已加载运行时的Assembly.LoadFile相关入口),加载DLL到目标进程的默认应用域中,然后直接调用你之前写好的静态入口方法完成DLL初始化。
- 通过
注入后跨进程调用DLL内部方法
最稳妥、兼容性最高的方案是在DLL的初始化入口里启动一个本地IPC服务,推荐用命名管道实现:DLL作为服务端监听专属的命名管道地址,注入器作为客户端连接这个管道,之后所有方法调用请求都通过管道发送,DLL收到请求后通过反射调用自身内部的对应方法,参数和返回值做序列化传输即可。
不推荐直接获取方法JIT后的内存地址、通过远程线程直接跳转调用的方案:.NET运行时的GC回收、JIT动态重编译都会导致方法内存地址变动,非常容易触发目标进程崩溃。
- 安全软件会对远程线程注入、内存写入操作做拦截,测试时记得加白名单,不要在生产环境无提示做注入操作。
- 如果目标进程做了强签名校验、进程内存防篡改保护,注入会失败,需要先过对应校验逻辑。
- .NET Core单文件自包含发布的进程,注入时要注意不要把DLL路径写错,优先用进程当前的运行时上下文加载,不要硬编码系统安装的.NET路径。
注:注入行为需要符合对应软件的用户协议和当地法律法规要求,仅可用于合法的调试、自研程序扩展场景。
内容的提问来源于stack exchange,提问作者root823746374

