文章出处: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作为参数来将修改后的内容写回缓冲区中,此处不再赘述。