Android 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注入遠程進程的流程圖,借用的騰訊遊戲安全實驗室的圖:
這裏寫圖片描述

三、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 =0;
   pid_t   pid =-1;
   FILE *fp = NULL;
   char filename[MAX_PATH] ={0};
   char  cmdline[MAX_PATH]={0};

    struct dirent * entry =NULL;

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

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

   while((entry=readdir(dir))!=NULL){
      ProcessDirID=atoi(entry->d_name);//將數字文件名轉換爲int ,轉換失敗的話返回0;
      if( ProcessDirID!=0){
         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))==0)
              {
               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 1403

2.附加到遠程進程上

// Attach遠程進程
    if (ptrace_attach(pid) == -1)
        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 = 0;

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

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

    return 0;    
} 

其中options參數爲WUNTRACTED時,表示如果對應pid的遠程進程進入暫停狀態,則函數馬上返回,可用於等待遠程進程進入暫停狀態。

3.保存寄存器狀態

1.獲取遠程進程的寄存器值

struct pt_regs CurrentRegs
// 獲取遠程進程的寄存器值
    if (ptrace_getregs(pid, &CurrentRegs) == -1)
    {
        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) < 0)
    {
        //LOGD("Get Regs error, pid:%d", pid);
        return -1;
    }

    return 0;
}

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(-1, 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 = 0;    
    char *ModulePath, *MapFileLineItem;
    char szFileName[50] = {0};    
    char szMapFileLine[1024] = {0};
    char szProcessInfo[1024] = {0};

    // 讀取"/proc/pid/maps"可以獲得該進程加載的模塊
    if (pid < 0) {    
        //  枚舉自身進程模塊 
        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, 16 );    

                if (ModuleBaseAddr == 0x8000)    
                    ModuleBaseAddr = 0;    

                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[6];  
    parameters[0] = 0;  // 設置爲NULL表示讓系統自動選擇分配內存的地址    
    parameters[1] = 0x1000; // 映射內存的大小    
    parameters[2] = PROT_READ | PROT_WRITE | PROT_EXEC;  // 表示映射內存區域可讀可寫可執行   
    parameters[3] =  MAP_ANONYMOUS | MAP_PRIVATE; // 建立匿名映射    
    parameters[4] = 0; //  若需要映射文件到內存中,則爲文件的fd  
    parameters[5] = 0; //文件映射偏移量    

3. 調用遠程進程的mmap函數,建立遠程進程的內存映射

// 調用遠程進程的mmap函數,建立遠程進程的內存映射
    if (ptrace_call(pid, (long)mmap_addr, parameters, 6, &CurrentRegs) == -1)
    {
        //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 = 0;
    // ARM處理器,函數傳遞參數,將前四個參數放到r0-r3,剩下的參數壓入棧中
    for (i = 0; i < num_params && i < 4; 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))  == -1)
            return -1;
    }    

    regs->ARM_pc = ExecuteAddr;           //設置ARM_pc寄存器爲需要調用的函數地址
    // 與BX跳轉指令類似,判斷跳轉的地址位[0]是否爲1,如果爲1,則將CPST寄存器的標誌T置位,解釋爲Thumb代碼
    // 若爲0,則將CPSR寄存器的標誌T復位,解釋爲ARM代碼
    if (regs->ARM_pc & 1) {    
        /* thumb */    
        regs->ARM_pc &= (~1u);    
        regs->ARM_cpsr |= CPSR_T_MASK;    
    } else {    
        /* arm */    
        regs->ARM_cpsr &= ~CPSR_T_MASK;    
    }    

    regs->ARM_lr = 0;        

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

    int stat = 0;  
    // 對於使用ptrace_cont運行的子進程,它會在3種情況下進入暫停狀態:①下一次系統調用;②子進程退出;③子進程的執行發生錯誤。
    // 參數WUNTRACED表示當進程進入暫停狀態後,立即返回
    // 將ARM_lr(存放返回地址)設置爲0,會導致子進程執行發生錯誤,則子進程進入暫停狀態
    waitpid(pid, &stat, WUNTRACED);  

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

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

    return 0;    
}    

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;      
}
發佈了45 篇原創文章 · 獲贊 31 · 訪問量 12萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章