Post

ptrace

ptrace ( process trace )
1
2
3
#include <sys/ptrace.h>
long ptrace(enum \_\_ptrace\_request request, pid\_t pid, void \*addr, void \*data);

ptrace를 호출하는 tracer 프로세스가 타겟 프로세스인 tracee 프로세스를 컨트롤할 수 있도록 하는 함수. gdb에서도 내부적으로는 ptrace를 사용한다. windows에서는 ATTACH에 DebugActiveProcess()를 사용하고, 아직 실행되지 않은 프로그램을 디버거로 실행할 때는 CreateProcess()에서 dwCreationFlags 속성에 DEBUG\_PROCESS를 지정해서 실행한다. 그러나 Unix-based 에서는 둘 다 ptrace()를 사용한다.

man ptrace

PTRACE\_CONT Restart the stopped tracee process. If data is nonzero, it is interpreted as the number of a signal to be delivered to the tracee; otherwise, no signal is delivered. Thus, for example, the tracer can control whether a signal sent to the tracee is delivered or not. (addr is ignored.) PTRACE\_SYSCALL, PTRACE\_SINGLESTEP Restart the stopped tracee as for PTRACE_CONT, but arrange for the tracee to be stopped at the next entry to or exit from a system call, or after execution of a single instruction, respectively. (The tracee will also, as usual, be stopped upon receipt of a signal.) From the tracer’s perspective, the tracee will appear to have been stopped by receipt of a SIGTRAP. So, for PTRACE\_SYSCALL, for example, the idea is to inspect the arguments to the system call at the first stop, then do another PTRACE\_SYSCALL and inspect the return value of the system call at the second stop. The data argument is treated as for PTRACE\_CONT. (addr is ignored.) PTRACE\_CONT는 next enrty까지, PTRACE\_SYSCALL은 system call을 빠져 나올 때까지, PTRACE\_SINGLESTEP은 single instruction만 실행하고 나서 stop한다. 기본적으로 tracee는 signal(SIGTRAP)을 수취(receipt)하면서 stop된다. 그래서 PTRACE\_SYSCALL은 첫번째 stop때 system call argument를 조사하고, 다시 PTRACE\_SYSCALL해서 두번째 stop때 system call의 return value를 조사한다.

레지스터 접근

레지스터에 접근할 때는 주로 PTRACE\_GETREGS를 사용하지만, 다음과 같은 방법으로도 사용 가능하다.

1
2
3
4
5
#include <sys/user.h>
struct user\_regs\_struct regs;
...
regs.orig\_eax = ptrace(PTRACE\_PEEKUSER, tracee, ORIG\_EAX \* 4, NULL);

PTRACE\_PEEKUSER는 addr 파라미터를 tracee의 USER area의 offset으로 사용한다. USER area가 별게 아니고, 그냥 sys/user.h에 정의되어 있는 정보들이다. 레지스터나 프로세스의 다른 정보들. 그래서 ORIG\_EAX \* 4가 tracee의 user\_regs\_struct구조체안의 c orig\_eax값을 가져오는 것. 사실 몰라도 된다.

제약

기본적으로 타인 소유 프로세스에 ptrace하는 것은 불가능하다. 타인 소유 파일을 실행해서 ptrace하는 것은 실행 권한만 있다면 가능하지만, 타인의 권한으로 실행된 프로세스에는 ptrace할 수 없다. 예를들어 setUID 걸린 파일은 타인의 권한으로 실행되기 때문에 TRACEME도 불가능하고, ATTACH도 불가능하다.

PTRACE_TRACEME

setUID가 걸려있는 경우, ptrace(PTRACE\_TRACEME, NULL, NULL, NULL)을 호출하면 exec(file, ...)가 안된다. exec(file, ...)하는 순간 : Operation not permitted 가 출력되면서 종료된다. gdb에서도 마찬가지. 즉 setUID 걸려있으면 PTRACE\_TRACEME를 사용할 수 없으므로, 자식 프로세스로 실행해서 ptrace하는건 안된다. * 그냥 exec(file, ...)만 하는건 당연히 된다.

PTRACE_ATTACH

  • non-root user는 non-child process에 ATTACH가 안된다. ( 일반 user가 ATTACH를 수행할 경우 tracer와 tracee가 부모/자식 관계일 경우에만 가능하다. )
  • 타인 소유 프로세스에 ATTACH는 불가능 하다. * 타인 소유 파일을 내가 실행해서 ATTACH하는건 가능하다. 타인의 권한으로 실행된 프로세스의 경우를 말하는 것.

모든 OS에서 제약이 동일한 것은 아니고, 설정에 따라 다르다. 예를 들어, tracee를 실행한 uid가 tracer를 실행한 uid와 같다면 ATTACH 가능하게 할 수도 있다. 설정은 /proc/sys/kernel/yama/ptrace\_scope

gdb와 ptrace

gdb도 내부적으로 ptrace를 사용하기 때문에, 일반 user로 gdb attach시 ptrace: Operation not permitted 문구가 출력된다.

root로 하니 잘 된다.

  • gdb에서 ATTACH하는 것 말고, 그냥 실행파일 불러와서 run 하는건 일반 user도 가능하다. run하면 불러온 실행파일이 gdb의 child로 실행되기 때문이다.
This post is licensed under CC BY 4.0 by the author.