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

使用ptrace向已运行进程中注入.so并执行相关函数(转)的更多相关文章

  1. 使用ptrace向已运行进程中注入.so并执行相关函数

    这个总结的很好,从前一个项目也用到这中技术 转自:http://blog.csdn.net/myarrow/article/details/9630377 1. 简介 使用ptrace向已运行进程中注 ...

  2. Linux查询已开启文件或已运行进程开启之文件fuser,lsof,pidof

    fuser:藉由文件(或文件系统)找出正在使用该文件的程序 [root@www ~]# fuser [-umv] [-k [i] [-signal]] file/dir 选项与参数: -u :除了进程 ...

  3. 2.添加键盘钩子。向进程中注入dll

    学习笔记 1.首先要建立mfc的动态链接库.在def文件中放入要导出的函数名. 2.添加函数如下 //安装钩子 //HHOOK SetWindowsHookEx( // int idHook,//钩子 ...

  4. 将dll文件注入到其他进程中的一种新方法

    http://www.45it.com/windowszh/201212/33946.htm http://www.hx95.cn/Article/OS/201212/65095.html 我们知道将 ...

  5. INNO setup安装卸载钱判断进程中是否在运行总结

    1.安装前判断进程中是否有程序在运行. [files] ; 安装前判断进程,dll文件放在inno的安装目录中Source: compiler:psvince.dll; Flags: dontcopy ...

  6. linux中应用程序main函数中没有开辟进程的,它应该在那个进程中运行呢?

    1.main函数是一个进程还是一个线程? 不知道你是用c创建的,还是用java创建的. 因为它们都是以main()做为入口开始运行的. 是一个线程,同时还是一个进程. 在现在的操作系统中,都是多线程的 ...

  7. 错误 : 资产文件“项目\obj\project.assets.json”没有“.NETCoreApp,Version=v2.0”的目标。确保已运行还原,且“netcoreapp2.0”已包含在项目的 TargetFrameworks 中。

    升级 vs201715.6.3之后发布出现 错误 : 资产文件“项目\obj\project.assets.json”没有“.NETCoreApp,Version=v2.0”的目标.确保已运行还原,且 ...

  8. pgrep---以名称为依据从运行进程队列中查找进程

    pgrep命令以名称为依据从运行进程队列中查找进程,并显示查找到的进程id.每一个进程ID以一个十进制数表示,通过一个分割字符串和下一个ID分开,默认的分割字符串是一个新行.对于每个属性选项,用户可以 ...

  9. 在Linux中通过Top运行进程查找最高内存和CPU使用率

    按内存使用情况查找前15个进程,在批处理模式下为"top" 使用top命令查看有关当前状态,系统使用情况的更详细信息:正常运行时间,负载平均值和进程总数. 分类:Linux命令操作 ...

随机推荐

  1. 「Android 开发」入门笔记

    「Android 开发」入门笔记(界面编程篇) ------每日摘要------ DAY-1: 学习笔记: Android应用结构分析 界面编程与视图(View)组件 布局管理器 问题整理: Andr ...

  2. 云计算--MPI

    [root@localhost mpi]# mpicc -c base.c[root@localhost mpi]# mpicc -o base base.o[root@localhost mpi]# ...

  3. u_boot移植之内存基础知识DDR【转】

    转自:http://blog.chinaunix.net/uid-25909619-id-4938411.html

  4. nginx安装报错:configure: error: the HTTP rewrite module requires the PCRE library

    参考:http://blog.51cto.com/williamx/958398 需要安装pcre-devel与openssl-devel yum -y install pcre-devel open ...

  5. 小白学习安全测试(三)——扫描工具-Nikto使用

    扫描工具-Nikto #基于WEB的扫描工具,基本都支持两种扫描模式.代理截断模式,主动扫描模式 手动扫描:作为用户操作发现页面存在的问题,但可能会存在遗漏 自动扫描:基于字典,提高速度,但存在误报和 ...

  6. 使用android模拟器开发程序

    自从android studio升级到3.0之后自带的模拟器已经很好用了,尤其是升级后可以想vmware那样休眠,再次开启就可以快速启动了 以下是几点可以更方便地使用系统模拟器进行开发的小技巧,毕竟模 ...

  7. socket编程之select相关

    FD_ZERO,FD_ISSET这些都是套节字结合操作宏 看看MSDN上的select函数, 这是在select   io   模型中的核心,用来管理套节字IO的,避免出现无辜锁定. int   se ...

  8. HDOJ题目分类

    模拟题, 枚举1002 1004 1013 1015 1017 1020 1022 1029 1031 1033 1034 1035 1036 1037 1039 1042 1047 1048 104 ...

  9. 在centos中修改yum源为阿里源

    cd /etc/yum.repos.d 备份旧的配置文件:mv CentOS-Base.repo CentOS-Base.repo.bak 下载阿里源的文件: wget -O CentOS-Base. ...

  10. SqlServer 中查询子节对应的上级自定义函数

    CREATE FUNCTION [dbo].[FN_TopGetOrgByUserName] ( @UserName NVARCHAR(128) ) RETURNS @showOrg TABLE(id ...