热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

android进程inject注入

android进程inject注入注入结果经过一段时间的学习和思考以及参考了许多相关的文章,今天终于把进程注入跑通了,感谢:http:blog.csdn.netjinzhuojun
android 进程inject注入

注入结果

经过一段时间的学习和思考以及参考了许多相关的文章,今天终于把进程注入跑通了,感谢:http://blog.csdn.net/jinzhuojun/article/details/9900105 的作者。

下面将注入的结果呈上:
《android进程inject注入》
其中Hook success,pid=158为宿主进程中被注入代码的结果。
设备信息:
*系统:android 4.1.2
* CPU型号:ARMv7

原理分析

inject源码解析

具体代码见:http://blog.csdn.net/jinzhuojun/article/details/9900105

源码中对关键的语句做了注释。

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#if defined(__i386__)
#define pt_regs user_regs_struct
#endif
#define ENABLE_DEBUG 1
#if ENABLE_DEBUG
#define LOG_TAG "INJECT"
#define LOGD(fmt, args...) __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG, fmt, ##args)
#define DEBUG_PRINT(format,args...) \
LOGD(format, ##args)
#else
#define DEBUG_PRINT(format,args...)
#endif
#define CPSR_T_MASK ( 1u <<5 )
const char *libc_path = "/system/lib/libc.so";
const char *linker_path = "/system/bin/linker";
/** * #define PTRACE_TRACEME 0 // 将当前被跟踪的进程切换到停止状态,对于每一个进程,PTRACE_TRACEME 只能被调用一次 * #define PTRACE_PEEKTEXT 1 #define PTRACE_PEEKDATA 2 //PTRACE_PEEKTEXT and PTRACE_PEEKDATA(read) 分别从代码区和正被调试进程的地址空间区读取机器字。在许多当代的平台中,这两个指令是等价的。 ptrace 函数接收目标地址 addr ,并返回读到的结果。 #define PTRACE_PEEKUSR 3 // 从USER区域中读取一个字节,pid表示被跟踪的子进程, * USER区域地址由addr给出,data为用户变量地址用于返回读到的数据。 * USER结构为core文件的前面一部分,它描述了进程中止时的一些状态, * 如:寄存器值,代码、数据段大小,代码、数据段开始地址等。 * 在Linux(i386)中通过PTRACE_PEEKUSER和PTRACE_POKEUSR可以访问USER结构的数据有寄存器和调试寄存器 * * #define PTRACE_POKETEXT 4 #define PTRACE_POKEDATA 5 // PTRACE_POKETEXT and PTRACE_POKEDATA(write) 将由 data 传入的机器字写入 addr 所指定的地址 * #define PTRACE_POKEUSR 6 // 往USER区域中写入一个字节,pid表示被跟踪的子进程,USER区域地址由addr给出,data为需写入的数据 #define PTRACE_CONT 7 // PTRACE_CONT and PTRACE_SYSCALL 继续进程标志为 pid 的被调试进程的执行,而不中断与调试器进程的通信。如果 addr == 0 , * 从上次停止的地址继续执行;否则,从指定的地址继续执行。参数 data 指定发送到被调试进程的信号数量(零说明没有信号 * #define PTRACE_KILL 8 // 将 sigkill 发送到被调试进程,以终止其执行 #define PTRACE_SINGLESTEP 9 // 标志为 pid 的进程的单步执行,即执行下一条机器指令并切换为停止状态 #define PTRACE_ATTACH 0x10 // PTRACE_ATTACH 将进程标志为 pid 的运行进程切换为停止状态,在这种情形下,调试器进程成为“父进程”。 * 其他的所有参数都被忽略。进程必须具有与调试进程相同的用户标志( UID ),并且不能是 setuid/setduid 进程(否则就要用 root 来调试)。 * 它会向子进程发送SIGSTOP信号,于是我们可以察看和修改子进程,然后使用 ptrace( PTRACE_DETACH, …)来使子进程继续运行下去 #define PTRACE_DETACH 0x11 // 停止进程标志为 pid 进程(由 PTRACE_ATTACH 和 PTRACE_TRACEME指定)的调试,并继续其常态运行。其他的所有参数都被忽略 #define PTRACE_SYSCALL 24 #define PTRACE_GETREGS 12 // PTRACE_GETREGS and PTRACE_GETFPREGS将一般用途寄存器、段寄存器和调试寄存器的值读入到地址由 addr 指针所指定的调试器进程的内存区中。寄存器结构的描述放在头文件 * #define PTRACE_SETREGS 13 // PTRACE_SETREGS and PTRACE_SETFPREGS 通过拷贝由addr 指针所指定的内存区域的内容来设置被调试 进程 的寄存器的值 #define PTRACE_GETFPREGS 14 #define PTRACE_SETFPREGS 15 #define PTRACE_SETOPTIONS 0x4200 #define PTRACE_GETEVENTMSG 0x4201 #define PTRACE_GETSIGINFO 0x4202 #define PTRACE_SETSIGINFO 0x4203 */
int ptrace_readdata(pid_t pid, uint8_t *src, uint8_t *buf, 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 = buf;
for (i = 0; i d.val = ptrace(PTRACE_PEEKTEXT, pid, src, 0);
memcpy(laddr, d.chars, 4);
src += 4;
laddr += 4;
}
if (remain > 0) {
d.val = ptrace(PTRACE_PEEKTEXT, pid, src, 0);
memcpy(laddr, d.chars, remain);
}
return 0;
}
/* Func : 将size字节的data数据写入到pid进程的dest地址处 @param dest: 目的进程的栈地址 @param data: 需要写入的数据的起始地址 @param size: 需要写入的数据的大小,以字节为单位 */
int ptrace_writedata(pid_t pid, uint8_t *dest, uint8_t *data, size_t size)
{
uint32_t i, j, remain;
uint8_t *laddr;
//很巧妙的联合体,这样就可以方便的以字节为单位写入4字节数据,再以long为单位ptrace_poketext到栈中
union u {
long val;
char chars[sizeof(long)];
} d;
j = size / 4;
remain = size % 4;
laddr = data;
for (i = 0; i // 4字节为单位进行数据写入
memcpy(d.chars, laddr, 4);
// PTRACE_POKETEXT 往内存地址中写入一个字节。pid表示被跟踪的子进程,内存地址由addr给出,data为所要写入的数据
ptrace(PTRACE_POKETEXT, pid, dest, d.val);
dest += 4;
laddr += 4;
}
//为了最大程度的保持原栈的数据,先读取dest的long数据,然后只更改其中的前remain字节,再写回
// 这里remain意思是把不足4字节单独写入,最大限度保证原有堆栈信息
if (remain > 0) {
// PTRACE_PEEKTEXT 从内存地址中读取一个字节,pid表示被跟踪的子进程,内存地址由addr给出,data为用户变量地址用于返回读到的数据。在Linux(i386)中用户代码段与用户数据段重合所以读取代码段和数据段数据处理是一样的
d.val = ptrace(PTRACE_PEEKTEXT, pid, dest, 0);
for (i = 0; i d.chars[i] = *laddr ++;
}
ptrace(PTRACE_POKETEXT, pid, dest, d.val);
}
/* * 总结一下ptrace_call_wrapper,它的完成两个功能: * 一是调用ptrace_call函数来执行指定函数,执行完后将子进程挂起; * 二是调用ptrace_getregs函数获取所有寄存器的值,主要是为了获取r0即函数的返回值 */
return 0;
}
#if defined(__arm__)
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 4; i ++) {
regs->uregs[i] = params[i];
}
//
// push remained params onto stack
//
if (i regs->ARM_sp -= (num_params - i) * sizeof(long) ;
ptrace_writedata(pid, (void *)regs->ARM_sp, (uint8_t *)&params[i], (num_params - i) * sizeof(long));
}
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; // #define CPSR_T_MASK ( 1u <<5 ) 寄存器状态
}
regs->ARM_lr = 0; //设置子程序的返回地址为空,以便函数执行完后,返回到null地址,产生SIGSEGV错误
/* *Ptrace_setregs就是将修改后的regs写入寄存器中,然后调用ptrace_continue来执行我们指定的代码 */
if (ptrace_setregs(pid, regs) == -1
|| ptrace_continue(pid) == -1) {
printf("error\n");
return -1;
}
/* WUNTRACED告诉waitpid,如果子进程进入暂停状态,那么就立即返回。如果是被ptrace的子进程,那么即使不提供WUNTRACED参数,也会在子进程进 * 入暂停状态的时候立即返回。对于使用ptrace_cont运行的子进程,它会在3种情况下进入暂停状态:①下一次系统调用;②子进程退出;③子进程的执行发 * 生错误。这里的0xb7f就表示子进程进入了暂停状态,且发送的错误信号为11(SIGSEGV),它表示试图访问未分配给自己的内存, 或试图往没有写权限的 * 内存地址写数据。那么什么时候会发生这种错误呢?显然,当子进程执行完注入的函数后,由于我们在前面设置了regs->ARM_lr = 0,它就会返回到0地址处继续执行,这样就会产生SIGSEGV了! */
int stat = 0;
waitpid(pid, &stat, WUNTRACED);
//这个循环是否必须我还不确定。因为目前每次ptrace_call调用必定会返回0xb7f,不过在这也算是增加容错性吧~
while (stat != 0xb7f) {
if (ptrace_continue(pid) == -1) {
printf("error\n");
return -1;
}
waitpid(pid, &stat, WUNTRACED);
}
/* * 通过看ndk的源码sys/wait.h以及man waitpid可以知道这个0xb7f的具体作用。首先说一下stat的值: * 高2字节用于表示导致子进程的退出或暂停状态信号值,低2字节表示子进程是退出(0x0)还是暂停(0x7f)状态。0xb7f就表示子进程为暂停状态,导致它暂停的信号量为11即sigsegv错误 */
return 0;
}
#elif defined(__i386__)
long ptrace_call(pid_t pid, uint32_t addr, long *params, uint32_t num_params, struct user_regs_struct * regs)
{
regs->esp -= (num_params) * sizeof(long) ;
ptrace_writedata(pid, (void *)regs->esp, (uint8_t *)params, (num_params) * sizeof(long));
long tmp_addr = 0x00;
regs->esp -= sizeof(long);
ptrace_writedata(pid, regs->esp, (char *)&tmp_addr, sizeof(tmp_addr));
regs->eip = addr;
if (ptrace_setregs(pid, regs) == -1
|| ptrace_continue( pid) == -1) {
printf("error\n");
return -1;
}
int stat = 0;
waitpid(pid, &stat, WUNTRACED);
while (stat != 0xb7f) {
if (ptrace_continue(pid) == -1) {
printf("error\n");
return -1;
}
waitpid(pid, &stat, WUNTRACED);
}
return 0;
}
#else
#error "Not supported"
#endif
int ptrace_getregs(pid_t pid, struct pt_regs * regs)
{
// 读取寄存器值,pid表示被跟踪的子进程,data为用户变量地址用于返回读到的数据。此功能将读取所有17个基本寄存器的值
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)
{
// 设置寄存器值,pid表示被跟踪的子进程,data为用户数据地址。此功能将设置所有17个基本寄存器的值
if (ptrace(PTRACE_SETREGS, pid, NULL, regs) <0) {
perror("ptrace_setregs: Can not set register values");
return -1;
}
return 0;
}
int ptrace_continue(pid_t pid)
{
// 继续执行。pid表示被跟踪的子进程,signal为0则忽略引起调试进程中止的信号,若不为0则继续处理信号signal。
if (ptrace(PTRACE_CONT, pid, NULL, 0) <0) {
perror("ptrace_cont");
return -1;
}
return 0;
}
int ptrace_attach(pid_t pid)
{
// 跟踪指定pid进程。pid表示被跟踪进程。被跟踪进程将成为当前进程的子进程,并进入中止状态。调试器进程成为"父进程"
if (ptrace(PTRACE_ATTACH, pid, NULL, 0) <0) {
perror("ptrace_attach");
DEBUG_PRINT("ptrace_attach error : %d", errno);
return -1;
}
int status = 0;
waitpid(pid, &status , WUNTRACED); // WUNTRACED 若子进程进入暂停状态,则马上返回,但子进程的结束状态不予以理会
return 0;
}
int ptrace_detach(pid_t pid)
{
// 结束跟踪。 pid表示被跟踪的子进程。结束跟踪后被跟踪进程将继续执行
if (ptrace(PTRACE_DETACH, pid, NULL, 0) <0) {
perror("ptrace_detach");
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);// 获取self内存maps
} else {
snprintf(filename, sizeof(filename), "/proc/%d/maps", pid); // 获取pid进程内存映射maps
}
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], local_addr[%x]\n", local_handle, remote_handle, local_addr);
// 获取mmap在目标函数中的偏移地址,mmap在libc.so动态库中
/** * 查看正在运行的动态链接的程序中,某个动态库中函数的虚拟地址 * 方法很简单,首先确定,你需要查看的函数,在哪个动态库中,并且确定该函数相对于该动态库的相对地址。 * 其次,在程序运行期间,查看程序的映射表,找到动态库的加载地址。最后将这两个地址相加,就是你要的库函数在运行时的虚拟地址 * * 1. (void *)((uint32_t)local_addr - (uint32_t)local_handle 获取mmap在inject进程中的相对地址 * 2. remote_handle为libc.so在target_pid偏移地址 * 1 + 2结果是mmap在target_pid中的虚拟地址 */
void * ret_addr = (void *)((uint32_t)local_addr - (uint32_t)local_handle + (uint32_t)remote_handle);
#if defined(__i386__)
if (!strcmp(module_name, libc_path)) {
ret_addr += 2;
}
#endif
return ret_addr;
}
int find_pid_of(const char *process_name)
{
int id;
pid_t pid = -1;
DIR* dir;
FILE *fp;
char filename[32];
char cmdline[256];
struct dirent * entry;
if (process_name == NULL)
return -1;
dir = opendir("/proc");
if (dir == NULL)
return -1;
while((entry = readdir(dir)) != NULL) {
id = atoi(entry->d_name);
if (id != 0) {
sprintf(filename, "/proc/%d/cmdline", id);
fp = fopen(filename, "r");
if (fp) {
fgets(cmdline, sizeof(cmdline), fp);
fclose(fp);
if (strcmp(process_name, cmdline) == 0) {
/* process found */
pid = id;
break;
}
}
}
}
closedir(dir);
return pid;
}
long ptrace_retval(struct pt_regs * regs)
{
#if defined(__arm__)
return regs->ARM_r0;
#elif defined(__i386__)
return regs->eax;
#else
#error "Not supported"
#endif
}
long ptrace_ip(struct pt_regs * regs)
{
#if defined(__arm__)
return regs->ARM_pc;
#elif defined(__i386__)
return regs->eip;
#else
#error "Not supported"
#endif
}
/** * 调用mmap函数,在目标进程中为libxxx.so分配内存 */
int ptrace_call_wrapper(pid_t target_pid, const char * func_name, void * func_addr, long * parameters, int param_num, struct pt_regs * regs)
{
DEBUG_PRINT("[+] Calling %s in target process.\n", func_name);
if (ptrace_call(target_pid, (uint32_t)func_addr, parameters, param_num, regs) == -1)
return -1;
if (ptrace_getregs(target_pid, regs) == -1)
return -1;
DEBUG_PRINT("[+] Target process returned from %s, return value=%x, pc=%x \n",
func_name, ptrace_retval(regs), ptrace_ip(regs));
return 0;
}
int inject_remote_process(pid_t target_pid, const char *library_path, const char *function_name, const char *param, size_t param_size)
{
int ret = -1;
void *mmap_addr, *dlopen_addr, *dlsym_addr, *dlclose_addr, *dlerror_addr;
void *local_handle, *remote_handle, *dlhandle;
uint8_t *map_base = 0;
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 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)
goto exit;
if (ptrace_getregs(target_pid, ®s) == -1)
goto exit;
/* save original registers */
memcpy(&original_regs, ®s, sizeof(regs));
mmap_addr = get_remote_addr(target_pid, libc_path, (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
if (ptrace_call_wrapper(target_pid, "mmap", mmap_addr, parameters, 6, ®s) == -1)
goto exit;
// 从寄存器中获取mmap函数的返回值,即申请的内存首地址:
map_base = ptrace_ip(®s);
// map_base = ptrace_retval(®s);
DEBUG_PRINT("map_base addr = %p\n", map_base);
// 依次获取linker中dlopen、dlsym、dlclose、dlerror函数的地址:
// void * dlopen( const char * pathname, int mode ); RTLD_LAZY 暂缓决定,等有需要时再解出符号
// RTLD_NOW 立即决定,返回前解除所有未决定的符号。
// RTLD_LOCAL
// RTLD_GLOBAL 允许导出符号
// RTLD_GROUP
// RTLD_WORLD 打开一个动态链接库
//
// void*dlsym(void* handle,const char* symbol) 根据动态链接库操作句柄与符号,返回符号对应的地址
// 使用这个函数不但可以获取函数地址,也可以获取变量地址
// dlclose用于关闭指定句柄的动态链接库,只有当此动态链接库的使用计数为0时,才会真正被系统卸载。
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 );
dlerror_addr = get_remote_addr( target_pid, linker_path, (void *)dlerror );
DEBUG_PRINT("[+] Get imports: dlopen: %x, dlsym: %x, dlclose: %x, dlerror: %x\n",
dlopen_addr, dlsym_addr, dlclose_addr, dlerror_addr);
printf("library path = %s\n", library_path);
// 调用dlopen函数:
/** * ①将要注入的so名写入前面mmap出来的内存 * ②写入dlopen代码 * ③执行dlopen("libxxx.so", RTLD_NOW ! RTLD_GLOBAL) RTLD_NOW之类的参数作用可参考:http://baike.baidu.com/view/2907309.htm?fr=aladdin * ④取得dlopen的返回值,存放在sohandle变量中 */
ptrace_writedata(target_pid, map_base, library_path, strlen(library_path) + 1);
parameters[0] = map_base;
parameters[1] = RTLD_NOW| RTLD_GLOBAL;
if (ptrace_call_wrapper(target_pid, "dlopen", dlopen_addr, parameters, 2, ®s) == -1)
goto exit;
// 从寄存器中获取libxxx.so句柄值
// void * sohandle = ptrace_ip(®s);
void * sohandle = ptrace_retval(®s);
DEBUG_PRINT("sohandle addr = %p\n", sohandle);
// 调用dlsym函数:
//#define FUNCTION_NAME_ADDR_OFFSET 0x100 //为function name另找一块区域
#define FUNCTION_NAME_ADDR_OFFSET 0x00 //为function name另找一块区域
ptrace_writedata(target_pid, map_base + FUNCTION_NAME_ADDR_OFFSET, function_name, strlen(function_name) + 1);
// ptrace_writedata(target_pid, map_base, function_name, strlen(function_name) + 1);
parameters[0] = sohandle;
parameters[1] = map_base + FUNCTION_NAME_ADDR_OFFSET;
// 这里的用法是不是可以更改为:(void(*)())dlsym(sohandle,"hook_entry");,但由于parameters是地址,而不是字符串
// 但是map_base + FUNCTION_NAME_ADDR_OFFSE就确定为hook_entry的地址吗??
if (ptrace_call_wrapper(target_pid, "dlsym", dlsym_addr, parameters, 2, ®s) == -1)
goto exit;
// void * hook_entry_addr = ptrace_ip(®s);
void * hook_entry_addr = ptrace_retval(®s);
DEBUG_PRINT("hook_entry_addr = %p\n", hook_entry_addr);
#define FUNCTION_PARAM_ADDR_OFFSET 0x200
// ptrace_writedata(target_pid, map_base + FUNCTION_PARAM_ADDR_OFFSET, param, strlen(param) + 1);
ptrace_writedata(target_pid, map_base, param, strlen(param) + 1);
parameters[0] = map_base + FUNCTION_PARAM_ADDR_OFFSET;
// 调用hook_entry函数:
if (ptrace_call_wrapper(target_pid, "hook_entry", hook_entry_addr, parameters, 1, ®s) == -1)
goto exit;
printf("Press enter to dlclose and detach\n");
// getchar(); // 屏蔽enter按键
parameters[0] = sohandle;
// 调用dlclose关闭lib:
if (ptrace_call_wrapper(target_pid, "dlclose", dlclose, parameters, 1, ®s) == -1)
goto exit;
/* restore */
// 恢复现场并退出ptrace:
ptrace_setregs(target_pid, &original_regs);
ptrace_detach(target_pid);
ret = 0;
exit:
return ret;
}
/////////////////////////////////JNI/////////////////////////////////////////
// 实现自定义JNI_OnLoad JNI_OnUnload
JNIEXPORT jstring JNICALL native_fromJNI( JNIEnv * env,
jclass class)
{
LOGD("from JNI IN c Native");
pid_t target_pid;
// target_pid = find_pid_of("/system/bin/servicemanager");
target_pid = find_pid_of("/system/bin/surfaceflinger");
LOGD("target_pid = %d",target_pid);
// int success = inject_remote_process( target_pid, "/data/libhello.so", "hook_entry", "I'm parameter!", strlen("I'm parameter!") );
int success = inject_remote_process( target_pid, "/data/data/com.example.hellojni/lib/libhello.so", "hook_entry", "I'm parameter!", strlen("I'm parameter!") );
if(success!=0)
return (*env)->NewStringUTF(env, "fail");
else
return (*env)->NewStringUTF(env, "success");
}
#define JNIREC_CLASS "com/example/hellojni/HelloJni" // 指定要注册的类
/** * Table of methods associated with a single class. */
static JNINativeMethod gMethods[] = {
{
"fromJNI" , "()Ljava/lang/String;", (void *)native_fromJNI},
};
/** * register several native methods for one class */
static int registerNativeMethods(JNIEnv *env, const char * className,
JNINativeMethod * gMethods, int numMethods)
{
jclass class;
class = (*env)->FindClass(env, className);
if (class == NULL ) {
LOGD("FindClass Failed !!!");
return JNI_FALSE;
}
if ((*env)->RegisterNatives(env, class, gMethods, numMethods) <0 ) {
LOGD("Register Natives Failed !!!!!");
return JNI_FALSE;
}
LOGD("Register Natives Succes");
return JNI_TRUE;
}
/** * Register native methods for all classes we know about. */
static int registerNatives(JNIEnv * env)
{
if (!registerNativeMethods(env, JNIREC_CLASS, gMethods, sizeof(gMethods)/ sizeof(gMethods[0]))) {
return JNI_FALSE;
}
return JNI_TRUE;
}
/* * Set some test stuff up. * * Returns the JNI version on success, -1 on failure. */
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved)
{
JNIEnv* env = NULL;
jint result = -1;
LOGD("JNI_OnLoad!!");
if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
LOGD("GetEnv Failed !!!");
return -1;
}
assert(env != NULL);
if (!registerNatives(env)) {
//注册
return -1;
}
/* success -- return valid version number */
result = JNI_VERSION_1_4;
return result;
}
JNIEXPORT jint JNICALL JNI_UnOnLoad(JavaVM* vm, void* reserved)
{
JNIEnv* env = NULL;
jint result = -1;
LOGD("JNI_UnOnLoad!!");
return result;
}
int main(int argc, char** argv) {
pid_t target_pid;
target_pid = find_pid_of("/system/bin/surfaceflinger");
if (-1 == target_pid) {
printf("Can't find the process\n");
return -1;
}
// inject_remote_process(target_pid, "/data/data/com.example.hellojni/lib/libhello.so", "hook_entry", "I'm parameter!", strlen("I'm parameter!") );
inject_remote_process(target_pid, "/data/data/com.example.hellojni/libhello.so", "hook_entry", "I'm parameter!", strlen("I'm parameter!") );
return 0;
}

关键讲解一下几点

  1. dlsym获取函数hook_entry符号地址

#define FUNCTION_NAME_ADDR_OFFSET 0x00 //为function name另找一块区域
ptrace_writedata(target_pid, map_base + FUNCTION_NAME_ADDR_OFFSET, function_name, strlen(function_name) + 1);
// ptrace_writedata(target_pid, map_base, function_name, strlen(function_name) + 1);
parameters[0] = sohandle;
parameters[1] = map_base + FUNCTION_NAME_ADDR_OFFSET;
// 这里的用法是不是可以更改为:(void(*)())dlsym(sohandle,"hook_entry");,但由于parameters是地址,而不是字符串
// 但是map_base + FUNCTION_NAME_ADDR_OFFSE就确定为hook_entry的地址吗??
if (ptrace_call_wrapper(target_pid, "dlsym", dlsym_addr, parameters, 2, ®s) == -1)
goto exit;

当时对FUNCTION_NAME_ADDR_OFFSET的获取费了一番周折,这个代表什么意思?为何要用偏移量,后来读了《程序员的自我修养:链接、装载和库》以及linux一些相关的书籍,似乎懂了一点,做了如下操作:

由于libhello.so是arm架构下的动态库,会用到arm-linux-androideabi-readelf.exe以及arm-linux-androideabi-objdump.exe对libhello.so文件进行分析,动态库和elf文件的布局结构是一样的:

《android进程inject注入》

这里的文件头占用52字节,其它情况图中都有清晰的描述。
下面是引用的一张elf存储表:
《android进程inject注入》

关键是找出hook_entry符号所在地址的偏移量,通过这个偏移量+mmap基地址,就是函数hook_entry的地址。

.symbol表:
《android进程inject注入》

从图中可以看到hook_entry的value是0000c0c,也就是hook_entry函数的偏移地址是c0c

下面在通过libhello.so的反编译结果验证:
《android进程inject注入》

那么就找出了FUNCTION_NAME_ADDR_OFFSET = 0XC0C

  1. 调用hook_entry函数,并获取hook_entry参数地址

具体hook_entry的参数地址如何获取,后面会继续分析。

文章中有许多不准确或者有误的地方,请见谅!


推荐阅读
  • 开发笔记:实验7的文件读写操作
    本文介绍了使用C++的ofstream和ifstream类进行文件读写操作的方法,包括创建文件、写入文件和读取文件的过程。同时还介绍了如何判断文件是否成功打开和关闭文件的方法。通过本文的学习,读者可以了解如何在C++中进行文件读写操作。 ... [详细]
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • 本文详细介绍了GetModuleFileName函数的用法,该函数可以用于获取当前模块所在的路径,方便进行文件操作和读取配置信息。文章通过示例代码和详细的解释,帮助读者理解和使用该函数。同时,还提供了相关的API函数声明和说明。 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • Spring源码解密之默认标签的解析方式分析
    本文分析了Spring源码解密中默认标签的解析方式。通过对命名空间的判断,区分默认命名空间和自定义命名空间,并采用不同的解析方式。其中,bean标签的解析最为复杂和重要。 ... [详细]
  • VScode格式化文档换行或不换行的设置方法
    本文介绍了在VScode中设置格式化文档换行或不换行的方法,包括使用插件和修改settings.json文件的内容。详细步骤为:找到settings.json文件,将其中的代码替换为指定的代码。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • 1,关于死锁的理解死锁,我们可以简单的理解为是两个线程同时使用同一资源,两个线程又得不到相应的资源而造成永无相互等待的情况。 2,模拟死锁背景介绍:我们创建一个朋友 ... [详细]
  • 《数据结构》学习笔记3——串匹配算法性能评估
    本文主要讨论串匹配算法的性能评估,包括模式匹配、字符种类数量、算法复杂度等内容。通过借助C++中的头文件和库,可以实现对串的匹配操作。其中蛮力算法的复杂度为O(m*n),通过随机取出长度为m的子串作为模式P,在文本T中进行匹配,统计平均复杂度。对于成功和失败的匹配分别进行测试,分析其平均复杂度。详情请参考相关学习资源。 ... [详细]
  • Linux环境变量函数getenv、putenv、setenv和unsetenv详解
    本文详细解释了Linux中的环境变量函数getenv、putenv、setenv和unsetenv的用法和功能。通过使用这些函数,可以获取、设置和删除环境变量的值。同时给出了相应的函数原型、参数说明和返回值。通过示例代码演示了如何使用getenv函数获取环境变量的值,并打印出来。 ... [详细]
  • 本文介绍了PE文件结构中的导出表的解析方法,包括获取区段头表、遍历查找所在的区段等步骤。通过该方法可以准确地解析PE文件中的导出表信息。 ... [详细]
  • C++字符字符串处理及字符集编码方案
    本文介绍了C++中字符字符串处理的问题,并详细解释了字符集编码方案,包括UNICODE、Windows apps采用的UTF-16编码、ASCII、SBCS和DBCS编码方案。同时说明了ANSI C标准和Windows中的字符/字符串数据类型实现。文章还提到了在编译时需要定义UNICODE宏以支持unicode编码,否则将使用windows code page编译。最后,给出了相关的头文件和数据类型定义。 ... [详细]
  • 本文主要解析了Open judge C16H问题中涉及到的Magical Balls的快速幂和逆元算法,并给出了问题的解析和解决方法。详细介绍了问题的背景和规则,并给出了相应的算法解析和实现步骤。通过本文的解析,读者可以更好地理解和解决Open judge C16H问题中的Magical Balls部分。 ... [详细]
  • 在说Hibernate映射前,我们先来了解下对象关系映射ORM。ORM的实现思想就是将关系数据库中表的数据映射成对象,以对象的形式展现。这样开发人员就可以把对数据库的操作转化为对 ... [详细]
  • Python正则表达式学习记录及常用方法
    本文记录了学习Python正则表达式的过程,介绍了re模块的常用方法re.search,并解释了rawstring的作用。正则表达式是一种方便检查字符串匹配模式的工具,通过本文的学习可以掌握Python中使用正则表达式的基本方法。 ... [详细]
author-avatar
徐修林
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有