首页 > 其他 > 详细

ptrace理解

时间:2019-06-18 21:43:50      阅读:250      评论:0      收藏:0      [点我收藏+]

参考文献:

http://man7.org/linux/man-pages/man2/ptrace.2.html

https://www.linuxjournal.com/article/6100?page=0,0

《程序员的自我修养》

目标:

1)对ptrace 有正确理解, 代码实现

2)ptrace的用处

3)ptrace代码注入

4)如何防止反调试

一. ptrace 介绍

  Ptrace 提供了一种父进程可以控制子进程运行,并可以检查和改变它的核心image。它主要用于实现断点调试。一个被跟踪的进程运行中,直到发生一个信号。则进程被中止,并且通知其父进程。在进程中止的状态下,进程的内存空间可以被读写。父进程还可以使子进程继续执行,并选择是否是否忽略引起中止的信号

  man手册介绍:http://man7.org/linux/man-pages/man2/ptrace.2.html

二.  ptrace 的函数详解:

2.1 函数声明

1 long ptrace(enum __ptrace_request request,
2             pid_t pid,
3             void *addr,
4             void *data);

.参数request:请求ptrace执行的操作

.参数pid:目标进程的ID

.参数addr:目标进程的地址值

.参数data:作用则根据request的不同而变化,如果需要向目标进程中写入数据,data存放的是需要写入的数据;如果从目标进程中读数据,data将存放返回的数据

request参数决定了CODE的行为以及后续的参数是如何被使用的,参数request的常用的值如下:

 

技术分享图片

三. 代码示例理解

在 i386 平台下(本文所有代码都基于 i386), 系统调用的编号会被放在寄存器 %eax 中,而系统调用的参数会被依次放到 %ebx,%ecx,%edx,%exi 和 %edi中,比如说,对于下面的系统调用:

write(2, "Hello", 5)

汇编代码:

1 movl   $4, %eax
2 movl   $2, %ebx
3 movl   $hello,%ecx
4 movl   $5, %edx
5 int    $0x80

看完上面简单的例子,现在我们来看看 ptrace 又是怎样执行的:

参考的文献中的代码

 1 #include <sys/ptrace.h>
 2 #include <sys/types.h>
 3 #include <sys/wait.h>
 4 #include <unistd.h>
 5 #include <linux/user.h>   /* For constants
 6                                    ORIG_EAX etc */
 7 int main()
 8 {   pid_t child;
 9     long orig_eax;
10     child = fork();
11     if(child == 0) {
12         ptrace(PTRACE_TRACEME, 0, NULL, NULL);
13         execl("/bin/ls", "ls", NULL);
14     }
15     else {
16         wait(NULL);
17         orig_eax = ptrace(PTRACE_PEEKUSER,
18                           child, 4 * ORIG_EAX,
19                           NULL);
20         printf("The child made a "
21                "system call %ldn", orig_eax);
22         ptrace(PTRACE_CONT, child, NULL, NULL);
23     }
24     return 0;
25 }

gcc -o ptrace ptrace.c

报错:

ptrace.c:6:24: error: linux/user.h: No such file or directory
ptrace.c: In function ‘main’:
ptrace.c:18: error: ‘ORIG_EAX’ undeclared (first use in this function)
ptrace.c:18: error: (Each undeclared identifier is reported only once
ptrace.c:18: error: for each function it appears in.)
ptrace.c:20: warning: incompatible implicit declaration of built-in function ‘printf’

由于我的环境是64 位系统

这里有两个地方有问题

  1. The ‘linux/user.h’ 不存在
  2. 64位寄存器  R*X,  所以EAX  改成 RAX

两个修改方案:

1)  linux/user.h 改成  sys/reg.h

long original_rax = ptrace(PTRACE_PEEKUSER, child, 8 * ORIG_RAX, NULL);

  地址从“4*orig-eax”更改为“8*orig-rax”,因为它是要在用户区域中读取的地址,而user-regs-struct中的orig-rax成员是第15个成员(从0开始)。文件‘sys/reg.h‘中的orig_rax定义指定其位置:定义orig_rax 15

因为其他成员在64位机器上的大小是8,所以地址是:8*orig_rax

我们看一下头文件结构体

技术分享图片
 1 struct user_regs_struct
 2 {
 3   unsigned long int r15;
 4   unsigned long int r14;
 5   unsigned long int r13;
 6   unsigned long int r12;
 7   unsigned long int rbp;
 8   unsigned long int rbx;
 9   unsigned long int r11;
10   unsigned long int r10;
11   unsigned long int r9;
12   unsigned long int r8;
13   unsigned long int rax;
14   unsigned long int rcx;
15   unsigned long int rdx;
16   unsigned long int rsi;
17   unsigned long int rdi;
18   unsigned long int orig_rax;
19   unsigned long int rip;
20   unsigned long int cs;
21   unsigned long int eflags;
22   unsigned long int rsp;
23   unsigned long int ss;
24   unsigned long int fs_base;
25   unsigned long int gs_base;
26   unsigned long int ds;
27   unsigned long int es;
28   unsigned long int fs;
29   unsigned long int gs;
30 };
31  
32 struct user
33 {
34   struct user_regs_struct   regs;
35   int               u_fpvalid;
36   struct user_fpregs_struct i387;
37   unsigned long int     u_tsize;
38   unsigned long int     u_dsize;
39   unsigned long int     u_ssize;
40   unsigned long int     start_code;
41   unsigned long int     start_stack;
42   long int          signal;
43   int               reserved;
44   struct user_regs_struct*  u_ar0;
45   struct user_fpregs_struct*    u_fpstate;
46   unsigned long int     magic;
47   char              u_comm [32];
48   unsigned long int     u_debugreg [8];
49 };
View Code

2)修改‘linux/user.h’ 为 ‘sys/user.h’

1 struct user_regs_struct regs;
2 ptrace(PTRACE_GETREGS, child, NULL, &regs);
3 printf("The child made a system call %ldn", regs.orig_rax);

第二个更简单,因为它不需要计算位置,但它读取的数据比第一个多

我认为,如果我们直接使用orig_x字段的地址,会更清楚、更容易理解:

1 struct user* user_space = (struct user*)0;
2 long original_rax = ptrace(PTRACE_PEEKUSER, child, &user_space->regs.orig_rax, NULL);

我们现在可以编译和运行它了,但是我们得到了:“子系统调用59”,这与原来的“11”不同,有什么问题吗?
在文件sys/syscall.h中,它包含文件‘asm/unistd.h‘,注释中指出该文件列出了系统调用:

1 /* This file should list the numbers of the system calls the system knows.
2    But instead of duplicating this we use the information available
3    from the kernel sources.  */
4 #include <asm/unistd.h>

是由于头文件asm/unistd.h 包含了不同的文件,根据 __i386__ 和_ILP32__:

1 # ifdef __i386__
2 #  include <asm/unistd_32.h>
3 # elif defined(__ILP32__)
4 #  include <asm/unistd_x32.h>
5 # else
6 #  include <asm/unistd_64.h>
7 # endif

从头文件里面asm/unistd_64.h 我们可以看到64位系统调用:

#define __NR_execve 59

第一个程序搞定, 让我们继续往下看吧, 蹩脚的英语确实让人头疼

 

第二个示例:读取系统调用的参数

通过调用ptrace并传入PTRACE_PEEKUSER作为第一个参数,我们可以检查子进程中,保存了该进程的寄存器的内容(及其它一些内容)的用户态内存区域(USER area)。内核把寄存器的内容保存到这块区域,就是为了能够让父进程通过ptrace来读取,下面举一个例子来说明一下:

 1 #include <sys/ptrace.h>
 2 #include <sys/types.h>
 3 #include <sys/wait.h>
 4 #include <unistd.h>
 5 #include <linux/user.h>
 6 #include <sys/syscall.h>   /* For SYS_write etc */
 7 int main()
 8 {   pid_t child;
 9     long orig_eax, eax;
10     long params[3];
11     int status;
12     int insyscall = 0;
13     child = fork();
14     if(child == 0) {
15         ptrace(PTRACE_TRACEME, 0, NULL, NULL);
16         execl("/bin/ls", "ls", NULL);
17     }
18     else {
19        while(1) {
20           wait(&status);
21           if(WIFEXITED(status))
22               break;
23           orig_eax = ptrace(PTRACE_PEEKUSER,
24                      child, 4 * ORIG_EAX, NULL);
25           if(orig_eax == SYS_write) {
26              if(insyscall == 0) {
27                 /* Syscall entry */
28                 insyscall = 1;
29                 params[0] = ptrace(PTRACE_PEEKUSER,
30                                    child, 4 * EBX,
31                                    NULL);
32                 params[1] = ptrace(PTRACE_PEEKUSER,
33                                    child, 4 * ECX,
34                                    NULL);
35                 params[2] = ptrace(PTRACE_PEEKUSER,
36                                    child, 4 * EDX,
37                                    NULL);
38                 printf("Write called with "
39                        "%ld, %ld, %ld\n",
40                        params[0], params[1],
41                        params[2]);
42                 }
43           else { /* Syscall exit */
44                 eax = ptrace(PTRACE_PEEKUSER,
45                              child, 4 * EAX, NULL);
46                     printf("Write returned "
47                            "with %ld\n", eax);
48                     insyscall = 0;
49                 }
50             }
51             ptrace(PTRACE_SYSCALL,
52                    child, NULL, NULL);
53         }
54     }
55     return 0;
56 }

待续 ...

ptrace理解

原文:https://www.cnblogs.com/mysky007/p/11047943.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!