
这是两个系统调用,这对系统调用用于在调用进程("本地进程")的虚拟地址空间与由 pid 标识的进程("远程进程")的虚拟地址空间之间传输数据,数据直接在这两个进程的地址空间之间移动,不经过内核空间的缓冲区。
process_vm_readv 系统调用将数据从远程进程传输到本地进程。待传输的数据由 remote_iov 和 riovcnt 描述,remote_iov 是一个 iovec 指针,指向一个描述远程进程(pid)中地址范围的 iovec 数组,riovcnt 指定了 remote_iov 中的元素个数。数据被传输到由 local_iov 和 liovcnt 指定的位置,local_iov 也是一个 iovec 指针,指向一个描述调用/本地进程中地址范围的 iovec 数组,liovcnt 指定了 local_iov 中的元素个数。
process_vm_writev 系统调用与 process_vm_readv 相反,它将数据从本地进程写到远程进程。除传输方向不同外,参数 liovcnt、local_iov、riovcnt 和 remote_iov 的含义与 process_vm_readv 中的完全相同。
需要说明的是这两个系统调用在两个进程之间传输数据不经过内核的准确含义是,数据不会先被拷贝进内核缓冲区、再拷贝回用户态,而是由内核直接在两个进程的页表所映射的物理内存之间完成复制。换句话说,虽然拷贝动作由内核执行(毕竟是系统调用),但数据本身没有"绕道"内核的中间缓冲区,避免了数据在内核缓冲区与用户缓冲区之间的额外拷贝,所以这两个系统调用的性能还是不错的。




#define _GNU_SOURCE#include<stdio.h>#include<stdlib.h>#include<stdint.h>#include<string.h>#include<fcntl.h>#include<unistd.h>#include<sys/uio.h>#include<sys/types.h>#include<errno.h>/** 程序功能:* 在指定进程的可读可写内存区域中搜索字符串 pattern,* 并将其就地替换为 replace(要求 replace 长度 <= pattern 长度)。** 用法:./program <pid> <search_string> <replace_string>*/intmain(int argc, char *argv[]){if (argc != 4) {fprintf(stderr, "Usage: %s <pid> <search_string> <replace_string>\n", argv[0]);return EXIT_FAILURE;}pid_t pid = atoi(argv[1]); /* 目标进程 PID */const char *pattern = argv[2]; /* 要查找的原始字符串 */const char *replace = argv[3]; /* 替换后的字符串 */size_t pattern_len = strlen(pattern); /* 原始串长度 */size_t replace_len = strlen(replace); /* 替换串长度 *//* 由于是“就地覆盖”,替换串不能比原串长,否则会破坏相邻内存 */if (replace_len > pattern_len) {fprintf(stderr, "Error: Replacement string longer than original\n");return EXIT_FAILURE;}/* ---------- 2. 打开目标进程的 maps 文件 ---------- *//* /proc/<pid>/maps 描述了该进程各内存区域的地址范围与权限 */char maps_path[64];snprintf(maps_path, sizeof(maps_path), "/proc/%d/maps", pid);FILE *maps = fopen(maps_path, "r");if (!maps) {perror("fopen maps");return EXIT_FAILURE;}size_t modified_count = 0; /* 统计成功修改的次数 */char line[1024]; /* 每行 maps 内容的缓冲区 *//* ---------- 3. 逐行解析 maps,遍历每个内存区域 ---------- */while (fgets(line, sizeof(line), maps)) {uint64_t start, end; /* 区域起始 / 结束地址 */char perms[5]; /* 权限字段,如 "rw-p" *//* 解析一行:起始地址-结束地址 权限 */if (sscanf(line, "%lx-%lx %4s", &start, &end, perms) != 3)continue;/* 跳过不可读的区域(无法读取内容就无从搜索) */if (perms[0] != 'r')continue;/* 跳过不可写的区域(无法写入就不能修改) */if (perms[1] != 'w')continue;size_t region_size = end - start;if (region_size == 0)continue;/* ---------- 4. 分配本地缓冲区 ---------- */unsigned char *buffer = malloc(region_size);if (!buffer)continue;/* 本地 iovec:数据读到本进程的 buffer 中 */struct iovec local_iov = {.iov_base = buffer,.iov_len = region_size};/* 远程 iovec:从目标进程地址 start 开始读取 */struct iovec remote_iov = {.iov_base = (void *)start,.iov_len = region_size};/* 跨进程读取目标内存到本地 buffer */ssize_t nread = process_vm_readv(pid, &local_iov, 1, &remote_iov, 1, 0);if (nread <= 0) {free(buffer);continue;}/* ---------- 5. 在本地缓冲区中搜索要查找的字符串 ---------- */unsigned char *ptr = buffer;/** memmem:在 ptr 指向的剩余字节中查找 pattern。* 每次找到后令 ptr 前进 pattern_len,继续向后搜索(支持重叠/连续匹配)。*/while ((ptr = memmem(ptr, nread - (ptr - buffer), pattern, pattern_len)) != NULL) {uintptr_t offset = ptr - buffer; /* 命中位置在 buffer 内的偏移 */uintptr_t found_addr = start + offset; /* 换算成目标进程中的真实地址 *//* 准备一个长度为 pattern_len 的写入缓冲 */char *write_buffer = malloc(pattern_len);if (!write_buffer) {perror("malloc");free(buffer);fclose(maps);return EXIT_FAILURE;}memset(write_buffer, 0, pattern_len);memcpy(write_buffer, replace, replace_len); /* 拷入替换串 *//* 本地 iovec:要写入的数据来自 write_buffer */struct iovec w_local_iov = {.iov_base = write_buffer,.iov_len = pattern_len};/* 远程 iovec:写入目标进程的 found_addr 处 */struct iovec w_remote_iov = {.iov_base = (void *)found_addr,.iov_len = pattern_len};/* 将替换内容写回目标进程内存 */ssize_t nwritten = process_vm_writev(pid, &w_local_iov, 1, &w_remote_iov, 1, 0);if (nwritten == (ssize_t)pattern_len) {printf("[+] Modified at: 0x%lx\n", found_addr);modified_count++;} else if (nwritten < 0) {perror("process_vm_writev");}free(write_buffer);ptr += pattern_len; /* 移动到下一次搜索起点 */}free(buffer);}fclose(maps);printf("Finished. Total modified: %zu\n", modified_count);return EXIT_SUCCESS;}