Android Ptrace Inject
之前介绍了Android平台上3种常见的hook方法,而hook的前提是进程注入,通过进程注入我们可以将模块或代码注入到目标进程中以便对其空间内的数据进行操作,本篇文章介绍基于ptrace函数的注入技术。
对ptrace函数不熟悉的朋友可以参考我之前写的linux ptrace I和linux ptrace II,跟hook相比,在熟悉了ptrace函数的使用方式后注入过程并不复杂,但在细节的处理上要多加留意,稍有不慎就会造成目标进程发生崩溃。
注入流程如下:
- 附加目标进程
- 保存寄存器环境
- 远程调用mmap函数分配空间
- 远程调用dlopen函数注入模块
- 远程调用注入模块中的函数
- 远程调用munmap函数释放空间
- 恢复寄存器环境
- 脱离目标进程
整个注入过程都是围绕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,
¶meters[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(¤t_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, , ¤t_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, , ¤t_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, , ¤t_regs));
remote_func = current_regs.ARM_r0;
在获取函数地址后,直接对其进行远程调用,这里假设该函数不需要参数。
//int func();
CODE_CHECK(ptrace_call(pid, (void*)remote_func, NULL, , ¤t_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, , ¤t_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的更多相关文章
- android使用inject需要注意的地方
android使用inject需要注意的地方1.viewmodel里面添加注解@Inject FavoritesDBManager mFavoritesDBManager; 2.Component里面 ...
- Android Hook框架adbi的分析(3)---编译和inline Hook实践
本文博客地址:http://blog.csdn.net/qq1084283172/article/details/75200800 一.序言 在前面的博客中,已经分析过了Android Hook框架a ...
- android应用中增加权限判断
android6.0系统允许用户管理应用权限,可以关闭/打开权限. 所以需要在APP中增加权限判断,以免用户关闭相应权限后,APP运行异常. 以MMS为例,在系统设置——应用——MMS——权限——&g ...
- Android定位服务关闭和定位(悬浮)等权限拒绝的判断
public void checkLocationPermission() { if (!PermissionHelper.isLocServiceEnable(this)) {//检测是否开启定位服 ...
- Android so注入(inject)和Hook技术学习(一)
以前对Android so的注入只是通过现有的框架,并没有去研究so注入原理,趁现在有时间正好拿出来研究一下. 首先来看注入流程.Android so的注入流程如下: attach到远程进程 -> ...
- Android的so注入( inject)和函数Hook(基于got表) - 支持arm和x86
本文博客地址:http://blog.csdn.net/qq1084283172/article/details/53942648 前面深入学习了古河的Libinject注入Android进程,下面来 ...
- Android so注入(inject)和Hook技术学习(三)——Got表hook之导出表hook
前文介绍了导入表hook,现在来说下导出表的hook.导出表的hook的流程如下.1.获取动态库基值 void* get_module_base(pid_t pid, const char* modu ...
- Android so注入(inject)和Hook技术学习(二)——Got表hook之导入表hook
全局符号表(GOT表)hook实际是通过解析SO文件,将待hook函数在got表的地址替换为自己函数的入口地址,这样目标进程每次调用待hook函数时,实际上是执行了我们自己的函数. GOT表其实包含了 ...
- Android Hook学习之ptrace函数的使用
Synopsis #include <sys/ptrace.h> long ptrace(enum __ptrace_request request, pid_t pid, void *a ...
随机推荐
- 大白话Vue源码系列(02):编译器初探
阅读目录 编译器代码藏在哪 Vue.prototype.$mount 构建 AST 的一般过程 Vue 构建的 AST 题接上文,上回书说到,Vue 的编译器模块相对独立且简单,那咱们就从这块入手,先 ...
- admin密码重置方式
1.在项目根目录下运行:python manage.py shell 2.重设密码 from django.contrib.auth.models import User user =User.obj ...
- iOS日历中给一个事件加入多个提醒
大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 假设认为写的不好请多提意见,假设认为不错请多多支持点赞.谢谢! hopy ;) iOS自带的日历应用中,我们最多仅仅能给一个事件设置2个提醒, ...
- java_多态
一.多态(对象的多种形态)1.引用的多态 父类的引用指向本类的对象 父类的引用指向子类的对象(引用多态) (不允许子类对象指向父类)2.方法多态 创建本类对象时调用的方法为本类的方法 创建子类对象时, ...
- 基于 HTML5 Canvas 的 3D 机房创建
对于 3D 机房来说,监控已经不是什么难事,不同的人有不同的做法,今天试着用 HT 写了一个基于 HTML5 的机房,发现果然 HT 简单好用.本例是将灯光.雾化以及 eye 的最大最小距离等等功能在 ...
- sqlser 2005 使用执行计划来优化你的sql
一:sqlserver 执行计划介绍 sqlserver 执行计是在sqlser manager studio 工具中打开,是检查一条sql执行效率的工具.建议配合SET STATISTICS ...
- 导入maven项目时出现 Version of Spring Facet could not be detected. 解决方法
问题出现在: 导入maven项目的时候,其中,我的这个maven项目是由Spring,Struts2,Mybatis搭建的. 问题截图: 即Spring的版本不能被检测到.此时需要做的就是找到spr ...
- Visual Studio 实用技能
1快捷键使用 1. Ctrl K F 代码对齐
- Java IO学习要点导图
Java IO的一些基础知识: 导图源文件保存地址:https://github.com/wanghaoxi3000/xmind
- DataBase MongoDB集群方案介绍
MongoDB集群方案介绍 一.什么是mongodb集群? 是指由多台mongodb服务器组成的服务器集群,称之为mongodb集群. 二.mongodb集群搭建的方式: 1.Replica Set ...