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
:
@sub r1, r1, #
@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, # @把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, # @把返回值<hook_init在目标进程中的地址>保存在r3中
beq 1f @call our function
ldr r0, _inject_function_param_s @设置hook_init第一个参数
blx r3 @执行hook_init
subs r0, r0, #
beq 2f :
@dlclose
mov r0, r4 @把dlopen的返回值设为dlcose的第一个参数
ldr r3, _dlclose_addr_s @设置dlclose函数
blx r3 @执行dlclose函数 :
@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, @代码段空间大小 .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
void *param, // the parameters of init function
size_t param_size ) // number of parameters
{
int ret = -;
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; // extern global variable in the assembler code
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[]; // make target_pid as its child process and stop
if ( ptrace_attach( target_pid ) == - )
return -; // get the values of 18 registers from target_pid
if ( ptrace_getregs( target_pid, ®s ) == - )
goto exit; // save original registers
memcpy( &original_regs, ®s, sizeof(regs) ); // get mmap address from target_pid
// the mmap is the address of mmap in the cur process
mmap_addr = get_remote_addr( target_pid, "/system/lib/libc.so", (void *)mmap ); // set mmap parameters
parameters[] = ; // addr
parameters[] = 0x4000; // size
parameters[] = PROT_READ | PROT_WRITE | PROT_EXEC; // prot
parameters[] = MAP_ANONYMOUS | MAP_PRIVATE; // flags
parameters[] = ; //fd
parameters[] = ; //offset // execute the mmap in target_pid
if ( ptrace_call( target_pid, (uint32_t)mmap_addr, parameters, , ®s ) == - )
goto exit; // get the return values of mmap <in r0>
if ( ptrace_getregs( target_pid, ®s ) == - )
goto exit; // get the start address for assembler code
map_base = (uint8_t *)regs.ARM_r0; // get the address of dlopen, dlsym and dlclose in target process
dlopen_addr = get_remote_addr( target_pid, "/system/bin/linker", (void *)dlopen );
dlsym_addr = get_remote_addr( target_pid, "/system/bin/linker", (void *)dlsym );
dlclose_addr = get_remote_addr( target_pid, "/system/bin/linker", (void *)dlclose ); // set the start address for assembler code in target process
remote_code_ptr = map_base + 0x3C00; // set the start address for assembler code in cur process
local_code_ptr = (uint8_t *)&_inject_start_s; // set global variable of assembler code
// and these address is in the target process
_dlopen_addr_s = (uint32_t)dlopen_addr;
_dlsym_addr_s = (uint32_t)dlsym_addr;
_dlclose_addr_s = (uint32_t)dlclose_addr; 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; // save library path to assembler code global variable
strcpy( dlopen_param1_ptr, library_path );
_dlopen_param1_s = REMOTE_ADDR( dlopen_param1_ptr, local_code_ptr, remote_code_ptr ); // save function name to assembler code global variable
strcpy( dlsym_param2_ptr, function_name );
_dlsym_param2_s = REMOTE_ADDR( dlsym_param2_ptr, local_code_ptr, remote_code_ptr ); // save cpsr to assembler code global variable
_saved_cpsr_s = original_regs.ARM_cpsr; // save r0-r15 to assembler code global variable
memcpy( saved_r0_pc_ptr, &(original_regs.ARM_r0), * ); // r0 ~ r15
_saved_r0_pc_s = REMOTE_ADDR( saved_r0_pc_ptr, local_code_ptr, remote_code_ptr ); // save function parameters to assembler code global variable
memcpy( inject_param_ptr, param, param_size );
_inject_function_param_s = REMOTE_ADDR( inject_param_ptr, local_code_ptr, remote_code_ptr ); // write the assembler code into target process
// now the values of global variable is in the target process space
ptrace_writedata( target_pid, remote_code_ptr, local_code_ptr, 0x400 ); memcpy( ®s, &original_regs, sizeof(regs) ); // set sp and pc to the start address of assembler code
regs.ARM_sp = (long)remote_code_ptr;
regs.ARM_pc = (long)remote_code_ptr; // set registers for target process
ptrace_setregs( target_pid, ®s ); // make the target_pid is not a child process of cur process
// and make target_pid continue to running
ptrace_detach( target_pid ); // now finish it successfully
ret = ; exit:
return ret;
}
5.2 attach目标进程ptrace_attach
int ptrace_attach( pid_t pid )
{
// after PTRACE_ATTACH, the proc<pid> will stop
if ( ptrace( PTRACE_ATTACH, pid, NULL, ) < )
{
perror( "ptrace_attach" );
return -;
} // wait proc<pid> stop
waitpid( pid, NULL, WUNTRACED ); // after PTRACE_SYSCALL, the proc<pid> will continue,
// but when exectue sys call function, proc<pid> will stop
if ( ptrace( PTRACE_SYSCALL, pid, NULL, ) < )
{
perror( "ptrace_syscall" );
return -;
} // wait proc<pid> stop
waitpid( pid, NULL, WUNTRACED ); return ;
}
5.3 获取目标进程寄存器值ptrace_getregs
int ptrace_getregs( pid_t pid, struct pt_regs* regs )
{
if ( ptrace( PTRACE_GETREGS, pid, NULL, regs ) < )
{
perror( "ptrace_getregs: Can not get register values" );
return -;
} return ;
}
5.4 获取目标进程中指定模块中指定函数的地址get_remote_addr
/* find the start address of module whose name is module_name
* in the designated process
*/
void* get_module_base( pid_t pid, const char* module_name )
{
FILE *fp;
long addr = ;
char *pch;
char filename[];
char line[]; if ( pid < )
{
/* 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, ); if ( addr == 0x8000 )
addr = ; 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( -, module_name );
remote_handle = get_module_base( target_pid, module_name ); return (void *)( (uint32_t)local_addr + (uint32_t)remote_handle - (uint32_t)local_handle );
}
5.5 在目标进程中执行指定函数ptrace_call
int ptrace_call( pid_t pid, uint32_t addr, long *params, uint32_t num_params, struct pt_regs* regs )
{
uint32_t i; // put the first 4 parameters into r0-r3
for ( i = ; i < num_params && i < ; i ++ )
{
regs->uregs[i] = params[i];
} // push remained params into stack
if ( i < num_params )
{
regs->ARM_sp -= (num_params - i) * sizeof(long) ;
ptrace_writedata( pid, (void *)regs->ARM_sp, (uint8_t *)¶ms[i], (num_params - i) * sizeof(long) );
}
// set the pc to func <e.g: mmap> that will be executed
regs->ARM_pc = addr;
if ( regs->ARM_pc & )
{
/* thumb */
regs->ARM_pc &= (~1u);
regs->ARM_cpsr |= CPSR_T_MASK;
}
else
{
/* arm */
regs->ARM_cpsr &= ~CPSR_T_MASK;
} // when finish this func, pid will stop
regs->ARM_lr = ; // set the regsister and start to execute
if ( ptrace_setregs( pid, regs ) == -
|| ptrace_continue( pid ) == - )
{
return -;
} // wait pid finish work and stop
waitpid( pid, NULL, WUNTRACED ); return ;
}
5.6 把代码写入目标进程指定地址ptrace_writedata
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; j = size / ;
remain = size % ; laddr = data; for ( i = ; i < j; i ++ )
{
memcpy( d.chars, laddr, );
ptrace( PTRACE_POKETEXT, pid, dest, d.val ); dest += ;
laddr += ;
} if ( remain > )
{
d.val = ptrace( PTRACE_PEEKTEXT, pid, dest, );
for ( i = ; i < remain; i ++ )
{
d.chars[i] = *laddr ++;
} ptrace( PTRACE_POKETEXT, pid, dest, d.val ); } return ;
}
5.7 设置目标进程寄存器ptrace_setregs
int ptrace_setregs( pid_t pid, struct pt_regs* regs )
{
if ( ptrace( PTRACE_SETREGS, pid, NULL, regs ) < )
{
perror( "ptrace_setregs: Can not set register values" );
return -;
} return ;
}
5.8 detach目标进程ptrace_detach
int ptrace_detach( pid_t pid )
{
if ( ptrace( PTRACE_DETACH, pid, NULL, ) < )
{
perror( "ptrace_detach" );
return -;
} return ;
}
6. 需要被加载的.so
需要被加载的.so例子程序如下,其目的是替换目标进程libapp.so中的strlen函数。其主要实现见hook_init。
int g_isInit = ;
pthread_t g_hThread; __attribute__((visibility("default"))) void hook_init( char *args )
{
if( g_isInit == )
{
printf("i am already in!");
return;
} void* soHandle = NULL; // the libapp.so is a .so of target process, and it call strcmp
soHandle = dlopen( "libapp.so", RTLD_GLOBAL );
if( soHandle != NULL )
{
g_realstrcmp = NULL;
replaceFunc( soHandle, "strcmp", my_strcmp, (void**)&g_realstrcmp ); int ret = pthread_create( &g_hThread, NULL, my_thread, NULL );
if( ret != )
{
printf("create thread error:%d", ret );
} g_isInit = ;
} }
6.1 替换函数replaceFunc
// replace function of libapp.so
// e.g: replace strcmp of libapp.so with my_strcmp
void replaceFunc(void *handle,const char *name, void* pNewFun, void** pOldFun )
{ if(!handle)
return; soinfo *si = (soinfo*)handle;
Elf32_Sym *symtab = si->symtab;
const char *strtab = si->strtab;
Elf32_Rel *rel = si->plt_rel;
unsigned count = si->plt_rel_count;
unsigned idx; // these external functions that are called by libapp.so
// is in the plt_rel
for(idx=; idx<count; idx++)
{
unsigned type = ELF32_R_TYPE(rel->r_info);
unsigned sym = ELF32_R_SYM(rel->r_info);
unsigned reloc = (unsigned)(rel->r_offset + si->base);
char *sym_name = (char *)(strtab + symtab[sym].st_name); if(strcmp(sym_name, name)==)
{
*pOldFun = (void *)*((unsigned*)reloc);
*((unsigned*)reloc)= pNewFun;
break;
}
rel++;
}
}
6.2 新函数及其它函数
// global function variable, save the address of strcmp of libapp.so
int (*g_realstrcmp)(const char *s1, const char *s2); // my strcmp function
int my_strcmp(const char *s1, const char *s2)
{
if( g_realstrcmp != NULL )
{
int nRet = g_realstrcmp( s1, s2 );
printf("***%s: s1=%s, s2=%s\n",__FUNCTION__, s1, s2 );
return nRet;
} return -;
} // create a thread
void* my_thread( void* pVoid )
{
int sock;
sock = socket(AF_INET, SOCK_DGRAM, );
if( sock < - )
{
printf("create socket failed!\n");
return ;
} struct sockaddr_in addr_serv;
int len;
memset(&addr_serv, , sizeof(struct sockaddr_in));
addr_serv.sin_family = AF_INET;
addr_serv.sin_port = htons();
addr_serv.sin_addr.s_addr = inet_addr("127.0.0.1");
len = sizeof(addr_serv); int flags = fcntl( sock, F_GETFL, );
fcntl( sock, F_SETFL, flags | O_NONBLOCK);
int nPreState = -;
unsigned char data=;
while( )
{
data++;
sendto( sock, &data, sizeof( data ), , (struct sockaddr *)&addr_serv, sizeof( addr_serv ) );
usleep( );
}
}