目录
1. 基本原理
ptrace用于实现跟踪task相关的功能,主要包括以下几种
PTRACE_TRACEME:用于trace当前task;
PTRACE_PEEKTEXT/PTRACE_PEEKDATA:用于读取指定tracee内存地址所在位置的代码段/数据段的一个WORD,Linux没有独立的代码段和数据段空间,因此,这两种请求是等价的,具体参考官方说明或者ptrace的man page;
PTRACE_PEEKUSR:用于读取tracee USER区域地址偏移处的一个WORD,这里的用户区域保存了进程的寄存器等信息,详细信息参考sys/user.h,这里的偏移必须是按WORD对齐,但是为了维护内核的完整性,对于USER区域的修改是不允许的;
PTRACE_POKETEXT/PTRACE_POKEDATA:复制WORD数据到指定tracee内存地址处,目前这两种请求也是等价的;
PTRACE_POKEUSER:分别复制通用或者浮点寄存器数据到tracer的地址空间,这里的数据格式参考sys/user.h,SPARC系统(针对ASIC架构)的data与addr(ptrace系统调用的两个参数)已经反转,即该系统下,寄存器数据只copy到地址所指的空间。
PTRACE_ATTACH/PTRACE_DETACH:用于附着或者去附着进程,一般在操作task私有空间之前,都需要先执行ATTACH请求,在完成对应操作之后,需要DETACH,如果不DETACH,则会task的私有空间可能会被一直占用,导致无法估量的危险。
其余请求说明参考ptrace理解
官方说明:https://man7.org/linux/man-pages/man2/ptrace.2.html
2.系统调用
(注意:以下源码是基于Linux 5.12.1分析的)
(1)内核系统函数入口
函数定义位置:kernel/ptrace.h
SYSCALL_DEFINE4(ptrace, long, request, long, pid, unsigned long, addr,
unsigned long, data);
参数说明:request:表示的请求类型,详细参考第一节;
pid:表示的是被trace的进程ID;
addr:表示的是被trace的内存地址,这个地址可由readelf/objdump命令获取并计算,如果想执行函数替换,则需要通过这两个命令获取到函数入栈(push)的地址,然后在此地址基础上计算偏移,用于替换对象函数,如果是PTRACE_ATTACH请求,该值可为NULL;
data:用于保存被trace的内存内容,可以是从内存地址读取到的,也可以是给内存地址写入的内容,具体需要看request的值。
(2)过程分析
1)判断是否是trace当前task,如果是,则调用ptrace_traceme,该函数主要是调用security_ptrace_traceme,通过当前task的父进程trace,通过当前进程的用户命名空间
static int ptrace_traceme(void)
{
int ret = -EPERM;
write_lock_irq(&tasklist_lock);
/* Are we already being traced? */
if (!current->ptrace) {
ret = security_ptrace_traceme(current->parent);//该函数调用了security钩子函数cap_trace_traceme
/*
* Check PF_EXITING to ensure ->real_parent has not passed
* exit_ptrace(). Otherwise we don't report the error but
* pretend ->real_parent untraces us right after return.
*/
if (!ret && !(current->real_parent->flags & PF_EXITING)) {
current->ptrace = PT_PTRACED;
ptrace_link(current, current->real_parent);
}
}
write_unlock_irq(&tasklist_lock);
return ret;
}
int cap_ptrace_traceme(struct task_struct *parent