Heng30的博客
搜索 分类 关于 订阅

如何在Linux上修改另一个程序的内存数据?

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"