理论不说了,以下提供三个demo,几乎逐行注释,看懂了应该就懂了。

一.完成了两件事儿:

1.儿子告诉爸爸,儿子到信息可以被查看/修改 PTRACE_TRACEME

2.爸爸查看儿子的信息 PTRACE_PEEKUSER

#include <stdio.h>
#include <unistd.h>
#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/reg.h>   /* For constants ORIG_RAX etc */
int main(){
   pid_t child;
   long orig_rax;
   child=fork();
   // 这里其实是一个分割点,当fork后,会分为两个进程
   // 0是儿子 非0是爸爸(当前进程到id)
   // 儿子直接执行,而爸爸会调用wait等待儿子执行完毕
   if(child==0){
	  printf("1.1 child:%d\n",child);
	  //trace me:允许爸爸查看我到信息(告诉内核,我是被夫进程附加的)
      ptrace(PTRACE_TRACEME,0,NULL,NULL);
      printf("2. /bin/ls\n");
      //子进程运行到这里暂停,由父进程到PTRACE_CONT恢复,这个时候寄存器已经保存了子进程到信息
      execl("/bin/ls","ls",NULL);
   }else{
	printf("1.0 child:%d\n",child);
	//等待子进程停下来(execl那里),参数为null,不在乎子进程是什么状态
        //这里说一下他们到生死关系,首先是执行fork分出了一个爸爸,然后儿子执行了traceme告诉内核
        //我是可以被爸爸控制到,然后运行到execl方法企图执行但还未执行,这个时候儿子停止了,
        //同时爸爸被激活.执行wait后到语句
        wait(NULL);
        //peek user:查看儿子到信息(查看自进程中寄存器到内容,ORIG_RAX寄存器值的保存地址)
        orig_rax = ptrace(PTRACE_PEEKUSER,child,8*ORIG_RAX,NULL);
        printf("3. The child made a system call %ld\n",orig_rax);
        //cont: 让子进程恢复运行
        ptrace(PTRACE_CONT,child,NULL,NULL);
   }
}

二. 完成一件事儿

1.查看儿子所有到信息 PTRACE_GETREGS

/*
 ptrace之读取目标进程寄存器到值到user_regs_struct 
 */
#include <stdio.h>
#include <sys/ptrace.h>
#include <sys/wait.h>
#include <sys/reg.h>
#include <sys/user.h> //user_regs_struct regs寄存器的头部
#include <sys/syscall.h>
#include <unistd.h>
#include <sys/types.h>

int main(){
	pid_t child;
	long orig_rax;
	int status;
	int iscalling=0;
	struct user_regs_struct regs;

	//fork出两个进程
	child=fork();
	
	if(child==0){
		//子进程 child为0
	    printf("1.0 child:%d\n",child);
	    // 告诉内核,本子进程可以被父进程查看/修改
		ptrace(PTRACE_TRACEME,0,NULL,NULL);
		//运行ls -l -h
		execl("/bin/ls","ls","-l","-h",NULL);
	}else{
		//父进程 child为子进程pid
		printf("1.1 child:%d\n",child);
		while(1){
			// 如果status不为null,status将返回子进程到是否结束到状态
			// 如果其所有子进程都还在运行,则阻塞
			// 如果一个子进程已经终止,正等待父进程获取其终止状态,则获取该子进程的终止状态然后立即返回
			// 如果没有任何子进程,则立即出错返回
			wait(&status);
			
			// 检查子进程是暂停还准备退出
			// 如若正常结束子进程返回的状态,则为真
			if(WIFEXITED(status))
				break;
			//读取ORIG_RAX的内容,peek user:查看儿子到信息(查看子进程中寄存器的内容,ORIG_RAX:保存了系统调用号)
			orig_rax=ptrace(PTRACE_PEEKUSER,child,8*ORIG_RAX,NULL);
			//printf("1.2 orig_rax:%ld\n",orig_rax);
			if(orig_rax==SYS_write){
				//GET REGS:获取child进程寄存器到结构体user_regs_struct中
				ptrace(PTRACE_GETREGS,child,NULL,®s);
				if(!iscalling){
					iscalling=1;
					// 打印子进程寄存器的值
					printf("0. SYS_write call with %lld, addr:%lld, len:%lld\n",regs.rdi,regs.rsi,regs.rdx);
				}else{
					// 打印系统调用号的值
					printf("1. SYS_write call return %lld\n",regs.rax);
					iscalling=0;
				}
			}
			// PTRACE_SYSCALL:和PTRACE_CONT一样使暂停的子进程继续执行,唯一不同到是syscall会在child进程下一次执行系统掉调用到时候再次让child进程暂停(SINTRAP信号)。
			ptrace(PTRACE_SYSCALL,child,NULL,NULL);
			//ptrace(PTRACE_CONT,child,NULL,NULL);
		}
	}
	return 0;
}

三.完成两件事儿

1.获取儿子到信息 PTRACE_PEEKDATA

2.修改儿子到信息 PTRACE_POKEDATA

/*
 * 通过ptrace(PTRACE_PEEKDATA)获取内存到值
 *    ptrace(PTRACE_POKEDATA)更改内存到值
 * */
#include <sys/ptrace.h>
#include <sys/wait.h>
#include <sys/reg.h>
#include <sys/syscall.h>
#include <sys/user.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#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;
  
  // 获取次数 = 数据总长度/long_size
  j=len/long_size;
  printf("long_size: %ld j:%d len:%d\n",long_size,j,len);
  // 获取字符从变量
  laddr=str;
  while(i<j){
   // 获取child进程到addr地址处sizeof(long)长度到数据
   data.val=ptrace(PTRACE_PEEKDATA, child, addr+i*long_size, NULL);
   if(data.val == -1){
     if(errno){
        printf("READ error: %s\n",strerror(errno));
     }
   }
   
    //printf("data.chars: %s\n",data.chars);
    // 拷贝上面获取到八个字节数据到laddr里。这里不晓得data.chars是什么时候赋值到
    memcpy(laddr,data.chars,long_size);
    ++i;
    laddr +=long_size;
  };
  
  //打个比方,上面是一次获取8个字节,如果内容由17个字节,那么就有1个字节没有获取到,
  //而在此处获取剩下到那1个字节
  j=len % long_size;
  if(j!=0){
     data.val=ptrace(PTRACE_PEEKDATA,child,addr+i*long_size,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);
         //POKE DATA:往child进程的 【addr +i*long_size】内存地址中写入[data.val]字节
         ptrace(PTRACE_POKEDATA,child,addr +i*long_size,data.val);
         ++i;
         //改变源数据到地址
         laddr+=long_size;
     }
     //这里同样是担心字节总数并非8到倍数 导致写入不全
     j=len%long_size;
     if(j!=0){
           //注意:由于写入时也是按字写入的,所以正确的做法是先将该字的高地址数据读出保存在data的高地址上 ,然后将该字再写入
           memcpy(data.chars,laddr,j);
           ptrace(PTRACE_POKEDATA,child,addr +i*long_size,data.val);
     }

}

int main(){
    pid_t child;
        int status;
        struct user_regs_struct regs;
        //fork两个进程 0子 1父
        child =fork();
        if(child ==0){
		  //告知内核,本进程可由父进程查看/修改
          ptrace(PTRACE_TRACEME,0,NULL,NULL);
          //运行指令
          execl("/bin/ls","ls",NULL);
          //execl("/bin/ls","ls","-l","-h",NULL);
       }else{
           long orig_eax;
           char *str,*laddr;
           int toggle =0;
           while(1){
			   
			 // 阻塞等待子进程运行完毕,并返回状态到status
             wait(&status);
             
             // 判断子进程是否退出
             if(WIFEXITED(status))
                 break;
                 
             //获取系统调用号RAX
             orig_eax = ptrace(PTRACE_PEEKUSER,child,8*ORIG_RAX,NULL);
             if(orig_eax == SYS_write){
               if(toggle == 0){
                 toggle =1;
                 
                 //获取子进程寄存器到值,并保存到regs里
                 ptrace(PTRACE_GETREGS,child,NULL,®s);
				 
				 //开辟一块内存
                 str=(char * )calloc((regs.rdx+1),sizeof(char));
                 
                 // 参数说明: pid,addr,str,len
                 // 为什么 regs.rsi 和 regs.rdx 是地址和长度?
                 // write函数第二个参数是字符从到地址,第三个参数是字符串长度
                 getdata(child,regs.rsi,str,regs.rdx);
                 reverse(str);
                 putdata(child,regs.rsi,str,regs.rdx);
              }else{
              toggle =0;
               }
            }

         ptrace(PTRACE_SYSCALL,child,NULL,NULL);
          }
       }
      return 0;
}

本篇主要记录了一个进程如何获取到另一个进程的相关信息,抛砖引玉,同时自己可时长查看,便于加深记忆.

下一边说如何注入到另一个进程