Using ptrace to inject so into the running process and execute related functions

Keywords: Linker socket Android

               

1. introduction

Using ptrace to inject. so into the running process and execute related functions, the real meaning of the word "injection" is: this. so is link ed to the running process (hereinafter referred to as the target process) space, so that functions in. so have corresponding addresses in the target process space, and then can be called in the target process through this address.

How is it injected?

The realization scheme of this paper is that in the target process, the. so needed to be injected is loaded into the space of the target process through dlopen.

2. How can the target process execute dlopen loading. so?

Obviously, the target process was not able to load the. so we want to inject through dlopen. In order to achieve this function, we need the target process to execute a piece of code that we implemented. The function of this code is to load A. so through dlopen.

3. [Implementation code for loading. so]

Loading the implementation code of.so that needs to be injected is as follows:

Note: The following global changes can be read and written in C. Global_dlopen_param1_s @dlopen parameter 1<.so> address in the target process. Global_dlopen_param2_s @dlopen parameter 2 address in the target process. Global_dl_sym_addr_s @dlopen function in the target process The address of the address. global_dlsym_param2_s @dlsym parameter 2 in the target process is actually the address of the function name. global_dlclose_addr_s @dlcose in the target process. global_inject_start_s @ the starting address of the assembly code segment. global_inject_end_s @ the end address of the assembly code segment. global_inject_param_s @hook_init Reference Global_saved_cpsr_s @ saves CPSR to restore environment after hook_init is executed. Global_saved_r0_pc_s @ saves r0-r15 to restore environment after hook_init is executed. data_inject_start_s: @ debug_p3: @sub-loor1, r1,  0@B_3b@ dl_ldr_1, open_dl_param2 @ sets up the second @open dl_dl_dl_m2 @ Parameters, flag ldr r0, _dlopen_param1_s @ Set the first parameter of dlopen. So ldr r3, _dlopen_addr_s @ Set the dlopen function blx r3 @ Execute the dlopen function, the return value is located in R0 subs r4, r0,  0 @ Save the return value of dlopen in r4, to the square. Then dlclose uses BEQ 2f@dlsym LDR r1,_dlsym_param2_s,@ to set the second parameter of dlsym. The first parameter is already in r0. LDR r3,_dlsym_addr_s,@ to set the dlsym function blx r3,... to execute the dlsym function, and the return value is in r0, subs r3,  r0, 0 #0.@ Save the return value < hook_init address in the target process > in r3, beq 1f@call our function ldr r0, _inject_function_param_s@Set hook_init first parameter, blx r3, @ Execute hook_init subs r0, r0,  beq_2f1@dlclose, @ mov, > r0, R4 @ Set the return value of dlopen as the first parameter of dlcose, ldr r3, _dlclose_addr_s,@set the dlclose function blx r3,@ execute the dlclose function 2: @restore context ldr r1, _saved_cpsr_s,@ restore CPSR, mscpsr_cf, R1 Sp, _saved_r0_pc_s, @recovery register r0-r15 ldmfd sp, {r0-pc} dlopen_addr_s: = word, @initialization_dlopen_addr_s.word 0x11111_dlopen_param1_s:.0x 11111_dl_param2_s:.0x x 2 * * * * * * * * * * * RTLD_sym_GLOL_adds Word 0x11111111_dlsym_param2_s:.word 0x11111111_dlclose_addr_s:.word 0x11111111_inject_function_param_s:.word 0x11111111_saved_cpsr_s:.word 0x11111_saved_r0_pc_s:.word 0x11111_inject_end_s:> code closing address.0x400,... 0... code section size.

4. How to write [the implementation code of loading. so] to the target process and start execution?

In order to write the implementation code of loading.so into the target process, there are two main steps:

1) Find the space to store [the implementation code of loading. so] in the target process (through mmap)

2) Write [the implementation code of loading. so] into the space specified by the target process

3) Start execution

4.1 Find space to store [load. so implementation code] in the target process

Through mmap, the steps are as follows:

1) Get mmap address in target process
2) Put mmap parameters into r0-r3 and write the other two to the target process sp
3) pc is set to mmap address and lr is set to 0
4) Write the prepared registers to the target process (PTRACE_SETREGS) and start the target process running (PTRACE_CONT)
5) The allocated memory header address is located at r0 (PTRACE_GETREGS)

4.2 is the assignment of global variables in the implementation code of loading.so

1) Get the dlopen address in the target process and assign it to _dlopen_addr_s

2) Get the dlsym address in the target process and assign it to _dlsym_addr_s

3) Get the dlclose address in the target process and assign it to _dlclose_addr_s

4) Put the path of.so that needs to be loaded into assembly code, get the address of the path in the target process, and assign it to _dlopen_param1_s.

5) Put hook_init in. so that needs to be loaded into assembly code, get the address of this path in the target process, and assign it to _dlsym_param2_s

6) Save the CPSR in the target process in _saved_cpsr_s

7) Store r0-r15 in the target process in assembly code, get the address of the variable in the target process and assign it to _saved_r0_pc_s

8) Write the assembly code into the target process through ptrace (PTRACE_POKETEXT,...), and the starting address is assigned by the mmap above.

9) Set the pc of the target process as the starting address of the assembly code, and then call ptrace(PTRACE_DETACH,...) to start the execution of the target process

5. Write assembly code into the target process and execute the implementation code

5.1 Main function 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 = -1void *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[10];    // make target_pid as its child process and stop if ( ptrace_attach( target_pid ) == -1 )  return -1;    // get the values of 18 registers from target_pid if ( ptrace_getregs( target_pid, ®s ) == -1 )  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[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    // execute the mmap in target_pid if ( ptrace_call( target_pid, (uint32_t)mmap_addr, parameters, 6, ®s ) == -1 )  goto exit;    // get the return values of mmap <in r0> if ( ptrace_getregs( target_pid, ®s ) == -1 )  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), 16 * 4 ); // 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 = 0;exitreturn ret;}

5.2 attach target process ptrace_attach

int ptrace_attach( pid_t pid ){    // after PTRACE_ATTACH, the proc<pid> will stop    if ( ptrace( PTRACE_ATTACH, pid, NULL, 0  ) < 0 )    {     perror( "ptrace_attach" );     return -1;    }    // 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, 0  ) < 0 )    {     perror( "ptrace_syscall" );     return -1;    }    // wait proc<pid> stop    waitpid( pid, NULL, WUNTRACED );    return 0;}

5.3 Get the target process register value ptrace_getregs

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;}


5.4 Get the address get_remote_addr of the specified function in the specified module in the target process

 /* 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 = 0char *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 ); return (void *)( (uint32_t)local_addr + (uint32_t)remote_handle - (uint32_t)local_handle );}


5.5 Execute the specified function ptrace_call in the target process

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 = 0; i < num_params && i < 4; 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 & 1 )    {     /* 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 = 0;     // set the regsister and start to execute    if ( ptrace_setregs( pid, regs ) == -1      || ptrace_continue( pid ) == -1 )    {     return -1;    }    // wait pid finish work and stop    waitpid( pid, NULL, WUNTRACED );    return 0;}

5.6 Write the code to the specified address of the target process 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 / 4;    remain = size % 4;    laddr = data;    for ( i = 0; i < j; i ++ )    {     memcpy( d.chars, laddr, 4 );     ptrace( PTRACE_POKETEXT, pid, dest, d.val );     dest  += 4;     laddr += 4;    }    if ( remain > 0 )    {     d.val = ptrace( PTRACE_PEEKTEXT, pid, dest, 0 );     for ( i = 0; i < remain; i ++ )     {      d.chars[i] = *laddr ++;     }     ptrace( PTRACE_POKETEXT, pid, dest, d.val );         }    return 0;}


5.7 Setting the target process register ptrace_setregs

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;}


5.8 detach target process ptrace_detach

int ptrace_detach( pid_t pid ){    if ( ptrace( PTRACE_DETACH, pid, NULL, 0 ) < 0 )    {     perror( "ptrace_detach" );     return -1;    }    return 0;}


6. so that needs to be loaded.

The. so example program that needs to be loaded is as follows, which is intended to replace the strlen function in the target process libapp.so. The main implementation is hook_init.

int g_isInit = 0;    pthread_t g_hThread;     __attribute__((visibility("default"))) void hook_init( char *args ){   if( g_isInit == 1 )   {      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 != 0 )      {         printf("create thread error:%d", ret );      }            g_isInit = 1;   }   }

6.1 Replace Func

// replace function of libapp.so// e.g: replace strcmp of libapp.so with my_strcmpvoid 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=0; 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)==0)       {          *pOldFun = (void *)*((unsigned*)reloc);        *((unsigned*)reloc)= pNewFun;         break;      }       rel++;     } }

6.2 New Functions and Other Functions

// global function variable, save the address of strcmp of libapp.soint (*g_realstrcmp)(const char *s1, const char *s2);// my strcmp functionint 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 -1;}// create a threadvoid* my_thread( void* pVoid ){    int sock;    sock = socket(AF_INET, SOCK_DGRAM, 0);    if( sock < -1 )    {      printf("create socket failed!\n");      return 0;    }    struct sockaddr_in addr_serv;      int len;      memset(&addr_serv, 0, sizeof(struct sockaddr_in));      addr_serv.sin_family = AF_INET;      addr_serv.sin_port = htons(9999);       addr_serv.sin_addr.s_addr = inet_addr("127.0.0.1");      len = sizeof(addr_serv);      int flags = fcntl( sock, F_GETFL, 0);     fcntl( sock, F_SETFL, flags | O_NONBLOCK);    int nPreState = -1;    unsigned char data=0;    while( 1 )    {        data++;        sendto( sock, &data,  sizeof( data ), 0, (struct sockaddr *)&addr_serv, sizeof( addr_serv ) );        usleep( 30000 );    }}




           

Posted by kaszu on Sat, 04 May 2019 23:10:39 -0700