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 ...
随机推荐
- Django1.10+Mysql 5.7存储emoji表情,报Incorrect string value: '\\xF0\\x9F\\x90\\xA8' for column 'signature' at row 1的解决方法
问题: 在做webapp项目的时候,用户提交emoji数据,控制台报错:Incorrect string value: '\\xF0\\x9F\\x90\\xA8' for column 'signa ...
- C++ - 复制容器(container)的元素至还有一个容器
复制容器(container)的元素至还有一个容器 本文地址: http://blog.csdn.net/caroline_wendy C++复制容器(container)元素, 能够使用标准库(ST ...
- 【日常学习】【IDA*】codevs2449 骑士精神题解
题目描写叙述 Description 在一个5×5的棋盘上有12个白色的骑士和12个黑色的骑士, 且有一个空位.在不论什么时候一个骑士都能依照骑士的走法(它能够走到和它横坐标相差为1.纵坐标相差为2或 ...
- java多线程编程核心技术——全书总结
这本书大致上是看完了,不过第七章结束的匆匆忙忙很不好. 不过好在还是看完了,勉强算吧. 回想这一年,挺感慨的,心里一直谋求着进步,却很难行动起来. 仔细想想确实啊,想一直进步肯定要牺牲自己的业余时间, ...
- 解决IOS微信内置浏览器返回后不执行js脚本的问题
在A页面写一个$(function(){}) 后随便点击一个URL跳转到B页面 利用微信内置浏览器 返回键返回到A页面后发现这段JS不执行,后来找到了解决方案 $(function () { var ...
- Office 365 机器人(Bot)开发入门
作者:陈希章 发表于 2017年7月29日 前言 作为人工智能技术的一个主要的表现形式,这些年机器人(bot)的应用越来越广泛.不管是有实物的,还是纯软件的,现在的机器人技术应该说已经走入寻常百姓家了 ...
- 《Head First 设计模式》【PDF】下载
<Head First 设计模式>[PDF]下载链接: https://u253469.ctfile.com/fs/253469-231196307 First 设计模式>[PDF] ...
- 微信小程序教学第四章第二节(含视频):小程序中级实战教程:详情-视图渲染
§ 详情 - 数据渲染 本文配套视频地址: https://v.qq.com/x/page/x055550lrvd.html 开始前请把 ch4-2 分支中的 code/ 目录导入微信开发工具 这一节 ...
- OC学习12——字符串、日期、日历
前面主要学习了OC的基础知识,接下来将主要学习Foundation框架的一些常用类的常用方法.Foubdation框架是Cocoa编程.IOS编程的基础框架,包括代表字符串的NSString(代表字符 ...
- HttpClient4.5 post请求xml到服务器
1.加入HttpClient4.5和junit依赖包 <dependencies> <dependency> <groupId>org.apache.httpcom ...