Ptrace允许写入可执行程序段,但process_vm_writev不允许。

4

在使用C语言编辑Linux上正在运行的进程操作码时,我发现我无法通过PID使用process_vm_writev来编辑程序操作码,但是使用ptracepwrite可以实现。 process_vm_writev返回-1,errno显示“Bad address”。但是当我使用相同地址的ptrace时,它成功了。我以root身份运行程序。这是否是GNU / Linux的问题,我应该在GitHub上提交问题吗?process_vm_writev的代码(为了简洁起见,不包括包含):

ssize_t write_pmem(pid_t pid, off_t addr, void* value, size_t size) {
    struct iovec local[1];
    local[0].iov_base = value;
    local[0].iov_len = size;
    struct iovec remote[1];
    remote[0].iov_base = (void*)addr;
    remote[0].iov_len = size;

    return process_vm_writev(pid, local, 1, remote, 1, 0);
}

int main() {
    pid_t pid;
    off_t opcode_ptr = 0x45B5A7;

    printf("PID: ");
    scanf("%d", &pid);

    char nops[5] = {0x90, 0x90, 0x90, 0x90, 0x90};
    ssize_t res = write_pmem(pid, opcode_ptr, &nops, sizeof(nops));

    printf("%ld\n%s", res, strerror(errno));
    /* and it prints:
       -1
       Bad address */
    return 0;
}

/proc/PID/maps 显示该进程中 0x45B5A7 在内存中的区域是 00406000-004f0000 r-xp 00006000(没有写入权限),但我认为 root 用户仍然可以进行写操作。

我想使用新的 process_vm Linux API,所以我想修复这段代码。有什么想法吗?


手册上说:“允许从另一个进程读取或写入的权限由 ptrace 访问模式PTRACE_MODE_ATTACH_REALCREDS 检查来管理;请参见 ptrace(2)。” 手册中的示例没有使用 ptrace 连接到该 PID,但如果您想写入只读内存,则可能需要这样做。也许我误解了它,它只是在谈论是否被允许写入远程内存,而不是页面权限。 - Peter Cordes
1
手册中的注释指出:“这些系统调用旨在通过允许使用单个复制操作交换消息来实现快速消息传递(而不是例如使用共享内存或管道时需要的双重复制)”-因此它们是为消息传递而设计的,而不是为调试而设计的。我不会期望有任何方法可以使用它来编写只读内存。 - Peter Cordes
1个回答

3
它能够使用ptrace而不能使用process_vm_writev的原因是,ptrace在内核中写入内存,而内核使用不同的页表和不同的访问权限,与用户模式进程不同。而process_vm_writev则使用用户模式页表将字节从一个用户空间地址空间复制到另一个用户空间地址空间。

process_vm_writev 是否真的使用 switch_to() 来将目标进程设置为此核心的“当前”进程,或者以其他方式设置硬件来使用其页表而不是调用此函数的调用方的页表? - Peter Cordes
如果这是真的,它使用目标进程的页表,那么它如何读取原始进程的内存?通过内核映射吗?我认为 process_vm_writev 只是检查针对目标进程的页面表的权限,使用软件来决定是否返回 -EFAULT (与 ptrace 一样总是允许写入,可能会触发写时复制例如可执行文件的 .text)。然后在两种情况下,都通过该物理页面的内核映射写入目标进程的内存。 - Peter Cordes
你引用的文本是关于 process_vm_mmap 的,它通过修改页表来创建共享映射(而不是通过访问数据来实现),与实际复制数据的 process_vm_writev 不同。看起来提议中的 process_vm_mmap 没有被合并到主线内核中(至少在我使用的 Arch Linux 桌面上的 6.2 版本中没有),但 process_vm_readv/writev 已经被合并了。 - Peter Cordes
啊,你说得对,我漏掉了那一点。它确实说,“这两个调用都在本地地址空间(由lvec数组描述)和远程空间(由rvec描述)之间复制数据;它们这样做而不通过内核空间移动数据。对于某些类型的流量,它们非常有效,但有一些例外,特别是在复制的数据量很大时。”虽然针对process_vm_writev,但这暗示着它并没有使用内核页表。然而,它并没有描述它是如何实现的。我会继续调查。 - Jessica
process_vm_readv/writev 直接在不同进程的用户空间页面之间进行复制,而不会反弹到内核缓冲区。没有页面表同时具有源和目标的用户空间地址。因此,内核必须手动检查至少一个进程(current 或远程)的映射以检查权限并找到物理地址,从而能够通过内核映射访问它。我认为这些系统调用会改变硬件页表(例如 x86 CR3 寄存器)似乎是不可行的,因为这并没有使任何事情变得更容易。 - Peter Cordes
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接