You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

通过PID读取其他进程内存异常:仅获取零值或换行符

无法读取进程内存内容的问题

我写了两段C代码,尝试通过PID读取其他进程的内存:

第一段代码(allocate_memory.c)

这段代码会在内存中存储一个字符串并保持运行:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main() {
    
    char* ptr = "Hello from allocate_memory!";
    
    printf("Allocated memory address: %p\n", (void*)ptr);
    printf("String written to memory: %s\n", ptr);
    
    // Keep the program running to examine memory
    while(1);
    return 0;
}

第二段代码(read_process_memory.c)

这段代码通过fork创建子进程,尝试读取子进程的内存:

#include <stdio.h>
#include <sys/ptrace.h>
#include <sys/wait.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
#include <signal.h>

#define MEM_DUMP_RANGE 0x10000

int main() {
    pid_t pid = fork();
    if (pid == 0) {
        
        // Child process
        printf("Child process ID: %d\n", getpid());
        execl("./allocate_memory", "allocate_memory", NULL);

    } else {

        // Parent process
        printf("Parent process ID: %d\n", getpid());
        printf("Child process ID: %d\n", pid);

        // wait(NULL);

        // Open file for reading
        char filename[20];
        sprintf(filename, "/proc/%d/mem", pid);
        int fd = open(filename, O_RDONLY);
        if (fd == -1) {
            perror("open");
            return 1;
        }

        // Read memory
        unsigned char* buffer = malloc(MEM_DUMP_RANGE);
        read(fd, buffer, MEM_DUMP_RANGE);

        // Dump memory to file
        FILE* file = fopen("memory_dump_file_proc.txt", "w");
        if (file == NULL) {
            perror("fopen");
            return 1;
        }
        for (unsigned long addr = 0; addr < MEM_DUMP_RANGE; addr++) {
            fprintf(file, "%02x ", buffer[addr]);
            if ((addr + 1) % 16 == 0) fprintf(file, "\n");
        }
        fclose(file);

        // Search for string
        char* search_str = "Hello from allocate_memory!";
        for (unsigned long addr = 0; addr < MEM_DUMP_RANGE; addr++) {
            if (memcmp(&buffer[addr], search_str, strlen(search_str)) == 0) {
                printf("Found string at address: %lx\n", addr);

                // Kill child process
                kill(pid, SIGKILL);
                printf("Child process killed\n");
                return 0;
            }
        }

        close(fd);
        free(buffer);
    }
    return 0;
}

问题现象

运行后,内存转储文件里全是零值或无效内容,无法找到目标字符串:

0x00CED010  30 36 35 32 39 30 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  065290..............................
0x00CED034  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ....................................

问题原因

  1. 未等待子进程初始化完成:父进程fork后立刻读取内存,此时子进程可能还没完成execl加载,目标字符串还没进入内存。
  2. 读取了错误的内存区域:/proc/PID/mem对应进程的整个虚拟地址空间,直接从地址0开始读的是低地址(通常是未映射或无效区域),而目标字符串存储在代码段的高虚拟地址中。
  3. 缺少进程访问权限:读取/proc/PID/mem需要目标进程处于停止状态(比如被ptrace附加),否则无法正确读取有效内存页。

解决方法

方法1:修正/proc/PID/mem读取逻辑

  • 等待子进程启动完成并输出地址
  • 捕获子进程输出的虚拟地址,定位到该地址后读取
  • 确保进程处于可读取状态(可通过ptrace附加暂停进程)

方法2:使用ptrace读取指定地址内存(更可靠)

ptrace是Linux下进程跟踪的标准接口,能安全读取目标进程的指定虚拟地址内容。


修改后的代码(使用ptrace实现)

第一步:编译allocate_memory.c

gcc allocate_memory.c -o allocate_memory

第二步:修改后的读取代码(read_process_memory_ptrace.c)

#include <stdio.h>
#include <sys/ptrace.h>
#include <sys/wait.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>

int main() {
    pid_t pid = fork();
    if (pid == 0) {
        // 子进程:允许父进程ptrace附加
        if (ptrace(PTRACE_TRACEME, 0, NULL, NULL) == -1) {
            perror("ptrace TRACEME");
            exit(1);
        }
        printf("Child process ID: %d\n", getpid());
        execl("./allocate_memory", "allocate_memory", NULL);
        exit(1); // execl失败则退出
    } else {
        // 父进程:等待子进程暂停
        wait(NULL);
        printf("Parent process ID: %d\n", getpid());
        printf("Child process ID: %d\n", pid);

        // 这里需要手动输入子进程输出的字符串地址,或者通过管道捕获输出
        // 示例:假设子进程输出的地址是0x555555554000(实际运行时替换为真实地址)
        unsigned long target_addr = 0x555555554000;
        char* search_str = "Hello from allocate_memory!";
        size_t str_len = strlen(search_str);
        char buffer[256] = {0};

        // 逐字节读取目标地址的内存
        for (size_t i = 0; i < str_len; i++) {
            errno = 0;
            long data = ptrace(PTRACE_PEEKDATA, pid, target_addr + i, NULL);
            if (data == -1 && errno != 0) {
                perror("ptrace PEEKDATA");
                kill(pid, SIGKILL);
                return 1;
            }
            buffer[i] = (char)data;
        }

        // 验证读取结果
        if (strcmp(buffer, search_str) == 0) {
            printf("Successfully read string: %s\n", buffer);
            printf("String located at virtual address: %lx\n", target_addr);
        } else {
            printf("Failed to find the target string\n");
        }

        // 恢复并终止子进程
        ptrace(PTRACE_CONT, pid, NULL, NULL);
        kill(pid, SIGKILL);
        printf("Child process killed\n");
    }
    return 0;
}

运行说明

  1. 先运行allocate_memory,记录它输出的内存地址,比如:
Allocated memory address: 0x555555554000
String written to memory: Hello from allocate_memory!
  1. 修改读取代码中的target_addr为上面的地址,编译并运行:
gcc read_process_memory_ptrace.c -o read_process_memory_ptrace
./read_process_memory_ptrace

另一种自动捕获地址的改进方案

如果不想手动输入地址,可以通过管道捕获子进程的输出,提取地址:

#include <stdio.h>
#include <sys/ptrace.h>
#include <sys/wait.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>
#include <ctype.h>

// 从字符串中提取十六进制地址
unsigned long extract_address(const char* output) {
    const char* addr_str = strstr(output, "0x");
    if (!addr_str) return 0;
    return strtoul(addr_str, NULL, 16);
}

int main() {
    int pipefd[2];
    if (pipe(pipefd) == -1) {
        perror("pipe");
        return 1;
    }

    pid_t pid = fork();
    if (pid == 0) {
        // 子进程:重定向stdout到管道
        close(pipefd[0]);
        dup2(pipefd[1], STDOUT_FILENO);
        close(pipefd[1]);

        if (ptrace(PTRACE_TRACEME, 0, NULL, NULL) == -1) {
            perror("ptrace TRACEME");
            exit(1);
        }
        execl("./allocate_memory", "allocate_memory", NULL);
        exit(1);
    } else {
        // 父进程:读取管道中的子进程输出
        close(pipefd[1]);
        char output[256] = {0};
        read(pipefd[0], output, sizeof(output)-1);
        close(pipefd[0]);

        wait(NULL);
        printf("Parent process ID: %d\n", getpid());
        printf("Child process ID: %d\n", pid);

        unsigned long target_addr = extract_address(output);
        if (target_addr == 0) {
            printf("Failed to extract memory address\n");
            kill(pid, SIGKILL);
            return 1;
        }
        printf("Extracted target address: %lx\n", target_addr);

        char* search_str = "Hello from allocate_memory!";
        size_t str_len = strlen(search_str);
        char buffer[256] = {0};

        for (size_t i = 0; i < str_len; i++) {
            errno = 0;
            long data = ptrace(PTRACE_PEEKDATA, pid, target_addr + i, NULL);
            if (data == -1 && errno != 0) {
                perror("ptrace PEEKDATA");
                kill(pid, SIGKILL);
                return 1;
            }
            buffer[i] = (char)data;
        }

        if (strcmp(buffer, search_str) == 0) {
            printf("Successfully read string: %s\n", buffer);
        } else {
            printf("Failed to find the target string\n");
        }

        ptrace(PTRACE_CONT, pid, NULL, NULL);
        kill(pid, SIGKILL);
        printf("Child process killed\n");
    }
    return 0;
}

内容的提问来源于stack exchange,提问作者rajpoot mhm

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 06:47:32