2025-04-07
在现代的操作系统上,每个进程都有自己的进程空间,采用虚拟内存映射的方式来访问内存。所以一个进程在没有操作系统的帮助下,想要修改另外一个进程的数据几乎是不可能的。不过Linux给我们提供了一个很好的机制,可以通过/proc/<pid>/maps
来查看一个进程的虚拟内存映射。通过/proc/<pid>/mem
来修改一个进程的内存。
下面是/proc/<pid>/maps
输出的内容:
...
7f2e4c517000-7f2e4c519000 rw-p 00000000 00:00 0
7f2e4c519000-7f2e4c51b000 r--p 00000000 00:00 0 [vvar]
7f2e4c51b000-7f2e4c51d000 r--p 00000000 00:00 0 [vvar_vclock]
7f2e4c51d000-7f2e4c51f000 r-xp 00000000 00:00 0 [vdso]
7f2e4c51f000-7f2e4c520000 r--p 00000000 00:1d 25816180 /nix/store/nqb2ns2d1lahnd5ncwmn6k84qfd7vx2k-glibc-2.40-36/lib/ld-linux-x86-64.so.2
7f2e4c520000-7f2e4c549000 r-xp 00001000 00:1d 25816180 /nix/store/nqb2ns2d1lahnd5ncwmn6k84qfd7vx2k-glibc-2.40-36/lib/ld-linux-x86-64.so.2
7f2e4c549000-7f2e4c554000 r--p 0002a000 00:1d 25816180 /nix/store/nqb2ns2d1lahnd5ncwmn6k84qfd7vx2k-glibc-2.40-36/lib/ld-linux-x86-64.so.2
7f2e4c554000-7f2e4c556000 r--p 00035000 00:1d 25816180 /nix/store/nqb2ns2d1lahnd5ncwmn6k84qfd7vx2k-glibc-2.40-36/lib/ld-linux-x86-64.so.2
7f2e4c556000-7f2e4c558000 rw-p 00037000 00:1d 25816180 /nix/store/nqb2ns2d1lahnd5ncwmn6k84qfd7vx2k-glibc-2.40-36/lib/ld-linux-x86-64.so.2
7ffcc08d0000-7ffcc08f2000 rw-p 00000000 00:00 0 [stack]
ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0 [vsyscall]
...
第一步是逐行解析maps
文件的内容:
typedef struct {
unsigned long start_addr;
unsigned long end_addr;
char permissions[5];
unsigned long offset;
char device[8];
unsigned long inode;
char path[256];
} mm_info_t;
mm_info_t *parse_mm_info(const char *line) {
mm_info_t *info = calloc(1, sizeof(mm_info_t));
assert(info);
int result = sscanf(line, "%lx-%lx %4s %lx %s %ld %255[^\n]",
&info->start_addr, &info->end_addr, info->permissions,
&info->offset, info->device, &info->inode, info->path);
if (result < 6) {
fprintf(stderr, "Failed to parse the line.\n");
return NULL;
}
return info;
}
mm_info_t **process_mm_infos(pid_t pid) {
mm_info_t **infos = calloc(MAX_MM_INFO_COUNT, sizeof(mm_info_t *));
assert(infos);
char maps_path[64];
sprintf(maps_path, "/proc/%d/maps", pid);
FILE *fp = fopen(maps_path, "r");
if (!fp) {
perror("fopen");
return NULL;
}
int i = 0;
char line[1024];
for (; fgets(line, sizeof(line), fp) && i < MAX_MM_INFO_COUNT;) {
mm_info_t *info = parse_mm_info(line);
if (info) {
infos[i] = info;
i++;
}
}
assert(i < MAX_MM_INFO_COUNT);
fclose(fp);
return infos;
}
第二步是扫描内存:
int scan_mem(pid_t pid, mm_info_t *info, const char *target,
unsigned long *save_addr) {
char mem_file[64];
sprintf(mem_file, "/proc/%d/mem", pid);
int fd = open(mem_file, O_RDONLY);
if (fd < 0) {
perror("scan_mem open");
return -1;
}
if (lseek(fd, info->start_addr, SEEK_SET) == -1) {
perror("scan_mem lseek");
close(fd);
return -1;
}
char buffer[1024];
int offset = 0, start_addr = info->start_addr;
while (start_addr < info->end_addr) {
ssize_t n = read(fd, buffer, sizeof(buffer));
if (n == -1) {
perror("scan_mem read");
close(fd);
return -1;
} else if (n == 0) {
break;
}
for (int i = 0; i < sizeof(buffer); i++, start_addr++) {
if (target[offset] == '\0') {
*save_addr = start_addr - strlen(target);
printf("Found \"%s\" in %s 0x%lx\n", target, info->path,
*save_addr);
return 0;
}
if (target[offset] == buffer[i]) {
offset++;
} else {
offset = 0;
}
}
}
close(fd);
return -1;
}
第三步是修改内存:
int overwrite_mem(pid_t pid, unsigned long addr, const char *target) {
char mem_file[64];
sprintf(mem_file, "/proc/%d/mem", pid);
int fd = open(mem_file, O_WRONLY);
if (fd < 0) {
perror("overwrite_mem open");
return -1;
}
if (lseek(fd, addr, SEEK_SET) == -1) {
perror("overwrite_mem lseek");
close(fd);
return -1;
}
int wlen = write(fd, target, strlen(target) + 1);
if (wlen == -1) {
perror("overwrite_mem wirte");
close(fd);
return -1;
}
close(fd);
return wlen;
}
// mm_read_write.c
#include <assert.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#define MAX_MM_INFO_COUNT 1024
// 55a1d5157000-55a1d51ba000 rw-p 00000000 00:00 0 [heap]
typedef struct {
unsigned long start_addr;
unsigned long end_addr;
char permissions[5];
unsigned long offset;
char device[8];
unsigned long inode;
char path[256];
} mm_info_t;
mm_info_t *parse_mm_info(const char *line) {
mm_info_t *info = calloc(1, sizeof(mm_info_t));
assert(info);
int result = sscanf(line, "%lx-%lx %4s %lx %s %ld %255[^\n]",
&info->start_addr, &info->end_addr, info->permissions,
&info->offset, info->device, &info->inode, info->path);
if (result < 6) {
fprintf(stderr, "Failed to parse the line.\n");
return NULL;
}
return info;
}
mm_info_t **process_mm_infos(pid_t pid) {
mm_info_t **infos = calloc(MAX_MM_INFO_COUNT, sizeof(mm_info_t *));
assert(infos);
char maps_path[64];
sprintf(maps_path, "/proc/%d/maps", pid);
FILE *fp = fopen(maps_path, "r");
if (!fp) {
perror("fopen");
return NULL;
}
int i = 0;
char line[1024];
for (; fgets(line, sizeof(line), fp) && i < MAX_MM_INFO_COUNT;) {
mm_info_t *info = parse_mm_info(line);
if (info) {
infos[i] = info;
i++;
}
}
assert(i < MAX_MM_INFO_COUNT);
fclose(fp);
return infos;
}
void free_mm_infos(mm_info_t **infos) {
for (int i = 0; i < MAX_MM_INFO_COUNT; i++) {
if (!infos[i])
break;
free(infos[i]);
}
free(infos);
}
void dump_mm_info(const mm_info_t *info) {
printf("Start Address: 0x%lx\n", info->start_addr);
printf("End Address: 0x%lx\n", info->end_addr);
printf("Permissions: %s\n", info->permissions);
printf("Offset: 0x%lx\n", info->offset);
printf("Device: %s\n", info->device);
printf("Inode: %ld\n", info->inode);
if (info->path[0]) {
printf("Path: %s\n", info->path);
} else {
printf("Path: (none)\n");
}
}
void dump_mm_infos(mm_info_t **infos) {
for (int i = 0; i < MAX_MM_INFO_COUNT; i++) {
if (!infos[i])
return;
dump_mm_info(infos[i]);
printf("\n");
}
}
int scan_mem(pid_t pid, mm_info_t *info, const char *target,
unsigned long *save_addr) {
char mem_file[64];
sprintf(mem_file, "/proc/%d/mem", pid);
int fd = open(mem_file, O_RDONLY);
if (fd < 0) {
perror("scan_mem open");
return -1;
}
if (lseek(fd, info->start_addr, SEEK_SET) == -1) {
perror("scan_mem lseek");
close(fd);
return -1;
}
char buffer[1024];
int offset = 0, start_addr = info->start_addr;
while (start_addr < info->end_addr) {
ssize_t n = read(fd, buffer, sizeof(buffer));
if (n == -1) {
perror("scan_mem read");
close(fd);
return -1;
} else if (n == 0) {
break;
}
for (int i = 0; i < sizeof(buffer); i++, start_addr++) {
if (target[offset] == '\0') {
*save_addr = start_addr - strlen(target);
printf("Found \"%s\" in %s 0x%lx\n", target, info->path,
*save_addr);
return 0;
}
if (target[offset] == buffer[i]) {
offset++;
} else {
offset = 0;
}
}
}
close(fd);
return -1;
}
int overwrite_mem(pid_t pid, unsigned long addr, const char *target) {
char mem_file[64];
sprintf(mem_file, "/proc/%d/mem", pid);
int fd = open(mem_file, O_WRONLY);
if (fd < 0) {
perror("overwrite_mem open");
return -1;
}
if (lseek(fd, addr, SEEK_SET) == -1) {
perror("overwrite_mem lseek");
close(fd);
return -1;
}
int wlen = write(fd, target, strlen(target) + 1);
if (wlen == -1) {
perror("overwrite_mem wirte");
close(fd);
return -1;
}
close(fd);
return wlen;
}
int main(int argc, char *argv[]) {
if (argc != 2) {
fprintf(stderr, "Usage: %s <pid>\n", argv[0]);
return -1;
}
pid_t pid = atoi(argv[1]);
mm_info_t **infos = process_mm_infos(pid);
// dump_mm_infos(infos);
int is_found = 0;
unsigned long save_addr = 0;
const char *scan_target = "hello world";
const char *overwrite_target = "foobar";
const char *paths[] = {"[heap]", "[stack]"};
for (int j = 0; j < sizeof(paths) / sizeof(paths[0]); j++) {
for (int i = 0; i < MAX_MM_INFO_COUNT; i++) {
if (!infos[i])
break;
int ret = memcmp(infos[i]->path, paths[j], strlen(paths[j]));
if (ret)
continue;
ret = scan_mem(pid, infos[i], scan_target, &save_addr);
if (ret)
continue;
is_found = 1;
ret = overwrite_mem(pid, save_addr, overwrite_target);
if (ret == strlen(overwrite_target) + 1) {
printf("Found \"%s\" in %s. Overwrite to \"%s\" successfully\n",
scan_target, paths[j], overwrite_target);
} else {
fprintf(stderr,
"Found \"%s\" in %s. But overwrite to \"%s\" failed\n",
scan_target, paths[j], overwrite_target);
}
return 0;
}
}
if (!is_found) {
fprintf(stderr, "No found \"%s\" in %s and %s\n", scan_target, paths[0],
paths[1]);
}
return 0;
}
// mm_test.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
const char *str = "hello world";
char *buffer = calloc(1, 1024);
memcpy(buffer, str, strlen(str));
while (1) {
printf("pid = %d, addr = %p, buffer = \"%s\"\n", getpid(), buffer, buffer);
sleep(3);
}
return 0;
}
all: mm_read_write.c mm_test.c
gcc -o mm_test mm_test.c
gcc -o mm_read_write mm_read_write.c
clean:
-rm -f mm_read_write mm_test
编译:make
在一个终端运行:./mm_test
pid = 1311589, addr = 0x277fc2a0, buffer = "hello world"
sudo ./mm_read_write 1311589
Found "hello world" in [heap] 0x277fc2a0
Found "hello world" in [heap]. Overwrite to "foobar" successfully
mm_test
的输出会变成pid = 1311589, addr = 0x277fc2a0, buffer = "foobar"