登陆

Linux ptrace

admin 2019-07-14 323人围观 ,发现0个评论

嵌入式linux QQ沟通群:175159209,欢迎爱好者参加沟通技能问题!

ptrace供给让一个进程来操控另一个进程的才能,包含检测,修正被操控进程的代码,数据,寄存器,从而完成设置断点,注入代码和盯梢体系调用的功用。

这儿把运用ptrace函数的进程称为tracer,被操控的进程称为tracee。

 

运用ptrace函数来阻拦体系调用(system call)

操作体系向上层供给规范的API来履行与底层硬件交互的操作,这些规范API称为体系调用,每个体系调用都有一个调用编号,能够在unistd.h中查询。当进程触发一个体系调用时它会把参数放入寄存器中,然后经过软中止进入内核形式,经过内核来履行这个体系调用的代码。

在X86_64体系中,体系调用号保存在rax,调用参数顺次保存在rdi,rsi,rdx,rcx,r8和r9中;而在x86体系中,体系调用号保存在寄存器eax中,其他的参数顺次保存在ebx,ecx,edx,esi中

例如操控台打印所履行的体系调用为

write(1,"Hello",5)

翻译为汇编代码为

mov rax, 1
mov rdi, message
mov rdx, 5
syscall
message:
db "Hello"

在履行体系调用时,内核先检测一个进程是否为tracee,如果是的话内核就会暂停该进程,然后把操控权转交给tracer,之后tracer就能够检查或许修正tracee的寄存器了。

示例代码如下

#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <sys/reg.h>
#include <sys/user.h>
#include <stdio.h>

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);
}
return 0;
}

//输出:the child made a system call 59


该程序经过fork创建出一个咱们即将盯梢(trace)的子进程,在履行execl之前,子进程经过ptrace函数的PTRACE_TRACEME参数来奉告内核自己即将被盯梢。

关于execl,这个函数实践上会触发execve这个体系调用,这时内核发现此进程为tracee,然后将其暂停,发送一个signal唤醒等候中的tracer(此程序中为主线程)。

当触发体系调用时,内核会将保存调用编号的rax寄存器的内容保存在orig_rax中,咱们能够经过ptrace的PTRACE_PEEKUSER参数来读取。

ORIG_RAX为寄存器编号,保存在sys/reg.h中,而在64位体系中,每个寄存器有8个字节的巨细,所以此处用8*ORIG_RAX来获取该寄存器地址。

当咱们获取到体系调用编号今后,就能够经过ptrace的PTRACE_CONT参数来唤醒暂停中的子进程,让其持续履行。

 

ptrace参数

long ptrace(enum __ptrace_request request,pid_t pid,void addr, void *data);

参数request 操控ptrace函数的行为,界说在sys/ptrace.hLinux ptrace中。

参数pid 指定tracee的进程号。

以上两个参数是有必要的,之后两个参数分别为地址和数据,其意义由参数request操控。

详细request参数的取值及意义可检查协助文档(操控台输入:man ptrace)

留意回来值,man手册上的说法是回来一个字的数据巨细,在32位机器上是4个字节,在64位机器上是8个字节,都对应一个long的长度。百度能够搜到许多不担任的帖子说回来一个字节的数据是不对的!

 

读取体系调用参数

经过ptrace的PTRACE_PEEKUSER参数,咱们能够检查USER区域的内容,例如检查寄存器的值。USER区域为一个结构体(界说在sys/user.h中的user结构体)。

内核将寄存器的值储存在该结构体中,便于tracer经过ptrace函数检查。

示例代码如下

#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/reg.h>
#include <sys/user.h>
#include <sys/syscall.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main()
{
pid_t child;
long orig_rax,rax;
long params[3]={0};
int status;
int insyscall = 0;
child = fork();
if(child == 0)
{
ptrace(PTRACE_TRACEME,0,NULL,NULL);
execl("/bin/ls","ls",NULL);
}
else
{
while(1)
{
wait(&status);
if(WIFEXITED(status))
break;
orig_rax = ptrace(PTRACE_PEEKUSER,child,8*ORIG_RAX,NULL);
//printf("the child made a system call %ld\n",orig_rax);
if(orig_rax == SYS_write)
{
if(insyscall == 0)
{
insyscall = 1;
params[0] = ptrace(PTRACE_PEEKUSER,child,8*RDI,NULL);
params[1] = ptrace(PTRACE_PEEKUSER,child,8*RSI,NULL);
params[2] = ptrace(PTRACE_PEEKUSER,child,8*RDX,NULL);
printf("write called with %ld, %ld, %ld\n",params[0],params[1],params[2]);
}
else
{
rax = ptrace(PTRACE_PEEKUSER,child,8*RAX,NULL);
printf("write returned with %ld\n",rax);
insyscall = 0;
}
}
ptrace(PTRACE_SYSCALL,child,NULL,NULL);
}
}
return 0;

}
/***
输出:
write called with 1, 25226320, 65
ptrace_1.c  ptrace_2.c    ptrace_3.C  ptrace_4.C    ptrace_5.c  tLinux ptraceest.c
write returned with 65
***/

以上代码中咱们检查write体系调用(由ls指令向操控台打印文字触发)的参数。

为了追寻体系调用,鼓浪屿攻略咱们运用ptrace的PTRACE_SYSCALL参数,它会使tracee在触发体系调用或许完毕体系调用时暂停,一起向tracer发送signal。

在之前的比如中咱们运用PTRACE_PEEKUSER参数来检查体系调用的参数,相同的,咱们也能够检查保存在RAX寄存器中的体系调用回来值。

上边代码中的status变量时用来检测是否tracee现已履行完毕,是否需求持续等候tracee履行。

读取一切寄存器的值

这个比如中演示一个获取寄存器值的简洁办法

#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/user.h>
#include <sys/reg.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <stdio.h>

int main()
{
pid_t child;
long orig_rax ,rax;
long params[3] = {0};
int status = 0;
int insyscall = 0;
struct user_regs_struct regs;
child = fork();
if(child == 0)
{
ptrace(PTRACE_TRACEME,0,NULL,NULL);
execl("/bin/ls","ls",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)
{
if(insyscall == 0)
{
insyscall = 1;
ptrace(PTRACE_GETREGS,child,NULL,&regs);
printf("write called with %llu, %llu, %llu\n",regs.rdi,regs.rsi,regs.rdx);
}
else
{
ptrace(PTRACE_GETREGS,child,NULL,&regs);
printf("write returned with %ld\n",regs.rax);
insyscall = 0;
}
}
ptrace(PTRACE_SYSCALL,child,NULL,NULL);
}
}
return 0;
}


这个比如中经过PTRACE_GETREGS参数获取了一切的寄存器值。结构体user_regs_struct界说在sys/user.h中。

修正体系调用的参数

现在咱们现已知道怎么阻拦一个体系调用并检查其参数了,接下来咱们来修正它

#include <sys/ptrace.h>
#include <sys/user.h>
#include <sys/reg.h>
#include <sys/wait.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>


#define LONG_SIZE 8
//获取参数
char* getdata(pid_t child,unsigned long addr,unsigned long len)
{
char *str =(char*) malloc(len + 1);
memset(str,0,len +1);
union u{
long int val;
char chars[LONG_SIZE];
}word;
int i, j;
for(i = 0,j = len/LONG_SIZE; i<j; ++i)
{
word.val = ptrace(PTRACE_PEEKDATA,child,addr + i*LONG_SIZE,NULL);
if(word.val == -1)
perror("trace get data error");
memcpy(str+i*LONG_SIZE,word.chars,LONG_SIZE);
}
j = len % LONG_SIZE;
if(j != 0)
{
word.val = ptrace(PTRACE_PEEKDATA,child,addr + i*LONG_SIZE,NULL);
if(word.val == -1)
perror("trace get data error");
memcpy(str+i*LONG_SIZE,word.chars,j);
}
return str;
}
//提交参数
void putdata(pid_t child,unsigned long addr,unsigned long len, char *newstr)
{
union u
{
long val;
char chars[LONG_SIZE];
}word;
int i,j;
for(i = 0, j = len/LONG_SIZE; i<j ; ++i)
{
memcpy(word.chars,newstr+i*LONG_SIZE,LONG_SIZE);
if(ptrace(PTRACE_POKEDATA, child, addr+i*LONG_SIZE,word.val) == -1)
perror("trace error");

}
j = len % LONG_SIZE;
if(j !=0 )
{
memcpy(word.chars,newstr+i*LONG_SIZE,j);
ptrace(PTRACE_POKEDATA, child, addr+i*LONG_SIZE,word.val);
}
}

//修正参数
void reserve(char *str,unsigned int len)
{
int i,j;
char tmp;
for(i=0,j=len-2; i<=j; ++i,--j )
{
tmp = str[i];
str[i] = str[j];
str[j] = tmp;
}
}

int main()
{
pid_t child;
child = fork();
if(child == 0)
{
ptrace(PTRACE_TRACEME,0,NULL,NULL);
execl("/bin/ls","ls",NULL);
}
else
{
struct user_regs_struct regs;
int status = 0;
int toggle = 0;
while(1)
{
wait(&status);
if(WIFEXITED(status))
break;
memset(&regs,0,sizeof(struct user_regs_struct));
if(ptrace(PTRACE_GETREGS,child,NULL,&regs) == -1)
{
perror("trace error");
}

if(regs.orig_rax == SYS_write)
{
if(toggle == 0)
{
toggle = 1;
//in x86_64 system call ,pass params with %rdi, %rsi, %rdx, %rcx, %r8, %r9
//no system call has over six params
printf("make write call params %llu, %llu, %llu\n",regs.rdi,regs.rsi,regs.rdx);
char *str = getdata(child,regs.rsi,regs.rdx);
printf("old str,len %lu:\n%s",strlen(str),str);
reserve(str,reLinux ptracegs.rdx);
printf("hook str,len %lu:\n%s",strlen(str),str);
putdata(child,regs.rsi,regs.rdx,str);
free(str);
}
else
{
toggle = 0;
}
}
ptrace(PTRACE_SYSCALL,child,NULL,NULL);
}
}
return 0;
}
/***
输出:
make write call params 1, 9493584, 66
old str,len 66:
ptrace        ptrace2    ptrace3     ptrace4    ptrace5     test    test.s
hook str,len 66:
s.tset    tset     5ecartp    4ecartp     3ecartp    2ecartp        ecartp
s.tset    tset     5ecartp    4ecartp     3ecartp    2ecartp        ecartp
make write call params 1, 9493584, 65
old str,len 65:
ptrace_1.c  ptrace_2.c    ptrace_3.C  ptrace_4.C    ptrace_5.c  test.c
hook str,len 65:
c.tset  c.5_ecartp    C.4_ecartp  C.3_ecartp    c.2_ecartp  c.1_ecartp
c.tset  c.5_ecartp    C.4_ecartp  C.3_ecartp    c.2_ecartp  c.1_ecartp
***/

 这个比如中,归纳了以上咱们说到的一切常识。进一步得,咱们运用了ptrace的PTRACE_POKEDATA参数来修正体系调用的参数值。

这个参数和PTRACE_PEEKDATA参数的效果相反,它能够修正tracee指定地址的数据。

单步调试

接下来介绍一个调试器中常用的操作,单步调试,它就用到了ptrace的PTRACE_SINGLESTEP参数。

#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/user.h>
#include <sys/reg.h>
#include <sys/syscall.h>
#include <sys/wait.h>
#include <stdio.h>
#include <unistd.h>

#define LONG_SIZE 8

void main()
{
pid_t chid;
chid = fork();
if(chid == 0)
{
ptrace(PTRACE_TRACEME,0,NULL,NULL);
     //这儿的test是一个输出hello world的小程序
execl("./test","test",NULL);
}
else
{
int status = 0;
struct user_regs_struct regs;
int start = 0;
long ins;
while(1)
{
wait(&status);
if(WIFEXITED(status))
break;
ptrace(PTRACE_GETREGS,chid,NULL,&regs);
if(start == 1)
{
ins = ptrace(PTRACE_PEEKTEXT,chid,regs.rip,NULL);
printf("EIP:%llx Instuction executed:%lx\n",regs.rip,ins);
}
if(regs.orig_rax == SYS_write)
{
start = 1;
ptrace(PTRACE_SINGLESTEP,chid,NULL,NULL);
}else{
ptrace(PTRACE_SYSCALL,chid,NULL,NULL);
}
}
}
}

经过rip寄存器的值来获取下一条要履行指令的地址,然后用PTRACE_PEEKDATA读取。

这样,就能够看到要履行的每条指令的机器码。


嵌入式Linux中文站

最专业的中文嵌入式Linux网站,8年磨剑,注册用户数万人

共享 嵌入式 & Linux 技能干货、教程、资讯、高薪职位

订阅点击标题下方“嵌入式Linux中文站”

共享点击右上角共享按钮

投稿admin@embeddedlinux.org.cn

沟通QQ群:175159209

      点击下方“阅览原文”检查更多


请关注微信公众号
微信二维码
不容错过
Powered By Z-BlogPHP