文章出处:http://www.linuxjournal.com/article/6100
ptrace --- 允许在用户层进行系统调用的拦截与修改;
你是否想知道系统调用是如何被拦截的?
你是否尝试过通过修改系统调用的参数来愚弄内核?
你是否考虑过调试器是如何暂停运行中的进程并将控制权交给你的?
无需复杂的内核编程,ptrace( Process trace )系统调用就可以帮你实现上面的功能。
ptrace提供了一种机制,使得父进程能够观察并控制其他的进程,它可以检查和修改其他进程的内核映像,因此被优先用于实现断点调试和系统调用跟踪。
本文中将主要学习如何拦截系统调用并修改它的参数。在Part II中,将会学习到一些高级技巧 ---设置断点并向运行中的程序中注入代码。我们将会窥探子进程的寄存器及数据段,并修改其中的内容。我们也将讲述一种代码注入方法,使得进程可以停止并执行任意指令。
基础知识
系统调用( system call ) --- 系统调用是沟通应用层与内核之间的桥梁,应用程序通过系统调用可以访问底层硬件和下层服务(如:文件系统等)。当应用程序进行系统调用时,会将参数放入寄存器中并调用0x80软中断。该软中断就像是进入内核模式的一扇门,内核完成参数检查后就开始执行具体的操作了。
在i386体系架构上(本文中的代码都是基于i386架构),系统调用号被放在%eax寄存器中,而系统调用的参数则依次放在%ebx, %ecx, %edx, %esi 和 %edi 寄存器中。
例如:
write( 2, "Hello", 5 );
翻译为汇编语言后大致为如下形式:
movl $4,%eax movl $2,%ebx movl $hello,%ecx movl $5,%edx int $0x80
其中,$hello指向字符串常量“Hello”.
那么,ptrace应该出现在什么位置呢?
在执行系统调用之前,内核会检查进程是否被跟踪( being traced ),若是,内核将暂停被跟踪进程,并将控制权交到跟踪进程,让跟踪进程可以修改被跟踪进程的寄存器。
下面让我们通过一个例子来看看进程是如何工作的:
#include <sys/ptrace.h> #include <sys/types.h> #include <sys/wait.h> #include <unistd.h> #include <linux/user.h> /* For constants ORIG_EAX etc */ int main() { pid_t child; long orig_eax; child = fork(); if(child == 0) { ptrace(PTRACE_TRACEME, 0, NULL, NULL); execl("/bin/ls", "ls", NULL); } else { wait(NULL); orig_eax = ptrace(PTRACE_PEEKUSER, child, 4 * ORIG_EAX, NULL); printf("The child made a " "system call %ld\n", orig_eax); ptrace(PTRACE_CONT, child, NULL, NULL); } return 0; }
运行该程序,会打印出:
The child made a system call 11
一并输出的还有ls命令的执行结果。(系统调用号可以参考/usr/include/asm/unistd.h)
正如你所看到的,父进程fork出一个子进程,并在子进程中执行我们想要跟踪的过程( ls )。在运行exec之前,子进程先调用ptrace且其第一个参数为PTRACE_TRACEME. 这就告诉了内核该进程是一个被跟踪的进程,当它执行execve系统调用时,就会将控制权交到其父进程手中(父进程中通过wait()调用等待来自内核的通知),之后,父进程就可以检查系统调用的参数或者做些其他事情,如查看寄存器等。
系统调用发生时,内核将保存eax寄存器中的原始值(即系统调用号),将ptrace的第一个参数设为PTRACE_PEEKUSER,可以读到用户数据段的数值。
将ptrace的第一个参数设为PTRACE_CONT,可以让子进程中的系统调用继续执行。
ptrace 的参数
long ptrace( enum __ptrace_request request,
pid_t pid,
void *addr,
void *data );
其中,第一个参数决定了ptrace的行为以及其他参数的使用方式,request的值可能是下列中的某一个:
PTRACE_TRACEME,
PTRACE_PEEKTEXT,
PTRACE_PEEKDATA,
PTRACE_PEEKUSER,
PTRACE_POKETEXT,
PTRACE_POKEDATA,
PTRACE_POKEUSER,
PTRACE_GETREGS,
PTRACE_GETFPREGS,
PTRACE_SETREGS,
PTRACE_SETFPREGS,
PTRACE_CONT,
PTRACE_SYSCALL,
PTRACE_SINGLESTEP,
PTRACE_DETACH.
每个值的意义将在本文的余下部分中介绍。