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