1. 简介
使用ptrace向已运行进程中注入.so并执行相关函数,其中的“注入”二字的真正含义为:此.so被link到已运行进程(以下简称为:目标进程)空间中,从而.so中的函数在目标进程空间中有对应的地址,然后通过此地址便可在目标进程中进行调用。
到底是如何注入的呢?
本文实现方案为:在目标进程中,通过dlopen把需要注入的.so加载到目标进程的空间中。
2. 如何让目标进程执行dlopen加载.so?
显然,目标进程本来是没有实现通过dlopen来加载我们想注入的.so,为了实现此功能,我们需要目标进程执行一段我们实现的代码,此段代码的功能为通过dlopen来加载一个.so。
3. 【加载.so的实现代码】
加载需要注入的.so的实现代码如下所示:
- .global _dlopen_addr_s @dlopen函数在目标进程中的地址 注:以下全局变化在C中可读写
- .global _dlopen_param1_s @dlopen参数1<.so>在目标进程中的地址
- .global _dlopen_param2_s @dlopen参数2在目标进程中的地址
- .global _dlsym_addr_s @dlsym函数在目标进程中的地址
- .global _dlsym_param2_s @dlsym参数2在目标进程中的地址,其实为函数名
- .global _dlclose_addr_s @dlcose在目标进程中的地址
- .global _inject_start_s @汇编代码段的起始地址
- .global _inject_end_s @汇编代码段的结束地址
- .global _inject_function_param_s @hook_init参数在目标进程中的地址
- .global _saved_cpsr_s @保存CPSR,以便执行完hook_init之后恢复环境
- .global _saved_r0_pc_s @保存r0-r15,以便执行完hook_init之后恢复环境
- .data
- _inject_start_s:
- @ debug loop
- 3:
- @sub r1, r1, #0
- @B 3b
- @ dlopen
- ldr r1, _dlopen_param2_s @设置dlopen第二个参数, flag
- ldr r0, _dlopen_param1_s @设置dlopen第一个参数 .so
- ldr r3, _dlopen_addr_s @设置dlopen函数
- blx r3 @执行dlopen函数,返回值位于r0中
- subs r4, r0, #0 @把dlopen的返回值soinfo保存在r4中,以方便后面dlclose使用
- beq 2f
- @dlsym
- ldr r1, _dlsym_param2_s @设置dlsym第二个参数,第一个参数已经在r0中了
- ldr r3, _dlsym_addr_s @设置dlsym函数
- blx r3 @执行dlsym函数,返回值位于r0中
- subs r3, r0, #0 @把返回值<hook_init在目标进程中的地址>保存在r3中
- beq 1f
- @call our function
- ldr r0, _inject_function_param_s @设置hook_init第一个参数
- blx r3 @执行hook_init
- subs r0, r0, #0
- beq 2f
- 1:
- @dlclose
- mov r0, r4 @把dlopen的返回值设为dlcose的第一个参数
- ldr r3, _dlclose_addr_s @设置dlclose函数
- blx r3 @执行dlclose函数
- 2:
- @restore context
- ldr r1, _saved_cpsr_s @恢复CPSR
- msr cpsr_cf, r1
- ldr sp, _saved_r0_pc_s @恢复寄存器r0-r15
- ldmfd sp, {r0-pc}
- _dlopen_addr_s: @初始化_dlopen_addr_s
- .word 0x11111111
- _dlopen_param1_s:
- .word 0x11111111
- _dlopen_param2_s:
- .word 0x2 @RTLD_GLOBAL
- _dlsym_addr_s:
- .word 0x11111111
- _dlsym_param2_s:
- .word 0x11111111
- _dlclose_addr_s:
- .word 0x11111111
- _inject_function_param_s:
- .word 0x11111111
- _saved_cpsr_s:
- .word 0x11111111
- _saved_r0_pc_s:
- .word 0x11111111
- _inject_end_s: @代码结束地址
- .space 0x400, 0 @代码段空间大小
- .end
4. 如何把【加载.so的实现代码】写入目标进程并启动执行?
为了把【加载.so的实现代码】写入目标进程,主要有以下两步操作:
1) 在目标进程中找到存放【加载.so的实现代码】的空间(通过mmap实现)
2) 把【加载.so的实现代码】写入目标进程指定的空间
3) 启动执行
4.1 在目标进程中找到存放【加载.so的实现代码】的空间
通过mmap来实现,其实现步骤如下:
1) 获取目标进程中mmap地址
2) 把mmap参数据放入r0-r3,另外两个写入目标进程sp
3) pc设置为mmap地址,lr设置为0
4) 把准备好的寄存器写入目标进程(PTRACE_SETREGS),并启动目标进程运行(PTRACE_CONT)
5) 分配的内存首地址位于r0 (PTRACE_GETREGS)
4.2 为【加载.so的实现代码】中的全局变量赋值
1) 获取目标进程中dlopen地址并赋值给_dlopen_addr_s
2) 获取目标进程中dlsym地址并赋值给_dlsym_addr_s
3) 获取目标进程中dlclose地址并赋值给_dlclose_addr_s
4) 把需要加载的.so的路径放入 汇编代码中,并获取此路径在目标进程中的地址然后赋值给_dlopen_param1_s
5) 把需要加载的.so中的hook_init放入 汇编代码中,并获取此路径在目标进程中的地址然后赋值给_dlsym_param2_s
6) 把目标进程中的cpsr保存在_saved_cpsr_s中
7) 把目标进程中的r0-r15存入汇编代码中,并获取此变量在目标进程中的地址然后赋值给_saved_r0_pc_s
8) 通过ptrace( PTRACE_POKETEXT,...)把汇编代码写入目标进程中,起始地址由前面的mmap所分配
9) 把目标进程的pc设置为汇编代码的起始地址,然后调用ptrace(PTRACE_DETACH,...)以启动目标进程执行
5. 把汇编代码写入目标进程并执行的实现代码
5.1 主函数 writecode_to_targetproc
- #include <stdio.h>
- #include <stdlib.h>
- #include <asm/ptrace.h>
- #include <asm/user.h>
- #include <asm/ptrace.h>
- #include <sys/wait.h>
- #include <sys/mman.h>
- #include <dlfcn.h>
- #include <dirent.h>
- #include <unistd.h>
- #include <string.h>
- #include <android/log.h>
- #include <sys/types.h>
- #include <sys/socket.h>
- #include <netinet/in.h>
- #include <sys/stat.h>
- #define MAX_PATH 0x100
- #define REMOTE_ADDR( addr, local_base, remote_base ) ( (uint32_t)(addr) + (uint32_t)(remote_base) - (uint32_t)(local_base) )
- /* write the assembler code into target proc,
- * and invoke it to execute
- */
- int writecode_to_targetproc(
- pid_t target_pid, // target process pid
- const char *library_path, // the path of .so that will be
- // upload to target process
- const char *function_name, // .so init fucntion e.g. hook_init