注入技术在外挂、热修复技术中非常常见,由于Android使用Linux内核,所以先实现Linux下ptrace注入,后面再逐步改为Android版本
被注入的程序
#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文件
#include <stdio.h>
void testEntry(){
FILE *f = fopen("/tmp/test.txt","w");
fputs("Ok to load",f);
fclose(f);
}
使用下面的命令编译为so文件
gcc -o libtest.so test.c -fPIC -shared
注入器
#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[i]);
}
// 每次写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[i]=rdata;
}
}
void ptraceAttach(pid_t pid){
if(ptrace(PTRACE_ATTACH,pid,NULL,NULL)==-1){
printf("[INJECT F]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[MAX_PATH];
void*moduleBaseAddr=NULL;
snprintf(filepath,MAX_PATH,"/proc/%d/maps",pid);
FILE *f = fopen(filepath,"r");
char line[MAX_PATH];
char base[MAX_PATH],name[MAX_PATH];
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]!='/')cnt++;
start=cnt;
while(line[cnt]){
name[cnt-start]=line[cnt];
cnt++;
}
name[cnt-start-1]=0;
if(strncmp(name,moduleName,MAX_PATH))continue;
memset(base,0,MAX_PATH);
cnt=0;
while(line[cnt]!='-'){
base[cnt]=line[cnt];
cnt++;
}
base[cnt]=0;
sscanf(base,"%llx",(long long*)(&moduleBaseAddr));
printf("[INJECT] 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("[INJECT] 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[6],sizeof(long)*(paraLen-6));
}
// 前6个参数通过寄存器传递
switch(paraLen){
case 6:
regs->r9=paras[5];
case 5:
regs->r8=paras[4];
case 4:
regs->rcx=paras[3];
case 3:
regs->rdx=paras[2];
case 2:
regs->rsi=paras[1];
case 1:
regs->rdi=paras[0];
break;
}
// 调用函数
regs->rip=(unsigned long long)funcAddr;
ptraceSetRegs(pid,regs);
int stat=0;
while(stat!=0xb7f){
ptraceContinue(pid);
waitpid(pid,&stat,WUNTRACED);
printf("[INJECT] 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[6];
char realLibPath[MAX_PATH];
realpath(libname,realLibPath);
printf("[INJECT]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]=0;
paras[1]=0x1000;
paras[2]=PROT_READ|PROT_WRITE|PROT_EXEC;
paras[3]=MAP_ANONYMOUS|MAP_PRIVATE;
paras[4]=0;
paras[5]=0;
ptraceCall(pid,mmapAddr,paras,6,®s);
void *remoteMemAddr=(void*)regs.rax;
printf("[INJECT] 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[MAX_PATH];
memset(buf,0,MAX_PATH);
ptraceReadData(pid,remoteMemAddr,buf,strlen(realLibPath)+3);
printf("%s\n",buf);
//debug end
paras[0]=(long)remoteMemAddr;
paras[1]=RTLD_NOW|RTLD_GLOBAL;
ptraceCall(pid,dlopenAddr,paras,2,®s);
void*libBaseAddr=(void*)regs.rax;
printf("[INJECT]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[0]=(long)libBaseAddr;
paras[1]=(long)remoteMemAddr;
ptraceCall(pid,dlsymAddr,paras,2,®s);
void*remoteFuncAddr = (void*)regs.rax;
printf("[INJECT]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[MAX_PATH];
char cmdline[MAX_PATH];
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]!='/')cnt--; // cmdline是完整路径,这里只找最后一个/之后的,就是可执行文件的文件名
cnt++;
if(!strncmp(name,&cmdline[cnt],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[1]);
inject(pid,argv[2],argv[3]);
return 0;
}
参考
https://xz.aliyun.com/t/5361?spm=5176.12901015.0.i12901015.2ad3525c5zq8Kz 一直跟着这个教程学的
https://man7.org/linux/man-pages/ linux的手册真的挺详细的