[关闭]
@oro-oro 2015-09-17T18:04:53.000000Z 字数 9695 阅读 2585

1. Ptrace For X86[1]

DroidHOOK


目的:初步了解 Linux 下的 ptrace
平台:虚拟机 Ubuntu32


1. man ptrace[2]

1.1 命名

ptrace - process trace

1.2 定义

  1. #include <sys/ptrace.h>
  2. long ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data);
  3. // request,表示请求的操作
  4. // pid,表示请求的目标进程
  5. // addr,内存地址
  6. // data,操作的数据

1.3 简介

ptrace() 提供了跟踪和调试的功能。它允许一个进程(跟踪进程tracer)去控制另外一个进程(被跟踪进程tracee)。

tracer可以观察和控制tracee的运行,可以查看和改变tracee的内存和寄存器。它主要用来实现断点调试和系统调用跟踪。

2 ATTACH

tracer如果要跟踪一个tracee,首先得附上(attach)tracee。
注:tracee不能同时被2个tracer附加,第二个tracer附加会失败。

当 tracee 被附加上后,它会每收到一个信号都会停止一次,即使这个信号将会被忽略(除了 SIGKILL,这个信号通常都有效)。此时 tracer 可使用 wait/waitpid (这个调用会返回一个状态值,表示 tracee 停止的原因)或发送一个 SIGCHLD 信号,检查停止状态的 tracee,去分析和修改 tracee;根据情况终止它或者让 tracee 继续运行,可选地忽略已发送的信号(或者甚至发送一个不同的信号替换掉)。当 tracer 完成跟踪,可以通过 PTRACE_DETACH 让 tracee 回到一般、非跟踪模式下继续运行。

附上一个进程有两种方法:

2.1 直接附上正在运行的进程

tracer使用PTRACE_ATTACH或PTRACE_SEIZE直接附上正在运行的tracee,用PTRACE_DETACH可退出调试。注意:这种情况下,tracer需要root权限!

例子:让一个死循环跳转到一个fun函数里面,不断打印 hello。
IP(instruction pointer)指令指针寄存器。存放当前指令的下一条指令,CPU 将要执行的下一条指令。

EIP 是32位,RIP是64位。
获取当前的 eip,然后,修改eip为fun函数的地址,则tracee执行下一条指令fun了。

https://git.oschina.net/acgmohu/PtraceX86Examples/blob/master/tracee3.c

  1. #include <stdio.h> /* printf */
  2. #include <unistd.h> /* sleep */
  3. int fun()
  4. {
  5. int i = 0;
  6. while(i < 10) {
  7. printf("hello\n");
  8. sleep(1);
  9. i++;
  10. }
  11. return 0;
  12. }
  13. void main()
  14. {
  15. while(1);
  16. }

https://git.oschina.net/acgmohu/PtraceX86Examples/blob/master/tracer3.c

  1. /**
  2. * This program need root, run with sudo.
  3. */
  4. #include <stdio.h> /* scanf */
  5. #include <sys/user.h> /* user_regs_struct */
  6. #include <sys/ptrace.h> /* ptrace */
  7. #include <sys/types.h> /* wait */
  8. #include <sys/wait.h> /* wait */
  9. #include <unistd.h> /* sleep */
  10. void main()
  11. {
  12. int status = 0, pid;
  13. int addr = 0x0804844d; // tracee fun address
  14. struct user_regs_struct uregs;
  15. printf("pid : ");
  16. scanf("%d", &pid);
  17. ptrace(PTRACE_ATTACH, pid, 0, 0);
  18. wait(&status);
  19. if(WIFSTOPPED(status))
  20. printf("tracee has stopped...\n");
  21. ptrace(PTRACE_GETREGS, pid, 0, &uregs);
  22. printf("eip: %x\n", uregs.eip);
  23. uregs.eip = addr;
  24. printf("eip: %x\n", uregs.eip);
  25. ptrace(PTRACE_SETREGS, pid, 0, &uregs);
  26. ptrace(PTRACE_DETACH, pid, 0, 0);
  27. printf("tracer detach\n");
  28. return;
  29. }

2.2 附加它的子进程

tracer使用fork获得它的子进程,子进程执行PTRACE_TRACEME,再执行execve启动要跟踪的程序。

例子1:PTRACE_TRACEME 在 tracee 中:
https://git.oschina.net/acgmohu/PtraceX86Examples/blob/master/tracee1.c

  1. #include <sys/ptrace.h> /* ptrace */
  2. #include <stdio.h> /* printf */
  3. #include <unistd.h> /* sleep */
  4. int main()
  5. {
  6. ptrace(PTRACE_TRACEME, 0, 0, 0);
  7. int i = 0;
  8. while(i < 10) {
  9. printf("hello %d.\n", i++);
  10. sleep(1);
  11. }
  12. return 0;
  13. }

https://git.oschina.net/acgmohu/PtraceX86Examples/blob/master/tracer1.c

  1. /* `tracer' parent */
  2. #include <unistd.h> /* fork, execl */
  3. #include <stdlib.h> /* exit */
  4. #include <sys/types.h> /* kill, wait */
  5. #include <signal.h> /* kill */
  6. #include <sys/wait.h> /* wait */
  7. #include <sys/ptrace.h> /* ptrace */
  8. #include <stdio.h> /* printf */
  9. int main()
  10. {
  11. int pid, status;
  12. if((pid = fork()) == 0) {
  13. execl("./tracee1", "tracee1", 0, NULL);
  14. printf("exec failed...\n");
  15. exit(0);
  16. } else {
  17. sleep(1);
  18. printf("send a signal SIGINT to interrupt tracee and waiting ...\n");
  19. kill(pid, SIGINT);
  20. wait(&status);
  21. if(WIFSTOPPED(status))
  22. printf("tracee has stopped...\n");
  23. printf("tracer sleep 5 second ... \n");
  24. sleep(5);
  25. printf("ptrace continue ... \n");
  26. ptrace(PTRACE_CONT, pid, 0, 0);
  27. /* Will not come out of this wait because
  28. * child does not terminate.
  29. */
  30. wait(&status);
  31. if(WIFEXITED(status))
  32. printf("tracee has exited.\n");
  33. }
  34. printf("tracer exit.\n");
  35. }

例子2:PTRACE_TRACEME 在 tracer 中:
https://git.oschina.net/acgmohu/PtraceX86Examples/blob/master/tracee2.c

  1. #include <sys/ptrace.h> /* ptrace */
  2. #include <stdio.h> /* printf */
  3. #include <unistd.h> /* sleep */
  4. int main()
  5. {
  6. int i = 0;
  7. while(i < 10) {
  8. printf("hello %d.\n", i++);
  9. sleep(1);
  10. }
  11. return 0;
  12. }

https://git.oschina.net/acgmohu/PtraceX86Examples/blob/master/tracer2.c

  1. /* `tracer' parent */
  2. #include <unistd.h> /* fork, execl */
  3. #include <stdlib.h> /* exit */
  4. #include <sys/types.h> /* kill, wait */
  5. #include <signal.h> /* kill */
  6. #include <sys/wait.h> /* wait */
  7. #include <sys/ptrace.h> /* ptrace */
  8. #include <stdio.h> /* printf */
  9. int main()
  10. {
  11. int pid, status;
  12. if((pid = fork()) == 0) {
  13. ptrace(PTRACE_TRACEME, pid, 0, 0);
  14. execl("./tracee2", "tracee2", 0, NULL);
  15. printf("exec failed...\n");
  16. exit(0);
  17. } else {
  18. wait(&status);
  19. if(WIFSTOPPED(status))
  20. printf("tracee has stopped...\n");
  21. printf("tracer sleep 5 second ... \n");
  22. sleep(5);
  23. printf("ptrace continue ... \n");
  24. ptrace(PTRACE_CONT, pid, 0, 0);
  25. /* Will not come out of this wait because
  26. * child does not terminate.
  27. */
  28. wait(&status);
  29. if(WIFEXITED(status))
  30. printf("tracee has exited.\n");
  31. }
  32. printf("tracer exit.\n");
  33. }

3. GETREGS & SETREGS

获取和修改寄存器。

PTRACE_GETREGS, PTRACE_GETFPREGS

Copy  the  tracee's  general-purpose  or  floating-point  registers,  respectively,  to  the address data in the tracer.  See <sys/user.h> for information on the format of this data.  (addr is ignored.)  Note that SPARC systems  have  the  meaning  of  data  and  addr  reversed;  that  is,  data  is ignored and the registers are copied to the address addr.  PTRACE_GETREGS and PTRACE_GETFPREGS are not present on all architectures.

PTRACE_SETREGS, PTRACE_SETFPREGS

Modify the tracee's general-purpose or floating-point registers, respectively, from the address data in the tracer.   As  for PTRACE_POKEUSER,  some general-purpose register modifications may be disallowed.  (addr is ignored.)  Note that SPARC systems  have the meaning of data and addr reversed; that is, data is ignored and the registers are  copied  from  the  address  addr. PTRACE_SETREGS and PTRACE_SETFPREGS are not present on all architectures.

子进程是一个死循环,如果如果ebx 等于 245,则退出循环。
tracer 停止 tracee,获取 ebx 的值,并将这个值修改为 245,使 tracee 退出循环。

https://git.oschina.net/acgmohu/PtraceX86Examples/blob/master/tracer4.c

  1. #include <sys/ptrace.h>
  2. #include <signal.h>
  3. #include <stdio.h>
  4. #include <sys/user.h>
  5. #include <sys/types.h>
  6. #include <sys/wait.h>
  7. void main()
  8. {
  9. int i = 0, status = 0, pid;
  10. struct user_regs_struct uregs;
  11. if ((pid=fork())==0) {
  12. ptrace(PTRACE_TRACEME, 0, 0, 0);
  13. execl("./tracee4", "tracee4", 0, NULL);
  14. printf("execl error...\n");
  15. } else {
  16. wait(&status);
  17. ptrace(PTRACE_CONT, pid, 0, 0);
  18. sleep(1);
  19. //child should have got into the loop by
  20. //this time!
  21. kill(pid, SIGINT);
  22. wait(&status);
  23. ptrace(PTRACE_GETREGS, pid, 0, &uregs);
  24. printf("ebx = %d\n", uregs.ebx);
  25. uregs.ebx = 245;
  26. ptrace(PTRACE_SETREGS, pid, 0, &uregs);
  27. ptrace(PTRACE_CONT, pid, 0, 0);
  28. wait(&status);
  29. if (WIFEXITED(status)) {
  30. printf("tracee has exited.\n");
  31. }
  32. printf("tracer exits.\n");
  33. }
  34. }

https://git.oschina.net/acgmohu/PtraceX86Examples/blob/master/tracee4.c

  1. #include <stdio.h>
  2. void main()
  3. {
  4. printf("tracee starts...\n");
  5. asm("pushl %ebx\n\t"
  6. "movl $143, %ebx\n\t"
  7. "L1: cmpl $245, %ebx\n\t"
  8. "jne L1\n\t"
  9. "popl %ebx\n\t");
  10. printf("tracee outside loop...\n");
  11. }

4. PEEKDATA & POKEDATA

读写数据段数据。

PTRACE_PEEKTEXT, PTRACE_PEEKDATA

Read a word at the address addr in the tracee's memory, returning the word as the result of the ptrace() call. Linux does not have separate text and data address spaces, so these two requests are currently equivalent. (data is ignored.) 

PTRACE_POKETEXT, PTRACE_POKEDATA

Copy the word data to the address addr in the tracee's memory. As for PTRACE_PEEKTEXT and PTRACE_PEEKDATA, these two requests are currently equivalent. 

例子:
tracee5 中,是个死循环,只有 i 等于 245才能退出循环。
tracer5 将tracee5 中的 i 的数据修改为 245,使之退出循环。
使用 nm tracee5,可以查看 tracee5 中参数 i 的数据(objdump,readelf也可以)。

https://git.oschina.net/acgmohu/PtraceX86Examples/blob/master/tracee5.c

  1. #include <stdio.h>
  2. int i = 143;
  3. void main()
  4. {
  5. printf("tracee starts...\n");
  6. while(i != 245);
  7. printf("tracee outside loop...\n");
  8. }

https://git.oschina.net/acgmohu/PtraceX86Examples/blob/master/tracer5.c

  1. #include <sys/ptrace.h>
  2. #include <signal.h>
  3. #include <stdio.h>
  4. #include <sys/user.h>
  5. #include <sys/types.h>
  6. #include <sys/wait.h>
  7. void main()
  8. {
  9. int i = 0, status = 0, pid;
  10. struct user_regs_struct uregs;
  11. int data;
  12. int addr = 0x0804a020;
  13. if ((pid=fork())==0) {
  14. ptrace(PTRACE_TRACEME, 0, 0, 0);
  15. execl("./tracee5", "tracee5", 0, NULL);
  16. printf("execl error...\n");
  17. } else {
  18. wait(&status);
  19. ptrace(PTRACE_CONT, pid, 0, 0);
  20. sleep(1);
  21. //child should have got into the loop by
  22. //this time!
  23. kill(pid, SIGINT);
  24. wait(&status);
  25. //segment of code which alters value of
  26. //a variable in a child process. Identical
  27. //to code which reads and changes register value.
  28. //`addr' is address of variable being accessed.
  29. data = ptrace(PTRACE_PEEKDATA, pid, addr, 0);
  30. printf("data = %d\n", data);
  31. data = 245;
  32. ptrace(PTRACE_POKEDATA, pid, addr, data);
  33. ptrace(PTRACE_CONT, pid, 0, 0);
  34. wait(&status);
  35. printf("tracer exits.\n");
  36. }
  37. }

5. PTRACE_SINGLESTEP

这是传说中的单步调试了。

tracee6 i 初始值为 10,默认打印10.
i 的地址为addr。
tracer6 单步执行 tracee6,如果 addr 中的数据的值为10,则修改为2341.
最终tracee6 打印2341.

https://git.oschina.net/acgmohu/PtraceX86Examples/blob/master/tracee6.c

  1. #include <stdio.h>
  2. int i;
  3. void main()
  4. {
  5. i = 10;
  6. printf("child: i = %d\n", i);
  7. }

https://git.oschina.net/acgmohu/PtraceX86Examples/blob/master/tracer6.c

  1. #include <sys/ptrace.h>
  2. #include <signal.h>
  3. #include <stdio.h>
  4. #include <sys/user.h>
  5. #include <sys/types.h>
  6. #include <sys/wait.h>
  7. void main()
  8. {
  9. int i = 0, status = 0, pid;
  10. struct user_regs_struct uregs;
  11. int dat;
  12. int addr = 0x0804a024;
  13. if ((pid=fork())==0) {
  14. ptrace(PTRACE_TRACEME, 0, 0, 0);
  15. execl("./tracee6", "tracee6", 0, NULL);
  16. printf("execl error...\n");
  17. } else {
  18. //wait for child to stop at the `exec'
  19. wait(&status);
  20. while(1) {
  21. // do just one instruction
  22. ptrace(PTRACE_SINGLESTEP, pid, 0, 0);
  23. wait(&status);
  24. if(WIFEXITED(status))
  25. break;
  26. // `addr' is address of `i' in child process
  27. dat = ptrace(PTRACE_PEEKDATA, pid, addr, 0);
  28. if(dat == 10) {
  29. ptrace(PTRACE_POKEDATA, pid, addr, 2341);
  30. ptrace(PTRACE_CONT, pid, 0, 0);
  31. break;
  32. }
  33. }
  34. wait(&status);
  35. if(WIFEXITED(status))printf("child over\n");
  36. printf("tracer exits.\n");
  37. }
  38. }

6. PTRACE_SYSCALL

ptrace 有跟踪系统调用的能力。
PTRACE_SYSCALL 请求会重启子进程(类似PTRACE_CONT),但会在下次进入或退出系统调用时停止。

例子:
子进程会打印它自己的pid以及它的父进程pid,然后,发一个信号给自己。
因为子进程被父进程附加了,所以,它收到信号后会暂停。
父进程会通过请求 PTRACE_SYSCALL 重启子进程。

getpid 为系统调用,它有一个唯一的系统调用号。父进程通过 PTRACE_GETREGS 获得这个系统调用号,再用 PTRACE_SETREGS 将 getpid 系统调用号,替换为 getppid 系统调用号。之后,用 PTRACE_CONT 让子进程跑完。由于系统调用号已经被修改了,所以,子进程会执行getppid而不是getpid

  1. #include <sys/ptrace.h>
  2. #include <signal.h>
  3. #include <linux/user.h>
  4. #include <sys/types.h>
  5. #include <sys/wait.h>
  6. main()
  7. {
  8. int status = 0, pid, r;
  9. struct user_regs_struct uregs;
  10. if ((pid=fork())==0) {
  11. printf("pid = %d, ppid = %d\n", getpid(), getppid());
  12. ptrace(PTRACE_TRACEME, 0, 0, 0);
  13. kill(getpid(), SIGINT);
  14. r = getpid();
  15. printf("%d\n", r);
  16. } else {
  17. wait(&status);
  18. ptrace(PTRACE_SYSCALL, pid, 0, 0);
  19. wait(&status);
  20. ptrace(PTRACE_GETREGS, pid, 0, &uregs);
  21. //this should print 20, syscall number of getpid
  22. printf("syscall nr: %d\n", uregs.orig_eax);
  23. // 64 is syscall number of getppid
  24. uregs.orig_eax = 64;
  25. ptrace(PTRACE_SETREGS, pid, 0, &uregs);
  26. ptrace(PTRACE_CONT, pid, 0, 0);
  27. wait(&status);
  28. if(WIFEXITED(status))printf("child over\n");
  29. sleep(1000);
  30. }
  31. }
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注