由於安卓採用的是修改後的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 *)¶meters[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;
}