You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 19:39:18