天天看点

Android Ptrace 注入一、Ptrace函数介绍二、Ptrace 注入进程流程三、Ptrace注入的详细实现过程

由于安卓采用的是修改后的linux内核,所以linux上的很多注入技术都可以用于安卓。ptrace远程注入技术便是一种。现在我们将实现对一款游戏进行注入。该例子是腾讯游戏安全实验室提供的,再此表示感谢!如有侵权的话,希望联系我。

一、Ptrace函数介绍

Ptrace注入技术主要使用的是linux系统下的ptrace函数。关于如何深入学习Ptrace函数。大家可以参看我前面写的几篇文章:

1.系统调用理论基础:系统调用与api

2.Ptrace 的使用: Linux Ptrace 详解

3.Ptrace 源码的介绍: Linux源码分析之Ptrace

这里就默认大家会基本使用Linux 下的ptrace函数了。

long ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data);
           

request部分请求参数:

PTRACE_ATTACH,表示附加到指定远程进程;

PTRACE_DETACH,表示从指定远程进程分离

PTRACE_GETREGS,表示读取远程进程当前寄存器环境

PTRACE_SETREGS,表示设置远程进程的寄存器环境

PTRACE_CONT,表示使远程进程继续运行

PTRACE_PEEKTEXT,从远程进程指定内存地址读取一个word大小的数据

PTRACE_POKETEXT,往远程进程指定内存地址写入一个word大小的数据
           

二、Ptrace 注入进程流程

Ptrace注入的目的是将自己的模块注入到目标进程中,让后让目标进程执行被注入模块的代码,对目进程的代码和数据修改。Ptrace 注入模块到目标进程有两种方法。第一种方法是使用ptrace将shellcode注入到远程进程的内容空间中,然后通过执行shellcode加载远程进程模块(不好意思,这种方法我目前不会,会的话分享给大家)第二种是直接远程调用dlopen、dlsym等函数加载注入模块并执行指令的代码。本文主要进行第二种方法的介绍。

下面是ptrace注入远程进程的流程图,借用的腾讯游戏安全实验室的图:

Android Ptrace 注入一、Ptrace函数介绍二、Ptrace 注入进程流程三、Ptrace注入的详细实现过程

三、Ptrace注入的详细实现过程

下面我们通过一个例子详细讲解Ptrace的注入过程。被注入模块的程序叫做<<超级玛丽快跑>>

1.由进程名获取进程的PID

我们知道Linux操作系统下的 /proc/* 目录 是一种伪文件系统(虚拟文件系统),它保存了内核的一些相关信息供我们以文件形式读取。其中进程的相关信息保存在/proc//*目录下。我们通过遍历/proc/目录中的每个目录项来找到指定进程名的PID,方法实现如下:

#include <stdio.h>
#include <dirent.h>
#include <stdlib.h>
#include <string.h>
#define MAX_PATH  1024
pid_t FindPidByProcessName(const char * process_name){
   int ProcessDirID =;
   pid_t   pid =-;
   FILE *fp = NULL;
   char filename[MAX_PATH] ={};
   char  cmdline[MAX_PATH]={};

    struct dirent * entry =NULL;

   if(process_name==NULL){
       return -;
   }

   DIR * dir =opendir("/proc");
   if(dir == NULL){
    return -;
  } 

   while((entry=readdir(dir))!=NULL){
      ProcessDirID=atoi(entry->d_name);//将数字文件名转换为int ,转换失败的话返回0;
      if( ProcessDirID!=){
         snprintf(filename,MAX_PATH,"/proc/%d/cmdline",ProcessDirID);// 文件/proc/<pid>/cmdline 为进程的启动命令行。安卓平台的为app包名;
         fp=fopen(filename,"r");
         if(fp)
          {
              fgets(cmdline,sizeof(cmdline),fp);
              fclose(fp);
              if(strncmp(process_name,cmdline,strlen(process_name))==)
              {
               pid = ProcessDirID;
               break;
              }
          }
      }
   }
   closedir(dir);
   return pid;
}

int main(int argc,char * argv[]){

    char InjectProcessName[MAX_PATH] = "com.android.settings";
    pid_t pid = FindPidByProcessName(InjectProcessName);
    printf(" pid is %d\n",(unsigned int)pid);
}
           

可看到执行成功会输出:

pid is 
           

2.附加到远程进程上

// Attach远程进程
    if (ptrace_attach(pid) == -)
        return iRet;//int iRet=-1;
           

ptrace_attach(pid)方法实现

/*************************************************
  Description:    使用ptrace Attach到指定进程
  Input:          pid表示远程进程的ID
  Output:         无
  Return:         返回0表示attach成功,返回-1表示失败
  Others:         无
*************************************************/  
int ptrace_attach(pid_t pid)    
{
    int status = ;

    if (ptrace(PTRACE_ATTACH, pid, NULL, ) < ) {    
        LOGD("attach process error, pid:%d", pid);    
        return -;    
    }    

    //LOGD("attach process pid:%d", pid);          
    waitpid(pid, &status , WUNTRACED);          

    return ;    
} 
           

其中options参数为WUNTRACTED时,表示如果对应pid的远程进程进入暂停状态,则函数马上返回,可用于等待远程进程进入暂停状态。

3.保存寄存器状态

1.获取远程进程的寄存器值

struct pt_regs CurrentRegs
// 获取远程进程的寄存器值
    if (ptrace_getregs(pid, &CurrentRegs) == -)
    {
        ptrace_detach(pid);
        return iRet;
    }

    //LOGD("ARM_r0:0x%lx, ARM_r1:0x%lx, ARM_r2:0x%lx, ARM_r3:0x%lx, ARM_r4:0x%lx, ARM_r5:0x%lx, ARM_r6:0x%lx, ARM_r7:0x%lx, ARM_r8:0x%lx, ARM_r9:0x%lx, ARM_r10:0x%lx, ARM_ip:0x%lx, ARM_sp:0x%lx, ARM_lr:0x%lx, ARM_pc:0x%lx", \
        CurrentRegs.ARM_r0, CurrentRegs.ARM_r1, CurrentRegs.ARM_r2, CurrentRegs.ARM_r3, CurrentRegs.ARM_r4, CurrentRegs.ARM_r5, CurrentRegs.ARM_r6, CurrentRegs.ARM_r7, CurrentRegs.ARM_r8, CurrentRegs.ARM_r9, CurrentRegs.ARM_r10, CurrentRegs.ARM_ip, CurrentRegs.ARM_sp, CurrentRegs.ARM_lr, CurrentRegs.ARM_pc);


           

先调用ptrace函数读取寄存器的值,然后将寄存器的值保存在寄存器结构 pt_regs中,

ptrace_getregs(pid_t pid,struct pt_regs *regs)

方法才实现。

/*************************************************
  Description:    使用ptrace获取远程进程的寄存器值
  Input:          pid表示远程进程的ID,regs为pt_regs结构,存储了寄存器值
  Output:         无
  Return:         返回0表示获取寄存器成功,返回-1表示失败
  Others:         无
*************************************************/ 
int ptrace_getregs(pid_t pid, struct pt_regs *regs)
{
    if (ptrace(PTRACE_GETREGS, pid, NULL, regs) < )
    {
        //LOGD("Get Regs error, pid:%d", pid);
        return -;
    }

    return ;
}
           

2.保存远程进程空间中当前的上下文寄存器环境

// 保存远程进程空间中当前的上下文寄存器环境
memcpy(&OriginalRegs, &CurrentRegs, sizeof(CurrentRegs)); 
           

4.远程调用mmap函数分配内存空间

1.获取mmap函数在远程进程中的地址

在远程调用这些函数前,需要知道这些函数在远程进程中的地址,mmap函数在“/system/lib/lic.so”模块中

// 获取mmap函数在远程进程中的地址
    mmap_addr = GetRemoteFuncAddr(pid, libc_path, (void *)mmap);
    //LOGD("mmap RemoteFuncAddr:0x%lx", (long)mmap_addr);
           

获取远程进程与本进程都加载的模块中函数的地址,计算方法是远程模块函数地址=本进程函数的绝对地址-本进程模块加载地址+远程进程模块的加载地址

/*************************************************
  Description:    获取远程进程与本进程都加载的模块中函数的地址
  Input:          pid表示远程进程的ID,ModuleName表示模块名称,LocalFuncAddr表示本地进程中该函数的地址
  Output:         无
  Return:         返回远程进程中对应函数的地址
  Others:         无
*************************************************/ 
void* GetRemoteFuncAddr(pid_t pid, const char *ModuleName, void *LocalFuncAddr)
{
    void *LocalModuleAddr, *RemoteModuleAddr, *RemoteFuncAddr;

    LocalModuleAddr = GetModuleBaseAddr(-, ModuleName);
    RemoteModuleAddr = GetModuleBaseAddr(pid, ModuleName);

    RemoteFuncAddr = (void *)((long)LocalFuncAddr - (long)LocalModuleAddr + (long)RemoteModuleAddr);

    return RemoteFuncAddr;
           

获取本进程中模块的加载地址,通过读取“/proc//maps”中的信息获得

/*************************************************
  Description:    在指定进程中搜索对应模块的基址
  Input:          pid表示远程进程的ID,若为-1表示自身进程,ModuleName表示要搜索的模块的名称
  Output:         无
  Return:         返回0表示获取模块基址失败,返回非0为要搜索的模块基址
  Others:         无
*************************************************/ 
void* GetModuleBaseAddr(pid_t pid, const char* ModuleName)    
{
    FILE *fp = NULL;    
    long ModuleBaseAddr = ;    
    char *ModulePath, *MapFileLineItem;
    char szFileName[] = {};    
    char szMapFileLine[] = {};
    char szProcessInfo[] = {};

    // 读取"/proc/pid/maps"可以获得该进程加载的模块
    if (pid < ) {    
        //  枚举自身进程模块 
        snprintf(szFileName, sizeof(szFileName), "/proc/self/maps");    
    } else {    
        snprintf(szFileName, sizeof(szFileName), "/proc/%d/maps", pid);    
    }    

    fp = fopen(szFileName, "r");    

    if (fp != NULL) 
    {    
        while (fgets(szMapFileLine, sizeof(szMapFileLine), fp)) {
            if (strstr(szMapFileLine, ModuleName))
            {
                MapFileLineItem = strtok(szMapFileLine, " \t"); // 基址信息
                char *Addr = strtok(szMapFileLine, "-");    
                ModuleBaseAddr = strtoul(Addr, NULL,  );    

                if (ModuleBaseAddr == )    
                    ModuleBaseAddr = ;    

                break;                  
            }
        }    

        fclose(fp) ;    
    }    

    return (void *)ModuleBaseAddr;       
}    
           

2.设置mmap的参数

// void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offsize);
long parameters[];  
    parameters[] = ;  // 设置为NULL表示让系统自动选择分配内存的地址    
    parameters[] = ; // 映射内存的大小    
    parameters[] = PROT_READ | PROT_WRITE | PROT_EXEC;  // 表示映射内存区域可读可写可执行   
    parameters[] =  MAP_ANONYMOUS | MAP_PRIVATE; // 建立匿名映射    
    parameters[] = ; //  若需要映射文件到内存中,则为文件的fd  
    parameters[] = ; //文件映射偏移量    
           

3. 调用远程进程的mmap函数,建立远程进程的内存映射

// 调用远程进程的mmap函数,建立远程进程的内存映射
    if (ptrace_call(pid, (long)mmap_addr, parameters, , &CurrentRegs) == -)
    {
        //LOGD("Call Remote mmap Func Failed");
        ptrace_detach(pid);
        return iRet;
    }
           

4. 调用远程进程的mmap函数,建立远程进程的内存映射

// 获取mmap函数执行后的返回值,也就是内存映射的起始地址
    RemoteMapMemoryAddr = (void *)ptrace_getret(&CurrentRegs);
    //LOGD("Remote Process Map Memory Addr:0x%lx", (long)RemoteMapMemoryAddr);
           
/*************************************************
  Description:    使用ptrace远程call函数
  Input:          pid表示远程进程的ID,ExecuteAddr为远程进程函数的地址
                  parameters为函数参数的地址,regs为远程进程call函数前的寄存器环境
  Output:         无
  Return:         返回0表示call函数成功,返回-1表示失败
  Others:         无
*************************************************/ 
int ptrace_call(pid_t pid, uint32_t ExecuteAddr, long *parameters, long num_params, struct pt_regs* regs)    
{    
    int i = ;
    // ARM处理器,函数传递参数,将前四个参数放到r0-r3,剩下的参数压入栈中
    for (i = ; i < num_params && i < ; i ++) {    
        regs->uregs[i] = parameters[i];    
    }    

    if (i < num_params) {    
        regs->ARM_sp -= (num_params - i) * sizeof(long) ;    // 分配栈空间,栈的方向是从高地址到低地址
        if (ptrace_writedata(pid, (void *)regs->ARM_sp, (uint8_t *)&parameters[i], (num_params - i) * sizeof(long))  == -)
            return -;
    }    

    regs->ARM_pc = ExecuteAddr;           //设置ARM_pc寄存器为需要调用的函数地址
    // 与BX跳转指令类似,判断跳转的地址位[0]是否为1,如果为1,则将CPST寄存器的标志T置位,解释为Thumb代码
    // 若为0,则将CPSR寄存器的标志T复位,解释为ARM代码
    if (regs->ARM_pc & ) {    
        /* thumb */    
        regs->ARM_pc &= (~);    
        regs->ARM_cpsr |= CPSR_T_MASK;    
    } else {    
        /* arm */    
        regs->ARM_cpsr &= ~CPSR_T_MASK;    
    }    

    regs->ARM_lr = ;        

    if (ptrace_setregs(pid, regs) == - || ptrace_continue(pid) == -) {    
       // LOGD("ptrace set regs or continue error, pid:%d", pid);  
        return -;    
    }    

    int stat = ;  
    // 对于使用ptrace_cont运行的子进程,它会在3种情况下进入暂停状态:①下一次系统调用;②子进程退出;③子进程的执行发生错误。
    // 参数WUNTRACED表示当进程进入暂停状态后,立即返回
    // 将ARM_lr(存放返回地址)设置为0,会导致子进程执行发生错误,则子进程进入暂停状态
    waitpid(pid, &stat, WUNTRACED);  

    // 判断是否成功执行函数
    //LOGD("ptrace call ret status is %d\n", stat); 
    while (stat != ) {  
        if (ptrace_continue(pid) == -) {  
          //  LOGD("ptrace call error");  
            return -;  
        }  
        waitpid(pid, &stat, WUNTRACED);  
    }

    // 获取远程进程的寄存器值,方便获取返回值
    if (ptrace_getregs(pid, regs) == -)
    {
        //LOGD("After call getregs error");
        return -;
    }

    return ;    
}    
           

5.获取mmap函数执行后的返回值,也就是内存映射的起始地址

RemoteMapMemoryAddr = (void *)ptrace_getret(&CurrentRegs);
    //LOGD("Remote Process Map Memory Addr:0x%lx", (long)RemoteMapMemoryAddr);
           
/*************************************************
  Description:    获取返回值,ARM处理器中返回值存放在ARM_r0寄存器中
  Input:          regs存储远程进程当前的寄存器值
  Output:         无
  Return:         在ARM处理器下返回r0寄存器值
  Others:         无
*************************************************/ 
long ptrace_getret(struct pt_regs * regs)    
{       
    return regs->ARM_r0;      
}
           

继续阅读