Linux跨进程内存操作的3种方法及防护方案

最近有开发者在群里问:怎么实现一个进程去读写另一个进程的内存?这个问题在游戏外挂、调试工具、系统监控等场景都很常见。今天就给大家分享Linux下三种主流的跨进程内存操作方式,以及相应的防护思路。

一、ptrace:老牌的进程追踪神器

说到ptrace,搞过调试的都不陌生。gdb、strace这些工具底层用的就是它。说白了,ptrace就是系统给你开的一扇"后门",让你能控制另一个进程的执行流,顺带读写它的内存。

核心原理

ptrace是个系统调用,长这样:

c 复制代码
long ptrace(enum __ptrace_request op, pid_t pid, void *addr, void *data);

要用它读写内存,主要靠这两个操作:

  • PTRACE_PEEKDATA:一次读一个long大小的数据
  • PTRACE_POKEDATA:一次写一个long大小的数据

代码实战

我封装了个Tracer类,用起来比较直观:

cpp 复制代码
// Tracer.h
class Tracer {
public:
    bool attach(int pid);      // 附加到目标进程
    void detach();             // 脱离
    bool continueRun();        // 让目标继续跑
    void stop();               // 暂停目标
    
    size_t readMemory(uintptr_t address, void* buffer, size_t size);
    size_t writeMemory(uintptr_t address, void* buffer, size_t size);
};

实现的时候有个坑:必须先让目标进程进入STOP状态才能操作。来看核心代码:

cpp 复制代码
size_t Tracer::readMemory(uintptr_t address, void *buffer, size_t size) {
    // 按long大小分块读写,效率很一般
    size_t readsize = 0;
    long tmp;
    
    for(size_t i = 0; i < size / sizeof(long); ++i) {
        errno = 0;
        tmp = ptrace(PTRACE_PEEKDATA, pid_, (void*)(address + readsize), nullptr);
        if(tmp == -1 && errno != 0) return readsize; // 出错了
        
        memcpy((uint8_t*)buffer + i * sizeof(long), &tmp, sizeof(tmp));
        readsize += sizeof(tmp);
    }
    // 处理剩余字节...
}

写内存更麻烦,最后几个字节要"读-改-写"合并,不然会把后面的数据覆盖掉。

优缺点总结

  • ✅ 权限控制严格,需要root或同用户
  • ✅ 功能强大,还能单步调试
  • ❌ 效率低,每次都是单次syscall
  • ❌ 目标进程必须暂停,影响业务

二、/proc/[pid]/mem:虚拟文件的妙用

这招更直接。Linux的/proc目录下,每个进程都有一个mem文件,映射了它的整个虚拟内存空间。我们用普通的文件API就能操作。

实现要点

关键代码很简单,就是open/read/write:

cpp 复制代码
bool ProcFile::openMemory() {
    char path[64];
    snprintf(path, sizeof(path), "/proc/%d/mem", pid_);
    mem_fd_ = open(path, O_RDWR); // 要读写权限
    return mem_fd_ != -1;
}

size_t ProcFile::readMemory(intptr_t address, void *buffer, size_t size) {
    if (mem_fd_ == -1) return 0;
    
    // 定位到目标地址
    lseek(mem_fd_, static_cast<off_t>(address), SEEK_SET);
    
    // 必须STOP目标进程
    kill(pid_, SIGSTOP);
    size_t read_size = read(mem_fd_, buffer, size);
    kill(pid_, SIGCONT); // 操作完赶紧恢复
    
    return read_size;
}

注意几个细节:

  1. 需要O_RDWR权限,普通用户只能读自己进程的
  2. 操作前后要用SIGSTOP/SIGCONT包裹,不然会报错
  3. 地址必须用lseek定位,不能像普通文件那样顺序读

优缺点总结

  • ✅ 代码简单,符合Unix一切皆文件的理念
  • ✅ 批量读写效率比ptrace高
  • ❌ 还是要暂停进程
  • ❌ 需要处理权限问题,SELinux可能会拦截

三、process_vm_readv/writev:现代API

这是Linux 3.2+引入的"官方"解决方案。专门为跨进程内存操作设计,用起来最优雅。

使用方法

cpp 复制代码
#include <sys/uio.h>

ssize_t process_vm_readv(pid_t pid,
    const struct iovec *local_iov,  // 本地缓冲区
    unsigned long liovcnt,
    const struct iovec *remote_iov, // 远程进程地址
    unsigned long riovcnt,
    unsigned long flags);

看实战代码:

cpp 复制代码
int api_memory(int pid) {
    intptr_t address;
    std::string content(256, '\0');
    
    // 读内存
    iovec local_iov = {content.data(), content.size()};
    iovec remote_iov = {(void*)address, content.size()};
    ssize_t nread = process_vm_readv(pid, &local_iov, 1, &remote_iov, 1, 0);
    
    // 写内存
    std::string new_data = "hacked";
    local_iov.iov_base = new_data.data();
    local_iov.iov_len = new_data.size();
    process_vm_writev(pid, &local_iov, 1, &remote_iov, 1, 0);
}

这个API最爽的地方:不需要暂停目标进程。系统帮你处理好原子性问题。

优缺点总结

  • ✅ 无需STOP,对业务无影响
  • ✅ 支持向量读写,一次多个不连续内存块
  • ✅ 效率最高
  • ❌ 需要Linux 3.2+
  • ❌ 权限限制严格,只能读写同用户进程

三种方案对比

特性 ptrace /proc/[pid]/mem process_vm_xxx
效率
需暂停进程
权限要求 严格
代码复杂度 最低
兼容性 所有Linux 所有Linux Linux 3.2+
典型应用 调试器 监控工具 现代注入工具

四、防护方案:让攻击者知难而退

了解攻击手段是为了更好防护。跨进程内存攻击通常分两步:先静态/动态分析找漏洞,再实施内存读写。

防静态分析

  1. 代码虚拟化:把核心函数翻译成自定义指令集,在虚拟机里执行。反汇编工具看到的是虚拟机代码,完全摸不清逻辑。
  2. 代码混淆:控制流平坦化+虚假分支,把代码逻辑搅成一锅粥。
  3. 符号隐藏:strip掉符号表,加密导入表,让攻击者找不到关键函数。

防动态调试

  1. 反ptrace:检测父进程是否在用ptrace调试,是就直接退出。还可以fork子进程互相ptrace,占用调试接口。
  2. 内存校验:定时检查代码段hash值,发现被修改就自毁。可以用信号或线程做后台监控。
  3. 权限最小化:以最低权限运行,减少被攻击面。敏感操作放独立进程,IPC通信。

产品化方案

如果是商业软件,建议用专业保护方案。比如Virbox Protector这类工具,把上述防护手段打包,提供一站式保护。从代码加密、虚拟化到反调试一条龙,比自己造轮子靠谱。当然,任何防护都不是绝对的安全,只是大幅提高攻击成本。

五、总结

三种方案各有适用场景:

  • 做调试器?用ptrace
  • 做监控采集?用/proc/mem
  • 做游戏注入?用process_vm_xxx

从攻防角度看,没有绝对安全的系统。了解攻击原理,针对性加固,把攻击成本抬高到不划算,就是胜利。

相关推荐
互亿无线明明2 小时前
国际短信通知服务:如何为全球业务构建稳定的跨国消息触达体系?
java·c语言·python·php·objective-c·ruby·composer
HalvmånEver2 小时前
Linux:基础IO(一)
linux·运维·服务器
Lynnxiaowen2 小时前
今天我们学习kubernetes内容持久化存储
linux·运维·学习·容器·kubernetes
Starry_hello world2 小时前
Linux 信号
linux
KingRumn2 小时前
Linux进程间通信之消息队列
linux·服务器·网络
jerryinwuhan2 小时前
1210_linux_2
linux·运维·服务器
Jerry952706282 小时前
1.什么式可用性
java·分布式·后端·架构·高可用·秒杀
轻描淡写6062 小时前
二进制存储数据
java·开发语言·算法
Two_brushes.2 小时前
字符串<--->网络字节序<--->主机
网络