ptrace 函数简介
函数原型
1 |
|
参数意义
request:请求ptrace执行的操作pid:目标进程的IDaddr:目标进程的地址data:根据request的不同而变化,向目标进程写入数据时,data存放要写入的数据的地址;从目标进程中读数据时,data接收返回数据的地址
request参数决定ptrace的行为以及后续的参数是如何被使用的,参数request的常用的值如下:
描述
ptrace()系统调用函数提供了一个进程(the “tracer”监视者)监察和控制另一个进程(the “tracee”被监视者)的方法。并且可以检查和改变tracee进程的内存和寄存器里的数据。它可以用来实现断点调试和系统调用跟踪。
tracee首先需要被附着到tracer。在多线程进程中,每个线程都可以被附着到一个tracer。ptrace命令总是以ptrace(PTARCE_foo,pid,..)的形式发送到tracee进程。pid是tracee线程ID。
一个进程可以通过调用fork函数创建子进程并让子进程执行PTRACE_TRACEME来初始化一个ptrace,然后通常子进程再调用execve()(如果当前进程被ptrace,execve()成功执行后 SIGTRAP信号量会被发送到当前进程)。一个进程也可以使用PTRACE_ATTACH或者PTRACE_SEIZE来跟踪另一个进程。
当进程被跟踪后,每当信号量传给当前进程,甚至信号量被忽略时,tracee会暂停(SIGKILL除外)。tracer会在下次调用waitpid(或者其它wait系统调用)处被通知。该调用会返回一个包含tracee暂停原因信息的状态码。当tracee暂停后,tracer可以使用一系列ptrace请求来查看和修改tracee中的信息。tracer接着可以让tracee继续执行。tracee传递给tracer中的信号量通常被忽略(即使是一个不同的信号)。
当PTRACE_O_TRACEEXEC项未起作用时,所有成功执行execve()的tracee进程会被发送一个 SIGTRAP信号量后暂停,在新程序执行之前,父进程将会取得该进程的控制权。
当tracer结束跟踪后,可以通过调用PTRACE_DETACH让tracee在未被trace下继续执行。
那么,ptrace会在什么时候出现呢?在执行系统调用之前,内核会先检查当前进程是否处于被“跟踪”(traced)的状态。如果是的话,内核暂停当前进程并将控制权交给跟踪进程,使跟踪进程得以察看或者修改被跟踪进程的寄存器。
示例
以下示例若无特殊说明均在该系统中测试
1 | Operating System: Ubuntu 19.04 |
追踪系统调用号
系统调用
操作系统提供了一种标准的服务来让程序员实现对底层硬件和服务的控制(比如文件系统),叫做系统调用(system calls)。当一个程序需要作系统调用的时候,它将相关参数放进系统调用相关的寄存器,然后以不同方式调用,就像一个让程序得以接触到内核模式的窗口,程序将参数和系统调用号交给内核,内核来完成系统调用的执行。
详见附录 不同平台的系统调用方式 不同平台使用的参数寄存器
比如Write(2, “Hello”, 5)的i386汇编形式大概是这样的
1 | movl $4, %eax |
这里的$hello指向的是标准字符串”Hello”。
开始追踪
1 |
|
使用gcc编译该文件gcc ptrace.c -o ptrace 然后运行
ptrcer通过PTRACE_PEEKUSER访问user结构体读取RAX的值 第二个参数指定RAX在user中的偏移量。
在
reg.h中定义了各个变量在user.h中的偏移量,/usr/include/x86_64-linux-gnu/reg.h源码/usr/include/x86_64-linux-gnu/user.h源码
59是
execve的系统调用号,这是该程序调用的第一个系统调用。系统调用号的详细内容,
64位linux请察看 /usr/include/x86_64-linux-gnu/asm/unistd_64.h
32位linux请察看 /usr/include/i386-linux-gnu/asm/unistd_32.h
execl()函数对应的系统调用为__NR_execve,系统调用值为59。父进程通过调用fork()来创建子进程。在子进程中,先运行patrce().请求参数设为PTRACE_TRACE,来告诉内核当前进程被父进程trace,每当有信号量传递到当前进程,该进程会暂停,提醒父进程在wait()调用处继续执行。然后再调用execl()。当execl()函数成功执行后,继续运行之前,SIGTRAP信号量会被发送到该进程,让子进程停止,这时父进程会在wait相关调用处被通知,获取子进程的控制权,可以查看子进程内存和寄存器相关信息。当检查完系统调用之后,调用ptrace并设置参数PTRACE_CONT让子进程继续进行。
读取系统调用参数
1 | ptrace(PTRACE_GETREGS, child, NULL, ®s); |

1 |
|
结果
大部分的代码还是比较好懂,部分需要说明一下
对于PTRACE_STSCALL参数,该参数会像PTRACE_CONT一样使暂停的子进程继续执行,并在子进程下次进行系统调用前或系统调后,向子进程发送SINTRAP信号量,让子进程暂停。
WIFEXITED函数(宏)函数用来检查子进程是暂停还准备退出。
SYS_write被定义在/usr/include/x86_64-linux-gnu/bits/syscall.h里面# define SYS_write __NR_write与unistd.h对应 源码
修改子进程系统调用参数
1 | val = ptrace(PTRACE_PEEKDATA,child,addr,NULL) |

1 |
|
结果
向其它程序注入指令
1 | ptrace(PTRACE_ATTACH, pid, NULL, NULL) |

使pid进程成为被追踪的tracee进程。tracee进程会被发送一个SIGTOP信号量,tracee进程不会立即停止,直到完成本次系统调用。如果要结束追踪,则调用PTRACE_DETACH即可。
debug 设置断点的功能可以通过ptrace实现。原理是ATTACH正在运行的进程使其停止。然后读取该进程的指令寄存器IR(32位x86为EIP,64位的是RIP)内容所指向的指令,备份后替换成目标指令,再使其继续执行,此时被追踪进程就会执行我们替换的指令,运行完注入的指令之后,我们再恢复原进程的IR
,从而达到改变原程序运行逻辑的目的。
1 |
|
1 |
|
结果
在运行ptracer后ptracee被插入一个int 3断点
注意:ptracer应在root权限下运行
附录
不同平台的系统调用方式

不同平台使用的参数寄存器

参考文档
http://man7.org/linux/man-pages/man2/syscall.2.html
http://man7.org/linux/man-pages/man2/ptrace.2.html
https://blog.csdn.net/u012417380/article/details/60470075