之前介绍了Android平台上3种常见的hook方法,而hook的前提是进程注入,通过进程注入我们可以将模块或代码注入到目标进程中以便对其空间内的数据进行操作,本篇文章介绍基于ptrace函数的注入技术。

ptrace函数不熟悉的朋友可以参考我之前写的linux ptrace Ilinux ptrace II,跟hook相比,在熟悉了ptrace函数的使用方式后注入过程并不复杂,但在细节的处理上要多加留意,稍有不慎就会造成目标进程发生崩溃。

注入流程如下:

  1. 附加目标进程
  2. 保存寄存器环境
  3. 远程调用mmap函数分配空间
  4. 远程调用dlopen函数注入模块
  5. 远程调用注入模块中的函数
  6. 远程调用munmap函数释放空间
  7. 恢复寄存器环境
  8. 脱离目标进程

整个注入过程都是围绕ptrace函数进行,所以我们需要对ptrace函数进行封装以便实现特定的功能。

1、进程附加

 #define CODE_CHECK(code) do {             \
if ((code) != ) { \
return -; \
} \
} while() int ptrace_attach(pid_t pid)
{
int status = ;
CODE_CHECK(ptrace(PTRACE_ATTACH, pid, NULL, NULL));
while(waitpid(pid, &status, WUNTRACED) == -) {
if (errno == EINTR) {
continue;
} else {
return -;
}
}
return ;
}

2、脱离进程

 int ptrace_detach(pid_t pid)
{
CODE_CHECK(ptrace(PTRACE_DETACH, pid, NULL, NULL));
return ;
}

3、恢复进程运行状态

 int ptrace_continue(pid_t pid)
{
CODE_CHECK(ptrace(PTRACE_CONT, pid, NULL, NULL));
return ;
}

4、获取寄存器信息

 int ptrace_getregs(pid_t pid, struct pt_regs *regs)
{
CODE_CHECK(ptrace(PTRACE_GETREGS, pid, NULL, regs));
return ;
}

5、设置寄存器信息

 int ptrace_setregs(pid_t pid, const struct pt_regs *regs)
{
CODE_CHECK(ptrace(PTRACE_SETREGS, pid, NULL, regs));
return ;
}

6、向目标进程写入数据

int ptrace_writedata(pid_t pid, const void *addr, const void *data, int size)
{
int write_count = size / sizeof(long);
int remain_size = size % sizeof(long);
long write_buffer;
for (int i = ; i < write_count; ++i) {
memcpy(&write_buffer, data, sizeof(long));
CODE_CHECK(ptrace(PTRACE_POKETEXT, pid, addr, write_buffer));
data = ((long*)data) + ;
addr = ((long*)addr) + ;
}
if (remain_size > ) {
write_buffer = ptrace(PTRACE_PEEKTEXT, pid, addr, NULL);
memcpy(&write_buffer, data, remain_size);
CODE_CHECK(ptrace(PTRACE_POKETEXT, pid, addr, write_buffer));
}
return ;
}

7、调用目标进程函数

 int ptrace_call(pid_t pid, const void* addr, const long *parameters, int num, struct pt_regs *regs)
{
int i;
//根据函数调用约定,前4个参数分别放入r0、r1、r3、r4寄存器,其余放入栈中。
//如果需要传入字符串等信息需要提前将数据写入目标进程。
for (i = ; i < num && i < ; ++i) {
regs->uregs[i] = parameters[i];
}
if (i < num) {
LOG_INFO("write %d parameters to stack", num - i);
regs->ARM_sp -= (num - i) * sizeof(long);
CODE_CHECK(ptrace_writedata(pid, (void*)regs->ARM_sp,
&parameters[i], (num - i) * sizeof(long)));
}
//设置pc寄存器
regs->ARM_pc = (long)addr;
//根据pc寄存器的第0bit位判断目标地址指令集
if (regs->ARM_pc & ) {
//for thumb
regs->ARM_pc &= (~1u);
regs->ARM_cpsr |= CPSR_T_MASK;
} else {
regs->ARM_cpsr &= ~CPSR_T_MASK;
}
//设置lr寄存器值为0,当函数返回时进程会接收到异常信号而停止运行。
regs->ARM_lr = ;
//设置寄存器信息
CODE_CHECK(ptrace_setregs(pid, regs));
//恢复目标进行运行
CODE_CHECK(ptrace_continue(pid));
LOG_INFO("wait for stopping...");
int stat = ;
while(waitpid(pid, &stat, WUNTRACED) == -) {
if (errno == EINTR) {
continue;
} else {
return -;
}
}
if(!WIFSTOPPED(stat)) {
LOG_INFO("status is invalid: %d", stat);
return -;
}
CODE_CHECK(ptrace_getregs(pid, regs));
//平衡因传递参数而使用的栈
regs->ARM_sp += (num - i) * sizeof(long);
return ;
}

8、获取目标进程函数地址

 void* get_module_addr(pid_t pid, const char *module_name)
{
FILE *fp;
char file_path[MAX_PATH];
char file_line[MAX_LINE];
if (pid < ) {
snprintf(file_path, sizeof(file_path), "/proc/self/maps");
} else {
snprintf(file_path, sizeof(file_path), "/proc/%d/maps", pid);
}
fp = fopen(file_path, "r");
if (fp == NULL) {
return NULL;
}
unsigned long addr_start = , addr_end = ;
while (fgets(file_line, sizeof(file_line), fp)) {
if (strstr(file_line, module_name)) {
if ( == sscanf(file_line, "%8lx-%8lx", &addr_start, &addr_end)) {
break;
}
}
}
fclose(fp);
LOG_INFO("library :%s %lx-%lx, pid : %d", module_name, addr_start, addr_end, pid);
return (void*)addr_start;
} void* get_remote_func_addr(pid_t pid, const char *module_name, const void *func_local_addr)
{
void *module_local_addr, *module_remote_addr, *func_remote_addr;
module_remote_addr = get_module_addr(pid, module_name);
module_local_addr = get_module_addr(-, module_name);
if (module_remote_addr == NULL || module_local_addr == NULL) {
return NULL;
}
return (void*)((unsigned long)func_local_addr - (unsigned long)module_local_addr + (unsigned long)module_remote_addr);
}

有了这些封装后的函数,我们便可以着手对注入流程进行实现了。

首先附加目标进程,并获取一些必要函数在目标进程中的地址。

//附加目标进程
pid_t pid = 目标进程号;
CODE_CHECK(ptrace_attach(pid));
//备份目标进程寄存器
struct pt_regs current_regs, origin_regs;
CODE_CHECK(ptrace_getregs(pid, &origin_regs));
memcpy(&current_regs, &origin_regs, sizeof(struct pt_regs));
//获取远程函数地址
void *addr_mmap, *addr_munmap, *addr_dlopen, *addr_dlsym;
addr_mmap = get_remote_func_addr(pid, "/system/lib/libc.so", (void*)mmap);
addr_munmap = get_remote_func_addr(pid, "/system/lib/libc.so", (void*)munmap);
addr_dlopen = get_remote_func_addr(pid, "/system/bin/linker", (void *)dlopen);
ddr_dlsym = get_remote_func_addr(pid, "/system/bin/linker", (void *)dlsym);

接着远程调用目标进程mmap函数分配空间,因为我们在调用目标进程的dlopen等函数时,需要传递字符串信息,所以需要开辟一块空间写入字符串。

long remote_addr, remote_handle, remote_func, remote_ret;
//void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offsize);
long remote_params[];
remote_params[] = ;
remote_params[] = MMAP_SIZE;
remote_params[] = PROT_READ | PROT_WRITE | PROT_EXEC;
remote_params[] = MAP_ANONYMOUS | MAP_PRIVATE;
remote_params[] = ;
remote_params[] = ;
CODE_CHECK(ptrace_call(pid, addr_mmap, remote_params, , &current_regs));
//函数返回值在r0寄存器
remote_addr = current_regs.ARM_r0;

之后我们需要远程调用dlopen函数使目标进程加载我们的模块,但在调用之前需要先将模块路径先写入目标进程。

//将模块路径写入目标进程
char *lib_path = "模块路径";
CODE_CHECK(ptrace_writedata(pid, (void*)remote_addr, lib_path, strlen(lib_path) + ));
//void *dlopen(const char *filename, int flag);
remote_params[] = remote_addr;
remote_params[] = RTLD_NOW| RTLD_GLOBAL;
LOG_INFO("call remote dlopen");
CODE_CHECK(ptrace_call(pid, addr_dlopen, remote_params, , &current_regs));
remote_handle = current_regs.ARM_r0;

此时已成功将模块注入目标进程,需要远程调用dlsym函数获取模块在加载到目标进程后其中的函数地址,同之前一样需要先将函数名写入目标进程空间。

char *func_name = "函数名";
CODE_CHECK(ptrace_writedata(pid, (void*)remote_addr, func_name, strlen(func_name) + ));
//void *dlsym(void *handle, const char *symbol);
remote_params[] = remote_handle;
remote_params[] = remote_addr;
CODE_CHECK(ptrace_call(pid, addr_dlsym, remote_params, , &current_regs));
remote_func = current_regs.ARM_r0;

在获取函数地址后,直接对其进行远程调用,这里假设该函数不需要参数。

//int func();
CODE_CHECK(ptrace_call(pid, (void*)remote_func, NULL, , &current_regs));
remote_ret = current_regs.ARM_r0;

这时我们注入的代码已成功执行,可以恢复目标进程的运行状态了。

//释放之前在目标进程分配的空间
//int munmap(void *start, size_t length);
remote_params[] = remote_addr;
remote_params[] = MMAP_SIZE;
CODE_CHECK(ptrace_call(pid, addr_munmap, remote_params, , &current_regs));
//恢复寄存器信息
CODE_CHECK(ptrace_setregs(pid, &origin_regs));
//脱离目标进程
CODE_CHECK(ptrace_detach(pid));

一些需要包含的头文件:

#include <sys/ptrace.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <asm/ptrace.h>
#include <asm/mman.h>
#include <stdlib.h>
#include <unistd.h>
#include <dlfcn.h>
#include <stdio.h>
#include <string.h>

Android Ptrace Inject的更多相关文章

  1. android使用inject需要注意的地方

    android使用inject需要注意的地方1.viewmodel里面添加注解@Inject FavoritesDBManager mFavoritesDBManager; 2.Component里面 ...

  2. Android Hook框架adbi的分析(3)---编译和inline Hook实践

    本文博客地址:http://blog.csdn.net/qq1084283172/article/details/75200800 一.序言 在前面的博客中,已经分析过了Android Hook框架a ...

  3. android应用中增加权限判断

    android6.0系统允许用户管理应用权限,可以关闭/打开权限. 所以需要在APP中增加权限判断,以免用户关闭相应权限后,APP运行异常. 以MMS为例,在系统设置——应用——MMS——权限——&g ...

  4. Android定位服务关闭和定位(悬浮)等权限拒绝的判断

    public void checkLocationPermission() { if (!PermissionHelper.isLocServiceEnable(this)) {//检测是否开启定位服 ...

  5. Android so注入(inject)和Hook技术学习(一)

    以前对Android so的注入只是通过现有的框架,并没有去研究so注入原理,趁现在有时间正好拿出来研究一下. 首先来看注入流程.Android so的注入流程如下: attach到远程进程 -> ...

  6. Android的so注入( inject)和函数Hook(基于got表) - 支持arm和x86

    本文博客地址:http://blog.csdn.net/qq1084283172/article/details/53942648 前面深入学习了古河的Libinject注入Android进程,下面来 ...

  7. Android so注入(inject)和Hook技术学习(三)——Got表hook之导出表hook

    前文介绍了导入表hook,现在来说下导出表的hook.导出表的hook的流程如下.1.获取动态库基值 void* get_module_base(pid_t pid, const char* modu ...

  8. Android so注入(inject)和Hook技术学习(二)——Got表hook之导入表hook

    全局符号表(GOT表)hook实际是通过解析SO文件,将待hook函数在got表的地址替换为自己函数的入口地址,这样目标进程每次调用待hook函数时,实际上是执行了我们自己的函数. GOT表其实包含了 ...

  9. Android Hook学习之ptrace函数的使用

    Synopsis #include <sys/ptrace.h> long ptrace(enum __ptrace_request request, pid_t pid, void *a ...

随机推荐

  1. c++邻接表存储图(无向),并用广度优先和深度优先遍历(实验)

    一开始我是用c写的,后面才发现广搜要用到队列,所以我就直接使用c++的STL队列来写, 因为不想再写多一个队列了.这次实验写了两个多钟,因为要边写边思考,太菜了哈哈. 主要参考<大话数据结构&g ...

  2. 优先队列 poj3253 Fence Repair

    Fence Repair Time Limit: 2000MS   Memory Limit: 65536K Total Submissions: 51411   Accepted: 16879 De ...

  3. Effective Java 第三版——2. 当构造方法参数过多时使用builder模式

    Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...

  4. OSChinaclient源代码学习(1)--Android与Server的交互

    这里主要针对"综合"这个选项卡下的"资讯"模块为样例.对核心代码进行解读. 准备工作:參考我的另外一篇博客 http://blog.csdn.net/csp27 ...

  5. x86内存映射

    Contents 1 "Low" memory (< 1 MiB) 1.1 Overview 1.2 BIOS Data Area (BDA) 1.3 Extended BI ...

  6. Linux下select的用法--实现一个简单的回射服务器程序

    1.先看man手册 SYNOPSIS       /* According to POSIX.1-2001 */       #include <sys/select.h>       / ...

  7. Java web轻量级开发面试教程的前言

    本文来是从 java web轻量级开发面试教程从摘录的. 为什么要从诸多的Java书籍里选择这本?为什么在当前网络信息量如此大的情况下还要买这本书,而不是自己通过查阅网络资料学习?我已经会开发Java ...

  8. java注解(转并做修改)

    本文由 ImportNew - 人晓 翻译自 idlebrains.欢迎加入翻译小组.转载请见文末要求. 自Java5.0版本引入注解之后,它就成为了Java平台中非常重要的一部分.开发过程中,我们也 ...

  9. centos6.5安装git

    1.git源码地址:http://codemonkey.org.uk/projects/git-snapshots/git/

  10. 使用Flink时遇到的问题(不断更新中)

    1.启动不起来 查看JobManager日志: WARN org.apache.flink.runtime.webmonitor.JobManagerRetriever - Failed to ret ...