TongShuai 发表于 2021-1-21 16:58

Linux下使用ptrace注入教程

注入技术在外挂、热修复技术中非常常见,由于Android使用Linux内核,所以先实现Linux下ptrace注入,后面再逐步改为Android版本

## 被注入的程序

```c
#include <stdio.h>
#include <dlfcn.h>
#include <sys/mman.h>
#include <unistd.h>

int main(){
    printf("HelloWorld\n");
    //mmap(0,0x1000,PROT_READ|PROT_WRITE|PROT_EXEC,MAP_ANONYMOUS|MAP_PRIVATE,0,0);
    printf("pid:%d\n",getpid());
    printf("mmap:%p\ndlopen%p\ndlclose%p\ndlsym%p\ndlerror%p\n",mmap,dlopen,dlclose,dlsym,dlerror);
    int a;
    while(1){
      scanf("%d",&a);
      printf("%d\n",a);
      // sleep(1);
    }
    return 0;
}
```

这里打印各个函数的地址,便于出问题的时候debug

## 要注入的so文件

```c
#include <stdio.h>

void testEntry(){
    FILE *f = fopen("/tmp/test.txt","w");
    fputs("Ok to load",f);
    fclose(f);

}
```

使用下面的命令编译为so文件
```sh
gcc -o libtest.so test.c -fPIC -shared
```
## 注入器

```c
#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
#include <sys/ptrace.h>
#include <sys/user.h>
#include <memory.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <dlfcn.h>
#include <sys/ptrace.h>
#include <dirent.h>
#include <sys/types.h>
#include <string.h>
#define MAX_PATH 0x512
char libcPath[]="/usr/lib/libc-2.32.so";

void ptraceWriteData(pid_t pid,void*addr,const char*data,size_t len){
    size_t i=0;
    for(;i<len;i+=sizeof(long)){
      ptrace(PTRACE_POKETEXT,pid,addr+i,(void*)*(long*)&data);
    }
    // 每次写sizeof(long)字节,直接对齐
}
void ptraceReadData(pid_t pid,void*addr,char*data,size_t len){
    size_t i=0;
    long rdata;
    for(;i<len;i+=sizeof(long)){
      rdata=ptrace(PTRACE_PEEKTEXT,pid,addr+i,NULL);
      *(long*)&data=rdata;
    }
}
void ptraceAttach(pid_t pid){
    if(ptrace(PTRACE_ATTACH,pid,NULL,NULL)==-1){
      printf("Failed to attach:%d\n",pid);
    }
    int stat=0;
    waitpid(pid,&stat,WUNTRACED);
}
void ptraceGetRegs(pid_t pid,struct user_regs_struct*regs_addr){
    if(ptrace(PTRACE_GETREGS,pid,NULL,(void*)regs_addr)==-1){
      printf("Get regs error\n");
    }
}
void ptraceSetRegs(pid_t pid,struct user_regs_struct*regs_addr){
    if(ptrace(PTRACE_SETREGS,pid,NULL,(void*)regs_addr)==-1){
      printf("Set regs error\n");
    }
}
void ptraceDetach(pid_t pid){
    ptrace(PTRACE_DETACH,pid,NULL,NULL);
}
void ptraceContinue(pid_t pid){
    if(ptrace(PTRACE_CONT,pid,NULL,NULL)==-1){
      printf("ptrace continue error\n");
    }
}
void *getModuleBaseAddr(pid_t pid,const char*moduleName){
    if(pid==-1)pid=getpid();
    // 通过解析/proc/pid/maps 获得基址
    char filepath;
    void*moduleBaseAddr=NULL;
    snprintf(filepath,MAX_PATH,"/proc/%d/maps",pid);
    FILE *f = fopen(filepath,"r");
    char line;
    char base,name;
    size_t cnt,start;
    while(!feof(f)){
      memset(line,0,MAX_PATH);
      memset(name,0,MAX_PATH);
      fgets(line,MAX_PATH,f);
      cnt=0;
      while(line!='/')cnt++;
      start=cnt;
      while(line){
            name=line;
            cnt++;
      }
      name=0;

      if(strncmp(name,moduleName,MAX_PATH))continue;
      memset(base,0,MAX_PATH);
      cnt=0;
      while(line!='-'){
            base=line;
            cnt++;
      }
      base=0;
      sscanf(base,"%llx",(long long*)(&moduleBaseAddr));
      printf(" GotBaseAddr %p of %s\n",moduleBaseAddr,moduleName);
      break;
    }
    fclose(f);
    return moduleBaseAddr;
}
void *getRemoteFuncAddr(pid_t pid,const char *moduleName,void *localFuncAddr){
    void *localModuleAddr,*remoteModuleAddr,*remoteFuncAddr;
    // 通过计算指定函数的偏移量获取目标函数地址
    localModuleAddr = getModuleBaseAddr(-1,moduleName);
    remoteModuleAddr = getModuleBaseAddr(pid,moduleName);
    remoteFuncAddr=localFuncAddr-localModuleAddr+remoteModuleAddr;
    printf(" GotFuncAddr:%p\n",remoteFuncAddr);
    return remoteFuncAddr;
}
int ptraceCall(pid_t pid,void* funcAddr,long*paras,long paraLen,struct user_regs_struct *regs){

    // 多于6个参数通过栈传递
    regs->rsp-=sizeof(void*);
    size_t errRet=0;
    // 返回地址弄成0导致错误停止
    ptraceWriteData(pid,(void*)regs->rsp,(char*)&errRet,sizeof(void*));
    if(paraLen>6){
      regs->rsp-=(paraLen-6)*sizeof(long);
      ptraceWriteData(pid,(void*)regs->rsp,(char*)&paras,sizeof(long)*(paraLen-6));
    }
    // 前6个参数通过寄存器传递
    switch(paraLen){
      case 6:
            regs->r9=paras;
      case 5:
            regs->r8=paras;
      case 4:
            regs->rcx=paras;
      case 3:
            regs->rdx=paras;
      case 2:
            regs->rsi=paras;
      case 1:
            regs->rdi=paras;
            break;

    }
    // 调用函数
    regs->rip=(unsigned long long)funcAddr;
    ptraceSetRegs(pid,regs);
    int stat=0;
    while(stat!=0xb7f){
      ptraceContinue(pid);
      waitpid(pid,&stat,WUNTRACED);
      printf(" substatus: %x\n",stat);
    }
   
    ptraceGetRegs(pid,regs);
    return 0;
}
void inject(pid_t pid,const char*libname,const char*funcName){
    struct user_regs_struct oldRegs;
    struct user_regs_struct regs;
    long paras;
    char realLibPath;
    realpath(libname,realLibPath);
    printf("Real path of lib found:%s\n",realLibPath);
   
    ptraceAttach(pid);
    // 保存寄存器环境
    ptraceGetRegs(pid,&oldRegs);
    memcpy(&regs,&oldRegs,sizeof(struct user_regs_struct));
    // 获取mmap地址
    void *mmapAddr = getRemoteFuncAddr(pid,libcPath,mmap);
    // 调用mmap
    paras=0;
    paras=0x1000;
    paras=PROT_READ|PROT_WRITE|PROT_EXEC;
    paras=MAP_ANONYMOUS|MAP_PRIVATE;
    paras=0;
    paras=0;
    ptraceCall(pid,mmapAddr,paras,6,&regs);
    void *remoteMemAddr=(void*)regs.rax;
    printf(" remote mmaped addr:%p\n",remoteMemAddr);
    // 调用dlopen
    void *dlopenAddr=getRemoteFuncAddr(pid,libcPath,dlopen);
    void *dlcloseAddr=getRemoteFuncAddr(pid,libcPath,dlclose);
    void *dlErrorAddr=getRemoteFuncAddr(pid,libcPath,dlerror);
    ptraceWriteData(pid,remoteMemAddr,realLibPath,strlen(realLibPath)+1);   // 作为dlopen的参数
    //debug start
    char buf;
    memset(buf,0,MAX_PATH);
    ptraceReadData(pid,remoteMemAddr,buf,strlen(realLibPath)+3);
    printf("%s\n",buf);
    //debug end
    paras=(long)remoteMemAddr;
    paras=RTLD_NOW|RTLD_GLOBAL;
    ptraceCall(pid,dlopenAddr,paras,2,&regs);
    void*libBaseAddr=(void*)regs.rax;
    printf("remote lib base:0x%llx of %s\n",(long long)libBaseAddr,realLibPath);
    // 调用dlsym
    void*dlsymAddr=getRemoteFuncAddr(pid,libcPath,dlsym);
    ptraceWriteData(pid,remoteMemAddr,funcName,strlen(funcName)+1);
    //debug start
    memset(buf,0,MAX_PATH);
    ptraceReadData(pid,remoteMemAddr,buf,strlen(funcName)+3);
    printf("%s\n",buf);
    //debug end
    paras=(long)libBaseAddr;
    paras=(long)remoteMemAddr;
    ptraceCall(pid,dlsymAddr,paras,2,&regs);
    void*remoteFuncAddr = (void*)regs.rax;
    printf("addr:0x%llx of func %s\n",(long long)remoteFuncAddr,funcName);
    // 调用目标函数
    ptraceCall(pid,remoteFuncAddr,paras,0,&regs);
    // 恢复寄存器环境
    ptraceSetRegs(pid,&oldRegs);
    ptraceDetach(pid);
}
pid_t findPIdByName(const char *name){
    pid_t pid = -1;
    int pDirId = 0; // process dir id
    FILE *f;
    char filename;
    char cmdline;
    struct dirent *entry=NULL;
    int cnt;
    if(name==NULL){
      return -1;
    }
    DIR *dir = opendir("/proc");
    if(dir==NULL){
      return -1;
    }
    while((entry=readdir(dir))!=NULL){
      pDirId=atoi(entry->d_name);
      if(pDirId!=0){
            snprintf(filename,MAX_PATH,"/proc/%d/cmdline",pDirId);
            f = fopen(filename,"r");
            if(f){
                fgets(cmdline,sizeof(cmdline),f);
                cnt = (int)strlen(cmdline);
                while(cnt>=0&&cmdline!='/')cnt--; // cmdline是完整路径,这里只找最后一个/之后的,就是可执行文件的文件名
                cnt++;
                if(!strncmp(name,&cmdline,strlen(name))){
                  pid=pDirId;
                  break;
                }
            }
            fclose(f);
      }
    }
    closedir(dir);
    return pid;
}

int main(int argc,char **argv){
    if(argc!=4){
      printf("usage: inject processname libexample.so funcname\n");
      return 0;
    }
    pid_t pid = findPIdByName(argv);
    inject(pid,argv,argv);
    return 0;
}
```

## 参考
https://xz.aliyun.com/t/5361?spm=5176.12901015.0.i12901015.2ad3525c5zq8Kz 一直跟着这个教程学的

https://man7.org/linux/man-pages/ linux的手册真的挺详细的

Eaglecad 发表于 2021-1-21 19:15

有图有真相,需要搞点图

白衣国度 发表于 2021-1-21 19:36

想起了当年的青葱岁月 永远回不去的时光

TongShuai 发表于 2021-1-27 15:41

Eaglecad 发表于 2021-1-21 19:15
有图有真相,需要搞点图

谢谢,下次会插图的
页: [1]
查看完整版本: Linux下使用ptrace注入教程