C#访问外部程序内存报AccessViolation异常的解决方法
异常触发原因
System.AccessViolationException是跨进程读内存最常见的错误,核心原因有三个:
- Windows系统为每个进程分配了独立的虚拟地址空间,进程默认没有权限访问其他进程的内存段,你代码里用的
Marshal.Copy只能读写当前进程自身地址空间内的内存,直接传入其他进程的地址必然触发访问违规。 - 你写的地址输出代码本身有缺陷:将指针强转为
int类型存储,在64位系统下指针长度为8字节,int仅4字节,会直接截断高位导致地址值错误;加上系统默认开启ASLR(地址空间布局随机化),程序每次启动时栈、堆、模块的加载基址都会变化,硬编码上一次运行拿到的地址,下次启动时完全无效。 - 你示例里要读的
x是栈上的局部变量,地址本身就会随着函数调用、栈帧移动动态变化,哪怕在同一个进程里,保存临时地址过段时间再读也可能拿到无效值。
可行实现方案
方案1:使用Windows原生API ReadProcessMemory(适合内存读写实验场景)
这是Windows提供的官方跨进程读内存接口,也是做这类课程实验最常用的方式,实现步骤如下:
- 提前匹配程序架构:目标程序为32位(x86)编译时,你的读取程序也要选x86平台;目标为64位(x64)则对应选x64,否则地址长度不匹配会读错值。
- 调用
OpenProcess获取目标进程的操作句柄,需要申请PROCESS_VM_READ和PROCESS_QUERY_INFORMATION权限,如果目标程序以管理员身份运行,你的读取程序也必须启动管理员权限,否则会拿不到有效句柄。 - 不要硬编码地址:每次启动目标程序后,先获取目标变量的完整地址(注意用
IntPtr或long类型存储,不要用int截断),再传入读取接口。 - 调用
ReadProcessMemory将目标进程指定地址的内容拷贝到当前进程的缓冲区,读取完成后调用CloseHandle释放进程句柄,避免资源泄漏。
修正后的代码示例:
首先是目标程序(输出变量地址的程序)的修正代码:
// 目标程序:输出变量地址 static unsafe void Main(string[] args) { // 建议把要读的变量设为静态,存在全局数据区,地址偏移更稳定 static int x = 100; int* ptr = &x; // 直接输出IntPtr类型的完整指针,不要转int截断 Console.WriteLine((IntPtr)ptr); Console.WriteLine(*ptr); Console.ReadKey(); }
然后是读取程序的代码:
using System.Diagnostics; using System.Runtime.InteropServices; class CrossProcessMemoryReader { [DllImport("kernel32.dll", SetLastError = true)] static extern IntPtr OpenProcess(uint dwDesiredAccess, bool bInheritHandle, int dwProcessId); [DllImport("kernel32.dll", SetLastError = true)] static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, int nSize, out int lpNumberOfBytesRead); [DllImport("kernel32.dll")] static extern bool CloseHandle(IntPtr hObject); const uint PROCESS_VM_READ = 0x0010; const uint PROCESS_QUERY_INFORMATION = 0x0400; static void Main(string[] args) { // 替换成你自己的目标程序进程名,不带.exe后缀 Process target = Process.GetProcessesByName("MemoryTarget")[0]; IntPtr hProcess = OpenProcess(PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, false, target.Id); if (hProcess == IntPtr.Zero) { Console.WriteLine($"打开进程失败,错误码:{Marshal.GetLastWin32Error()},请确认以管理员权限运行"); return; } Console.WriteLine("请输入目标程序输出的内存地址:"); // 解析完整长度的地址,不要用int存 IntPtr targetAddr = new IntPtr(Convert.ToInt64(Console.ReadLine(), 16)); byte[] buffer = new byte[4]; // int类型占4字节,分配对应长度缓冲区 int readLen; bool readSuccess = ReadProcessMemory(hProcess, targetAddr, buffer, 4, out readLen); if (readSuccess && readLen == 4) { int res = BitConverter.ToInt32(buffer, 0); Console.WriteLine($"读取到的目标值:{res}"); } else { Console.WriteLine($"读内存失败,错误码:{Marshal.GetLastWin32Error()}"); } CloseHandle(hProcess); Console.ReadKey(); } }
方案2:用标准进程间通信方式传值(适合稳定数据交互场景)
如果只是需要两个程序之间传递数值,完全不需要用读内存这种不稳定的hack方式,可以直接用操作系统提供的IPC机制:
- 简单场景可以直接重定向目标程序的标准输出,把要传的值直接打印到控制台,读取程序捕获输出流就能拿到值。
- 复杂交互场景可以用命名管道、共享内存、Socket等方式传值,不受ASLR、权限、程序架构影响,稳定性远高于直接读内存。
注意事项
- 这类直接读其他进程内存的方式仅适合操作系统、编程原理类的课程实验使用,正式生产环境不要用这种方式做数据交互,只要目标程序更新、编译选项变化、系统开启更强的进程安全隔离策略,代码就会直接失效。
- 不要尝试读取系统进程、受保护进程(比如杀毒软件、反作弊程序)的内存,会直接触发权限错误,甚至被安全软件判定为恶意行为。
内容的提问来源于stack exchange,提问作者Jadisson Amorim
相关产品推荐
相关产品推荐

