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*)¶s,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(®s,&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,®s);
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,®s);
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,®s);
void*remoteFuncAddr = (void*)regs.rax;
printf("addr:0x%llx of func %s\n",(long long)remoteFuncAddr,funcName);
// 调用目标函数
ptraceCall(pid,remoteFuncAddr,paras,0,®s);
// 恢复寄存器环境
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
有图有真相,需要搞点图
谢谢,下次会插图的
页:
[1]