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

Aaa*_*Bbb 1 linux memory paging x86-64 linux-kernel

我想过编写一个程序集存根来mprotect使用页面地址进行调用,但随后我需要在进程的同一地址空间中运行此存根,而且我不知道该怎么做。还有其他方法吗?

Mar*_*lli 8

是的,只要有适当的权限,您就可以。根据情况,您可能需要CAP_SYS_PTRACE能力。请参阅此文档页面了解/proc/sys/kernel/yama/ptrace_scope更多信息。

使用 GDB 的简单解决方案

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

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

  2. 打开终端并将 GDB 连接到进程gdb --pid PID

  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]
    ...
    
    Run Code Online (Sandbox Code Playgroud)

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

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

    (gdb) call (long)mprotect(0x555555577000, 0x1000, 0x7)
    $1 = 0  <-- return value, 0 == success
    
    Run Code Online (Sandbox Code Playgroud)
  5. 现在detach让进程以修改后的权限运行。

您还可以编写GDB 脚本来自动执行此操作。例如,如果您知道在某个时刻 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
Run Code Online (Sandbox Code Playgroud)

然后在你的终端中:

$ gdb -x yourscript.txt
Run Code Online (Sandbox Code Playgroud)

使用高级手动解决方案ptrace

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

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

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

请参阅我关于“编写最简单的程序集调试器”的答案,以获取更多信息和它的外观的代码示例。

GitHub 上还有一个更复杂的示例:在这种情况下,程序员希望通过ptrace.