之前介紹了Android平台上3種常見的hook方法,而hook的前提是進程注入,通過進程注入我們可以將模塊或代碼注入到目標進程中以便對其空間內的數據進行操作,本篇文章介紹基於ptrace函數的注入技術。
對ptrace函數不熟悉的朋友可以參考我之前寫的linux ptrace I和linux ptrace II,跟hook相比,在熟悉了ptrace函數的使用方式后注入過程並不復雜,但在細節的處理上要多加留意,稍有不慎就會造成目標進程發生崩潰。
注入流程如下:
- 附加目標進程
- 保存寄存器環境
- 遠程調用mmap函數分配空間
- 遠程調用dlopen函數注入模塊
- 遠程調用注入模塊中的函數
- 遠程調用munmap函數釋放空間
- 恢復寄存器環境
- 脫離目標進程
整個注入過程都是圍繞ptrace函數進行,所以我們需要對ptrace函數進行封裝以便實現特定的功能。
1、進程附加
1 #define CODE_CHECK(code) do { \ 2 if ((code) != 0) { \ 3 return -1; \ 4 } \ 5 } while(0) 6 7 int ptrace_attach(pid_t pid) 8 { 9 int status = 0; 10 CODE_CHECK(ptrace(PTRACE_ATTACH, pid, NULL, NULL)); 11 while(waitpid(pid, &status, WUNTRACED) == -1) { 12 if (errno == EINTR) { 13 continue; 14 } else { 15 return -1; 16 } 17 } 18 return 0; 19 }
2、脫離進程
1 int ptrace_detach(pid_t pid) 2 { 3 CODE_CHECK(ptrace(PTRACE_DETACH, pid, NULL, NULL)); 4 return 0; 5 }
3、恢復進程運行狀態
1 int ptrace_continue(pid_t pid) 2 { 3 CODE_CHECK(ptrace(PTRACE_CONT, pid, NULL, NULL)); 4 return 0; 5 }
4、獲取寄存器信息
1 int ptrace_getregs(pid_t pid, struct pt_regs *regs) 2 { 3 CODE_CHECK(ptrace(PTRACE_GETREGS, pid, NULL, regs)); 4 return 0; 5 }
5、設置寄存器信息
1 int ptrace_setregs(pid_t pid, const struct pt_regs *regs) 2 { 3 CODE_CHECK(ptrace(PTRACE_SETREGS, pid, NULL, regs)); 4 return 0; 5 }
6、向目標進程寫入數據
int ptrace_writedata(pid_t pid, const void *addr, const void *data, int size) { int write_count = size / sizeof(long); int remain_size = size % sizeof(long); long write_buffer; for (int i = 0; i < write_count; ++i) { memcpy(&write_buffer, data, sizeof(long)); CODE_CHECK(ptrace(PTRACE_POKETEXT, pid, addr, write_buffer)); data = ((long*)data) + 1; addr = ((long*)addr) + 1; } if (remain_size > 0) { write_buffer = ptrace(PTRACE_PEEKTEXT, pid, addr, NULL); memcpy(&write_buffer, data, remain_size); CODE_CHECK(ptrace(PTRACE_POKETEXT, pid, addr, write_buffer)); } return 0; }
7、調用目標進程函數
1 int ptrace_call(pid_t pid, const void* addr, const long *parameters, int num, struct pt_regs *regs) 2 { 3 int i; 4 //根據函數調用約定,前4個參數分別放入r0、r1、r3、r4寄存器,其余放入棧中。 5 //如果需要傳入字符串等信息需要提前將數據寫入目標進程。 6 for (i = 0; i < num && i < 4; ++i) { 7 regs->uregs[i] = parameters[i]; 8 } 9 if (i < num) { 10 LOG_INFO("write %d parameters to stack", num - i); 11 regs->ARM_sp -= (num - i) * sizeof(long); 12 CODE_CHECK(ptrace_writedata(pid, (void*)regs->ARM_sp, 13 ¶meters[i], (num - i) * sizeof(long))); 14 } 15 //設置pc寄存器 16 regs->ARM_pc = (long)addr; 17 //根據pc寄存器的第0bit位判斷目標地址指令集 18 if (regs->ARM_pc & 1) { 19 //for thumb 20 regs->ARM_pc &= (~1u); 21 regs->ARM_cpsr |= CPSR_T_MASK; 22 } else { 23 regs->ARM_cpsr &= ~CPSR_T_MASK; 24 } 25 //設置lr寄存器值為0,當函數返回時進程會接收到異常信號而停止運行。 26 regs->ARM_lr = 0; 27 //設置寄存器信息 28 CODE_CHECK(ptrace_setregs(pid, regs)); 29 //恢復目標進行運行 30 CODE_CHECK(ptrace_continue(pid)); 31 LOG_INFO("wait for stopping..."); 32 int stat = 0; 33 while(waitpid(pid, &stat, WUNTRACED) == -1) { 34 if (errno == EINTR) { 35 continue; 36 } else { 37 return -1; 38 } 39 } 40 if(!WIFSTOPPED(stat)) { 41 LOG_INFO("status is invalid: %d", stat); 42 return -1; 43 } 44 CODE_CHECK(ptrace_getregs(pid, regs)); 45 //平衡因傳遞參數而使用的棧 46 regs->ARM_sp += (num - i) * sizeof(long); 47 return 0; 48 }
8、獲取目標進程函數地址
1 void* get_module_addr(pid_t pid, const char *module_name) 2 { 3 FILE *fp; 4 char file_path[MAX_PATH]; 5 char file_line[MAX_LINE]; 6 if (pid < 0) { 7 snprintf(file_path, sizeof(file_path), "/proc/self/maps"); 8 } else { 9 snprintf(file_path, sizeof(file_path), "/proc/%d/maps", pid); 10 } 11 fp = fopen(file_path, "r"); 12 if (fp == NULL) { 13 return NULL; 14 } 15 unsigned long addr_start = 0, addr_end = 0; 16 while (fgets(file_line, sizeof(file_line), fp)) { 17 if (strstr(file_line, module_name)) { 18 if (2 == sscanf(file_line, "%8lx-%8lx", &addr_start, &addr_end)) { 19 break; 20 } 21 } 22 } 23 fclose(fp); 24 LOG_INFO("library :%s %lx-%lx, pid : %d", module_name, addr_start, addr_end, pid); 25 return (void*)addr_start; 26 } 27 28 void* get_remote_func_addr(pid_t pid, const char *module_name, const void *func_local_addr) 29 { 30 void *module_local_addr, *module_remote_addr, *func_remote_addr; 31 module_remote_addr = get_module_addr(pid, module_name); 32 module_local_addr = get_module_addr(-1, module_name); 33 if (module_remote_addr == NULL || module_local_addr == NULL) { 34 return NULL; 35 } 36 return (void*)((unsigned long)func_local_addr - (unsigned long)module_local_addr + (unsigned long)module_remote_addr); 37 }
有了這些封裝后的函數,我們便可以着手對注入流程進行實現了。
首先附加目標進程,並獲取一些必要函數在目標進程中的地址。
//附加目標進程
pid_t pid = 目標進程號; CODE_CHECK(ptrace_attach(pid)); //備份目標進程寄存器 struct pt_regs current_regs, origin_regs; CODE_CHECK(ptrace_getregs(pid, &origin_regs)); memcpy(¤t_regs, &origin_regs, sizeof(struct pt_regs)); //獲取遠程函數地址 void *addr_mmap, *addr_munmap, *addr_dlopen, *addr_dlsym; addr_mmap = get_remote_func_addr(pid, "/system/lib/libc.so", (void*)mmap); addr_munmap = get_remote_func_addr(pid, "/system/lib/libc.so", (void*)munmap); addr_dlopen = get_remote_func_addr(pid, "/system/bin/linker", (void *)dlopen); ddr_dlsym = get_remote_func_addr(pid, "/system/bin/linker", (void *)dlsym);
接着遠程調用目標進程mmap函數分配空間,因為我們在調用目標進程的dlopen等函數時,需要傳遞字符串信息,所以需要開辟一塊空間寫入字符串。
long remote_addr, remote_handle, remote_func, remote_ret; //void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offsize);
long remote_params[6]; remote_params[0] = 0; remote_params[1] = MMAP_SIZE; remote_params[2] = PROT_READ | PROT_WRITE | PROT_EXEC; remote_params[3] = MAP_ANONYMOUS | MAP_PRIVATE; remote_params[4] = 0; remote_params[5] = 0; CODE_CHECK(ptrace_call(pid, addr_mmap, remote_params, 6, ¤t_regs)); //函數返回值在r0寄存器 remote_addr = current_regs.ARM_r0;
之后我們需要遠程調用dlopen函數使目標進程加載我們的模塊,但在調用之前需要先將模塊路徑先寫入目標進程。
//將模塊路徑寫入目標進程 char *lib_path = "模塊路徑"; CODE_CHECK(ptrace_writedata(pid, (void*)remote_addr, lib_path, strlen(lib_path) + 1)); //void *dlopen(const char *filename, int flag); remote_params[0] = remote_addr; remote_params[1] = RTLD_NOW| RTLD_GLOBAL; LOG_INFO("call remote dlopen"); CODE_CHECK(ptrace_call(pid, addr_dlopen, remote_params, 2, ¤t_regs)); remote_handle = current_regs.ARM_r0;
此時已成功將模塊注入目標進程,需要遠程調用dlsym函數獲取模塊在加載到目標進程后其中的函數地址,同之前一樣需要先將函數名寫入目標進程空間。
char *func_name = "函數名"; CODE_CHECK(ptrace_writedata(pid, (void*)remote_addr, func_name, strlen(func_name) + 1)); //void *dlsym(void *handle, const char *symbol); remote_params[0] = remote_handle; remote_params[1] = remote_addr; CODE_CHECK(ptrace_call(pid, addr_dlsym, remote_params, 2, ¤t_regs)); remote_func = current_regs.ARM_r0;
在獲取函數地址后,直接對其進行遠程調用,這里假設該函數不需要參數。
//int func(); CODE_CHECK(ptrace_call(pid, (void*)remote_func, NULL, 0, ¤t_regs)); remote_ret = current_regs.ARM_r0;
這時我們注入的代碼已成功執行,可以恢復目標進程的運行狀態了。
//釋放之前在目標進程分配的空間 //int munmap(void *start, size_t length); remote_params[0] = remote_addr; remote_params[1] = MMAP_SIZE; CODE_CHECK(ptrace_call(pid, addr_munmap, remote_params, 2, ¤t_regs)); //恢復寄存器信息 CODE_CHECK(ptrace_setregs(pid, &origin_regs)); //脫離目標進程 CODE_CHECK(ptrace_detach(pid));
一些需要包含的頭文件:
#include <sys/ptrace.h> #include <sys/mman.h> #include <sys/wait.h> #include <asm/ptrace.h> #include <asm/mman.h> #include <stdlib.h> #include <unistd.h> #include <dlfcn.h> #include <stdio.h> #include <string.h>