文章出处:http://www.linuxjournal.com/article/6100?page=0,2
下面来看一些有意思的事情吧。在下面的例子中,我们要将传递给系统调用的字符串反转。
#include <sys/ptrace.h> #include <sys/types.h> #include <sys/wait.h> #include <unistd.h> #include <linux/user.h> #include <sys/syscall.h> const int long_size = sizeof(long); void reverse(char *str) { int i, j; char temp; for(i = 0, j = strlen(str) - 2; i <= j; ++i, --j) { temp = str[i]; str[i] = str[j]; str[j] = temp; } } void getdata(pid_t child, long addr, char *str, int len) { char *laddr; int i, j; union u { long val; char chars[long_size]; }data; i = 0; j = len / long_size; laddr = str; while(i < j) { data.val = ptrace(PTRACE_PEEKDATA, child, addr + i * 4, NULL); memcpy(laddr, data.chars, long_size); ++i; laddr += long_size; } j = len % long_size; if(j != 0) { data.val = ptrace(PTRACE_PEEKDATA, child, addr + i * 4, NULL); memcpy(laddr, data.chars, j); } str[len] = '\0'; } void putdata(pid_t child, long addr, char *str, int len) { char *laddr; int i, j; union u { long val; char chars[long_size]; }data; i = 0; j = len / long_size; laddr = str; while(i < j) { memcpy(data.chars, laddr, long_size); ptrace(PTRACE_POKEDATA, child, addr + i * 4, data.val); ++i; laddr += long_size; } j = len % long_size; if(j != 0) { memcpy(data.chars, laddr, j); ptrace(PTRACE_POKEDATA, child, addr + i * 4, data.val); } } int main() { pid_t child; child = fork(); if(child == 0) { ptrace(PTRACE_TRACEME, 0, NULL, NULL); execl("/bin/ls", "ls", NULL); } else { long orig_eax; long params[3]; int status; char *str, *laddr; int toggle = 0; while(1) { wait(&status); if(WIFEXITED(status)) break; orig_eax = ptrace(PTRACE_PEEKUSER, child, 4 * ORIG_EAX, NULL); if(orig_eax == SYS_write) { if(toggle == 0) { toggle = 1; params[0] = ptrace(PTRACE_PEEKUSER, child, 4 * EBX, NULL); params[1] = ptrace(PTRACE_PEEKUSER, child, 4 * ECX, NULL); params[2] = ptrace(PTRACE_PEEKUSER, child, 4 * EDX, NULL); str = (char *)calloc((params[2]+1) * sizeof(char)); getdata(child, params[1], str, params[2]); reverse(str); putdata(child, params[1], str, params[2]); } else { toggle = 0; } } ptrace(PTRACE_SYSCALL, child, NULL, NULL); } } return 0; }
ppadala@linux:~/ptrace > ls a.out dummy.s ptrace.txt libgpm.html registers.c syscallparams.c dummy ptrace.html simple.c ppadala@linux:~/ptrace > ./a.out txt.ecartp s.ymmud tuo.a c.sretsiger lmth.mpgbil c.llacys_egnahc c.elpmis lmth.ecartp ymmud
本例中除了用到了我们上面所讨论到的所有概念外,还增加了些新东西。我们使用了PTRACE_POKEDATA来改变数据的值,它与PTRACE_PEEKDATA 的工作方式基本是一样的,只是它能够读 和 写子进程传递给系统调用的参数,而PEEKDATA只能读这些数据。
下面我们来试着分析一下getdata函数:
getdata的调用方式如下:
getdata(child, params[1], str, params[2]);
其中:
child --- 子进程的进程号;
params[1] --- write系统调用的第二个参数,即缓冲区的地址;
str --- 指向动态申请的一段内存空间,用于存放wirte系统调用输出缓冲区中的内容;
params[2] --- write系统调用的第三个参数,即缓冲区的长度;
我们再来回顾一下ptrace系统调用的形式:
long ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data);
其中request参数为PTRACE_PEEKDATA时,ptrace每次可以从子进程的内存地址addr处读出一个字( word )的数据,在32位系统中,一个word的大小为32bit,与long类型同,所以我们通过 j 次( j = 缓冲区长度 / 一次能读出的长度 )循环,外加一次对剩余内容的读取(如果需要),即可完整的获取write系统调用中数据缓冲区的内容;
putdata则是一个反向的过程,它使用了PTRACE_POKEDATA作为参数来将修改后的内容写回缓冲区中,此处不再赘述。