之前介绍了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. Java开发小技巧(三):Maven多工程依赖项目

    前言 本篇文章基于Java开发小技巧(二):自定义Maven依赖中创建的父工程project-monitor实现,运用我们自定义的依赖包进行多工程依赖项目的开发. 下面以多可执行Jar包项目的开发为例 ...

  2. Qt音乐播放器制作(二)Easy Player

    两天没有公布新的动态.主要原因还是个人的生活和工作时间限制,如今赶晚贴出第二版. 先放个图: watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvamFuNV9y ...

  3. 关于Idea中右边的maven projects窗口找不到了如何调出来

    关于Idea中右边的maven  projects窗口找不到了如何调出来? 具体的idea版本我不太清楚,我用的是2016版,其他版本应该也是一样的. 首先idea自带了maven控件,不像Eclip ...

  4. 中国IT职业培训市场经历的几波浪潮,未来的浪潮又是那一波?

    第一波 电脑普及性培训时代 2000年至2003年左右,中国正处于PC计算机普及阶段,而IT职业教育也刚开始兴起,这一波浪潮主要以计算机办公自动化.平面设计.计算机硬件维修.为主:几家大的IT培训机构 ...

  5. Android开发之常见事件响应方式

    常见的事件有   (1)单击事件 onClickListener (2)长按事件 onLongClickListener (3)滑动事件 onTouchListener (4)键盘事件 onKeyLi ...

  6. 3D位置语音,引领吃鸡游戏体验升级

    欢迎大家前往云加社区,获取更多腾讯海量技术实践干货哦~ 作者:腾讯游戏云 导语:在刚刚结束的首届腾讯用户开放日上,腾讯音视频实验室带着3D位置音效解决方案,向所有用户亮相,为用户提供360度立体空间的 ...

  7. 挂载mount、卸载umount、挂载光盘U盘

    mount [root@localhost ~]# mount sysfs on /sys type sysfs (rw,nosuid,nodev,noexec,relatime,seclabel) ...

  8. SQL Server AlwaysOn添加监听器失败

    标签:MSSQL/ 一.错误描述 1.群集服务未能使群集服务或应用程序“Alwayson22”完全联机或脱机.一个或多个资源可能处于失败状态.这可能会影响群集服务或应用程序的可用性 2.群集服务中的群 ...

  9. PXE搭建

    前提最好是防火墙规则-F,关闭,selinux 是disable 这个在以后更新linux系统的时候还可以在这个基础上再次增加可以一体化安装的系统. 1.用yum来安装所需要的软件包,先来搭建yum光 ...

  10. linux系统下,安装centos7.0系统,配置网卡出现的问题(与centos5.x、centos6.x版本,有差异)

    1.新建虚拟机时,自己下载的是centos64系统,选择系统时,默认选择centos,而未选择centos64位,导致犯了一个低级错误,导致后面网卡安装一直有问题 2.查看ip命令与centos5.x ...