本文转载自: http://blog.csdn.net/ixidof/article/details/7673568
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; }
(注:需要包含的头文件与具体的系统有关,比如在本人的机器上还需要包含<sys/reg.h>)
运行该程序,会打印出:
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.
1) PTRACE_TRACEME
形式:ptrace(PTRACE_TRACEME,0 ,0 ,0)
描述:本进程被其父进程所跟踪。其父进程应该希望跟踪子进程。
2) PTRACE_PEEKTEXT, PTRACE_PEEKDATA
形式:ptrace(PTRACE_PEEKTEXT, pid, addr, data)
ptrace(PTRACE_PEEKDATA, pid, addr, data)
描述:从内存地址中读取一个字节,pid表示被跟踪的子进程,内存地址由addr给出,data为用户变量地址用于返回读到的数据。在Linux(i386)中用户代码段与用户数据段重合所以读取代码段和数据段数据处理是一样的。
3) PTRACE_POKETEXT, PTRACE_POKEDATA
形式:ptrace(PTRACE_POKETEXT, pid, addr, data)
ptrace(PTRACE_POKEDATA, pid, addr, data)
描述:往内存地址中写入一个字节。pid表示被跟踪的子进程,内存地址由addr给出,data为所要写入的数据。
4) PTRACE_PEEKUSR
形式:ptrace(PTRACE_PEEKUSR, pid, addr, data)
描述:从 USER区域中读取一个字节,pid表示被跟踪的子进程,USER区域地址由addr给出,data为用户变量地址用于返回读到的数据。USER结构为 core文件的前面一部分,它描述了进程中止时的一些状态,如:寄存器值,代码、数据段大小,代码、数据段开始地址等。在Linux(i386)中通过 PTRACE_PEEKUSER和PTRACE_POKEUSR可以访问USER结构的数据有寄存器和调试寄存器。
5) PTRACE_POKEUSR
形式:ptrace(PTRACE_POKEUSR, pid, addr, data)
描述:往USER区域中写入一个字节,pid表示被跟踪的子进程,USER区域地址由addr给出,data为需写入的数据。
6) PTRACE_CONT
形式:ptrace(PTRACE_CONT, pid, 0, signal)
描述:继续执行。pid表示被跟踪的子进程,signal为0则忽略引起调试进程中止的信号,若不为0则继续处理信号signal。
7) PTRACE_SYSCALL
形式:ptrace(PTRACE_SYS, pid, 0, signal)
描述:继续执行。pid表示被跟踪的子进程,signal为0则忽略引起调试进程中止的信号,若不为0则继续处理信号signal。与PTRACE_CONT不同的是进行系统调用跟踪。在被跟踪进程继续运行直到调用系统调用开始或结束时,被跟踪进程被中止,并通知父进程。
8) PTRACE_KILL
形式:ptrace(PTRACE_KILL,pid)
描述:杀掉子进程,使它退出。pid表示被跟踪的子进程。
9) PTRACE_SINGLESTEP
形式:ptrace(PTRACE_KILL, pid, 0, signle)
描述:设置单步执行标志,单步执行一条指令。pid表示被跟踪的子进程。signal为0则忽略引起调试进程中止的信号,若不为0则继续处理信号signal。当被跟踪进程单步执行完一个指令后,被跟踪进程被中止,并通知父进程。
10) PTRACE_ATTACH
形式:ptrace(PTRACE_ATTACH,pid)
描述:跟踪指定pid 进程。pid表示被跟踪进程。被跟踪进程将成为当前进程的子进程,并进入中止状态。
11) PTRACE_DETACH
形式:ptrace(PTRACE_DETACH,pid)
描述:结束跟踪。 pid表示被跟踪的子进程。结束跟踪后被跟踪进程将继续执行。
12) PTRACE_GETREGS
形式:ptrace(PTRACE_GETREGS, pid, 0, data)
描述:读取寄存器值,pid表示被跟踪的子进程,data为用户变量地址用于返回读到的数据。此功能将读取所有17个基本寄存器的值。
13) PTRACE_SETREGS
形式:ptrace(PTRACE_SETREGS, pid, 0, data)
描述:设置寄存器值,pid表示被跟踪的子进程,data为用户数据地址。此功能将设置所有17个基本寄存器的值。
14) PTRACE_GETFPREGS
形式:ptrace(PTRACE_GETFPREGS, pid, 0, data)
描述:读取浮点寄存器值,pid表示被跟踪的子进程,data为用户变量地址用于返回读到的数据。此功能将读取所有浮点协处理器387的所有寄存器的值。
15) PTRACE_SETFPREGS
形式:ptrace(PTRACE_SETREGS, pid, 0, data)
描述:设置浮点寄存器值,pid表示被跟踪的子进程,data为用户数据地址。此功能将设置所有浮点协处理器387的所有寄存器的值。