理论不说了,以下提供三个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;
}
本篇主要记录了一个进程如何获取到另一个进程的相关信息,抛砖引玉,同时自己可时长查看,便于加深记忆.
下一边说如何注入到另一个进程