.NET Framework C# DLL注入后无法执行代码的求助
解决.NET Framework托管DLL注入后无法执行的问题
核心问题分析
你的代码存在几个关键错误,导致托管DLL无法正常执行:
- 托管DLL无法直接被
LoadLibrary加载执行:普通C# DLL是托管程序集,依赖CLR运行时,若目标进程未加载.NET CLR,直接用LoadLibrary加载只会将DLL映射到进程内存,不会触发托管代码执行。 - 错误获取目标进程的DLL句柄:
GetModuleHandle(dllPath)是在注入器进程中调用,而非目标进程,无法获取目标进程中已加载的DLL句柄。 DllMain签名不符合原生规范:原生DLL的DllMain有固定签名(BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)),你定义的托管DllMain无法被原生系统调用识别,即便用DllExport也无法正确触发。- 断点无法命中:托管代码需JIT编译后才能执行,且调试器需附加到目标进程并加载托管符号,直接设置断点可能因符号未加载或代码未执行导致无法命中。
解决方案
方案一:用C++/CLI包装原生入口(推荐)
C++/CLI DLL可同时兼容原生和托管代码,能通过原生DllMain触发托管逻辑,无需复杂的CLR注入:
- 创建C++/CLI Class Library(目标平台x86,.NET Framework 4.8)
- 编写原生
DllMain,在进程加载时调用托管代码:
#include "pch.h" #include <msclr/marshal.h> using namespace msclr::interop; using namespace System; using namespace System::Windows::Forms; BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: // 触发托管代码 MessageBox::Show("works"); break; case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break; } return TRUE; }
- 编译后,用你的注入器加载这个C++/CLI DLL即可自动触发弹窗。
方案二:修复注入器逻辑 + 正确导出托管函数
若坚持用纯C# DLL,需通过DllExport导出符合原生调用规范的函数,并修正注入器的句柄获取逻辑:
1. 修正托管DLL代码(用DllExport)
using System; using System.Windows; using RGiesecke.DllExport; namespace ARKInjectableDLL { public class Main { [DllExport("RunManagedCode", CallingConvention = CallingConvention.StdCall)] public static void RunManagedCode() { // 加入调试触发,方便断点命中 System.Diagnostics.Debugger.Launch(); MessageBox.Show("works"); } } }
2. 修正注入器的InjectDLL方法
[DllImport("kernel32.dll", SetLastError = true)] static extern bool GetExitCodeThread(IntPtr hThread, out uint lpExitCode); public static void InjectDLL(int processId, string dllPath) { IntPtr processHandle = OpenProcess(PROCESS_ALL_ACCESS, false, processId); if (processHandle == IntPtr.Zero) return; // 分配内存存储DLL路径 int pathByteCount = Encoding.ASCII.GetByteCount(dllPath) + 1; IntPtr dllPathAddress = VirtualAllocEx(processHandle, IntPtr.Zero, (uint)pathByteCount, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); if (dllPathAddress == IntPtr.Zero) return; UIntPtr bytesWritten; bool writeSuccess = WriteProcessMemory(processHandle, dllPathAddress, Encoding.ASCII.GetBytes(dllPath), (uint)pathByteCount, out bytesWritten); if (!writeSuccess) return; // 获取LoadLibraryA地址 IntPtr loadLibraryAddress = GetProcAddress(GetModuleHandle("kernel32.dll"), "LoadLibraryA"); if (loadLibraryAddress == IntPtr.Zero) return; // 创建远程线程加载DLL IntPtr threadHandle; IntPtr threadId; threadHandle = CreateRemoteThread(processHandle, IntPtr.Zero, 0, loadLibraryAddress, dllPathAddress, 0, out threadId); if (threadHandle == IntPtr.Zero) return; // 等待线程结束,获取LoadLibrary的返回值(目标进程中的DLL句柄) WaitForSingleObject(threadHandle, INFINITE); uint dllHandleValue; GetExitCodeThread(threadHandle, out dllHandleValue); IntPtr dllHandle = new IntPtr(dllHandleValue); if (dllHandle == IntPtr.Zero) return; // 获取导出函数的地址 IntPtr methodAddress = GetProcAddress(dllHandle, "RunManagedCode"); if (methodAddress == IntPtr.Zero) return; // 创建远程线程执行托管函数 CreateRemoteThread(processHandle, IntPtr.Zero, 0, methodAddress, IntPtr.Zero, 0, out threadId); }
断点调试注意事项
- 在托管代码中加入
System.Diagnostics.Debugger.Launch(),执行时会弹出调试器选择窗口,选择你的VS实例即可附加调试。 - 确保VS调试器附加到目标进程,而非注入器进程。
- 若仍无法命中断点,检查项目是否生成了符号文件(
.pdb),并在调试器中手动加载符号(调试 -> 窗口 -> 模块,找到你的DLL,右键加载符号)。
内容的提问来源于stack exchange,提问作者Arkinetic
相关产品推荐
相关产品推荐

