博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Linux Ptrace 详解
阅读量:6715 次
发布时间:2019-06-25

本文共 9047 字,大约阅读时间需要 30 分钟。

转 https://blog.csdn.net/u012417380/article/details/60470075

 

Linux Ptrace 详解

一、系统调用

操作系统提供一系列系统调用函数来为应用程序提供服务。关于系统调用的详细相关知识,可以查看<<程序员的自我修养》第十二章。

对于x86操作系统来说,用中断命令“int 0x80”来进行系统调用,系统调用前,需要将系统调用号放入到%EAX寄存器中,将系统的参数依次放入到寄存器%ebx%ecx%edx以及%esi%edi中。

以write系统调用为例:

write(2,"Hello",5);
  • 1

在32位系统中会转换成:

movl $1,%eaxmovl $2,%ebxmovl $hello,%ecx movl $5,%edx int $0x80
  • 1
  • 2
  • 3
  • 4
  • 5

其中1为write的系统调用号,所有的系统调用号定义在unistd.h文件中,$hello 表示字符串“Hello”的地址;32位Linux系统通过0x80中断来进行系统调用。

64位系统用户应用层用整数寄存器%rdi ,%rsi,%rdx,%rcx, %r8以及 %r9来传参。而内核接口用%rdi ,%rsi,%rdx,%r10,&r8以及%r10来传参,并且用syscall指令而不是80中断进行系统调用。

x86和x64都用寄存器rax来保存调用号和返回值。

二、ptrace 函数简介

#include 
long ptrace(enum _ptrace_request request,pid_t pid,void * addr ,void *data);
  • 1
  • 2
  • 3

ptrace()系统调用函数提供了一个进程(the “tracer”)监察和控制另一个进程(the “tracee”)的方法。并且可以检查和改变“tracee”进程的内存和寄存器里的数据。它可以用来实现断点调试和系统调用跟踪。

tracee首先需要被附着到tracer。在多线程进程中,每个线程都可以被附着到一个tracer。ptrace命令总是以ptrace(PTARCE_foo,pid,..)的形式发送到tracee进程。pid是tracee线程ID。

当一个进程可以开始跟踪进程通过调用fork函数创建子进程并让子进程执行PTRACE_TRACEME,然后子进程再调用(如果当前进程被ptrace,execve()成功执行后 SIGTRAP信号量会被发送到该进程)。一个进程也可以使用”PTRACE_ATTACH”或者”PTRACE_SEIZE”来跟踪另一个进程。

当进程被跟踪后,每当信号量传来,甚至信号量会被忽略时,tracee会暂停。tracer会在下次调用(或者其它wait系统调用)处被通知。该调用会返回一个包含tracee暂停原因信息的状态码。当tracee暂停后,tracer可以使用一系列ptrace请求来查看和修改tracee中的信息。tracer接着可以让tracee继续执行。tracee传递给tracer中的信号量通常被忽略。

当PTRACE_O_TRACEEXEC项未起作用时,所有成功执行execve()的tracee进程会被发送一个 SIGTRAP信号量后暂停,在新程序执行之前,父进程将会取得该进程的控制权。

当tracer结束跟踪后,可以通过调用PTRACE_DETACH继续让tracee执行。

prace更多相关信息可以查看官方文档。

三、示例

1.ptrace追踪子进程执行exec()

#include 
#include
#include
#include
#include
#include
/* For constants ORIG_RAX etc */ int main(){ pid_t child; long orig_rax; child=fork(); if(child==0){ ptrace(PTRACE_TRACEME,0,NULL,NULL); execl("/bin/ls","ls",NULL); }else{ wait(NULL); orig_rax = ptrace(PTRACE_PEEKUSER,child,8*ORIG_RAX,NULL); printf("The child made a system call %ld\n",orig_rax); ptrace(PTRACE_CONT,child,NULL,NULL); } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

编译后输出:

The child made a system call 59user1@user-virtual-machine:~/hookTest$ a.out    attach.c~  ex1.c ex1.o ex2.c~ ex3.c ex3.o ex4.c~ victim.c~ attach.c attach.o ex1.c~ ex2.c ex2.o ex3.c~ ex4.c victim.c victim.o
  • 1
  • 2
  • 3
  • 4

execl()函数对应的系统调用为__NR_execve,系统调用值为59。父进程通过调用fork()来创建子进程。在子进程中,先运行patrce().请求参数设为PTRACE_TRACE,来告诉内核当前进程被父进程trace,每当有信号量传递到当前进程,该进程会暂停,提醒父进程在wait()调用处继续执行。然后再调用execl()。当execl()函数成功执行后,新程序运行之前,SIGTRAP信号量会被发送到该进程,让子进程停止,这时父进程会在wait相关调用处被通知,获取子进程的控制权,可以查看子进程内存和寄存器相关信息。

当进程进行系统调用时,int会在内核栈中依次压入用户态的寄存器SS、ESP、EFLAGS、CS、EIP.中断处理程序的SAVE_ALL宏会将 依次将EAX、EBP、EDI、ESI、EDX、ECX、EBX寄存器值压入内核栈。调用ptrace(PTRACE_PEEKUSER,child,8*ORIG_RAX,NULL) 获取USER area信息时<sys/reg.h>文件定义了与内核栈寄存器数组顺序相同的下标:

#ifndef _SYS_REG_H#define _SYS_REG_H  1#ifdef __x86_64__/* Index into an array of 8 byte longs returned from ptrace for   location of the users' stored general purpose registers.  */# define R15 0 # define R14 1 # define R13 2 # define R12 3 # define RBP 4 # define RBX 5 # define R11 6 # define R10 7 # define R9 8 # define R8 9 # define RAX 10 # define RCX 11 # define RDX 12 # define RSI 13 # define RDI 14 # define ORIG_RAX 15 # define RIP 16 # define CS 17 # define EFLAGS 18 # define RSP 19 # define SS 20 # define FS_BASE 21 # define GS_BASE 22 # define DS 23 # define ES 24 # define FS 25 # define GS 26 #else /* Index into an array of 4 byte integers returned from ptrace for * location of the users' stored general purpose registers. */ # define EBX 0 # define ECX 1 # define EDX 2 # define ESI 3 # define EDI 4 # define EBP 5 # define EAX 6 # define DS 7 # define ES 8 # define FS 9 # define GS 10 # define ORIG_EAX 11 # define EIP 12 # define CS 13 # define EFL 14 # define UESP 15 # define SS 16 #endif
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60

这样8*ORIG_RAX就找到USER area 中 ORIG_RAX 寄存器值的保存地址。ORIG_RAX保存了系统调用号。

当检查完系统调用之后,可以调用ptrace并设置参数PTRACE_CONT让子进程继续进行。

2.读取子进程系统调用参数

//64位下乌班图程序#include 
#include
#include
#include
#include
#include
int main(){ pid_t child; long orig_rax; int status; int iscalling=0; struct user_regs_struct regs; child = fork(); if(child==0){ ptrace(PTRACE_TRACEME,0,NULL,NULL); execl("/bin/ls","ls","-l","-h",NULL); }else{ while(1){ wait(&status); if(WIFEXITED(status)) break; orig_rax=ptrace(PTRACE_PEEKUSER,child,8*ORIG_RAX,NULL); if(orig_rax == SYS_write){ ptrace(PTRACE_GETREGS,child,NULL,&regs); if(!iscalling){ iscalling =1; printf("SYS_write call with %lld, %lld, %lld\n",regs.rdi,regs.rsi,regs.rdx); } else{ printf("SYS_write call return %lld\n",regs.rax); iscalling = 0; } } ptrace(PTRACE_SYSCALL,child,NULL,NULL); } } return 0; }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

编译后输出:

SYS_write call with 1, 140179049189376, 14总用量 28K SYS_write call return 14 SYS_write call with 1, 140179049189376, 51 -rw-rw-r-- 1 user1 user1 534 2月 26 18:02 ex1.c SYS_write call return 51 SYS_write call with 1, 140179049189376, 52 -rw-rw-r-- 1 user1 user1 534 2月 26 18:02 ex1.c~ SYS_write call return 52 SYS_write call with 1, 140179049189376, 53 -rw-rw-r-- 1 user1 user1 1.1K 3月 2 13:02 hook2.c SYS_write call return 53 SYS_write call with 1, 140179049189376, 54 -rw-rw-r-- 1 user1 user1 1.1K 3月 2 13:02 hook2.c~ SYS_write call return 54 SYS_write call with 1, 140179049189376, 53 -rwxrwxr-x 1 user1 user1 8.6K 3月 2 13:02 hook2.o SYS_write call return 53
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

可以看到ls -l -h 执行了六次SYS_write系统调用。

读取寄存器中的参数时,可以使用PTRACE_PEEKUSER一个字一个字读取,也可以使用PTRACE_GETREGS参数直接将寄存器的值读取到结构体user_regs_struct 中,该结构体定义在sys/user.h

对于PTRACE_STSCALL参数,该参数会像PTRACE_CONT一样使暂停的子进程继续执行,并在子进程下次进行系统调用前或系统调后,向子进程发送SINTRAP信号量,让子进程暂停。

WIFEXITED函数(宏)函数用来检查子进程是暂停还准备退出。

3.修改子进程系统调用参数

val = ptrace(PTRACE_PEEKDATA,child,addr,NULL)
  • 1

PTRACE_PEEKDATAPTRACE_PEEKTEXT参数是在tracee内存的addr地址处读取一个字(sizeof(long))的数据,反回值是long 型的,可多次读取addr

+i*sizeof(long)然后再合并得到最终字符串的内容。

现在,我们对系统调用write 输出的字符串参数进行反转:

#include 
#include
#include
#include
#include
#include
#include
#include
#include
#define 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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117

输出:

user1@user-virtual-machine:~/hookTest$ ./hook3.oo.3kooh  ~c.3kooh c.3kooh o.2kooh ~c.2kooh c.2kooh ~c.1xe c.1xe
  • 1
  • 2
  • 3

4.向其它程序注入指令

我们追踪其它独立运行的进程时,需要使用下面的命令:

ptrace(PTRACE_ATTACH, pid, NULL, NULL)
  • 1

使pid进程成为被追踪的tracee进程。tracee进程会被发送一个SIGTOP信号量,tracee进程不会立即停止,直到完成本次系统调用。如果要结束追踪,则调用PTRACE_DETACH即可。

debug 设置断点的功能可以通过ptrace实现。原理是ATTACH正在运行的进程使其停止。然后读取该进程的指令寄存器IR(32位x86为EIP,64w的是RIP)内容所指向的指令,备份后替换成目标指令,再使其继续执行,此时被追踪进程就会执行我们替换的指令,运行完注入的指令之后,我们再恢复原进程的IR

,从而达到改变原程序运行逻辑的目的。

tracee进程代码:

stdio.h>int main(){        int i=0;    while(1){ printf("Hello,ptrace! [pid:%d]! num is %d\n",getpid(),i++); sleep(2); } return 0; }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

tracer进程代码

#include
#include
#include
#include
#include
#include
#include
#include
#define long_size sizeof(long) void getdata(pid_t child, long addr ,char * str,int len){ char * laddr =str; int i,j; union u{ long val; char chars [long_size] ; } data; i=0; j=len/long_size; while(i
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116

先运行tracee.o 文件

$  ./tracee.o
  • 1

此时tracee.o输出:

Hello,ptrace! [pid:14384]! num is 0Hello,ptrace! [pid:14384]! num is 1Hello,ptrace! [pid:14384]! num is 2 Hello,ptrace! [pid:14384]! num is 3 ......
  • 1
  • 2
  • 3
  • 4
  • 5

再另打开一个shell运行attach.o文件

$  ./.attach.o  14384 //pid
  • 1

此时tracee.o执行到int 3断点指令停止,attach1,o输出:

tracee:RIP:0x7f48b0394f20 INST: 0x3173fffff0013d48 tracee:RIP:0x7f48b0394f23 INST: 0x8348c33100000000 Press Enter to continue tracee process
  • 1
  • 2
  • 3
  • 4
  • 5

按任意键tracee.o恢复执行

参考:

转载于:https://www.cnblogs.com/yibutian/p/9482972.html

你可能感兴趣的文章
将Eclipse包括第一3正方形jar包裹Project Export并产生能够执行jar
查看>>
Google Pagespeed,自动压缩优化JS/CSS/Image
查看>>
Gentoo源码安装图解
查看>>
【转载】COM 组件设计与应用(三)——数据类型
查看>>
Python yield与实现
查看>>
购物车特效收集
查看>>
Access中一句查询代码实现Excel数据导入导出
查看>>
2015第49周二
查看>>
Sphinx/Coreseek 4.1的安装流程
查看>>
邮件服务器Postfix的管理 重启php-fpm
查看>>
Android Studio 项目代码全部消失--出现原因及解决方法
查看>>
SQL Server---存储过程
查看>>
MySQL Performance-Schema(二) 理论篇
查看>>
搭建SSH详细步骤及相关说明
查看>>
Android IOS WebRTC 音视频开发总结(五五)-- 音视频通讯中的抗丢包与带宽自适应原理...
查看>>
Libgdx: 将Texturepacker打包的PNG图片还原成一张一张的单个的
查看>>
再议Swift操作符重载
查看>>
pc机进入android的shell
查看>>
javascript Date format(js日期格式化)
查看>>
Loadrunner中参数化实战(6)-Random+Each occurrence
查看>>