process_vm_readv/process_vm_writev 接口详解
process_vm_readv/process_vm_writev 接口详解
基础接口
process_vm_readv(2)
NAMEprocess_vm_readv, process_vm_writev - 在进程间传输数据SYNOPSIS#define _GNU_SOURCE#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);ssize_t process_vm_writev(pid_t pid,const struct iovec *local_iov,unsigned long liovcnt,const struct iovec *remote_iov,unsigned long riovcnt,unsigned long flags);DESCRIPTION这些系统调用允许直接在调用进程和指定进程(pid)的内存之间传输数据,无需通过内核缓冲区复制。process_vm_readv() 从远程进程读取数据到本地进程process_vm_writev() 从本地进程写入数据到远程进程参数说明:- pid: 目标进程ID- local_iov: 本地内存区域描述符数组- liovcnt: 本地iovec数组元素个数- remote_iov: 远程内存区域描述符数组- riovcnt: 远程iovec数组元素个数- flags: 保留字段,必须为0RETURN VALUE成功时返回传输的字节数,失败时返回-1并设置errnoERRORSEACCES 没有权限访问目标进程内存EFAULT 指定的地址范围无效EINVAL 参数无效ENOMEM 内存不足EPERM 没有权限操作目标进程ESRCH 目标进程不存在VERSIONSLinux 3.2+ 支持这些系统调用CONFORMING TO这些是Linux特有的系统调用NOTES需要相同用户ID或CAP_SYS_PTRACE权限目标进程必须正在运行不会触发目标进程的信号处理程序
数据结构
iovec 结构体
struct iovec {void *iov_base; /* 起始地址 */size_t iov_len; /* 缓冲区长度 */
};
使用示例
示例1:基本内存读取
#define _GNU_SOURCE
#include <sys/uio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>// 目标进程中需要读取的变量
int target_global_var = 42;
char target_string[] = "Hello from target process!";int read_remote_memory(pid_t target_pid) {struct iovec local_iov[2];struct iovec remote_iov[2];ssize_t result;int local_int;char local_buffer[100];// 设置本地缓冲区local_iov[0].iov_base = &local_int;local_iov[0].iov_len = sizeof(local_int);local_iov[1].iov_base = local_buffer;local_iov[1].iov_len = sizeof(local_buffer);// 设置远程内存地址(需要知道目标进程中的确切地址)remote_iov[0].iov_base = &target_global_var; // 实际使用中需要通过其他方式获取remote_iov[0].iov_len = sizeof(target_global_var);remote_iov[1].iov_base = target_string;remote_iov[1].iov_len = strlen(target_string) + 1;// 读取远程进程内存result = process_vm_readv(target_pid,local_iov, 2,remote_iov, 2,0);if (result == -1) {perror("process_vm_readv");return -1;}printf("Read %zd bytes\n", result);printf("Remote int value: %d\n", local_int);printf("Remote string: %s\n", local_buffer);return 0;
}
示例2:内存写入操作
#define _GNU_SOURCE
#include <sys/uio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>// 目标进程中的可修改变量
int target_writable_var = 100;
char target_writable_buffer[256] = "Original content";int write_remote_memory(pid_t target_pid) {struct iovec local_iov[2];struct iovec remote_iov[2];ssize_t result;int new_value = 999;char new_string[] = "Modified by process_vm_writev!";// 设置本地数据源local_iov[0].iov_base = &new_value;local_iov[0].iov_len = sizeof(new_value);local_iov[1].iov_base = new_string;local_iov[1].iov_len = strlen(new_string) + 1;// 设置远程内存地址remote_iov[0].iov_base = &target_writable_var;remote_iov[0].iov_len = sizeof(target_writable_var);remote_iov[1].iov_base = target_writable_buffer;remote_iov[1].iov_len = strlen(new_string) + 1;// 写入远程进程内存result = process_vm_writev(target_pid,local_iov, 2,remote_iov, 2,0);if (result == -1) {perror("process_vm_writev");return -1;}printf("Wrote %zd bytes to remote process\n", result);return 0;
}
示例3:完整的工作示例
目标进程代码 (target.c)
#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>volatile int shared_int = 12345;
char shared_string[256] = "This is shared data from target process";
int running = 1;void signal_handler(int sig) {printf("Received signal %d\n", sig);running = 0;
}int main() {printf("Target process PID: %d\n", getpid());printf("Shared int address: %p\n", &shared_int);printf("Shared string address: %p\n", shared_string);printf("Shared int value: %d\n", shared_int);printf("Shared string value: %s\n", shared_string);// 安装信号处理程序signal(SIGUSR1, signal_handler);printf("Target process running... Send SIGUSR1 to stop\n");while (running) {printf("shared_int = %d, shared_string = %s\n", shared_int, shared_string);sleep(2);}printf("Target process exiting...\n");printf("Final values - shared_int = %d, shared_string = %s\n", shared_int, shared_string);return 0;
}
访问进程代码 (accessor.c)
#define _GNU_SOURCE
#include <sys/uio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <signal.h>int main(int argc, char *argv[]) {if (argc != 2) {fprintf(stderr, "Usage: %s <target_pid>\n", argv[0]);exit(1);}pid_t target_pid = atoi(argv[1]);struct iovec local_iov[2];struct iovec remote_iov[2];ssize_t result;int local_int;char local_string[256];int new_int = 99999;char new_string[] = "Modified by accessor process!";printf("Accessing process PID: %d\n", getpid());printf("Target PID: %d\n", target_pid);// 读取远程进程内存printf("\n--- Reading remote memory ---\n");local_iov[0].iov_base = &local_int;local_iov[0].iov_len = sizeof(local_int);local_iov[1].iov_base = local_string;local_iov[1].iov_len = sizeof(local_string);// 注意:这里需要知道目标进程的确切内存地址// 在实际应用中,这些地址需要通过调试信息或其他方式获取remote_iov[0].iov_base = (void*)0x601040; // 需要根据实际情况调整remote_iov[0].iov_len = sizeof(int);remote_iov[1].iov_base = (void*)0x601060; // 需要根据实际情况调整remote_iov[1].iov_len = sizeof(local_string);result = process_vm_readv(target_pid,local_iov, 2,remote_iov, 2,0);if (result == -1) {perror("process_vm_readv");printf("Note: You need to adjust memory addresses based on target process\n");return 1;}printf("Read %zd bytes\n", result);printf("Remote int value: %d\n", local_int);printf("Remote string: %s\n", local_string);// 修改远程进程内存printf("\n--- Writing to remote memory ---\n");local_iov[0].iov_base = &new_int;local_iov[0].iov_len = sizeof(new_int);local_iov[1].iov_base = new_string;local_iov[1].iov_len = strlen(new_string) + 1;result = process_vm_writev(target_pid,local_iov, 2,remote_iov, 2,0);if (result == -1) {perror("process_vm_writev");return 1;}printf("Wrote %zd bytes to remote process\n", result);// 再次读取验证修改printf("\n--- Verifying changes ---\n");result = process_vm_readv(target_pid,local_iov, 2,remote_iov, 2,0);if (result != -1) {printf("Remote int value after write: %d\n", local_int);printf("Remote string after write: %s\n", local_string);}// 发送信号给目标进程printf("\n--- Sending signal to target process ---\n");if (kill(target_pid, SIGUSR1) == -1) {perror("kill");} else {printf("Signal sent successfully\n");}return 0;
}
示例4:实用工具 - 进程内存检查器
#define _GNU_SOURCE
#include <sys/uio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>// 简单的内存转储工具
int dump_remote_memory(pid_t pid, unsigned long addr, size_t size) {char *buffer;struct iovec local_iov[1];struct iovec remote_iov[1];ssize_t result;size_t i;buffer = malloc(size);if (!buffer) {perror("malloc");return -1;}local_iov[0].iov_base = buffer;local_iov[0].iov_len = size;remote_iov[0].iov_base = (void*)addr;remote_iov[0].iov_len = size;result = process_vm_readv(pid,local_iov, 1,remote_iov, 1,0);if (result == -1) {perror("process_vm_readv");free(buffer);return -1;}printf("Memory dump at 0x%lx (%zd bytes):\n", addr, result);for (i = 0; i < (size_t)result; i++) {if (i % 16 == 0) {printf("\n%08lx: ", addr + i);}printf("%02x ", (unsigned char)buffer[i]);}printf("\n");// 显示可打印字符printf("\nASCII: ");for (i = 0; i < (size_t)result; i++) {if (i % 16 == 0 && i > 0) {printf("\n ");}printf("%c", (buffer[i] >= 32 && buffer[i] <= 126) ? buffer[i] : '.');}printf("\n");free(buffer);return 0;
}int main(int argc, char *argv[]) {if (argc != 4) {fprintf(stderr, "Usage: %s <pid> <address> <size>\n", argv[0]);fprintf(stderr, "Example: %s 1234 0x601040 64\n", argv[0]);exit(1);}pid_t target_pid = atoi(argv[1]);unsigned long address = strtoul(argv[2], NULL, 0);size_t size = strtoul(argv[3], NULL, 0);printf("Dumping %zu bytes from process %d at address 0x%lx\n", size, target_pid, address);return dump_remote_memory(target_pid, address, size);
}
示例5:批量内存操作
#define _GNU_SOURCE
#include <sys/uio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>#define MAX_IOV 1024typedef struct {void *remote_addr;size_t size;void *local_buffer;
} memory_region_t;// 批量读取多个内存区域
int batch_read_memory(pid_t pid, memory_region_t *regions, int count) {struct iovec local_iov[MAX_IOV];struct iovec remote_iov[MAX_IOV];ssize_t result;int i;if (count > MAX_IOV) {fprintf(stderr, "Too many regions\n");return -1;}// 设置iovec数组for (i = 0; i < count; i++) {local_iov[i].iov_base = regions[i].local_buffer;local_iov[i].iov_len = regions[i].size;remote_iov[i].iov_base = regions[i].remote_addr;remote_iov[i].iov_len = regions[i].size;}result = process_vm_readv(pid,local_iov, count,remote_iov, count,0);if (result == -1) {perror("process_vm_readv");return -1;}printf("Batch read completed: %zd bytes total\n", result);return 0;
}// 批量写入多个内存区域
int batch_write_memory(pid_t pid, memory_region_t *regions, int count) {struct iovec local_iov[MAX_IOV];struct iovec remote_iov[MAX_IOV];ssize_t result;int i;if (count > MAX_IOV) {fprintf(stderr, "Too many regions\n");return -1;}// 设置iovec数组for (i = 0; i < count; i++) {local_iov[i].iov_base = regions[i].local_buffer;local_iov[i].iov_len = regions[i].size;remote_iov[i].iov_base = regions[i].remote_addr;remote_iov[i].iov_len = regions[i].size;}result = process_vm_writev(pid,local_iov, count,remote_iov, count,0);if (result == -1) {perror("process_vm_writev");return -1;}printf("Batch write completed: %zd bytes total\n", result);return 0;
}int main() {printf("Batch memory operations example\n");printf("This example shows how to perform batch operations\n");printf("You need to provide actual PID and memory addresses\n");return 0;
}
使用限制和注意事项
权限要求
- 相同用户: 调用进程和目标进程必须有相同的有效用户ID
- ptrace权限: 或者调用进程需要
CAP_SYS_PTRACE
权能 - 进程状态: 目标进程必须正在运行(不是僵尸进程)
安全限制
- 内存保护: 不能访问目标进程的受保护内存区域
- 地址有效性: 必须确保远程地址在目标进程的有效地址空间内
- 对齐要求: 某些体系结构可能有内存对齐要求
技术限制
- 最大IOV数量: 系统可能限制iovec数组的最大长度
- 性能考虑: 大量小的传输可能不如批量传输高效
- 原子性: 单次调用内的多个传输不是原子的
错误处理
- 部分传输: 可能只传输部分数据,需要检查返回值
- 地址错误: 无效地址会导致整个操作失败
- 进程终止: 目标进程在操作过程中终止会导致错误
最佳实践
- 地址获取: 使用调试信息或符号表获取准确的内存地址
- 缓冲区管理: 确保本地缓冲区足够大且生命周期正确
- 错误检查: 始终检查返回值并处理错误情况
- 权限验证: 在执行操作前验证权限和进程状态
这些系统调用提供了强大的进程间内存访问能力,但使用时需要谨慎处理安全和权限问题。