您可以更改 运行 进程的页面权限吗?

Can you change the page permissions of a running process?

我想写一个程序集存根来调用 mprotect 页面地址,但是我需要 运行 这个存根在进程的相同地址 space 中,我不知道该怎么做。还有其他方法吗?

是的,您可以,只要有适当的权限。根据情况,您可能需要 CAP_SYS_PTRACE 功能。请参阅 this documentation page 关于 /proc/sys/kernel/yama/ptrace_scope 以了解更多信息。

使用 GDB 的简单解决方案

最简单的方法是使用调试器,例如GDB:

  1. 运行目标进程并获取其PID。您可以使用 pshtoppidof 和类似命令找到进程的 PID。

  2. 打开终端并使用 gdb --pid PID.

    将 GDB 附加到进程
  3. 在 GDB 提示符下,检查内存映射:

    (gdb) info inferiors
      Num  Description       Connection           Executable
    * 1    process 19433     2 (native)           /usr/bin/ls
    (gdb) !cat /proc/19433/maps
    555555554000-555555558000 r--p 00000000 00:18 5154601                    /usr/bin/ls
    555555558000-55555556d000 r-xp 00004000 00:18 5154601                    /usr/bin/ls
    55555556d000-555555576000 r--p 00019000 00:18 5154601                    /usr/bin/ls
    555555577000-555555579000 rw-p 00022000 00:18 5154601                    /usr/bin/ls
    555555579000-55555557a000 rw-p 00000000 00:00 0                          [heap]
    7ffff7fcc000-7ffff7fd0000 r--p 00000000 00:00 0                          [vvar]
    ...
    

    注意:我在这里使用!cat /proc/PID/maps而不是info proc mappings,因为不幸的是后者没有显示权限。

  4. 调用mprotect() libc 函数更改您想要的页面的权限。例如这里我将单个页面更改为 RWX:

    (gdb) call (long)mprotect(0x555555577000, 0x1000, 0x7)
     = 0  <-- return value, 0 == success
    
  5. 现在 detach 并让进程 运行 具有修改后的权限。

您也可以编写一个 GDB script 来自动为您执行此操作。例如,如果您知道 RDI 在某些时候会在您要触摸的页面中包含一个地址,您可以这样做:

file path/to/your/elf
# or alternatively `attach PID`

# Set a breakpoint to some known address (assuming your ELF is not position independent).
# You could also do `break some_symbol+offset` if there are symbols.

break *0x123450
command 1
    set $page = $rdi & ~0xfff
    call (long)mprotect($page, 0x1000, 0x7)
    detach
end

run

然后在您的终端中:

$ gdb -x yourscript.txt

使用 ptrace

的高级手动解决方案

或者,您可以利用 GDB 在后台使用的相同低级工具来编写自动为您执行此操作的程序:ptrace 系统调用。您可以编写一个执行以下操作的程序:

  1. 通过 fork + execve(或者只是 运行 在另一个终端中生成目标进程)。
  2. PTRACE_ATTACH就可以了。
  3. 使用 PTRACE_GETREGS(保存初始寄存器状态以备后用)、PTRACE_PEEKDATA(检查内存)等查看
  4. 操纵被跟踪者的记忆来编写一个简单的存根,为您调用 mprotect (PTRACE_POKEDATA)。
  5. 强制被跟踪者执行那个小存根。
  6. 将一切重置为正常(恢复注册,可能还删除存根等)。
  7. 从tracee分离并让它继续执行。

这当然比基于调试器的方法难得多,但试验起来会很有趣。您基本上是在编写自己的非常专业的调试器。

查看我在 上的这个回答,了解更多信息和代码示例。

这里还有 a more complex example on GitHub:在这种情况下,程序员希望通过 ptrace.

关闭已经 运行ning 进程的任意文件描述符