怎么利用Ptrace拦截和模拟Linux系统调用

  介绍

本篇内容介绍了“怎么利用Ptrace拦截和模拟Linux系统调用”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!

  写在前面的话

ptrace(2)这个系统调用一般都跟调试离不开关系,它不仅是类Unix系统中本地调试器监控实现的主要机制,而且它还是strace系统调用常用的实现方法.ptrace()系统调用函数提供了一个进程(示踪剂)监察和控制另一个进程(“tracee”)的方法,它不仅可以监控系统调用,而且还能够检查和改变“tracee”进程的内存和寄存器里的数据,甚至它还可以拦截系统调用。

这里的“拦截”我指的是示踪剂能够改变系统调用参数,改变系统调用的返回值,甚至屏蔽特定的系统调用。这也就意味着,一个示踪剂将能够完全实现自己的系统调用,这就非常有趣了,也就是说,一个示踪剂将可以模拟出一整套操作系统机制,而且这一切都不需要内核提供任何其他帮助。

  但问题在于,一个进程一次只能够绑定一个示踪剂,因此我们无法在调试进程(GDB)的过程中模拟出一套外部操作系统,而另一个问题就是模拟系统调用将耗费更多的资源开销。

  在这篇文章中,我将主要讨论x86 - 64架构下的Linux Ptrace,并且我还会使用到一些特定的Linux扩展。除此之外,我可能会忽略错误检查,但最终发布的完整源码将会解决这些问题。

  strace

  在开始之前,我们先看一看strace的实现骨架.Ptrace一直都没有相应的使用标准,但在不同的操作系统中它的接口都是类似的,尤其是它的核心功能,但多多少少都会有一些细微的差别.Ptrace(2)的原型类似如下:

 long  ptrace (int 请求,pid_t  pid,, void  * addr, void  *数据),

  pid是tracee的进程ID,一个tracee一次只能绑定一个示踪剂,但一个示踪剂可以绑定多个tracee。

  请求域负责选择一个指定的Ptrace函数,例如ioctl(2)接口。对于strace来说,只有下面是必须的:

  PTRACE_TRACEME:它的父进程必须跟踪这个进程。

  PTRACE_SYSCALL:继续运行,但是会在下一个系统调用入口暂停运行。

  PTRACE_GETREGS:获取tracee的寄存器备份。

  另外两个数据域,即addr和数据,它们负责给选定的Ptrace函数提供参数,一般这两个数据都可以忽略,这里我选择传入0。

  strace接口本质上是其他命令的前缀:

 strace 美元;[strace 选项],program (参数)

  我的最小化配置不包含任何参数,所以要做的第一件事就是假设它至少包含一个参数(叉(2)),通过argv传递。在加载目标程序之前,新的进程会告知内核它的父进程将会对它进行跟踪监视,tracee将会被这个Ptrace系统调用挂起:

 pid_tpid =, fork();,开关(pid), {
  ,,,case  1:,/*, error  */,,,,,,,,,致命的(“% s",, strerror (errno));
  ,,,case  0:,/*, child  */,,,,,,,,, ptrace (PTRACE_TRACEME, 0, 0, 0);
  ,,,,,,,execvp (argv [1], argv  +, 1);
  ,,,,,,,致命的(“% s",, strerror (errno));
  }

  父进程将使用等(2)来等待子进程的PTRACE_TRACEME,当等待(2)返回值之后,子进程将会被挂起:

 wait 比例积分微分(pid, 0, 0), 

  在允许子进程继续运行之前,我们将告诉操作系统tracee应该跟它的父进程一起终止。真实场景下的strace实现还需要设置其他的参数,例如PTRACE_O_TRACEFORK:

 ptrace (PTRACE_SETOPTIONS、pid, 0,, PTRACE_O_EXITKILL); 

  捕捉系统调用的循环步骤如下:

  1只,等待进程进入下一次系统调用。

  2只,打印系统调用信息。

  3只,允许系统调用执行,并等待返回结果。

  4只,打印系统调用的返回值。

  PTRACE_SYSCALL请求可以完成等待下一个系统调用以及等待系统调用结束这两个任务,跟之前一样,这里也需要使用等(2)来等待tracee进入特定状态。

 ptrace (PTRACE_SYSCALL、pid, 0, 0);, waitpid (pid, 0, 0), 

  等(2)返回后,线程寄存器中将存储有系统调用号和相应参数。下一步就是收集系统调用信息,在不同的系统架构中这一步的实现方式也不同,在x86 - 64中,系统调用号是通过递交传递的,参数(最大为6)将传递给rdi,肢体重复性劳损症,rdx, r10, r8和r9机型。读取寄存器还需要其他的Ptrace调用,但这里就不需要等(2)了,因为tracee并不会改变状态。

怎么利用Ptrace拦截和模拟Linux系统调用