第一次接触到ptrace
是学习gdb的实现,它作为一个系统调用如其名提供对进程追踪的功能,能在一个进程里观察与控制另一个进程的运行状态,因此也可以作为沙箱保护的工具~
信号
ptrace是使用信号来进行进程间通信,所以先复习下信号,可以使用kill
向进程发送信号:
1 | ubuntu@VM-49-124-ubuntu:~$ kill -l |
Linux下信号为软中断,程序在接收到信号后会中断现有执行,若用户事先绑定了处理例程那么内核将回到用户态调用此例程处理它,否则根据相应类型信号进行默认操作(终止进程或忽略),若程序未终止那么在处理完信号后又会回到中断处继续执行。比如比赛常见到的定时结束进程:
1 | void handler(int signum){ |
ptrace
作用
- 编写动态分析工具,如gdb,strace
- 反追踪,一个进程只能被一个进程追踪(注:一个进程能同时追踪多个进程),若此进程已被追踪,其他基于ptrace的追踪器将无法再追踪此进程,更进一步可以实现子母进程双线执行动态解密代码等更高级的反分析技术
- 代码注入,往其他进程里注入代码。
- 本篇关注点–做沙箱,它也能监控系统调用,因此可限制tracee的系统调用。
说明
其原型如下,根据第一个参数request
表明要执行的操作,第二个参数pid
表明要监视与控制(本篇统一叫作追踪追踪)的进程(其实是线程,后不作区分)ID,第三个与第四个参数是一对,由第一个参数指明其含义。
1 | long ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data); |
在ptrace中有两个角色:
- tracee:被追踪者,它是被监控的进程,通过
ptrace
系统调用的操作作用在它之上。 - tracer:追踪者,它负责监视并处理被追踪者传来的信息。
理所当然的不是任何进程都能追踪其他进程,可追踪又三种情况:
- 拥有root权限的进程是可以追踪所有进程的
- 父进程可以追踪子进程
- 进程可以指定被某个进程极其祖先进程追踪
在使用ptrace
之前需要在两个进程间建立追踪关系,其中tracee可以不做任何事,也可使用prctl
和PTRACE_TRACEME
来进行设置,ptrace编程的主要部分是tracer,它可以通过附着的方式与tracee建立追踪关系,建立之后,可以控制tracee在特定的时候暂停并向tracer发送相应信号,而tracer则通过循环等待waitpid
来处理tracee发来的信号。
建立追踪关系
在进行追踪前需要先建立追踪关系,相关request有如下4个:
1 | PTRACE_TRACEME:tracee表明自己想要被追踪,这会自动与父进程建立追踪关系,这也是唯一能被tracee使用的request,其他的request都由tracer指定。 |
其中建立关系时,tracer使用如下方法:
1 | ptrace(PTRACE_ATTACH, pid, 0, 0); |
当建立追踪关系后,即使tracer
执行了execve
这种追踪关系依然存在。
tracee状态
在ptrace下tracee被定义为两种状态:ptrace-running(即使tracee在系统调用中阻塞也属于running态) 和 ptrace-stopped.(PTRACE_LISTEN
为一种特殊状态,下面提到)
除了PTRACE_ATTACH, PTRACE_SEIZE,PTRACE_TRACEME, PTRACE_INTERRUPT 和 PTRACE_KILL,其他类型的操作都需要tracee为ptrace-stop状态,实际上按照暂停发生的原因,还可以将ptrace-stop状态细分:
- signal-delivery-stop:当由kill等向整个进程发送除SIGKILL外的信号时,内核会任选一个线程去处理这个信号,而使用tgkill会明确的指定哪个线程处理该信号。无论如何,若被选中的线程正在被追踪,该线程将进入
signal-delivery-stop
状态,此时该信号将不会被直接投递给此线程,而是会被转交给tracer,tracer可以选择处理该信号,也可以在恢复tracee执行时将信号(可以为原来的信号也可以自己指定其他信号)交给tracee,这个过程被叫做Signal injection
,tracer恢复tracee运行时可以把收到的信号投递给tracee:1
ptrace(PTRACE_restart, pid, 0, sig) //若sig为0表示不注入信号
- group-stop
- Syscall-stops:如果tracee被PTRACE_SYSCALL或PTRACE_SYSEMU恢复,tracee在进入系统调用前会进入syscall-enter-stop状态,当使用PTRACE_SYSEMU恢复时,该系统调用将不会被执行,不管是使用哪种方式进入的syscall-entry-stop状态,只要再次使用PTRACE_SYSCALL恢复执行,在此次系统调用完成(或被信号中断)时tracee就会进入syscall-exit-stop状态。
- PTRACE_SINGLESTEP stops
- PTRACE_EVENT stops: 当tracer设置了PTRACE_O_TRACE_*选项,在对应事件发生时就会进入
1
2
3
4
5
6
7
8PTRACE_EVENT_VFORK:vfork和clone使用了CLONE_VFORK标识时,在他们返回前暂停
PTRACE_EVENT_FORK:fork和clone退出,信号设置为SIGCHLD,返回前停止。
PTRACE_EVENT_CLONE:停在clone返回前
PTRACE_EVENT_VFORK_DONE:vfork和clone使用了CLONE_VFORK标识时,在他们返回前暂停
PTRACE_EVENT_EXEC
PTRACE_EVENT_EXIT
PTRACE_EVENT_STOP
PTRACE_EVENT_SECCOMP:PTRACE_O_TRACESECCOMP被设置,seccomp规则被触发读写数据
当tracee处于ptrace-stopped
状态时可以读写context和memory:1
2
3
4
5
6
7
8
9
10
11ptrace(PTRACE_PEEKTEXT/PEEKDATA/PEEKUSER, pid, addr, 0);//读数据,TEXT和DATA不做区分,即同义,USER为内核中进程定义的user结构体,定义在/usr/include/sys/user.h
ptrace(PTRACE_POKETEXT/POKEDATA/POKEUSER, pid, addr, long_val);//写数据
ptrace(PTRACE_GETREGS/GETFPREGS, pid, 0, &struct);//读寄存器
ptrace(PTRACE_SETREGS/SETFPREGS, pid, 0, &struct);//写寄存器
ptrace(PTRACE_GETREGSET, pid, NT_foo, &iov);
ptrace(PTRACE_SETREGSET, pid, NT_foo, &iov);
ptrace(PTRACE_GETSIGINFO, pid, 0, &siginfo);//读信号信息
ptrace(PTRACE_SETSIGINFO, pid, 0, &siginfo);//写信号信息
ptrace(PTRACE_GETEVENTMSG, pid, 0, &long_var);//可获取事件信息,对于PTRACE_EVENT_EXIT得到退出状态
//对于PTRACE_EVENT_FORK, PTRACE_EVENT_VFORK,PTRACE_EVENT_VFORK_DONE, and PTRACE_EVENT_CLONE得到新进程的PID
//对于PTRACE_EVENT_SECCOMP得到触发规则后的SECCOMP_RET_DATA。设置选项
使用PTRACE_SETOPTIONS
可设置选项,这样程序触发相应事件时将会执行预定操作,这在安全保护中极其重要:PTRACE_O_flags可为以下值:1
ptrace(PTRACE_SETOPTIONS, pid, 0, PTRACE_O_flags);
当设置PTRACE_O_TRACEFORK, PTRACE_O_TRACEVFORK, or PTRACE_O_TRACECLONE,新进程将仍然被追踪,并且继承父进程的flags,新进程的PID可由PTRACE_GETEVENTMSG获取。1
2
3
4
5
6
7PTRACE_O_EXITKILL:当tracer终止时向tracee发送SIGKILL信号终止tracee,若不设置此选项线程将deattch并恢复运行,这个选项在安全保护中极其重要,不设置它子进程可以通过杀死父进程绕过保护。
PTRACE_O_TRACEEXEC:当tracee执行`execve`时暂停,即使未设置该选项,在新程序开始执行前也会用`SIGTRAP`暂停,此时tracer还有机会决定是否继续trace。
PTRACE_O_TRACEEXIT:当tracee执行`exit`时暂停,此时仍可读寄存器。
PTRACE_O_TRACEFORK:当tracee执行`fork`时暂停,tracer会自动追踪新fork出来的进程。
PTRACE_O_TRACEVFORK:当tracee执行`vfork`时暂停,tracer会自动追踪新vfork出来的进程。
PTRACE_O_TRACECLONE:当执行`clone`时暂停,tracer会自动追踪新clone出来的进程。
PTRACE_O_TRACEVFORKDONE:当下次tracee`vfork`执行完成时暂停。继续运行
当完成读写与设置选项操作以后,可以让tracee恢复执行,而且可以同时设置下次暂停的条件:其中cmd可以为:1
ptrace(cmd, pid, 0, sig);//在tracee为signal-delivery-stop状态且sig非0时,sig将作为signal被注入,否则一般会被忽略,要使tracee从ptrace-stop恢复运行,最好使sig为0.
1
2
3
4
5PTRACE_CONT:恢复tracee执行,若data非0,它将作为一个signal投递给tracee。
PTRACE_LISTEN
PTRACE_DETACH
PTRACE_SYSCALL,PTRACE_SINGLESTEP:两者类似,都像PTRACE_CONT一样恢复tracee执行,更多的前者会让tracee在下一次进入或退出系统调用是暂停,后者会在执行单步指令之后暂停,并且都会在收到信号时也会暂停
PTRACE_SYSEMU,PTRACE_SYSEMU_SINGLESTEP:当前只支持x86沙箱
类似seccomp,只需要在系统调用前判断是否允许该调用即可,相对seccomp为程序开发者自己嵌入的代码,若要对无保护的程序添加保护需要修改源码或为可执行程序打补丁,ptrace可以在不修改被保护程序的前提下对程序进行保护,而且恶意程序是不会使用seccomp降权的,ptrace可以轻松作用到不受信任的程序上,以保护系统安全。
例子
1 |
|
程序运行可以得到结果:
1 | ......... |
逃逸
未正确使用ptrace
的沙箱环境是可以逃逸的。例如:
- 未设置PTRACE_O_EXITKILL 杀掉tracer
kill(-1,SIGKILL)
- 未设置PTRACE_O_TRACECLONE类,使用类fork逃离
- 利用上例方式判断系统调用时序,可通过在系统调用时中断它,来破坏时序
alarm(1) sleep(2)
参考
http://www.man7.org/linux/man-pages/man2/ptrace.2.html
stcs 2016 final