Android下的注入的效果是类似于Windows下的dll注入,关于Windows下面的注入可以参考这篇文章Windows注入术。而Android一般处理器是arm架构,内核是基于linux,因此进程间是弱相互作用,不存在Windows下类似于CreateRemoteThread 作用的函数,可以在其他进程空间内创建线程来加载我们的.so文件,所以我们所采用的方法就是依赖于linux下的ptrace()函数,将目标进程作为我们进程的子进程操作目标进程的寄存器和内存来运行我们加载.so文件的代码将.so链接到目标进程。
源代码来自于看雪论坛Android 注入
关于arm汇编指令ARM汇编指令
ptrace使用部分可以参考ptrace分析
接下来看第一个部分,通过dlopen函数将我们的.so文件链接到目标进程,也就是我们要直接”注入”的代码,
.global _dlopen_addr_s @dlopen函数在目标进程中的地址 .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 @dlclose在目标进程中的地址 .global _inject_start_s @shellcode的起始地址 .global _inject_end_s @shellcode的结束地址 .global _inject_function_param_s .global _saved_cpsr_s @保存CPSR,以便执行完"hook_entry"之后恢复环境 .global _saved_r0_pc_s @保存r0-r15,以便恢复环境 .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函数 subs r4, r0, #0 @检测并保存dlopen的返回值 beq 2f @dlsym ldr r1, _dlsym_param2_s @设置dlsym第二个参数,第一个参数已经在r0中了 ldr r3, _dlsym_addr_s @dlsym函数地址 blx r3 @执行dlsym函数 subs r3, r0, #0 @检测并保存dlsym的返回值 "hook_entry"的地址 beq 1f @call our function ldr r0, _inject_function_param_s @设置"hook_entry"第一个参数 blx r3 @执行"hook_entry"函数, subs r0, r0, #0 beq 2f 1: @dlclose mov r0, r4 @保存的dlopen返回值设为dlcose的第一个参数 ldr r3, _dlclose_addr_s 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: .word 0x11111111 _dlopen_param1_s: .word 0x11111111 _dlopen_param2_s: .word 0x2 _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 @代码段空间大小 0x4000-0x3c00
.end
那怎么把我们的这段汇编代码写入目标进程空间并执行呢?
- 在目标进程中找到内存来存放我们代码,这里是通过调用mmap实现
- 把汇编代码写入到目标进程的内存中
- 执行我们的汇编代码
首先,我们要通过mmap在目标进程中找到存放我们汇编代码的内存:
- 获取目标进程中的mmap地址。这里和Windows不一样,在Windows中基本的模块的基地址是一样的,而且可以通过VirtualAllocEx()函数来目标进程中申请一块内存。而在linux下,我们是通过mmap函数来建立以个映射区,作用相当于申请一块内存,但是每个进程中的mmap函数地址不一样,所以我们要比较”libc.so”在我们进程中和目标进程中基地址来获得目标进程中mmap函数地址。
- 将mmap函数的参数写入目标进程中。mmap有6个参数,前4个参数通过r0~r3参数,剩下两个通过堆栈传参,写入SP。
- PC 寄存器设置为mmap的地址,lr设置为0 返回时,我们进程的waitpid( pid, NULL, WUNTRACED )会继续向下执行。
- 把我们准备好的寄存器写入目标进程的(PTRACE_SETREGS),并让目标进程继续执行,此时就直接去执行mmap函数了。
- mmap执行完之后,我们的waitpid继续执行,申请的内存首地址在r0寄存器中(PTREACE_GETREGS)。
之后我们要为我们汇编代码中的全局变量赋值:
- 获取目标进程中的dlopen函数地址赋值给_dlopen_addr_s
- 获取目标进程中的dlsym函数地址赋值给_dlsym_addr_s
- 获取目标进程中的dlclose函数地址赋值为_dlclose_addr_s
- 将要注入的.so的路径拷贝到汇编代码中,然后计算路径在目标进程中的绝对地址传递给_dlopen_param1_s
- 将要注入的.so文件中的”hook_entry”拷贝到汇编代码中,然后计算路径在目标进程中的绝对地址传递给_dlsym_param2_s
- 保存目标进程的CPSR寄存器的值 _saved_cpsr_s
- 将目标进程的r0~r15的值存入汇编代码变量中,计算此变量在目标进程的地址保存在_saved_r0_pc_s中
- 将整个汇编代码块写入目标进程(PTRACE_POKETEXE,……),内存为mmap所分配
- 将目标进程的pc寄存器设置为汇编代码在目标进程中的地址,然后PTRACE_DETACH,让目标进程执行,此时便会执行我们的汇编代码,链接到我们的.so文件
/*attach到目标进程ptrace_attach*/ int ptrace_attach( pid_t pid ) { if ( ptrace( PTRACE_ATTACH, pid, NULL, 0 ) < 0 ) { perror( "ptrace_attach" ); return -1; } //暂停目标进程 waitpid( pid, NULL, WUNTRACED ); //DEBUG_PRINT("attached\n"); //做出系统调用或者准备退出的时候暂停 if ( ptrace( PTRACE_SYSCALL, pid, NULL, 0 ) < 0 ) { perror( "ptrace_syscall" ); return -1; } //子进程暂停之后立即返回 waitpid( pid, NULL, WUNTRACED ); return 0; }
/*获取目标进程寄存器的值*/ int ptrace_getregs( pid_t pid, struct pt_regs* regs ) { if ( ptrace( PTRACE_GETREGS, pid, NULL, regs ) < 0 ) { perror( "ptrace_getregs: Can not get register values" ); return -1; } return 0; }
/*设置目标进程的寄存器*/ int ptrace_setregs( pid_t pid, struct pt_regs* regs ) { if ( ptrace( PTRACE_SETREGS, pid, NULL, regs ) < 0 ) { perror( "ptrace_setregs: Can not set register values" ); return -1; } return 0; }
/*获取指定模块在目标进程中的基地址*/ void* get_module_base( pid_t pid, const char* module_name ) { FILE *fp; long addr = 0; char *pch; char filename[32]; char line[1024]; if ( pid < 0 ) { /* self process */ snprintf( filename, sizeof(filename), "/proc/self/maps", pid ); } else { snprintf( filename, sizeof(filename), "/proc/%d/maps", pid ); } fp = fopen( filename, "r" ); if ( fp != NULL ) { while ( fgets( line, sizeof(line), fp ) ) { if ( strstr( line, module_name ) ) { pch = strtok( line, "-" ); addr = strtoul( pch, NULL, 16 ); if ( addr == 0x8000 ) addr = 0; break; } } fclose( fp ) ; } return (void *)addr; }
/*获取函数在目标进程中的地址*/ void* get_remote_addr( pid_t target_pid, const char* module_name, void* local_addr ) { void* local_handle, *remote_handle; /* 指定模块在我们自己进程的基地址*/ local_handle = get_module_base( -1, module_name ); /* 指定模块在目标进程的基地址*/ remote_handle = get_module_base( target_pid, module_name ); DEBUG_PRINT( "[+] get_remote_addr: local[%x], remote[%x]\n", local_handle, remote_handle ); /*mmap函数在目标进程的绝对地址*/ return (void *)( (uint32_t)local_addr + (uint32_t)remote_handle - (uint32_t)local_handle ); }
/*在目标进程中执行指定函数*/ int ptrace_call( pid_t pid, uint32_t addr, long *params, uint32_t num_params, struct pt_regs* regs ) { uint32_t i; for ( i = 0; i < num_params && i < 4; i ++ ) { regs->uregs[i] = params[i]; } // // push remained params onto stack // if ( i < num_params ) { /*sp-4 ,参数入栈*/ regs->ARM_sp -= (num_params - i) * sizeof(long) ; ptrace_writedata( pid, (void *)regs->ARM_sp, (uint8_t *)¶ms[i], (num_params - i) * sizeof(long) ); } /*pc寄存器指向要call的地址*/ regs->ARM_pc = addr; /* * 判断最后一位,如果是1就是thumb指令集 * 0是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; /*目标进程执行完mmap之后暂停*/ if ( ptrace_setregs( pid, regs ) == -1 || ptrace_continue( pid ) == -1 ) { return -1; } /*等待目标进程中mmap执行完成*/ waitpid( pid, NULL, WUNTRACED ); return 0; }
/*把代码写入目标进程*/ int ptrace_writedata( pid_t pid, uint8_t *dest, uint8_t *data, size_t size ) { uint32_t i, j, remain; uint8_t *laddr; union u { long val; char chars[sizeof(long)]; } d; /* * arm汇编都是以4字节对齐 */ j = size / 4; remain = size % 4; laddr = data; for ( i = 0; i < j; i ++ ) { memcpy( d.chars, laddr, 4 ); /*往内存地址中写入一个字节。pid表示被跟踪的子进程,内存地址由addr给出,data为所要写入的数*/ /*ptrace(PTRACE_POKETEXT, pid, addr, data)*/ ptrace( PTRACE_POKETEXT, pid, dest, d.val ); dest += 4; laddr += 4; } if ( remain > 0 ) { /* * ptrace(PTRACE_PEEKTEXT, pid, addr, data) * 从内存地址中读取一个字节,pid表示被跟踪的子进程,内存地址由addr给出,data为用户变量地址用于返回读到的数据 */ d.val = ptrace( PTRACE_PEEKTEXT, pid, dest, 0 ); for ( i = 0; i < remain; i ++ ) { d.chars[i] = *laddr ++; } /* * ptrace(PTRACE_POKETEXT, pid, addr, data) * 往内存地址中写入一个字节。pid表示被跟踪的子进程,内存地址由addr给出,data为所要写入的数据 */ ptrace( PTRACE_POKETEXT, pid, dest, d.val ); } return 0; }
int inject_remote_process( pid_t target_pid, const char *library_path, const char *function_name, void *param, size_t param_size ) { int ret = -1; void *mmap_addr, *dlopen_addr, *dlsym_addr, *dlclose_addr; void *local_handle, *remote_handle, *dlhandle; uint8_t *map_base; uint8_t *dlopen_param1_ptr, *dlsym_param2_ptr, *saved_r0_pc_ptr, *inject_param_ptr, *remote_code_ptr, *local_code_ptr; struct pt_regs regs, original_regs; /* * shellcode.s 中的全局变量 */ extern uint32_t _dlopen_addr_s, _dlopen_param1_s, _dlopen_param2_s, _dlsym_addr_s, \ _dlsym_param2_s, _dlclose_addr_s, _inject_start_s, _inject_end_s, _inject_function_param_s, \ _saved_cpsr_s, _saved_r0_pc_s; uint32_t code_length; long parameters[10]; DEBUG_PRINT( "[+] Injecting process: %d\n", target_pid ); /*作为目标进程的父进程,暂停目标进程*/ if ( ptrace_attach( target_pid ) == -1 ) return EXIT_SUCCESS; /*读取目标进程寄存器*/ if ( ptrace_getregs( target_pid, ®s ) == -1 ) goto exit; /* save original registers */ memcpy( &original_regs, ®s, sizeof(regs) ); /*mmap函数在目标进程的绝对地址*/ mmap_addr = get_remote_addr( target_pid, "/system/lib/libc.so", (void *)mmap ); DEBUG_PRINT( "[+] Remote mmap address: %x\n", mmap_addr ); /* call mmap */ parameters[0] = 0; // addr parameters[1] = 0x4000; // size parameters[2] = PROT_READ | PROT_WRITE | PROT_EXEC; // prot parameters[3] = MAP_ANONYMOUS | MAP_PRIVATE; // flags /*匿名映射,映射区不与任何文件关联*/ parameters[4] = 0; //fd parameters[5] = 0; //offset DEBUG_PRINT( "[+] Calling mmap in target process.\n" ); if ( ptrace_call( target_pid, (uint32_t)mmap_addr, parameters, 6, ®s ) == -1 ) goto exit; if ( ptrace_getregs( target_pid, ®s ) == -1 ) goto exit; DEBUG_PRINT( "[+] Target process returned from mmap, return value=%x, pc=%x \n", regs.ARM_r0, regs.ARM_pc ); /*mmap的返回值,映射区的基地址*/ map_base = (uint8_t *)regs.ARM_r0; dlopen_addr = get_remote_addr( target_pid, linker_path, (void *)dlopen ); dlsym_addr = get_remote_addr( target_pid, linker_path, (void *)dlsym ); dlclose_addr = get_remote_addr( target_pid, linker_path, (void *)dlclose ); DEBUG_PRINT( "[+] Get imports: dlopen: %x, dlsym: %x, dlclose: %x\n", dlopen_addr, dlsym_addr, dlclose_addr ); /*???*/ remote_code_ptr = map_base + 0x3C00; //0x400内存 shellcode space . 0x400 local_code_ptr = (uint8_t *)&_inject_start_s; //shellcode的开始的基地址 /* * dlopen dlsym dlclose在目标进程中的地址 */ _dlopen_addr_s = (uint32_t)dlopen_addr; _dlsym_addr_s = (uint32_t)dlsym_addr; _dlclose_addr_s = (uint32_t)dlclose_addr; DEBUG_PRINT( "[+] Inject code start: %x, end: %x\n", local_code_ptr, &_inject_end_s ); //shellcode 的长度 code_length = (uint32_t)&_inject_end_s - (uint32_t)&_inject_start_s; dlopen_param1_ptr = local_code_ptr + code_length + 0x20; dlsym_param2_ptr = dlopen_param1_ptr + MAX_PATH; saved_r0_pc_ptr = dlsym_param2_ptr + MAX_PATH; inject_param_ptr = saved_r0_pc_ptr + MAX_PATH; /* dlopen parameter 1: library name */ strcpy( dlopen_param1_ptr, library_path ); _dlopen_param1_s = REMOTE_ADDR( dlopen_param1_ptr, local_code_ptr, remote_code_ptr ); DEBUG_PRINT( "[+] _dlopen_param1_s: %x\n", _dlopen_param1_s ); /* dlsym parameter 2: function name */ strcpy( dlsym_param2_ptr, function_name ); /*hook_entry*/ _dlsym_param2_s = REMOTE_ADDR( dlsym_param2_ptr, local_code_ptr, remote_code_ptr ); DEBUG_PRINT( "[+] _dlsym_param2_s: %x\n", _dlsym_param2_s ); /* saved cpsr */ _saved_cpsr_s = original_regs.ARM_cpsr; /* saved r0-pc */ memcpy( saved_r0_pc_ptr, &(original_regs.ARM_r0), 16 * 4 ); // r0 ~ r15 _saved_r0_pc_s = REMOTE_ADDR( saved_r0_pc_ptr, local_code_ptr, remote_code_ptr ); DEBUG_PRINT( "[+] _saved_r0_pc_s: %x\n", _saved_r0_pc_s ); /* Inject function parameter */ memcpy( inject_param_ptr, param, param_size ); _inject_function_param_s = REMOTE_ADDR( inject_param_ptr, local_code_ptr, remote_code_ptr ); DEBUG_PRINT( "[+] _inject_function_param_s: %x\n", _inject_function_param_s ); DEBUG_PRINT( "[+] Remote shellcode address: %x\n", remote_code_ptr ); /*向目标进程写入shellcode*/ ptrace_writedata( target_pid, remote_code_ptr, local_code_ptr, 0x400 ); memcpy( ®s, &original_regs, sizeof(regs) ); regs.ARM_sp = (long)remote_code_ptr; /*执行链接.so文件的代码*/ regs.ARM_pc = (long)remote_code_ptr; ptrace_setregs( target_pid, ®s ); ptrace_detach( target_pid ); // inject succeeded ret = 0; exit: return ret; } int main(int argc, char** argv) { pid_t target_pid; target_pid = find_pid_of("/system/bin/servicemanager"); inject_remote_process( target_pid, "/dev/yuki/payload.so", "hook_entry", "I'm parameter!", strlen("I'm parameter!") ); }